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.
Related
I've been using HAML for over 10 years, and this simple use case continues to elude me.
Say I have the following haml code
- links.each do |link|
= link_to link.url do
%i.fa.fa-map
= link.text
This block works while looping only, but imagine a situation where I want to reuse the block in multiple loops, or in multiple places in the HAML file. I do not currently know how to do that without creating a separate partial or helper. When the block is big enough, I often will create a partial, but often the block is small and a partial seems tedious overkill. Sometimes I have several blocks like this that I only want re-used in one HAML file.
I tried first to create this with content_for but it doesn't accept arguments and can't be cached.
My second attempt was with capture_haml, but I think that has to be called directly indide of HAML, not a ruby method. The following obviously fails for syntax reasons, but you see what I'm trying to do:
:ruby
def entry_link(url, text)
capture_haml(url, text) do |url, text|
= link_to url do
%i.fa.fa-map
text
end
end
Update:
I was able to get this far, which is functional but ugly AF.
:ruby
def entry_link(url, text)
capture_haml(url, text) do |url, text|
link_to url do
haml_tag :i, {class: 'fa fa-map'}
haml_concat(text)
end
end
end
= entry_link('/', 'My Link')
The following would be a more acceptable syntax, but I get the strange error undefined local variable or method _hamlout'`
- def entry_link(url, text)
- link_to url do
%i.fa.fa-map
text
I'm trying to use a helper method to determine the value of an attribute for several records. Here is the basic function I am trying to get working (from the view):
<% if Baseline.where(subject_id: sub.subject_id).first.crf_status(crf) == 1 %>
<td bgcolor="#98FB98" >
<% else %>
My helper function is crf_status(crf), and it looks like this:
application_helper.rb
def crf_status(crf)
case crf
when Baseline then 'baseline_status'
when FollowUp3Week then 'follow_up_3_week'
...
end
end
So a working example would be if crf_status(Baseline) would return:
<% if Baseline.where(subject_id: sub.subject_id).first.baseline_status == 1 %>
<td bgcolor="#98FB98" >
<% else %>
Right now, the error is 'undefined method 'crf_status' for Baseline'. So based on what I've read, perhaps I have to reference ApplicationHelper in each controller? That doesn't sound right. Please let me know what you think.
Thanks.
edit. I forgot to make this more clear: crf_status(crf) is being passed an object from an array [Baseline, FollowUp3Week...].
The actual line starts with it as well -> if crf.where(subject_id:...
When you do method chaining like .first.crf_status(crf) you don't get a fresh global scope every time. I.e. to get this example to work your crf_status would need to be defined as an instance method on the Baseline model.
From a MVC design perspective, it's frowned upon to do database queries (i.e. where) from your views; you should do it from the controller instead. The choice to use helpers here is totally optional. By putting it in a helper all you're doing is making it inaccessible from code outside your views.
To cut to the chase, here's what you should write in your Baseline model file:
def crf_status(crf)
case crf
when Baseline then baseline_status
when FollowUp3Week then follow_up_3_week
end
end
Note that the baseline_status and follow_up_3_week are actually method calls with the implicit receiver self.
You are calling "crf_status" on an instance of a model, helpers can only be called on views and controllers.
You have to do something like this
<% if crf.where(subject_id: sub.subject_id).first.send(crf_status(crf)) == 1 %>
<td bgcolor="#98FB98" >
<% else %>
Anyway, that looks like a weird code smell (making queries on view is not right and that crf_status looks like something that you should move inside your models)
If you want to return a method that is to be called in the context, use the .send method.
Baseline.where(subject_id: sub.subject_id).first.send(crf_status(crf))
Whatever is returned from your method will be executed. This is a great metaprogramming example. You want to test against the class of the instance, so use the .class method on your case line. You'll want to return symbols not strings though, so do this:
def crf_status(crf)
case crf
when Baseline then :baseline_status
when FollowUp3Week then :follow_up_3_week
else :default
end
end
Edit: Changed case for type comparison
I'm confused about whether or not yield in Ruby and yield in Rails (specifically in views/templates) are the same entity or they're two different things which act differently depending on the context.
When I think about yield in Ruby, this usage comes to mind:
def some_method
yield 123
end
some_method { |a| a + 1 } # => 124
When using ERB templates in Rails, though, yield is used to render the views specific to the current controller/action or to render content specified through provide or content_for.
Is this the same yield, or does the Rails version just happen to be different functionality with the same name?
It's the same thing. A block is passed to the layout method that will render the document body when called.
yield is actually a keyword in Ruby. It is possible to define a method named "yield" (Enumerator does this, for example), but whenever you see yield without a receiver, it will always be the same keyword that passes control to a block.
One could use yield with a :name in views in rails:
= yield :some_place
so then using then using content_for :some_place do ... to insert a code block only in there where yield :some_place is placed (http://guides.rubyonrails.org/layouts_and_rendering.html#using-the-content-for-method).
Also ruby allows passing parameters in the yiled (http://www.tutorialspoint.com/ruby/ruby_blocks.htm):
def test
yield 5
puts "You are in the method test"
yield 100
end
test {|i| puts "You are in the block #{i}"}
But I didn't find anything about using yield/content_for both with names and parameters in rails views:
= yield :some_place, 5, 6
...
= content_for :some_place do |a,b|
h3 = "Yield provided parameters: #{a} and #{b}"
Is it possible? Where is the official rails or ruby syntax for yield statements and passing blocks?
I heard something about the Proc.new() that could be somehow related to the problem.
content_for(:name) evaluates first, and stores a snip of HTML for later use. yield(:name) only fetches this content. Hence, you can't pass arguments into a method that was already called, and won't be called again.
You probably merely need to cut a partial HTML.erb file, and render it from your target location. Render takes named parameters as a hash.
In 'Rails View' book that I'm reading, there is code about presenters
app/helpers/designers_helper.rb
module DesignerHelper
def designer_status_for(designer = #designer)
presenter = DesignerStatus.new(designer)
if block_given?
yield presenter
else
presenter
end
end
end
then in show.html.erb
<% designer_status_for do |status| %>
some html using <%= status %> variable
<% end %>
I'm having hard time understanding how the block of code gets the variable status assigned and why do we need 'if' statement in the helper function and why not just return the presenter variable directly?
Let's start with the view
<% designer_status_for do |status| %>
"some html using <%= status %> variable"
<% end %>
This calls the method designer_status_for with a block of code as shown below
designer_status_for {|status| "some html using <%= status %> variable" }
In the method,
presenter = DesignerStatus.new(designer)
#=>since no `designer` is given it takes the default value of `#designer`
# and finds the related presenter
if block_given?
#=> is true since a block is given
yield presenter
#=> here the method "yields" to the block
# i.e. passes the presenter as a variable to the block and executes it.
# For e.g. array.map{|x| x+1} works the same way
# `map` iterates over the array and yields each element to the given block.
{|status| "some html using <%= status %> variable"}
#=> status is the variable received from method which is presenter
# This block returns the html to the method that called it
end
#=>the last statement was yield which was equal to the html returned from block
# returns the html received from the block
The if statement in the method is to return the result of block of code if given else it returns the presenter. This allows for DRY code where we need presenter variable in bunch of places in a block of code.
Ok, lets do this step by step.
Firstly, you define a helper-method which assigns #designer as default. This is the reason, why you can call it without any arguments in your view:
<% designer_status_for do |status| %>
# more code
Then, DesignerStatus is the actual presenter, which is a different class than Designer and creates a total new object, but which is sort of based on the #designer object. So, presenter is a new object of type DesignerStatus
block_given?, as you might guess, checks, if this object should be passed to a block or if it should work as a normal object. This is most likely the tricky part where you are having trouble understanding. So let me explain it the other way around. You can call something like this
designer_status_for.some_presenter_method
in your view, and it would work. It works, because no block is given and a designer object is returned (in other words, the else part is triggered), on which you can call some_presenter_method directly. So, you could use that all throughout your view, and you would be just fine.
However, there is an even better option, namely yield this object into a block. The yield option is what makes this code-snippet
<% designer_status_for do |status| %>
status.some_presenter_method
<% end %>
possible in the first place. What this basically says is "take that presenter object, assign it to a variable called status and make this variable available to the block.
So, if you are having a hard time to wrap your head around yield right now, don't worry to much about it. Just call any method on designer_status_for itself. The benefit of using the block over the object itself is the fact, that your code becomes less brittle, but that's sort of another topic.
After posting this question, I tried to re-read the code and try some samples in irb and here are findings which answer my question.
In Ruby any method can take a block of code. For eg.
def method_can_take_block?
answer = 'yes it can take block of code'
end
method_can_take_block? { puts 'this code is not executed but gets passed to method' }
to have my block of code execute, it needs to be yielded inside the method. Yielding block of code can be done by just calling yield inside the method. By explictly calling yield we can control the line in which the block of code gets executed.
def method_can_take_block?
answer = 'yes it can take block of code'
yield
end
method_can_take_block? { puts 'this code gets executed after the variable 'answer' gets assigned with some value.' }
However we don't make any use of the variable 'answer' here. the code block passed doesn't have any access to the variable defined inside the function. So to give the block some access to the variables defined inside the method, we need make the block accept some argument. Note the use of |my_answer| in the code block below.
method_can_take_block? { |my_answer| 100.times { puts answer } }
Now that we have added an argument to the block to the block, we need to pass this argument while calling the block of code. So this is done by calling yield with additional arguments.
def method_can_take_block?
answer = 'yes it can take block of code'
yield answer
end
yield answer passes answer variable to the block of code that is passed to the method. In this case, answer is passed as my_answer variable inside the block which then prints it 100 times.
With this understanding, my answer to questions
how the block of code gets the variable status assigned
ans: by the line yield presenter. Here presenter variable is passed on to the block which receives the argument in the name status.
why do we need 'if' statement in the helper function and why not just return the presenter variable directly?
We could well avoid passing the entire erb as block to the method but then we need have a code like this at the top of the show.html.erb:
<% status = designer_status_for(#designer) %> and then start using the variable status. I think it's a matter of aesthetics to use block so that we can avoid using an assignment. The if statement in the method is there to use this kind of syntax rather using block.