开发者

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