Ruby on Rails: provide vs content_for
First of all, what is streaming? Why would you use it?
Streaming is alternate method of rendering pages top-down (outside-in). The default rendering behavior is inside-out. Streaming must be enabled in your controller:
class MyController
def action
render stream: true # Streaming enabled
end
end
According to the documentation:
Streaming may be considered to be overkill for lightweight actions
like new or edit. The real benefit of streaming is on expensive
actions that, for example, do a lot of queries on the database.
So, if you're not using streaming, is there still a difference?
Yes.
The difference is a template can define multiple content blocks by calling content_for
multiple times. Doing so will concatenate the blocks and pass that to the layout:
# layout.html.erb
<div class="heading"><%= yield :surprise %></div>
<div class="body">
<p><%= yield %></p>
<p>But it's not very interesting...</p>
</div>
# template.html.erb
<%= content_for :surprise, "Hello" %>
I've got your content!
<%= content_for :surprise, ", World!" %>
# Generated HTML
<div class="heading">Hello, World!</div>
<div class="body">
<p>I've got your content!</p>
<p>But it's not very interesting...</p>
</div>
Since provide
doesn't continue searching the provided template, only the block passed to the first provide
call will be sent to the template:
# layout.html.erb
<div class="heading"><%= yield :title %></div>
# template.html.erb
<%= provide :title, "Foo" %>
<%= provide :title, "bar" %>
# Generated HTML
<div class="heading">Foo</div>
Rails what is the difference between content_for and yield?
yield
is how you specify where your content areas is going to go within a layout. You might have something like this:
<div>
<h1> This is the wrapper!</h1>
<%= yield :my_content %>
</div>
content_for
is how you specify which content is going to be rendered into which content area. You might have something like this:
<% content_for :my_content do %>
This is the content.
<% end %>
The result would be
<div>
<h1> This is the wrapper!</h1>
This is the content.
</div>
They are opposite ends of the rendering process, with yield
specifying where content goes, and content_for
specifying what the actual content is.
Is there a generally accepted best practice?
The best practice is to use yield
in your layouts, and content_for
in your views. There is a special second use for content_for
, where you give it no block and it returns the previously rendered content. This is primarily for use in helper methods where yield
cannot work. Within your views, the best practice is to stick to yield :my_content
to recall the content, and content_for :my_content do...end
to render the content.
Difference between using provide() and assigning a variable for page titles in Rails?
If you want to simply render a variable in the view, the second method will do.
However, provide
and yield
offer a various ways to build the rendered content. For, example, you pass instance variable like @posts
which you may already assign a variable after a complicated algorithm, which you will not do in a view template.
http://api.rubyonrails.org/classes/ActionView/Helpers/CaptureHelper.html
http://guides.rubyonrails.org/layouts_and_rendering.html#understanding-yield
Rails: What's the difference between capture and content_for?
If you check out the content_for source, it calls capture internally:
def content_for(name, content = nil, &block)
if content || block_given?
content = capture(&block) if block_given?
@view_flow.append(name, content) if content
nil
else
@view_flow.get(name)
end
end
So, from reading through the method, it looks like the primary advantage of content_for is that it can be called multiple times with multiple blocks for the same named content and each additional call will just append onto whatever has already been rendered. Whereas, in the case of capture, if you call:
<% @greeting = capture do %>
Hello
<% end %>
and then later call:
<% @greeting = capture do %>
Or, in espanol, Hola
<% end %>
Then the last part is the only part that will be captured, and the 'Hello' will just be discarded. Whereas, doing something similar in content_for will result in the second call being appended to 'Hello'.
Rails: How to check if content_for provided or not?
The answer by @Beerlington is right, I'll just add another option to DRY the code a bit.
<h1>
<%= yield(content_for?(:page_heading)? :page_heading : :title) %>
</h1>
content_for vs yield in partials
From Rails 3.0 to Rails 3.2 content_for
was really changed:
3.0:
def content_for(name, content = nil, &block)
content = capture(&block) if block_given?
@_content_for[name] << content if content
@_content_for[name] unless content
end
3.2:
def content_for(name, content = nil, &block)
if content || block_given?
content = capture(&block) if block_given?
@view_flow.append(name, content) if content
nil
else
@view_flow.get(name)
end
end
This shows us, that from 3.2 content_for
works for showing/inserting content too, not only store it for named section.
Also, if you make an attempt to debug yield
logic you'll se that it yields before content_for
is correctly initialized.
So, leaving fragment caching out of this discussion I can conclude that content_for
is preferrable way to insert named sections anywhere except top-level layouts. In helpers and other situations yield
should render wrong results.
Setting content_for misunderstanding
If you're going to be rendering the title of the modal dynamically, it's typically better to use a helper method. This will keep your view logic DRY and readable. This method will accept an argument for the title and if none is present it will render a default title.
In application_helper.rb:
def modal_title(title)
if title.empty?
"My Default Title"
else
title
end
end
In your modal partial where the title should appear:
<%= modal_title(yield(:title)) %>
In the view, you will have access to dynamically set the title using content_for. For example, you could pass a string as your title or pass an instance variable from your controller.
<% content_for :title, @your_variable %>
Determining the correct usage of named yield with content_for
After difficulty in getting the form passed to the content_for block, I ended up removing the content_for and rearranging my code like so:
= render :layout => 'projects/shared/nav', locals: {:url => projects_path, :form => @form} do
= @f.text_field :name
= @f.text_field :number
And the yield like so:
= form_for(form, url: url, layout: :horizontal) do |f|
- @f = f
= f.alert_message "Please fix the errors below before saving this page."
= yield
It feels a bit messy but I couldn't work out another way to pass the form around, works as expected.
Rails check if yield :area is defined in content_for
@content_for_whatever
is deprecated.
Use content_for?
instead, like this:
<% if content_for?(:whatever) %>
<div><%= yield(:whatever) %></div>
<% end %>
Related Topics
How to Open Files Relative to Home Directory
Rails Development Server Is Slow and Takes a Long Time to Load a Simple Page
Cannot Install Aptana Plugin on Eclipse 4.2
Differencebetween Methods and Attributes in Ruby
Ruby Classes: Initialize Self VS. @Variable
Ruby: Initialize() VS Class Body
"Ago" Date/Time Functions in Ruby/Rails
Error When Starting Rails Server: Warning: Insecure World Writable Dir /Usr in Path, Mode 040777
Why Are Symbols Not Frozen Strings
Building a Windows Executable from My Ruby App
Neither Ruby and Nor Irb Can Load .Rb File in Current Directory
How to Install Ruby Gems When Using Rvm
MAC Os X Mountain Lion "Rails Is Not Currently Installed on This System."