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.