yield in Rails versus yield in Ruby - ruby-on-rails

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.

Related

Semi-global Rails partial

Is there a better way to achieve what I'm going for?
I have a partial in the /views/shared/ folder that has all the fields that are in a form being used to send an email.
A helper method with default options to render said partial (render partial: 'shared/email_fields' locals: locals where locals is a hash of default variables).
A helper method for every form sending an email that calls the above helper method and passes in either a FormBuilder object or a string containing the beginning of the name html attribute.
The problem I'm having: Most of the email forms differ slightly which results in me having to add additional options to the locals hash and I feel like the global partial is becoming bloated. Is there some way of using a global partial in this way such that the partial doesn't become super bloated?
I've thought of having each form completely separate but that's bad for upkeep and DRY. I've thought of passing in the name of a partial to be rendered inside the global partial but some of these forms need the same options and are rendered from different controllers and I wouldn't want to put a bunch of partials that aren't global in the /views/shared/ folder. Right now, I'm just sticking with the bloated global partial.
Any help would be appreciated!
Here's how I do it. This is going to sound weird, but bear with me.
So, I have basically two forms in my applications. For a form that submits via javascript, it looks like this:
#views/shared/_remote_form.html.haml
= form_tag #presenter.form_path,
remote: true,
id: #presenter.form_id,
class: #presenter.form_classes,
data: #presenter.form_data,
method: #presenter.form_method do
.well
= #presenter.form_inner
.form-controls-container
.form-controls-wrapper
= #presenter.form_controls
As you can see, I use presenters. The presenters are instantiated in the relevant controller as a controller variable, so that the presenter is available to the partial. Something like:
class FooController < ApplicationController
def new
#presenter = NewFooFormPresenter.new(self)
render partial: 'shared/remote_form'
end
...
end
You can see that I'm passing in the controller so that the presenter is able to render various parts of the form.
All FormPresenters inherit from FormPresenterBase that has stubbed methods for each of the methods called in the form. Something like this:
class FormPresenterBase
def initialize(controller)
#controller = controller
end
def form_path
root_path
end
def form_id
'bogus-form-id'
end
def form_classes
'something-bogus'
end
def form_inner; end
def form_controls; end
...
end
That let's me bootstrap the form without throwing a bunch of errors all the time. Naturally, that stubbed form won't really work, but that's okay because each FormPresenter will override the stubbed methods with real values. So, something like:
class NewFooFormPresenter < FormPresenterBase
def form_path
new_for_form_path
end
def form_id
'new-foo-form'
end
def form_classes
'something-not-bogus'
end
# The form fields could be unique to this form. Or, I might have a set of common
# fields that I use across multiple forms. I just decide which partial has the
# correct set of fields and render it here.
def form_inner
render partial: 'new_inner_fields'
end
# The controls are also rendered from partials. Here, I want to have an okay
# button and a cancel button. So, I just call the correct partial that
# renders those. I call html_safe on the resultant string so that it renders
# correctly.
def form_controls
[:okay, :cancel].each_with_object("") do |control_sym, to_return|
render partial: "shared/form_widgets/#{control_sym.to_s}_button"
end.html_safe
end
...
end
Of course, I can get tricky with my FormPresenters. If there are families that share common methods, I can either use further inheritance or module inclusion to keep everything DRY.
So, once I have all my basic form widgets (field combinations, controls, etc.) configured as partials, I can just mix and match in my presenter to my heart's delight. And (at least for forms), I basically never have to write another partial for the rest of my life. Whenever I need a new variant, I just spin up a new FormPresenter and customize it to give me the form I desire.
Actually, there's a little bit more to it than all of that, but hopefully this gives you a sense of another way to skin the cat.
An approach is to have a separate partial for each form. Take all of the items the forms have in common and put them in a partial. You can then reference the "common items" partial within your individual form partials. Depending on how your forms are structured, you may have several "common items" partials, but that is okay. The goal is to keep the code organized and DRY.

What is the syntax for named yield with both names and parameters in ruby/rails?

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.

Is there a way to cache a string fragment within a helper method?

I have a helper that generates complex HTML for common components in an engine.
Helper (very simplified):
def component(name)
component = Component.find_by_name!(name)
# a whole lot of complex stuff that uses component to build some HTML
end
View:
<%= component(:my_component) %>
I want to implement fragment caching on these components but I want to do it within #component itself to keep things DRY, e.g.
def component(name)
...
cache some_unique_fragment_name do
html
end
# or, more succinctly:
cache(some_unique_fragment_name, html)
end
The problem is that Rails' cache helper expects it's going to wrap a block of HTML in Erb and therefore won't work as I've described above.
Is there a way to use #cache for a string fragment in a helper method?
I'm a big fan of the fetch block, you can read more in the Rails docs:
def component(name)
# ...
cache.fetch('some_unique_fragment_name') do
html
end
end
What this does is it will return the value of some_unique_fragment_name if it's available, otherwise it will generate it inside the block. It's a readable, clean way of showing that caching is occurring.

