This is one of those things, that maybe so simple I'll never find it because everyone else already knows it.
I've got objects I have to check for nil in my views so I don't dereference a nil:
<%= if tax_payment.user; tax_payment.user.name; end %>
Or I could do this variant:
<%= tax_payment.user ? tax_payment.user.name : '' %>
So this is ok ... for most languages. But I feel like there must be some bit of shiny ruby or railness I'm still missing if this is the best I can do.
What about:
<%= tax_payment.user.name if tax_payment.user %>
You can also try the new Object.try syntax, pardon the pun.
This is in the shiny new Rails 2.3:
tax_payment.try(:user).try(:name)
The Ruby community has put an incredible amount of attention to automating this idiom. These are the solutions I know of:
try in Ruby on Rails
Another try
andand
A safer andand
Kernel::ergo
send-with-default
maybe
_?
if-not-nil
turtles!
method_ in Groovy style
do-or-do-not
The most well-known is probably the try method in Rails. However, it has received some criticism.
In any case, I think Ben's solution is perfectly sufficient.
I've always preferred this approach:
model:
class TaxPayment < ActiveRecord::Base
belongs_to :user
delegate :name, :to=>:user, :prefix=>true, :allow_nil=>true
end
view:
<%= tax_payment.user_name %>
http://apidock.com/rails/Module/delegate
For a little more comprehensive solution, you could check out the Introduce Null Object Refactoring. The basic mechanics of this refactoring is that instead of checking for nil in the client code you instead make sure that the provider never produces a nil in the first place, by introducing a context-specific null object and returning that.
So, return an empty string, an empty array, an empty hash or a special empty customer or empty user or something instead of just nil and then you will never need to check for nil in the first place.
So, in your case you would have something like
class NullUser < User
def name
return ''
end
end
However, in Ruby there is actually another, quite elegant, way of implementing the Introduce Null Object Refactoring: you don't actually need to introduce a Null Object, because nil is already an object! So, you could monkey-patch nil to behave as a NullUser – however, all the usual warnings and pitfalls regarding monkey-patching apply even more strongly in this case, since making nil silently swallow NoMethodErrors or something like that can totally mess up your debugging experience and make it really hard to track down cases where there is a nil that shouldn't be there (as opposed to a nil that serves as a Null Object).
I just do
<%= tax_payment.user.name rescue '' %>
Another option, which makes sense occasionally...
If tax_payment.user returns nil, nil.to_s (an empty string) is printed, which is harmless. If there is a user, it will print the user's name.
You could write a helper method which looks like this:
def print_if_present(var)
var ? var : ""
end
And then use it like this (in the view):
<%= print_if_present(your_var) %>
If the var is nil, it just prints nothing without raising an exception.
Related
I often (very often) see code like this in the conditions:
<% catalog.each do |article| %>
<% if article.image.nil? %>
...
or (e.g. seen there)
<% catalog.each do |article| %>
<% if article.image.exists? %>
...
However we all know that nil interprets like false in the conditions. ActiveRecord query returns nil if nothing found.
Why not simply write:
<% unless article.image %>
(unless there is article do something)
instead of
<% if article.image.nil? %>
(if there is nothing at article.image do something)
and
<% if article.image %> instead of <% if article.image.exists? %>
I usually write the code without nil? and exists?. What am I missing? Is there any pitfalls?
In your example, and in many typical RESTful Rails patterns, you're correct that using the implicit version is identical in behavior.
But there are certainly suations where nil? and exists? are necessary and/or more readable...
For instance, nil? is the only option when you're reading a boolean attribute, and you need different behavior for false vs nil (since both are falsey in Ruby).
Or, assume in your example, that each Article has many images, and you define a method to get an Article's first image according to display order:
def primary_image
images.order(display_order: "ASC").limit(1)
end
In this case, if your Article doesn't have any images, primary_image will return an empty collection, which is truthy. So, in this case, you'd need to use exists? (or present?). Rails provides several convenience methods for checking data records.
(or make sure the primary_image method returns nil or false when the collection is empty)
Personally, I rarely use either one of them. Like you suggested, I mostly just use if or unless depends on the situation without bothering with nil? or exists?
As for the difference between the two:
nil? is a Ruby method to see whether the object is nil or not.
exists? is a Rails method to see whether the record exists in the database
I guess, just use whichever is more efficient. I have been trying to convert my codes to use try to avoid no method error.
Most of the time it's just to be more explicit I suppose.
In your example above it doesn't matter if you check explicitly or implicitly.
But if you want to check explicitly for nil for example, you use the #nil? because sometimes you want to react differently if false is returned.
As an example: false and nil are often not interchangeable for boolean values in a database.
Like #lusketeer already said is nil? a method of the ruby standard library and exists? a method of the Rails framework.
But I think almost always you are good with the implicit way.
In a Rails view, one can use try to output only if there is a value in the database, e.g
#model.try(:date)
And one can chain trys if, for example, the output is needed as a string
#model.try(:date).try(:to_s)
But what if I need to call a scoped format? I've tried
#model.try(:date).try(:to_s(:long))
#model.try(:date).try(:to_s).try(:long)
What is the correct syntax for this? And what is a good reference for more explanation?
Thanks
From the fine manual:
try(*a, &b)
[...]
try also accepts arguments and/or a block, for the method it is trying
Person.try(:find, 1)
So I think you want:
#model.try(:date).try(:to_s, :long)
This one won't work:
#model.try(:date).try(:to_s(:long))
because you're trying to access the :to_s symbol as a method (:to_s(:long)). This one won't work:
#model.try(:date).try(:to_s).try(:long)
because you're trying to call the long method on what to_s returns and you probably don't have a String#long method defined.
mu is too short's answer shows the correct usage for the try method with parameters:
#model.try(:date).try(:to_s, :long)
However, if you are using Ruby 2.3 or later, you should stop using try and give the safe navigation operator a try (no pun intended):
#model&.date&.to_s(:long)
The following answer is here for historical purposes – adding a rescue nil to the end of statements is considered bad practice, since it suppresses all exceptions:
For long chains that can fail, I'd rather use:
#model.date.to_s(:long) rescue nil
Instead of filling up my view with try(...) calls.
Also, try to use I18n.localize for date formatting, like this:
l #model.date, format: :long rescue nil
See:
http://rails-bestpractices.com/posts/42-use-i18n-localize-for-date-time-formating
In case you often use try chains without blocks, an option is to extend the Object class:
class Object
def try_chain(*args)
args.inject(self) do |result, method|
result.try(method)
end
end
end
And then simply use #model.try_chain(:date, :to_s)
So I've got an object in my database with a date field, except sometimes it will be nil. Is there a way in the view I can show this as a string value. Something like TBA maybe?
<%= #event.date || "TBA" %> should do it.
In response to your comments, yes, you could do this in the model but it's a bad idea. Why?
First of all, it is about presentation of the data so for that reason it belongs in the view.
Secondly, it could break things. If you did it in the model, #event.date would sometimes return a date and sometimes a string. What would happen if you called #event.date.hour and date was "TBA"? You'd get an error. The only fix would be to check for it everywhere, which would be horrible.
If you really are going to be doing it a lot you could create a helper method in application_helper.rb that could look something like this:
def date_or_tba(date)
date || "TBA"
end
So you could then write in your view:
<%= date_or_tba #event.date %>
Which isn't much less typing but would have the not inconsiderable advantage of restricting the use of the string "TBA" to only one place - which means if you ever need to change it (for I18n purposes for example) - it's really easy.
I'm sure this has been asked already, but I can't find the answer.
I have a Project model, which has a belongs_to relationship with my Client model. A client has a name, but a project doesn't necessarily have a client.
In my view, I've got code like this:
<%=h project.client && project.client.name %>
because if the project doesn't have a client then trying to access project.client.name causes a NoMethodError (nil doesn't have a method called name).
The question is, is it acceptable to have this kind of nil checking in the view, or should I be looking for another way around it?
Just use
project.client.try(:name)
I think its perfectly acceptable - this is view logic, you are more or less deciding whether or not to show portions of your view, based on whether there is data.
I run into this all the time, and yes it's annoying. Even when there is supposed to never be a nil, dirty data that I inherited sometimes triggers it.
Your solution is one way of handling it. You could also add a method to Project called client_name that displays the client name if it exists, but then you are linking the models together more than some people recommend.
def client_name
client && client.name
end
You could also make a helper method to do it, but you can end up writing a lot of them. :)
As mentioned by Skilldrick below, this is also useful to add a default string:
def client_name
client ? client.name : "no client"
end
You can use delegate in your Project class, so this way you will respect the Law of demeter which says that you should "talk only to your immediate friends".
project.rb
class Project
delegate :name, to: :client, prefix: true, allow_nil: true
end
So this way the project object will know where to ask about the client's name:
#You can now call
project.client_name
See more about delegate in the Rails documentation.
my hacky solution is to yield a block and rescue the error. Many would say using rescue as logic is very bad form. Just don't use this where you would actually need to know when something is nil and shouldn't be.
In application_helper.rb:
def none_on_fail
begin
return yield
rescue
return "(none entered)"
end
end
Then in the view:
<%= none_on_fail { project.client.name } %>
Then methods can be chained as deep as needed and it can be used on any method BUT it will cover up other potential problems with models/relationships/methods if they exist. I would equate it to taking out a splinter with a flamethrower. Very effective with painful consequences if used improperly.
I think these checks can usually be eliminated with a bit of thought. This has the benefit of keeping your view code cleaner, and more importantly, keeping logic out of the view layer, which is a best practice. Some templating engines don't allow any logic in the view.
There are at least a couple of scenarios. Let's say you have a show action that depends on an instance variable. I'd say if the record is not found the controller should not render the html, by redirecting or something else. If you have a loop in the view for an array, use #array.each do |a| end so that it doesn't evaluate if the array is empty. If you truly want an application default in the view, try loading it from a config file, e.g. #page_title || #{#APP_CONFIG['page_title']} (see Railscasts #85). Remember you may want to change these strings later, for example translating the UI.
Those are a couple scenarios where presence checks and usage of try can be avoided. I'd try to avoid them if possible. If you can't avoid them, I'd put the conditional checks in a view helper and add a helper unit test for it to verify (and document) both code paths.
I'm trying to flatten an array for my form.
def update
#tour = Tour.find(params[:id])
params[:tour][:hotel_ids][0] = params[:tour][:hotel_ids][0].split(',')
...
This results in:
"hotel_ids"=>[["1","2"]]
Naturally I want it to be
"hotel_ids"=>["1","2"]
My Form:
<%= text_field_tag 'tour[hotel_ids][]', nil %>
Hope anyone can help with this.
EDIT
I've gotten it to work, somehow. This might be a bad way to do it though:
I changed the text_field that get's the array from jquery to:
<%= text_field_tag 'tour[h_ids][]', nil %>
then in my controller I did:
params[:tour][:hotel_ids] = params[:tour][:h_ids][0].split(',')
And this works, I had to add h_ids to attr_accessor though. And it will probably be a big WTF for anyone reading the coder later... but is this acceptable?
This is ruby!
params[:tour][:hotel_ids][0].flatten!
should do the trick!
ps: the '!' is important here, as it causes the 'flatten' to be saved to the calling object.
pps: for those ruby-related questions I strongly suggest experimenting with the irb or script/console. You can take your object and ask for
object.inspect
object.methods
object.class
This is really useful when debugging and discovering what ruby can do for you.
Simply use <%= text_field_tag 'tour[hotel_ids]', nil %> here, and then split like you do in example.
What really happens in your example is that Rails get param(-s) tour[hotel_ids][] in request and it thinks: "ok, so params[:tour][:hotel_ids] is an array, so I'll just push every value with this name as next values to this array", and you get exactly this behavior, you have one element in params[:tour][:hotel_ids] array, which is your value ("1,2"). If you don't need (or don't want) to assign multiple values to same param then don't create array (don't add [] at the end of the name)
Edit:
You can also go easy way (if you only want answer to posted question, not solution to problem why you have now what you expect) and just change your line in controller to:
params[:tour][:hotel_ids] = params[:tour][:hotel_ids][0].split(',')
#split returns array and in your example you assigned this new array to first position of another array. That's why you had array-in-array.