I've got a Rails 3 application where I'm using quite a few conditional statements to change the design of the page. What is the best practice for keeping the logic out of the view for having such drastic amounts of conditionals?
Hypothetical Example:
<% unless #ethos.blank? %>
<%= unless #work.nil? do %>
<%= link_to "Add Work", work_path %>
<% end %>
<%= #ethos.tagline %>
<% end %>
I've got many more conditionals inside of other conditionals. What is the best way to manage this inside of one view?
You should avoid complex conditionals (and most conditionals) in views. Extract them to a Helper, or better yet, to some kind of "presenter" so that you can work with a receiver instead of those "global looking/feeling helpers"
SomeHelper
module SomeHelper
def work_link
(#ethos.present? && #work) ? link_to("Add Work", work_path) : nil
end
end
View
<%= work_link %>
<%= #ethos.tagline if #ethos.present? %>
If #ethos is likely to be nil as opposed to an empty [] array, you could instead use:
<%= #ethos.try :tagline %>
Also note that in your original view <%= unless #work.nil? do %> should have been using a - and not a =.
Oh, and I encourage you to use HAML over ERB. With HAML, the view looks like this (easier to read, isn't it) :
= work_link %>
= #ethos. try :tagline
Your original view would look like this in HAML (remember, avoid conditionals in views as much as possible!)
- unless #ethos.blank?
- unless #work.nil? do
= link_to "Add Work", work_path
= #ethos.tagline
If the code works, what is your concern? Is it aesthetics or are you having difficulty reasoning about what the code is doing because there is so much nesting?.
The simplest solution is probably just to move the conditionals inline.
<%= link_to("Add Work", work_path) if #ethos.present? && #work %>
<%= #ethos.tagline if #ethos.present? %>
This will improve readability (and therefore maintainability), though it may not go far enough to keep the Rails purists happy. Zabba's answer presents several great choices that go further down the rabbit hole.
Related
I have this logic currently in my view
<% tools_count = #job.tools.count - 1 %>
<% count = 0 %>
<% #job.tools.each do |u|%>
<%= u.name %>
<% if count != tools_count %>
<% count += 1 %>
<%= "," %>
<%end%>
<% end %>
Which just loops through some users relations and puts in a , unless it is the end of the list.
My question: This kind of logic looks really messy and clogs up my views I know there must be a better way of doing this by moving it into the controller or maybe model, does anyone know the correct way to do this kind of logic?
You can add a method like this to your Job model:
def tool_names
tools.map(&:name).join(',')
end
And use it in your view like this:
<%= #job.tool_names %>
There are couple of ways to avoid putting this kind of logic in the view layer:
Create an instance method in the model class (as spickermann suggested)
This will work for simple logic and simple projects. However, when you will want to use some helpers from ActionView::Helpers such as jobs_path or number_to_currency, a model is not a good place for it.
Create a helper method in helper modules eq. JobHelpers
Generally you can put any helper methods related to view layer in helpers. For example to share common methods for building a view components.
Use the decorator/presenter pattern and put there the view logic so model won't be polluted. Here is some more explanation about the pattern and sample implementation using draper gem: http://johnotander.com/rails/2014/03/07/decorators-on-rails/
You can do it in a single line like
<%= #job.tools.map(&:name).join(',') %>
In rails, I often run into the situation where inside the views I'll do something like
<% if #some_condition_previusly_established_in_a_controller %>
<div class="one">123</div>
<% else %>
<div class="two">something else</div>
<% end %>
It looks a bit cluttery. Is this an acceptable way of working with views or not?
Unless you can think of a way to re-write this as a helper method, you're basically stuck with it looking kind of ugly. That's just how ERB is, as it was intended to be a minimal way of injecting Ruby into an otherwise plain-text template, not as something necessarily streamlined or elegant.
The good news is a syntax-highlighting editor will usually make your <% ... %> ERB blocks look visually different from your HTML so that can dramatically improve readability.
It's also why other representations like HAML have been created where that syntax is a lot less cluttered:
- if some_condition_previusly_established_in_a_controller
.one 123
- else
.two something else
For one or two such conditional logic in your views, I guess its fine but when your code gets bigger and you have multiple if..else..end and looks "cluttery", I think you should look at implementing "Presenter Pattern" which greatly cleans up your views by separating your logic to Presenters.
Here is a great tutorial I followed from Ryan Bates in his Rails Casts series on "Presenter Patterns from scratch". http://railscasts.com/episodes/287-presenters-from-scratch.
Have you tried?
<% #some_condition_previusly_established_in_a_controller ? <div class="one">123</div> : <div class="two">something else</div> %>
If your view contains lots of tags and HTML elements, you can put them into partials and logic into model
View:
<%= render :partial => #model.status %>
<%= render :partial => "file/path/#{#model.status}" %> # if your partial is in some different folder
If your status is one, then it would render the file _one.html.erb
If it is two, then it would render the file _two.html.erb automatically.
Model:
def status
if #some_condition
"one"
else
"two"
end
end
Yes, that is the standard (and yes, it looks cluttery).
If you're looking for a possibly cleaner alternative, check out: Conditional tag wrapping in Rails / ERB
You can always move the logic to the controller and leave the view clean(er).
Controller:
if #some_condition
#div_class = :one
#div_content = 123
else
#div_class = :two
#div_content = 'something else'
end
View:
<div class="<%= #div_class %>"><%= #div_content %></div>
Or using a helper:
<%= content_tag :div, #div_content, class: #div_class %>
One of the most common reasons my web application fails is because a user sometimes lacks a certain attribute that a view expects it to have. For instance, most users in my application have an education (school, degree, etc.) entry in our system, but some users do not. Assuming my view looks something like this:
<% #educations.each do |education| %>
<%= education.school %>
<%= education.degree %>
<% end %>
I want to avoid "Pokemon" exception handling and feel that there has to be a better way around dealing with a "undefined method `degree' for nil:NilClass" error in the case that a user does not have an education entry in our database. This just seems like an ugly/tedious fix:
<% #educations.each do |education| %>
<% if education.school %>
<%= education.school %>
<% end %>
<% if education.degree %>
<%= education.degree %>
<% end %>
<% end %>
Any input is appreciated. Thank you!
As long as you know the first object you're working on won't be nil, the easiest way is to just do this:
- #educations.each do |education|
= education.try :school
= education.try :degree
The #try method is pretty handy. You can also call .to_s on anything you think might be nil, Ie:
- #educations.each do |education|
= education.school.to_s
= education.degree.to_s
This will convert nils to an empty string. This isn't as useful in the view IMO, but comes in handy a lot of times if you have input that is expecting to be a string and might be empty. Ie a method like:
def put_in_parenthesis(string)
"(" + string.to_s + ")"
end
You have a couple of options here.
The easiest to implement is the try method. It is used like so:
<%= education.try( :degree ) %>
The problem is that try() is viewed as a bit of an anti-pattern. As the reference indicates, you can achieve similar functionality with something like:
<%= education && education.degree %>
This isn't really a lot different, intellectually, in my opinion. A popular way of handling this a little more cleanly is the Null Object pattern, which is basically an object with defined neutral ("null") behavior.
I have this code
<% if approved %>
<td>Flow Number</td>
<% end %>
and I'd like to shorten it using statement modifiers. Of course I can use
<%="<td>Flow Number</td>" if approved -%>
but is there a shorter way? I'd also like to get the markup out of quotes.
You could use "content_tag", which isn't actually shorter, but may be more appealing, keeping HTML out of your ruby blocks:
<%= content_tag :td, "Flow Number" if approved %>
Otherwise, you could consider writing a helper - which may be appealing if you need to reuse similar logic throughout the page (or over several pages).
Maybe HAML?
That'd be:
- if approved?
%td Flow Number
Not exactly what you're after I know.
Yeah, I think a helper method using content_tag internally would be the best short way.
Using a helper method, you could also yield to the desired output like this:
# in view helper
def show_if(condition, wrapper_tag)
condition ? content_tag(wrapper_tag, yield) : ''
end
# in view
<%= show_if(approved, :td) {'Flow Number'} %>
or
# in view helper
def show_if(condition)
condition ? yield : ''
end
# in view
<% show_if(approved) do %>
<td>Flow Number</td>
<% end %>
I like this last method for a nice generic way to show or hide whole blocks based on a condition. Hope that helps!
we use
(1..10).each do |i|
p i
end
so that a value is "yield" to i in a block...
but what about
<% form_for #story do |f| %>
<%= f.text_field :name %>
<% end %>
there is no loop at all... why do we need to make it look like a loop? Can't we do it without making it look like a loop? (write in another way)?
Also, must be use a Story instance here? Can't we just use :story and achieve the same result? The #story instance is just newly created and has no data at all -- does it actually help creating the form? Can't :story suffice already? thanks.
Update:
is the idea similar to: (just pseudo code)
with_model_give_form (#story) do |f|
f.begin_form
f.text_field :name
f.end_form
end
so i think the block method will save the begin_form and end_form because it automatically add the begin and end before and after calling the block. is that the main benefit?
I think you have misunderstood the way Ruby works in this case.
It is true that you put everything in a block, but it has nothing to do with a loop.
What you actually do in
(1..10).each do |i|
p i
end
is creating the block {|i| p(i); } and sending it to the function Range.each()
It is the same thing with the form_for. What you actually do is creating the block {|f| puts( f.text_field(:name)); } and passing it to the form_for function. (now it doesn't use puts, but some string concatenation, but you get the idea).
So it's basically not a loop, but a lambda function that will be called several times when called in a loop.
Here is some more information about blocks
Update:
Regarding your update. In a way, yes, that is the main benefit. It is not completely true, but you get the idea. The purpose for that block (and a lot other) are to make the code easier to read and understand. And also to speed up the development.
Dividing code into blocks have always been sought for, just look at the concept of functions, classes and other statements.
for instance, the form_for is mostly for speeding up the development. Yes, it creates the start and end tags, but that is not all.
<% form_for #story do |f| %>
<%= f.text_field :name -%>
<% end %>
Could actually be written something like:
<form action="<%= polymorphic_edit_path(#story) -%>"
id="<%= #story.class_name.underscore -%>">
<%= text_field :story, :name -%>
</form>
of course, it wouldn't exactly be like that, this is a lot simplified and the form_for function can do a lot more than that, but at least you should get the picture.
A "yield" does not always imply a loop - it's more of a "block" concept, it just happens to manifest itself in more of a iteration / loop context. The more you think about yield being a loop construct the more you pigeon hole yourself and prevent yourself from truly learning the awesomeness of blocks.
As for :story, yeah, it is kind of lame. You would think that Rails would just constantize it and infer its model from there. You could also just also Story.new in your view, versus doing that in your controller, but yeah, it's the same thing.