Consider the following:
view.html.erb:
<%= make_backwards do %>
stressed
<% end %>
helper.rb:
def make_backwards
yield.reverse
end
The view renders stresseddesserts instead of just desserts. How do I use the content in yield without rendering the code block?
ERB has an internal buffer, which makes using blocks a bit more complicated, as you can see in your code example.
Rails provides a capture method, which allows you to capture a string inside this buffer and return it from a block.
So your helper would become the following:
def make_backwards
capture do
yield.reverse
end
end
You could try doing the ff:
Option 1:
<%= make_backwards { "stressed" } %>
Option 2:
<%= make_backwards do %>
<% "stressed" %>
<% end %>
Let me know if it helps.
Related
In Rails 5.2.3, I need to render a partial which takes an optional block.
# users/_user.html.erb
...
<% if block_given? %>
<%= yield %>
<% else %>
<h1>Goodbye world</h1>
<% end %>
...
However block_given? returns true regardless of which version I choose to go with:
<%# Version 1 - block_given? returns true %>
<%= render partial: "users/_user" do %>
<h1>hello world</h1>
<% end %>
<%# Version 2 - block_given? also returns true %>
<%= render partial: "users/_user" %>
What's going on here and why is this happening?
Because all Rails templates support content_for :xyz, which is triggered by yield :xyz, it means all templates are always wrapped in a block that is prepared to fetch this content_for data.
Because this pre-programmed block is always there in order to accommodate content_for, it means block_given? will always return true.
I think this may actually be a small oversight in the Rails view design. It would be nice if we'd have a separate method to detect if a partial was supplied a block.
One idea for workaround:
<% if (block = yield).empty? %>
<h1>Goodbye world</h1>
<% else %>
<%= block %>
<% end %>
While being clever and a generic solution, I'm not a fan of the (block = yield).empty? in that particular instance.
In my use case and this one, where the default content is so simple, I prefer this approach:
<%= yield.presence || content_tag(:h1, "Goodbye world") %>
I have an ERB view with two blocks:
<%= test_h1 do %>
<%= 'test1' %>
<% end -%>
<%= test_h2 do %>
<%= 'test2' %>
<% end -%>
where test_h1 and test_h2 are similar helpers, but one is defined in a helper file, while another via helper_method in a controller:
module TestHelper
def test_h1(&block)
link_to '/url' do
capture(&block)
end
end
end
class TestController < ApplicationController
helper_method :test_h2
def test_h2(&block)
helpers.link_to '/url' do
helpers.capture(&block)
end
end
end
test_h1 produces the expected result and test_h2 renders the inner template block first:
test1
test2
Why? What would be an idiomatic way to write test_h2 ?
I think both examples of views should be re-written as:
<%= test_h1 do %>
<% 'test1' %>
<% end -%>
<%= test_h2 do %>
<% 'test2' %>
<% end -%>
My understanding that '<%=' forces to render the output of the block to the output stream, that was not an intended behavior in these two examples
capture overrides current output buffer and just calls the block (which is still bound to other view context), thus override has no effect when called from controller because view_context is not the same context the view is being rendered in.
To work around contexts you can define your helper like so:
# in controller
helper do
def test_h3(&block)
# this will run in view context, so call `controller.some_func` to access controller instance
link_to '/url' do
capture(&block)
end
end
end
When using capture from your controller the output is appended to the page buffer, as a result the <%= from of your erb is outputting immediately to the page output.
To work around, you need to use <% instead within your test_h2 block. So to get the expected behavior in both cases, use this syntax:
<%= test_h1 do %>
<%= 'test1' %>
<% end -%>
<%= test_h2 do %>
<% 'test2' %>
<% end -%>
More info in this article: https://thepugautomatic.com/2013/06/helpers/
The idomatic way to do it in rails would be to move the test_h2 method to a concern and include that concern in controller as well as helper class.
Or else define test_h2 as helper_method in your controller class.
But generally methods that are needed in multiple places should be placed in concerns, and include those concerns wherever needed.
Also if you need methods for views, then include concerns or define your own methods inside helpers.
Refer Can we call a Controller's method from a view (as we call from helper ideally)?
How to use concerns in Rails 4
I am trying to pass a string to my view from controller like this:
controller:
def index
#str = 'foo'
end
view:
String: <% #str %>
The variable itself seems to arrive because I get no error. However, it arrives empty (only "String" is in html, nothing else). And it seems to work great with other built-in types, e.g. Time. What am I missing here? I use Ruby 2.2.1 and Rails 4.
As others have said, you need to use
<%= #str %>
I'll give you an explanation as well - you use <% %> for when you need to run some Ruby code that you don't want displayed to the screen. For example, you might have conditional logic like
<% if user_signed_in? %>
<%= #welcome_string %>
<% end %>
Use <%= %> when you want to output, drop the '=' for conditional logic or anything that doesn't need to display.
in your view
String: <%= #str %>
In view user following code:
String: <%= #str %>
In your view, use:
<%= #str %>
As the other users have pointed out, you need to use <%=
The = is an ERB flag to so export the result of the code inside of the tags and put it into the DOM.
If you want to put some logic into your page that you don't want to evaluate, you leave the = out.
<% if user_wants_to_see_output? %>
<%= "User will see this" %>
<% end %>
I have an Each/do block in my view currently, but I'd prefer to push this code into a helper, as I need to add a few conditional statements in there so I don't want to clutter up my view. Here is the view I have currently that I have been trying to code as a helper method with no luck so far
<% update.voters_who_voted.each do |voter| %>
<%= link_to profile_path(voter) do %>
<%= thirty_avatar(voter) %>
<% end %>
<% end %>
How would this translate into a helper with this name
def find_voters_who_voted(update)
...
...
end
I've tried this with no luck
def find_voters_who_voted(update)
update.voters_who_voted.each do |voter|
link_to profile_path(voter) do
thirty_avatar(voter)
end
end
end
Remember that all you are doing is displaying the return value of this method. It looks like you are expecting it to act as if it's part of the view, but that's not the way helpers work. Something like this would return the equivalent of your original code block
def find_voters_who_voted(update)
update.voters_who_voted.collect do |voter|
link_to profile_path(voter) do
thirty_avatar(voter)
end
end.join
end
I want to do a conditional rendering at the layout level based on the actual template has defined content_for(:an__area), any idea how to get this done?
#content_for_whatever is deprecated.
Use content_for? instead, like this:
<% if content_for?(:whatever) %>
<div><%= yield(:whatever) %></div>
<% end %>
not really necessary to create a helper method:
<% if #content_for_sidebar %>
<div id="sidebar">
<%= yield :sidebar %>
</div>
<% end %>
then of course in your view:
<% content_for :sidebar do %>
...
<% end %>
I use this all the time to conditionally go between a one column and two column layout
<%if content_for?(:content)%>
<%= yield(:content) %>
<%end%>
Can create a helper:
def content_defined?(var)
content_var_name="#content_for_#{var}"
!instance_variable_get(content_var_name).nil?
end
And use this in your layout:
<% if content_defined?(:an__area) %>
<h1>An area is defined: <%= yield :an__area %></h1>
<% end %>
Ok I am going to shamelessly do a self reply as no one has answered and I have already found the answer :)
Define this as a helper method either in application_helper.rb or anywhere you found convenient.
def content_defined?(symbol)
content_var_name="#content_for_" +
if symbol.kind_of? Symbol
symbol.to_s
elsif symbol.kind_of? String
symbol
else
raise "Parameter symbol must be string or symbol"
end
!instance_variable_get(content_var_name).nil?
end
I'm not sure of the performance implications of calling yield twice, but this will do regardless of the internal implementation of yield (#content_for_xyz is deprecated) and without any extra code or helper methods:
<% if yield :sidebar %>
<div id="sidebar">
<%= yield :sidebar %>
</div>
<% end %>
I use #view_flow and value of the content method before checking if the content is present in the view like this:
#view_flow.content[:header_left_or_whatever_the_name_of_your_block_is].present?
Recently stumbled upon it when showing all local, global and instance variables of self in the console with byebug. I’m a fan using this because it’s straight from Rails, won’t throw an error, won’t hide anything w “Rails magic”, returns a definite true or false, + only checks the content in the current context of the view being rendered.
#view_flow is an instance attribute of ActionView::Context and because Action View contexts are supplied to Action Controller to render a template it will be available to any view that has been rendered by Rails. Although it checks for content, the content_for block will not be yielded if it isn’t there. So it’s been my perfect solution in similar situations.