What is the difference between render and yield in Rails

Can someone explain the difference between "<%= render %>" and "<%= yield %> with <% content_for :partial do %>/<% end %>"? specifically how the routing changes when switching from one to another, the benefits of using one over the other, when is it practical to use one over the other. THIS is the closest explanation I have found, but isn't quite clear enough for me.
I have been trying for several days to wrap my head around this, but it seems that each configuration I try either comes close, or errors out.
If theres are three views, aaa and bbb and ccc, and each has an index.html.erb, but bbb and ccc have a _content.html.erb partial (signified by the underscore) how can you accomplish getting the bbb or ccc partial in aaa using either render or yield?
The following works:
aaa's index.html.erb :
<div">
<%= render 'bbb/content' %>
</div>
and bbbs _content.html/erb :
<p>Content from bbb.</p>
BUT this does NOT:
aaa's index.html.erb :
<div">
<%= yield :container %>
</div>
and bbbs _content.html/erb :
<% content_for :container do %>
<p>Content from bbb.</p> ### viewed in aaa
<% end>
and cccs _content.html.erb would have nothing, or the content_for, but I still dont get aaa's index.html to be populated with content.
If I use the render, I can explicitly place the content in. But I thought that the benefit of using the yield :whatever would allow me to choose what to populate it with, and I can't get it to populate anything as soon as I change it from render to yield. Do I also have to update the routes file? If so, how do I choose which one to populate it with? Does that mean its in the controller? and needs an action?
I also have though that it depends on which file is initially routed to, but like I said, I think I need to understand the difference between the two before I can begin to use the partials to my advantage.
First of all, yield is ruby, render is rails. Usually one uses a common layout for the application whose inner content changes according to action/context. The problem usually lies in defining where our layout ends and context-specific template begins. Take, for instance, the HTML title tag. Let's say you have an application called Cities. In most cases, you want your page title to be "Cities" all the time. But, if you're for instance, inside Amsterdam page, then you would like the have "Amsterdam" as your page title.
# application.html.erb
<html>
<head>
<%= content_for?(:page_title) ? yield(:page_title) : "Cities" %>
......
# city/index.html.erb
<% content_for :page_title do %>
<%= #city.name %>
<% end %>
<div class="bla"...
Within Rails you usually define your application title in your application layout. One strategy for changing the page title would be to use content_for in the specific cities template and change accordingly.
Render, on the other hand, accomplishes different rendering strategies. Straight. When you call render, it renders. content_for/yield doesn't render automatically, it is stored somewhere and then fills up the missing spots in the due places. So, you can think of it as more as a "store/search/replace" in comparison to render, which just plain renders.
Good rule of thumb to use one over the other is: if the template you are writing needs to present different information per context, strongly consider using content_for.
yield
Ruby code (Proc class) and takes your block and does what it is supposed to do with it. Yield is also fast compared with other Ruby based ways of doing the same thing.
I'd assume (and I only) use it in the layouts because it's quick and I mindlessly do what's normal in Rails. yield is also used to pass content to a specific spot in your layout. I often have <%= yield :head %> in the head, just above the head tag, so that I can pass random weirdness that sometimes comes up.
Common Uses:
Mostly just used in layouts
(if you are fancy/inclined to do so in a Model) as a true Ruby Proc
statement.
render
Rails code that you pass arguments to that, as the docs say, "Renders the content that will be returned to the browser as the response body". partials, actions, text, files...etc.
Common Uses:
Used in both views and the controller.
When your controller method exits, it renders the associated file. So the edit controller renders edit.html.erb. It uses the specified layout or application.html.erb if none is specified.
Within your layout file, when you call yield it will fill in the information from your render. If you call yield with a parameter, it will look for a content_for section in your render file matching that parameter. I'm not completely sure, but I don't think you can call yield from outside of your layout file, and I don't think it will fill in any information except that found in your render file.
Anywhere in your layout file or your rendered file, you can render a partial by calling render with the partial name minus the underscore.
I hope that helps.
Edit to answer question in comment:
yield and render perform similar functions however yield only looks in the render file whereas render specifies which file to render. Also, render outputs the entire file, but yield with a parameter can output just a subsection of the file.
Here's a visual to put them both in perspective:
The render method is called at the end of a controller action and orchestrates what block is passed to the method that is actually rendering the application.html.erb by yielding the passed block.
https://richstone.io/debunk/

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.

Resources