content_for vs yield in partials - ruby-on-rails

In rails 3.0 with HAML (3.1.4) I have
some template-like partial, like _template.html.haml:
.panel.top
= yield :panel_top
.content
= yield
some another partial which will be displayed using prev template (all this stuff is rendered using AJAX, but this doesn't matter)
- content_for :panel_top do
.title.left
= title
content text
and this worked like a charm in Rails 3.0
But, after upgrade to 3.2 this fails! Yiels just yields "content text", so I have "content text" twice and no title at all
only changing = yield :panel_top to = content_for :panel_top works for 3.2
I am not sure that this solution is ok, and if it is stable or recommended, I cannot find any notes about changes in yield processing nor in Rails 3.1 release notes, nor in 3.2 ones.
Can you help what is the best way to organize yielding inside 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.

Related

Rails - print a "recursive" html variable in Slim

I would like to render in my view a "html"-content variable, let me explain:
Somewhere in a helper there is a pseudocode like this
# view helper for an ERB view
def render_something_recursively(i = 1)
html = "<li>"
html << "hi number #{i}"
if i < 1000
html << render_something_recursively(i++)
end
html << "</li>"
end
Sorry for the bad example but I hope that it give you an idea, in facts I would like to iterate on a hierarchy structure (a tree) with flexible depth. For this reason I need a recursive method and I would like to keep it out from the view.
My question is: how can I accomplish to the same result but in Slim (or eventually in HAML)? How can I give the information of "indentation" at the htmlvariable?
Is it possible or I must use an ERB view?
My final goal should be "easily" something like that:
# recursive_list.html.slim
ul class="a_recursive_list"
=render_something_recursively
Use content_tag and it will render whatever templating engine you are using:
def render_something_recursively(i = 1)
content_tag(:li, "hi number #{i}") do
if i < 1000
render_something_recursively(i++)
end
end
end
Additionally, I don't think you should nest <li> elements directly inside each other, you should either render text or a <ul> element nested inside the <li>.

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.

Passing in options param for content_for method

I found that you can flush content_for looking at the Rails source. https://github.com/rails/rails/blob/master/actionpack/lib/action_view/helpers/capture_helper.rb
Rails Source:
def content_for(name, content = nil, options = {}, &block)
if content || block_given?
if block_given?
options = content if content
content = capture(&block)
end
if content
options[:flush] ? #view_flow.set(name, content) : #view_flow.append(name, content)
end
nil
else
#view_flow.get(name)
end
end
I am trying to set options[:flush] = true, but am having some trouble. The options[:flush] is not evaluating to true in my code below.
My code:
content_for(affiliate_link_pos.to_sym, {:flush=>true}) do
render page
end
Edit:
I have also tried passing a 3rd params (content), but I get wrong number of argument error (3 for 2).
content_for(affiliate_link_pos.to_sym, "true", {:flush=>true}) do
Try this:
content_for(affiliate_link_pos.to_sym, null, :flush=>true) do
render page
end
Looks like the source quoted in the OP's question is actually from Rails 4. Even in Rails 3.2.14, content_for does not accept any options.
content_for(name, content = nil, &block)
Calling #content_for stores a block of markup in an identifier for later use. You can make subsequent calls to the stored content in other templates, helper modules or the layout by passing the identifier as an argument to content_for.

Ruby w/ Sinatra: what is the equivalent of a .js.erb from rails?

.js.erb's are nice, because you can use them to replace parts of a page without having to leave the current page, which gives a cleaner and unchopped up feel to the site / app.
Is there a way to use them in sinatra? or an equivalent?
Just add .js to the end of the symbol you're passing erb(). A la (to call mypage.js.erb):
erb "mypage.js".to_sym
Dirty, but it works.
Based on your description, I'm guessing that your desire is to have portions of a page editable and replaced via AJAX. If this is wrong, please clarify.
I do this in my Sinatra apps by including (my own) AJAXFetch jQuery library and writing code as shown below. This lets me use the partial both when rendering the page initially as well as when editing via AJAX, for maximum DRYness. The AJAXFetch library handles all AJAX fetch/swap through markup alone, without needing to write custom JS on the pages that use it.
helpers/partials.rb
require 'sinatra/base'
module Sinatra
module PartialPartials
ENV_PATHS = %w[ REQUEST_PATH PATH_INFO REQUEST_URI ]
def spoof_request( uri, headers=nil )
new_env = env.dup
ENV_PATHS.each{ |k| new_env[k] = uri.to_s }
new_env.merge!(headers) if headers
call( new_env ).last.join
end
def partial( page, variables={} )
haml page, {layout:false}, variables
end
end
helpers PartialPartials
end
routes/bug.rb
get '/bug/:bug_id' do
if #bug = Bug[params[:bug_id]]
# ...
haml :bug
end
end
# Generate routes for each known partial
partials = %w[ bugdescription bughistory bugtitle fixer
pain project relatedbugs status tags version votes ]
partials.each do |part|
[ part, "#{part}_edit" ].each do |name|
get "/partial/#{name}/:bug_id" do
id = params[:bug_id]
login_required
halt 404, "(no bug ##{id})" unless #bug = Bug[id]
partial :"_#{name}"
end
end
end
post "/update_bug/:partial" do
id = params[:bug_id]
unless params['cancel']=='cancel'
# (update the bug based on fields)
#bug.save
end
spoof_request "/partial/#{params[:partial]}/#{id}", 'REQUEST_METHOD'=>'GET'
end
views/bug.haml
#main
#bug.section
= partial :_bugtitle
.section-body
= partial :_bugdescription
<!-- many more partials used -->
views/_bugtitle.haml
%h1.ajaxfetch-andswap.editable(href="/partial/bugtitle_edit/#{#bug.pk}")= title
views/_bugtitle_edit.haml
%form.ajaxfetch-andswap(method='post' action='/update_bug/bugtitle')
%input(type="hidden" name="bug_id" value="#{#bug.id}")
%h1
%input(type="text" name="name" value="#{h #bug.name}")
%span.edit-buttons
%button(type="submit") update
%button(type="submit" name="cancel" value="cancel") cancel
sinatra really isn't meant to be a full stack framework. Its supposed to get you on the road very quickly. You could use an erb separately and then load into your sinatra code.

Rails 3 - yield return or callback won't call in view <%= yield(:sidebar) || render('shared/sidebar') %>

I'm migrating a Website from Rails 2 (latest) to Rails 3 (beta2).
Testing with Ruby 1.9.1p378 and Ruby 1.9.2dev (2010-04-05 trunk 27225)
Stuck in a situation, i don't know which part will work well. Suspect yield is the problem, but don't know exactly.
In my Layout Files I use the following technique quite often:
app/views/layouts/application.html.erb:
<%= yield(:sidebar) || render('shared/sidebar') %>
For Example the partial look like:
app/views/shared/_sidebar.html.erb:
<p>Default sidebar Content. Bla Bla</p>
Now it is time for the key part!
In any view, I want to create a content_for block (optional). This can contain a pice of HTML etc. example below. If this block is set, the pice HTML inside should render in application.html.erb.
If not, Rails should render the Partial at shared/_sidebar.html.erb on the right hand side.
app/views/books/index.html.erb:
<% content_for :sidebar do %>
<strong>You have to read REWORK, a book from 37signals!</strong>
<% end %>
So you've got the idea. Hopefully. This technique worked well in any Rails 2.x Application.
Now, in Rails 3 (beta2) only the yield Part is working.
|| render('shared/sidebar')
The or side will not process by rails or maybe ruby.
Thanks for input and time!
Ryan Bates from railscasts.com shows in Episode #227 - Upgrading to Rails 3 Part 3 a solution with content_for?() (video playback at 2:45 Min)
I think, that's the way we should use it:
content_for?(:sidebar) ? yield(:sidebar) : render("shared/sidebar")
I tested this out and it looks like Rails 3 is returning empty string instead of nil. So, unless they change this before the final release you will have to modify your code to see if the value is blank instead of just nil.
(sidebar = yield(:sidebar)).present? ? sidebar : render("shared/sidebar")
I usually set my site title with:
<title><%= ['My Site', yield(:title)].compact.join(' - ') %></title>
Due to this change, it would be ugly to add some conditions, so I created a helper like this:
module ApplicationHelper
def nil_empty(str)
str.blank? ? nil : str
end
end
Then I can do something like:
<title><%= ['My Site', nil_empty(yield :title)].compact.join(' - ') %></title>
It's still ugly, but a little bit less :)
Thanks Mike Dotterer. I took your idea and modified it a bit.
yield(:sidebar).presence || render("shared/sidebar")
object.presence is equivalent to object.present? ? object : nil
provide vs content_for

Resources