I've been away from Rails for a while now, so maybe I'm missing something simple.
How can you accomplish this:
<%= yield_or :sidebar do %>
some default content
<% end %>
Or even:
<%= yield_or_render :sidebar, 'path/to/default/sidebar' %>
In the first case, I'm trying:
def yield_or(content, &block)
content_for?(content) ? yield(content) : yield
end
But that throws a 'no block given' error.
In the second case:
def yield_or_render(content, template)
content_for?(content) ? yield(content) : render(template)
end
This works when there's no content defined, but as soon as I use content_for to override the default content, it throws the same error.
I used this as a starting point, but it seems it only works when used directly in the view.
Thanks!
How about something like this?
<% if content_for?(:whatever) %>
<div><%= yield(:whatever) %></div>
<% else %>
<div>default_content_here</div>
<% end %>
Inspiration from this SO question
Try this:
# app/helpers/application_helper.rb
def yield_or(name, content = nil, &block)
if content_for?(name)
content_for(name)
else
block_given? ? capture(&block) : content
end
end
so you could do
<%= yield_or :something, 'default content' %>
or
<%= yield_or :something do %>
block of default content
<% end %>
where the default can be overridden using
<%= content_for :something do %>
overriding content
<% end %>
I didn't know you could use content_for(:content_tag) without a block and it will return the same content as if using yield(:content_tag).
So:
def yield_or_render(content, template)
content_for?(content) ? content_for(content) : render(template)
end
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 want to render a link only if the condition is met with the following link_to_if:
<%= link_to_if policy(#user.add?), new_entry_path(), class: 'btn' do %>
<%= glyphicon("plus") %>
<% end %>
The block with the glypicon("plus") helper method should also only be called if the condition of the link_to_if is met. With the code above the block is always called, no matter whether the condition is true or false.
How do I create the link and its inner content only if the condition returns true?
Just use a simple if and a link_to:
<% if policy(#user.add?) %>
<%= link_to new_entry_path, class: 'btn' do %>
<%= glyphicon('plus') %>
<% end %>
<% end %>
Or you might want to consider a view helper method like this:
def link_to_add(url)
link_to(glyphicon('plus'), url, class: 'btn')
end
Which can be used in to view like:
<%= link_to_add(new_entry_path) if policy(#user.add?) %>
Or more generic:
def icon_link(icon, url)
link_to(glyphicon(icon), url, class: 'btn')
end
Which can be used in to view like:
<%= icon_link('plus', new_entry_path) if policy(#user.add?) %>
The block for this method is the "default" behavior, it's the equivalent of the else in an if statement. Here's the source:
def link_to_if(condition, name, options = {}, html_options = {}, &block)
if condition
link_to(name, options, html_options)
else
if block_given?
block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block)
else
ERB::Util.html_escape(name)
end
end
end
As pointed out by #Anthony if the condition is false it will use the block to determine output similar to an else statement.
If no block is given the "name" value will be rendered without an anchor tag. To avoid this you could pass an empty block like so
<%= link_to_if(policy(#user.add?),glyphicon('plus'),new_entry_path,class: 'btn') {} %>
Since the deference is to the block and the block returns nil nothing will be rendered but obviously the interpretation of what is happening here lacks greatly from that of the answer provided by #spickermann.
If you would want to show the block anyways, but only add the link if a certain condition is met, you may capture the block altogether:
<% block_content = capture do %>
<%# makes sense if this is much more complex %>
<%= glyphicon('plus') %>
<% end %>
<% if policy(#user.add?) %>
<%= link_to block_content, new_entry_path, class: 'btn' %>
<% else %>
<%= block_content %>
<% end %>
I have a loop that looks like this
<% #user.collections.each do |collection| %>
<h1 class="impact"> <%= collection.name %><br></h1>
<%= collection.stories.count %>
<% end %>
It works perfectly to show the Collections that belongs to a User, and then show how many Stories are in each Collection.
However, I want to use a helper that does this.
in the view
<% #user.collections.each do |collection| %>
<h1 class="impact"> <%= collection.name %><br></h1>
<%= number_of_stories_in_collection %>
<% end %>
in the helper
module CollectionsHelper
def number_of_stories_in_collection
collection.stories.count
end
def render_stories_count
if number_of_stories_in_collection.zero?
'No stories in this collection yet'
else
"#{number_of_stories_in_collection} #{'story'.pluralize(number_of_stories_in_collection)}"
end
end
end
I get an error that says
undefined method `stories' for #<Collection::ActiveRecord_Relation:0x007f510f504af8>
Any help is appreciated, thanks!
The 'collection' variable isn't an instance variable, so the helper can't see it.
Change your view to this:
<% #user.collections.each do |collection| %>
<h1 class="impact"> <%= collection.name %><br></h1>
<%= number_of_stories_in(collection) %>
<% end %>
And your helper method to:
def number_of_stories_in(collection)
collection.stories.count
end
This way you are passing the variable to the helper correctly.
extending #Richard's answer and little bit of optimisation to avoid n+1 queries..
<% #user.collections.includes(:stories).each do |collection| %>
<h1 class="impact"> <%= collection.name %><br></h1>
<%= render_stories_count(collection) %>
<% end %>
helper:
module CollectionsHelper
def number_of_stories_in(collection)
collection.stories.length
end
def render_stories_count(collection)
if (count = number_of_stories_in(collection)).zero?
'No stories in this collection yet'
else
"#{count} #{'story'.pluralize(count)}"
end
end
end
I have a partial that needs to have some controller logic run before it can render without issue. Is there some way to associate the partial with some controller logic that is run whenever it is rendered?
For example, this is what my current code looks like:
MyDataController:
class MyDataController < ApplicationController
def view
#obj = MyData.find(params[:id])
run_logic_for_partial
end
def some_method_i_dont_know_about
#obj = MyData.find(params[:id])
# Doesn't call run_logic_for_partial
end
def run_logic_for_partial
#important_hash = {}
for item in #obj.internal_array
#important_hash[item] = "Important value"
end
end
end
view.html.erb:
Name: <%= #obj.name %>
Date: <%= #obj.date %>
<%= render :partial => "my_partial" %>
some_method_i_dont_know_about.html.erb:
Name: <%= #obj.name %>
User: <%= #obj.user %>
<%# This will fail because #important_hash isn't initialized %>
<%= render :partial => "my_partial" %>
_my_partial.html.erb:
<% for item in #obj.internal_array %>
<%= item.to_s %>: <%= #important_hash[item] %>
<% end %>
How can I make sure that run_logic_for_partial is called whenever _my_partial.html.erb is rendered, even if the method isn't explicitly called from the controller? If I can't, are there any common patterns used in Rails to deal with these kinds of situations?
You should be using a views helper for this sort of logic. If you generated your resource using rails generate, a helper file for your resource should already be in your app/helpers directory. Otherwise, you can create it yourself:
# app/helpers/my_data.rb
module MyDataHelper
def run_logic_for_partial(obj)
important_hash = {}
for item in obj.internal_array
important_hash[item] = "Important value" // you'll need to modify this keying to suit your purposes
end
important_hash
end
end
Then, in your partial, pass the object you want to operate on to your helper:
# _my_partial.html.erb
<% important_hash = run_logic_for_partial(#obj) %>
<% for item in important_hash %>
<%= item.to_s %>: <%= important_hash[item] %>
<% end %>
Or:
# app/helpers/my_data.rb
module MyDataHelper
def run_logic_for_partial(item)
# Do your logic
"Important value"
end
end
# _my_partial.html.erb
<% for item in #obj.internal_array %>
<%= item.to_s %>: <%= run_logic_for_partial(item) %>
<% end %>
EDIT:
As commented Ian Kennedy points out, this logic can also reasonably be abstracted into a convenience method in your model:
# app/models/obj.rb
def important_hash
hash = {}
for item in internal_array
important_hash[item] = "Important value"
end
hash
end
Then, you'd access the important_hash attribute in the following manner in your partial:
# _my_partial.html.erb
<% for item in #obj.important_hash %>
<%= item.to_s %>: <%= item %>
<% end %>
What you're trying to do runs against the grain of how Rails controllers/views are designed to be used. It would be better to structure things a bit differently. Why not put run_logic_for_partial into a helper, and make it take an argument (rather than implicitly working on #obj)?
To see an example of a view "helper", look here: http://guides.rubyonrails.org/getting_started.html#view-helpers
I have a a helper that contains a simple on and off switch. I know I have it working because it's working on other pages. However, on this particular page it won't work.. I think its because theres an end within the if else, so it ends the if else early. Here's the code:
I believe this part is working:
<% if popup == "off" %>
<% content_for :main do %>
<% end %>
This part not so much:
<% if popup == "off" %>
<% end %> << this end should be displayed if popup = off
<% end %>
You could do this:
<% if popup == "off" %>
<%= "<% end %>" %> << this end should be displayed if popup = off
<% end %>
or try this:
<% if popup == "off" %>
<% end %> << this end should be displayed if popup = off
<% end %>
If you just want the word end to be displayed, don't enclose it in tags. Anything enclosed in tags is interpreted as Ruby code, anything not is printed exactly as it is.
<% if popup == "off" %>
end << this will now be interpreted as text, not ruby code
<% end %>
ERB (and Ruby) doesn't work like that.
I think you're treating it like you are trying to end an HTML tag instead of an end to a Ruby block, and that you want everything in between those two code segments to run in the content_for block.
Here's what you need. Everything in between will be included in the content_for block:
<% if popup == "off" %>
<% content_for :main do %>
your block code will be evaluated here.
<% end %>
<% end %>
Seems all the suggestions of doing <%= "<% end %>" %> results in a syntax error.. May seem like easy way out by ended up just restructuring my app and got rid of the requirement of <% content_for :main do %>