In Rails, what exactly is form_for? It seems magical
First of all, I realize I should have tried to fully grasp Ruby before jumping into Rails. However, nothing in Ruby seemed too difficult to quickly grasp (until this!), so I decided to get started while I was still enthusiastic about learning. ,__,
Anyway, here is a super-condensed example of form_for
:
<开发者_高级运维;%= form_for(@post) do |f| %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<% end %>
I understand that methods in Ruby need not be called with parenthesis. However, form_for
is being called with parenthesis, and yet somehow, it seems as though the do |f|
block is being passed to it!
Is form_for
returning a method that takes a block, and then is that method immediately being called (without parenthesis) by passing the do |f|
block? What's going on here?
Ruby uses a lot of what is called 'syntactic sugar' in that it is a bit more flexible than other languages in how certain operations are interpreted.
for instance:
model.property = "something"
is actually a function call:
model.property=("something")
your form_for
example is a similar case. Blocks are silently passed into Ruby functions as a parameter.
my_block = Proc.new { some code }
my_function(param1, param2, &my_block)
is equivalent to
my_function param1, param2 do
some code
end
and in the function def for my_function
you could write:
def my_function(param1, param2, &block)
and you could access the block via the parameter, as well as yield
.
So when you use block syntax, it's interpreted as a parameter, but it's really not.
What happens here is simply that the form_for
method is invoked with both parameters and a block in the same call.
Here's a basic example of a method taking both a parameter and a block, to illustrate the principle:
def hello(name)
puts "Hello, #{name}!"
yield if block_given?
puts "Goodbye, #{name}!"
end
This method can be called with a single parameter, or with a parameter and a block:
> hello("John")
Hello, John!
Goodbye, John!
> hello("John") do
* puts "Inside the block"
* end
Hello, John!
Inside the block
Goodbye, John!
Regarding the question in your comment:
Why are there parenthesis around only the first parameter? After encountering the open+closed parenthesis after form_for, how does Ruby know that it should "wait" before calling the method?
If I understand your question correctly, you're asking why there's only parentheses around @post
in the form_for
call, and not around the entire block. That's the syntax for passing a block to a method in Ruby - if a block follows immediately after the method and its regular parameters, the block is passed along to the method together with the parameters.
Here are a few of the most common ways of calling a method in Ruby:
# Calling a method without a block
mymethod(param1, param2)
# Same as above, but leaving out parentheses
mymethod param1, param2
# Calling a method with a block that takes no arguments
# (this works without parentheses too)
mymethod(param1, param2) { do_stuff_in_block() }
# or
# (this works without parentheses too)
mymethod(param1, param2) do
do_stuff_in_block()
end
# Calling a method with a block that takes arguments
# (this works without parentheses too)
mymethod(param1, param2) do |arg1, arg2|
do_stuff_in_block(arg1, arg2)
end
# or
# (this works without parentheses too)
mymethod(param1, param2) { |arg1, arg2| do_stuff_in_block(arg1, arg2) }
Have a look at Blocks and Iterators in Programming Ruby for more details about both how to call methods with blocks and how to write your own methods that accept blocks.
There are 2 separate questions, but I will answer the more pressing one first:
form_for @product do |f|
# stuff here
end
and
form_for(@product) do |f|
# stuff here
end
Are exactly the same thing. form_for
is a method that takes block
as a parameter, amongst other parameters.
Methods in ruby can be passed arguments (e.g. @post
) and a block (e.g. all the stuff inside the do
... end
).
If you look at how form_for
is defined within rails you will see:
def form_for(record, options = {}, &proc)
# a lot of funky magical rails stuff ...
end
Everything in the do
block is captured in proc
. The preceding &
in the method definition indicates that proc
is a block and can be executed. Somewhere along the line, rails will execute proc
.
Blocks are a big part of ruby's magical awesomeness. Check this awesome free ruby e-book for more info on blocks. Blocks are a kind of 'closure' and ruby has different flavors of closures which behave slightly differently (the other common closure is lambda
). If you want to dig real deep into how closures work in Ruby, check out this great tutorial.
精彩评论