Checking for nested param in Rails 4 - ruby-on-rails

In my controller, I used to be able to say:
if params[:business][:branch]
After Rails 4, when I try the same I get:
NoMethodError: undefined method `[]' for nil:NilClass
This is the only way I can find to do it in a single line now.
params.has_key?(:business) ? params[:business].has_key?(:branch_id) : false
Kind of verbose.

There are many possible answers, one of which you proposed in your question.
I really love the Hash#fetch method. It returns the value associated with a Hash key, and optionally allows you to supply a default to return in case the key is missing. With that, you can make a construct like this:
if params.fetch(:business, {}).fetch(:branch, false)
# do stuff
end
This way you don't even need any conditionals or Hash key presence checks in your code.

I am a fan of
if params[:business][:branch].present?
..
end
just because it keeps the params[sym] form so it's easier to read.
You can also use blank? http://api.rubyonrails.org/classes/Object.html#method-i-blank-3F
unless params[:one].blank? && params[:two].blank?
will return true if its empty or nil
also... that will not work if you are testing boolean values.. since
>> false.blank?
=> true
in that case you could use
unless params[:one].to_s.blank? && params[:two].to_s.blank?

Related

Check if a method/property is defined or not before calling

I am a newbie to RoR. I am trying to figure out how to check if a property is defined or not in the environment file(development.rb in this case).
We have a property defined in development.rb file, something like:
config.user = 'test-user'
Now in the code, I use it by calling:
Rails.application.config.user
which gives me the required value.
But the problem is this configuration may be disabled sometimes. So, I want to check if this property is defined or not before assigning it.
Something like
user_name = (if Rails.application.config.user is available)?
Rails.application.config.user : 'some_other_value'
I tried defined? and respond_to but did not work.
Any help/suggestions are appreciated. Thanks!
If you are using rails (it comes from active_support actually) every object will have a try method which also does what you want:
user_name = Rails.application.config.try(:user)
And ruby 2.3 brought us &.:
user_name = Rails.application.config&.user
Note that in both cases you can use the return value implicitly if nil should not be a valid user_name (because try and &. will return nil if config does not respond to user):
user_name = Rails.application.config&.user || 'Guest' # ruby >= 2.3.0
user_name = Rails.application.config.try(:user) || 'Guest'
If you are calling that piece of code more than twice (my rule of thumb), you should consider extracting it into an own method, e.g. Application#user_name.
Correction
In afterthought, I figured that &. will probably not work as expected here, because config is probably not nil. It really depends on the setting (is user configured, but empty? How is config implemented?). I keep that part of the answer though because it might be of interest for related problems (but remember: you'll need ruby 2.3 or later).
If there is a config.user defined in every environment, but sometimes it has a value, and sometimes it doesn't, for example, it could be nil or an empty string, you can use present?:
Rails.application.config.user.present?
If it is not defined, you will get a NoMethodError in the case above, so you can rescue it:
begin
user_name = Rails.application.config.user.present? ? Rails.application.config.user : 'some_other_value'
rescue NoMethodError
user_name = 'some_other_value'
end
respond_to? should also work, just make sure you don't confuse it with respond_to, which is a Rails method. It might look something like this:
if Rails.application.config.respond_to?(:user) && Rails.application.config.user.present?
user_name = Rails.application.config.user
else
user_name = 'some_other_value'
end

Reliable String interpolation in Rails

What is the best way to ensure that a model exists before doing string interpolation? I have a variable user, and I need to see what the user's major is. A table in between named user_attributes has the user's info.
#{user.user_attribute.major.name}
The user may not have specified a major yet, in which case they wouldn't have a major model instance yet. So when I try and get the name of the major, I would get an "undefined method on nil class type" error. Any advice on how to safely do this?
You could avoid try and add a method to your model or decorator..
def major_name
user_attribute.major && user_attribute.major.name
end
OR
def major_name
user_attribute.major.name if user_attribute.major?
end
Check: https://codereview.stackexchange.com/questions/28610/handling-nil-trying-to-avoid-try
You can use try method:
# if model is present
{user.user_attribute.major.try(:name)} # => "<MAJOR_NAME>"
# if model is NOT present
{user.user_attribute.major.try(:name)} # => ""
You can read more about try.
You could use the lonely operator. It is like try, but slightly less functional (which doesn't matter in your case).
user.user_attribute.major&.name
This might well be the 'worst' answer to ruby puritans, but it works for me in some scenarios:
"#{user.user_attribute.major.name rescue nil}"

Check if hash is nil before checking specific attribute

What I'm currently doing is
"Hello" if options && options[:greet]
What I would like to do is cut that line down. If options is nil, options[:greet] obviously will be too. Does Ruby/Rails provide a method that offers this "hash checking" ability? Or perhaps there's a way of writing this more succinctly?
There's also one more shortcut, I tend to use it more often, when I don't have control over options variable (i.e. it may be either nil or hash):
options.to_h[:greet] # just convert it to a hash before key access
Note, that it works only starting from Ruby 2.0.
I would argue that that line is perfectly fine.
Perhaps it might make sense to ensure that options are always set. If the options are passed in as a parameter to a method, you might want to set a default:
def method_name(options = {})
Or you might want to initialize options with an empty hash if nil before you start using them:
options ||= {}
Ruby on Rails also offers the try method that does not fail when you call methods on nil:
options.try([], :greet)

Checking for nil strings in Rails view

I'm looking for a good shortcut for Nil checking in my Rails views. I've seen other questions on SO about this, but none seem to simplify this as much as I'd like. What I'd like is a short syntax to return an empty string "" if a particular value is nil, otherwise return the value.
There is a suggestion here which I am inclined to try out. It basically allows a statement like this:
user.photo._?.url
-- or --
user.photo.url._?
Is this a good idea or is it fraught with peril?
My other option would be to handle nils on my models, but that seems too global.
You should check try method which runs a provided method on the object and returns the value if the object in question is not nil. Otherwise it'll just return nil.
Example
# We have a user model with name field
u = User.first
# case 1 : u is not nil
u.try(:name)
=> Foo Bar
# case 2 : u is nil
u.try(:name)
=> nil
# in your case
user.photo.try(:url)
For details have a look at this blog post.
The idiomatic Ruby way to accomplish this is the || operator, which will return the value of the right-hand expression if the left-hand expression is nil (or false):
puts(user.photo.url || '')
Any moderately experienced Ruby programmer will understand exactly what that does. If you write a custom _? method, I now have to go look up the purpose of that method and remember what it does and hope that it always does the right thing. I generally find that sticking to idiomatic code is far more beneficial than saving a few keystrokes here and there.
try this:
user && user.photo && user.photo.url.present?
This will not blow up if user is nil or user.photo is nil
What about just using nil?
someObject.nil? # true if someOBj is nil
Or am I misunderstanding what you want?
I suspect your options are problematic, because if photo is nil, both of your statements should return 'Undefined method'.
You shouldn't even need to check for '.nil?'. Since you implied your checking is in the view, and not in the controller, I imagine you are checking an #instance variable, which will always be nil if undefined. So just do:
if #someObject
...
else
...
If you are putting the conditional in your controller, then again, just use an #instance variable and you'll always have at least nil, if undefined.

What is a better way to check for a nil object before calling a method on it?

I have this method call I have to use...
financial_document.assets.length
But financial_document.assets could be nil.
I could use...
financial_document.assets.nil? ? '0' : financial_document.assets.length
Is there a less repetitive way to do that?
Dave W. Smith is on the right track.
Check this out: http://www.nach-vorne.de/2007/4/24/attr_accessor-on-steroids
One easy solution would look something like this:
class FinancialDocument
attr_accessor :assets
def assets
#assets ||= Array.new
end
...
end
Personally, I would use the or operator/keyword:
(financial_document.assets or []).length
Either way, .length is called on an array, giving you 0 if nil.
The less repetitive way of dealing with this is to ensure that financial_document.assets is always a non-null object, by arranging for it to hold an appropriate sentinel value (e.g., an empty collection, or a special object that has degenerate behavior).
See The Null Object Pattern.
Case 1:
financial_document and assets have has many relationship. In this case, financial_document.assets always returns an array. So financial_document.assets.size would give you 0 if no matching child entry is found, and size otherwise.
Case 2:
assets is just a method/attribute in financial_document.
Then have the assets method return array, so that you can always call .size on it. Just like Joel has pointed out.
In such case I use andand gem:
financial_document.assets.andand.length || 0
A more generic way to solve this class of problems is to add a try method to Object:
##
# #user.name unless #user.nil?
# vs
# #user.try(:name)
#
def try(method, *args, &block)
return nil unless method
return nil if is_a?(NilClass) and [:id, 'id'].include?(method)
self.send(method, *args, &block) if respond_to?(method)
end
I believe ruby 1.9 already has a try method on Object.
Then financial_document.assets.try(:length).to_i would achieve your desired result.
This is because nil.to_i returns 0
financial_document.assets.try(:length) || 0
try is a method that will invoke the object's method if its non nil otherwise just return nil. And try on nil methods will always return nil instead of throwing an exception.
http://api.rubyonrails.org/classes/Object.html#method-i-try
This is the Ruby way to do this!
You can do it without additional gems. I have been using ||, andand, try, but the following looks simpler. I think it is the ruby way to confirm to Dave's null object pattern.
financial_document.assets.to_a.length
This being Ruby, you could add a length method to NilClass and have it always return 0.
You can make it a bit shorter:
financial_document.assets ? financial_document.assets.length : '0'
because
financial_document.assets == !financial_document.assets.nil?
but in general, IMHO there's no less repetitive way, only various workarounds. (And this is one of the things I don't like so much in Ruby.) You can make sure that objects aren't null (as other people are suggesting here) - but you can't do that everywhere. You can wrap up the nil-checking code in helper methods or in begin-rescue blocks.
For example, rather than adding length method to nil object (which is IMHO a dirty hack), I'd wrote a helper method - a "length getter":
def fd_length(financial_document)
financial_document.assets ? financial_document.assets.length : '0'
end
Something in the model that returns 0 or the length. This keeps you from having to do a convaluted thing in your view. Things like this can normally be done in the model.
class FinancialDocument
def assets_length
assets.length.blank? 0 : assets.length
end
end

Resources