I have the following little problem.
In layout I yield to content_for to set up some classes on my body tag:
<body class="<%= yield(:body_classes) %>
They I would like to call content_for
<%= content_for(:body_classes, "one") %>
So far so good. I use content_for for the second time:
<%= content_for(:body_classes, "two") %>
In my HTML I get the following:
<body class="onetwo">
Is there an elegant way to separate those two classes by space? I can think of couple of hacky solution, but nothing feels right...
Many Thanks!
I don't think content_for is a good fit in this case. However, you can solve the problem elegantly with a couple of helper methods (extracted from one of my Rails projects):
def klass(*classes)
#classes = [] if #classes.nil?
#classes += classes
#classes.uniq!
nil
end
def has_klass?(klass)
!#classes.nil? && #classes.include?(klass)
end
def body_klasses
#classes.map(&:to_s).join(" ") rescue nil
end
Usage in templates:
<%= klass :one, :two %>
<%= klass :three %>
In the layout, determine if a certain class is set:
<% if has_klass? :one %>
And finally...
<body class="<%= body_klasses %>">
You can further customize these to better suit your needs.
Just put a space before (or after) the class each time you set content_for.
<% content_for(:body_classes, "one ") %>
By the way, you probably don't want the = in your setting tags; you generally don't want Rails to output into the HTML the content you're storing away for later.
Maybe you can check, before adding something to :body_clases, if there´s something already in it, in order to add the space before adding the new content.
<% content_for(:body_classes,content_for?(:body_classes) ? ' one' : 'one') %>
<% content_for(:body_classes,content_for?(:body_classes) ? ' two' : 'two') %>
The final Html will be:
<body class="one two">
Related
I am building an application where n users can talk to each other (like a messaging application) in public. Because you might want to have a different bio for each talk you do (for example a discussion about me regarding Ruby on Rails would need a different bio than one about Psychology) I have a Spkr model which has a User and a Tlk. The below code successfully means that on the users profile page, for each instance of them being a Spkr, the Tlk, and it's participants is visible with each Spkr's image (so if a Tlk has three participants, then all three images will be visible).
The setup is such where the default image is the User's image, but the Spkr can also customise their image by uploading one as a Spkr. I am worried that I am loading the front end with too much computation. Right now everything works... so is it ok? Or should I be limiting the computation happening when building views?
Thank you
<% #user.spkrs.each do |spkr| %>
<%= link_to show_tlk_path(spkr.tlk) do %>
<h4><%= spkr.tlk.title %></h4>
<% spkr.tlk.spkrs.each do |speaker| %>
<div class="tlk-tlking-image spkr-image image-spkr-<%= spkr.id %>"
<% if speaker.image.present? %>
style="background-image: url(<%= rails_blob_url(speaker.image) %>)"
<% elsif speaker.user.image.present? %>
style="background-image: url(<%= rails_blob_url(speaker.user.image) %>)"
<% end %>
>
</div>
<p><%= speaker.name %></p>
<% end %>
<% end %>
<% end %>
It tends to be considered good practice to keep the view as free of 'back end' calculations as possible. These files are often worked on by front end developers who may not even know how to code ruby, so the less of it that is in the view the better. It's also just not where it belongs in rail's Model Controller View framework.
First of all the code you've put can be simplified to:
<% #user.spkrs.each do |spkr| %>
<%= link_to show_tlk_path(spkr.tlk) do %>
<h4><%= spkr.tlk.title %></h4>
<% spkr.tlk.spkrs.each do |speaker| %>
<div class="tlk-tlking-image spkr-image image-spkr-<%= spkr.id %>"
style="background-image: url(<%= rails_blob_url((speaker.image || speaker.user.image) %>)"
>
</div>
<p><%= speaker.name %></p>
<% end %>
<% end %>
<% end %>
But as you say, if you want to handle this in a more appropriate place, I'd add a method to the Speaker class:
# app/models/speaker.rb
class Speaker << ApplicationBase
def image_for_view
image || user.image
end
end
This will let you call speaker.image_for_view which I think reads nicely in the view file itself.
Along with the great answer let me just add something that might help you to make views more clear. Might not be relevant to your question directly but might help you to get some idea how you can make views beautiful.
The first thing to make views look good are helpers. Though rails provide helpers for every controller, helpers are global meaning it can be used anywhere in any views. So, global formatings should be done with helpers. Like if you want a date formatter that needs to be used in a lot of view files, you can create a helper called date_helper.rb in app/helpers and put you desired date formatting -
module DateHelper
def formatted_date(date)
date.strftime([%m/%d/%Y')
end
end
Next is what rails people like to call a Presenter pattern. This is helpful when you don't want some logic to be shared across all views. Some logic that doesn't feel like belongs in controller or model are put there to make views readable. Suppose you have a view like below which is a bit messy -
<p>
Post title: <%= post.title.gsub("forbidden word", "") %>
<%= link_to "Read post", post, class: "w-75 p-3 text-#{post.draft? ? "orange" : "green"} border-#{post.draft? ? "orange" : "green"}" %>
</p>
To make this more beautiful you can create a presenter class named post_presenter.rb which should reside in app/presenters and write some code like -
class PostPresenter
def initialize(post)
#post = post
end
def title_without_forbidden_words
#post.title.gsub("forbidden word", "")
end
def css_color
#post.draft? ? "orange" : "green"
end
end
and in the view -
<% presenter = PostPresenter.new(post) %>
<p>
Post title: <%= presenter.title_without_forbidden_words %>
<%= link_to "Read post", post, class: "w-75 p-3 text-#{presenter.css_color} border-#{presenter.css_color}" %>
</p>
Such way a view might be more clear and also it can be lifesaver for frontend developers. This are the best two methods I found till now that makes a rails view beautiful which I always try to use.
Examples are taken from rubyguides website. Thanks to them,
I've watched this screencast to add a page title when in a view, is there a way I can do the same but add a class the body tag?
Not sure what you mean, you can do it the same way:
In a view:
<% content_for :body_class, "my_class" %>
In a layout file:
<body class="<%= yield (:body_class) %>">
I usually make a helper method for stuff like this so you can have defaults set up cleanly
application_helper.rb
def body_class(class_name="default_class")
content_for :body_class, class_name
end
view:
<% body_class "foo" %>
application.html.erb
<body class="<%= yield (:body_class) %>">
Sometimes using the current controller name as a class name we'll do:
<body class="<%= controller.controller_name %>">
I find this simpler and a bit more elegant, but of course thus you won't be able to assign individual class names.
s. Add Class To Body Using ERB In A View - Rails
In the layout page:
<% if content_for?(:body_class) %>
<body class="<%= content_for(:body_class) %>" >
<% else %>
<body>
<% end %>
In the content page:
<% content_for :body_class do 'my-body-class' end %>
I've used the accepted method in my app for a while, but never really loved how it worked, because if there is no class, you're gonna have that class=' ' on your body tag, littering your code. For my current use case, I just wanted a widescreen class (but you could easily get more advanced with different classes per your use case). I'm happy with this approach:
In your application helper:
def body_tag(&block)
content = capture(&block)
content_tag(:body, content, class: #widescreen ? "widescreen" : nil)
end
In application.html.erb
<%= body_tag do %>
<%# the rest of your content here %>
<% end %>
Then in your application controller:
private
def enable_widescreen
#widescreen = true
end
Then in any controller that you want it, just do:
before_action :enable_widescreen
Then feel free to make the class logic more advanced if you want to use it for different classes besides 'widescreen' - but the point is that this is an elegant way to allow for there NOT to be a class if you don't specify one, without
<body class>
showing up in your html.
I prefer to use the following method:
<body class="<%= content_for?(:body_class) ? yield(:body_class) : controller_name %>">
That method avoids the dreaded <body class>.
I frequently use the controller name to scope a number of styles so it's nice to not need to supply a content_for on every view if I only needed that one class.
I occasionally have to add a class to an html element based on a condition. The problem is I can't figure out a clean way of doing it. Here's an example of the stuff I've tried:
<div <%= if #status = 'success'; "class='ok'"; end %>>
some message here
</div>
OR
<% if #status == 'success' %>
<div class='success'>
<% else %>
<div>
<% end %>
some message here
</div>
I don't like the first approach because it's crowded looking and hard to read. I don't like the second approach because the nesting is screwed up. It'd be nice to put it in the model, (something like #status.css_class), but that doesn't belong there. What do most people do?
I use the first way, but with a slightly more succinct syntax:
<div class="<%= 'ok' if #status == 'success' %>">
Though usually you should represent success with boolean true or a numeric record ID, and failure with boolean false or nil. This way you can just test your variable:
<div class="<%= 'ok' if #success %>">
A second form using the ternary ?: operator is useful if you want to choose between two classes:
<div class="<%= #success ? 'good' : 'bad' %>">
Finally, you can use Rail's record tag helpers such as div_for, which will automagically set your ID and class based on the record you give it. Given a Person with id 47:
# <div id="person_47" class="person good">
<% div_for #person, class: (#success ? 'good' : 'bad') do %>
<% end %>
Avoiding logic in the views
The problem with the standard approach is that it requires logic in the form of if statements or ternaries in the view. If you have multiple conditional CSS classes mixed with default classes, then you need to put that logic into a string interpolation or ERB tag.
Here's an updated approach that avoids putting any logic into the views:
<div class="<%= class_string(ok: #success) %>">
some message here
</div>
class_string method
The class_string helper takes a hash with key/value pairs consisting of CSS class name strings and boolean values. The result of the method is a string of classes where the boolean value evaluated to true.
Sample Usage
class_names(foo: true, bar: false, baz: some_truthy_variable)
# => "foo baz"
Other Use Cases
This helper can be used within ERB tags or with Rails helpers such as link_to.
<div class="<%= class_string(ok: #success) %>">
some message here
</div>
<% div_for #person, class: class_string(ok: #success) do %>
<% end %>
<% link_to "Hello", root_path, class: class_string(ok: #success) do %>
<% end %>
Either/Or Classes
For use cases where a ternary would be necessary (e.g. #success ? 'good' : 'bad'), pass an array where the first element is the class for true and the other is for false
<div class="<%= [:good, :bad] => #success %>">
Inspired by React
This technique is inspired by an add-on called classNames (formerly known as classSet) from Facebook’s React front-end framework.
Using in your Rails projects
As of now, the class_names function does not exist in Rails, but this article shows you how to add or implement it into your projects.
class_names (Rails 6.1+)
Rails 6.1 introduces a class_names view helper
to make it easier to conditionally apply class names
in views.
Before:
<div class="<%= item.for_sale? ? 'active' : '' %>">
After:
<div class="<%= class_names(active: item.for_sale?) %>">
More examples:
class_names("foo", "bar")
# => "foo bar"
class_names({ foo: true, bar: false })
# => "foo"
class_names(nil, false, 123, "", "foo", { bar: true })
# => "123 foo bar"
Sources:
Link to the corresponding PR.
Link to the class_names docs.
You can also use the content_for helper, especially if the DOM is located in a layout and you want to set the css class depending on the partial loaded.
On the layout:
%div{class: content_for?(:css_class) ? yield(:css_class) : ''}
On the partial:
- content_for :css_class do
my_specific_class
That is it.
I've been looking around for a solution to this question for the last couple of days. It's a simple annoyance, but I hate not knowing how to do things...
Environment: Ruby, Rails, rhtml
The Problem: When I iterate a collection in rhtml I would like to reduce the number of <% %> and <%= %> tags I use. The following seems bloated:
Example
<% #products.each do |p| %>
<%= #p.name %>
<% end %>
EDIT: how do I exclude <%= %> inside of a block?
I would much rather do:
<% #products.each do |p|
puts #p.name
end %>
Certain situations could allow for use of either... However, I know that I could do this with jsp:
<% for(int i=0; i<10;i++){
System.out.print(i);
} %>
Thanks in advance for your input.
if you want to be less verbose look at haml, with your example it will be :
- #products.each do |p|
= #p.name
<% #products.each do |p|
_erbout << #p.name
end %>
_erbout is the default name of the variable that ERB (the class that's parsing your .rhtml template) uses to build its output. This is pretty ugly, and feels a bit hacky to me, but it works.
Use print instead of put.
Several other possibilities, depending on the context, if your view code seems too bloated:
Use partials. E.g.:
in your main file:
<%= render(:partial => "product", :collection => products) %>
and in the partial, just use:
<%= product.name %>
Now this seems contrived for a simple example such as this but assuming something more complex it abstracts away the looping and makes the code clearer.
Use helper methods
You could also try using something like haml to clean up the templates (along with helpers and partials).
You're going to have to use a <%= inside such a block. You can achieve the readability you want by using a <%= with a block:
<%= #products.map do |p|
p.name
end.join("\n") %>
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.