Check if hash is nil before checking specific attribute - ruby-on-rails

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)

Related

In Rails 5, is there a way to modify the underlying params in a controller? Or give it a default?

In a Rails 5 controller, you can call params and it returns a hash of the parameters from the request.
But you can't modify the params that way. Because what you're modifying is a copy of the params hash values, not a reference to the underlying params.
params[:starting_value] ||= "abc" # doesn't work for my purposes
What you're supposed to do is store the values elsewhere.
#starting_value = params[:starting_value] || "abc"
But if a bunch of other places in the code expect params[:starting_value], then this solution might require some messy changes.
Is there a way to set the default value of a param in the controller? Or am I going to have to do it the slightly messier way.
I could also accomplish what I want with a redirect, but that isn't ideal either.
I think you're looking for the merge! method. Docs Here
params = params.merge!(:starting_value, 'abc)
It returns the original params with the new one merged in or overwritten. Be aware that merge without an exclamation mark does not modify in place. You need it to keep the changes.

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.

Making dynamic scope helpers in Rails

Several of my partials can be rendered in two "modes". If full_display is false, I don't render several fields. To make things easy I wanted to make one of the "modes" default - if full_display is not defined, treat it as false. I came up with this code:
(((not defined?(full_display).nil?) && full_display) || false)
Quite a lot to move around. It would be nice to put it inside a helper or something, but since Ruby has only lexical scope I can't think of any good way to do it.
Bad ideas that I've already tried:
on the top of the partial do <% display = long code that is above %> and use display through the code, but creating local variables in a view looks bad and has to be copied into every partial using full_display.
wrap it inside a string, put it into a helper and use eval(display_helper) in view, but obviously this creates security concerns.
That's quite a convoluted way of saying something as simple as:
defined?(full_display) && full_display
In Ruby there are two values that are non-true, nil and false, all others evaluate as true, which includes 0, empty string, among others, that would otherwise evaluate as false in Perl, PHP, and C. Testing with .nil? is usually reserved for those rare cases where you want to differentiate between false and undefined, and this is sometimes the case with boolean fields where a missing value is different from a false value.
In any case, in the view space it is a lot easier to assign defaults using the or-equals operator ||= like this:
<% full_display ||= false %>
That is equivalent to full_display = full_display || false. This does two things. First, it defines the full_display variable even if it was not previously created, and secondly it ensures that the it will contain a value that is at least false, never undefined.
You will see the ||= default pattern a lot in Ruby code as it's an easy way to assign something in the case where it will be nil otherwise.

Why are these default parameters defined as they are?

I'm currently learning Ruby and RoR and I stumbled across this declaration:
link_to_remote(name, options = {}, html_options = nil)
I discovered that this pattern is used on several other Rails functions.
Why are the default values defined that way? Why not one of these two?
... options = {}, html_options = {})
... options = nil, html_options = nil)
Is this some kind of convention I should follow in my own functions, too?
The method is defined like this:
link_to_function(name, remote_function(options), html_options || options.delete(:html))
This allows you to specify the html_options as part of the options hash instead of as a separate parameter. On the other hand, the options hash is always passed to remote_function, so we need it.
It's also a bit more efficient to use the singleton nil rather than construct an array that will never be used every time the method is called. I wouldn't say this reason is so compelling that I wouldn't use {} if it made the resulting code cleaner, but in the absence of any other consideration, it seems like the logical thing to do.
Not an expert on it, but for options it might be because the calling code uses the merge method to combine whatever you pass in with the assumed defaults; setting it to nil would just remove all of the options. I'm not sure about html_options, but it might be something similar to that.
html_options might call a block and have something defined to check if it's given a hash or not. Like I said I'm not an expert but that might be why. I just checked on an Ubuntu VM and according to irb, an empty hash evaluates to true so that might be why. The calling code probably uses a block with something line:
if html_options
# do stuff
end
so it's nil by default because the code would execute and probably give you a nil error with an empty Hash

defined? method in Ruby and Rails

I have a quite old templating system written on top of ERB. It relies on ERB templates stored in database. Those are read and rendered. When I want to pass data from one template to another I use the :locals parameter to Rails render method. For setting default variables of those variables in some templates I use the defined? method which simply tells me if local variable has been defined and if not I initialize it with default value like this:
unless defined?(perex)
perex = true
end
I am upgrading the app to latest Rails and I see some weird behavior. Basically this sometimes works (sometimes perex is undefined) and sometimes it does not (perex is defined and set to nil). This happens without anything else changing.
I have two questions:
Is there any better way other than using defined? which is proving unreliable (was reliable for several years on top Rails 1.6)? Such a way should not result in me rewriting all the templates.
I have been going through Ruby docs and was not able to find anything about defined? method. Was it deprecated or am I just plain blind?
Edit: The actual issue was caused by what seems to be a Ruby/eRB bug. Sometimes the unless statement would work, but sometimes not. The weird thing is that even if the second line got executed perex stil stayed nil to the rest of the world. Removing defined? resolved that.
First: actually, defined? is an operator.
Second: if I understand your question correctly, the way to do it is with this Ruby idiom:
perex ||= true
That'll assign true to perex if it's undefined or nil. It's not exactly what your example does, since yours doesn't evaluate the assignment when the value is nil, but if you are relying on that then, in my opinion, without seeing it, you're not writing clear code.
Edit: As Honza noted, the statement above will replace the value of perex when it's false. Then I propose the following to rewrite the minimum number of lines:
perex ||= perex.nil? # Assign true only when perex is undefined or nil
The safest way of testing if a local is defined in a Rails template is:
local_assigns[:perex]
This is documented in the Rails API together with the explanation that defined? cannot be used because of a implementation restriction.
Per mislav's answer, I went looking for that documentation in the Rails API, and found it in Class ActionView::Base (under the heading "Passing local variables to sub templates"). It was hardly worth the search, though, since it barely said anything more than mislav did. Except that it recommends this pattern:
if local_assigns.has_key? :perex
Taking into considerationg mislav's original answer and KenB's elaboration, I think the following is the absolute best approach (though I'm open to opinion). It utilizes Ruby's Hash#fetch method to fallback on an alternate value if the key does not exist in the original hash.
perex = local_assigns.fetch(:perex, true)
This is even better than the ||= method that most users will suggest since sometimes you will want to allow false values. For example, the following code will never allow a false value to be passed in:
perex = local_assigns[:perex] || true

Resources