defined? method in Ruby and Rails - ruby-on-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

Related

Is there a way to directly access and/or manipulate the `locals` hash from inside of a `partial` Rails ERB template?

I am currently splitting some code out to make it more reusable, and other code more succinct.
I realize I am passing a good bit of options into the locals hash, besides just the form variable, and all of these seem to only apply directly to the input object I am creating.
I was thinking of merging the locals hash into another that contains some defaults, and passing that to the input creation function.
After some researching API docs like these:
Ruby on Rails - API - ActionView::Renderer
Ruby on Rails - API - ActionView::PartialRenderer
and digging around to find these last 2 methods in the stack trace:
From: .../.rvm/gems/ruby-2.1.5#ux-rails3.2/gems/actionpack-3.2.22.5/lib/action_view/template.rb # line 145 ActionView::Template#render_without_mini_profiler: ... 2.1.5 (#<ActionView::Template:0x007fa41e138cc8>):0 >
From: .../app/views/shared/_checkbox_group.html.erb # line 3 ActionView::CompiledTemplates#_app_views_shared__checkbox_group_html_erb___4197967411385159258_70171427980900: ... 2.1.5 (#<#<Class:0x007fa40f30fec0>:0x007fa40fe71c50>):0 >
RVM, Ruby, Rails, Formtastic, Pry, Byebug, & Pry-Byebug.
Old, not-so-helpful workaround answer
As it may be convenient from the question's phrasing, one method is to not spend time diving into the Rails template rendering framework code and documentaion, and instead simply provide the options I mentioned for the locals hash, inside of a single property containing a new hash containing them, and pass that into the template.
Later I use that hash to merge with the default hash I had inside of the template and pass that to the input function as normal.
I could leave the Q&A here in case there might still me reasons people would want to directly access the locals hash, or could even develop an alternative approach that would be possible as well.
Update
So I dove into the Rail template rendering framework...
Exposition
I started simply taking this:
2.1.5 (#<#<Class:0x007fc01d352e08>:0x007fc018879698>):0 > self.class
=> #<Class:0x007fc01d352e08>
That wasn't much helpful, then went up into actionpack-3.2.22.5/lib/action_view/template.rb:145 to find this:
2.1.5 (#<ActionView::Template:0x007fc011e90510>):0 > method_name
=> "_app_views_shared__adrad_index_html_erb___3381877171036202789_70231455466120"
2.1.5 (#<ActionView::Template:0x007fc011e90510>):0 > view.method(method_name.to_sym)
=> #<Method: #<Class:0x007fc01d352e08>(ActionView::CompiledTemplates)#_app_views_shared__adrad_index_html_erb___3381877171036202789_70231455466120>
2.1.5 (#<ActionView::Template:0x007fc011e90510>):0 > view.method(method_name.to_sym).source_location
=> ["<our project_path>/app/views/shared/some.html.erb", 0]
Where the last point wasn't great, but then I started looking for ActionView::CompiledTemplates, which unfortunately, you cannot use source_location to find, or instance variable declarations for that matter (different issue :) ).
I decided to randomly search the actionpack-.../lib/action_view/ directory to find a reference or so in context.rb, but instead of looking inside of template.rb, I skipped straight to searching for locals in that gem's files now.
resolver.rb did not help much, and neither did template_renderer.rb.
Once I delved into template.rb and found the familiar above stack frame for render that my answer had to be between that and my actual page with the included locals properties,
I followed compile! to its definition,
saw a familiar ActionView::CompiledTemplates,
then a compile,
found an interesting arbitrary, literal, meta source manipulation, as I was sort of expecting with the lack of more stack frames in-between to scour, and the callback-esque naming scheme, I found this curious variable, as a parameter to the dynamically-generated template function: local_assigns.
I quickly jump around to locals_code's definition since it is also used and verify this is source variable enumeration I was looking for!
Answer
local_assigns!
Also, I currently don't believe the hash provides any properties I would want to avoid, such as any information specific to only the current partial template page that I wouldn't want to be propagated to the child partial template page.
Update
I am starting to do more with this variable, and I wonder if I am limited.
I want to mix-in some default values, which might involve using binding.local_variable_set, but it is not able to create new local variables (How to dynamically create a local variable?), so I might just have to use my own nested data structure now..

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)

How to DRY up a ruby conditional structure needed for Rails

I'm finding I often have to use a structure to avoid a Rails error of undefined method 'name' for nil:NilClass.
The structure looks like this:
if country.state
country.state.name
end
It seems like a classic case of repeating oneself with country.state appearing twice in one simple block. Is there any way to DRY this up?
Rails adds a try method to object that mimics object#send but does not raise an exception if the object returns nil.
I think the syntax is
country.try(:state).name
Well not really. One option is to install the andand gem, but introducing a dependency for this may be a little much.
Other than using the slightly more concise syntax of:
country.state.name unless country.state.nil?
I don't think there's a DRY way to do this with the information given. I would argue that if you can't be sure whether country.state is nil or not, you may want to look at the code responsible for setting that value and determine whether that's a normal case or whether a validator upstream should be catching that.

NilClass definitions

I am tired of trapping for nil when looking for a dependent record when most of the time a return of 0 will do nicely. What sort of trouble am I creating for myself by adding "id" to the NilClass thus
class NilClass
def id
0
end
end
What might the unintended consequences be? I know about exists?(), but somehow thought this might be cleaner.
Your thoughts?
If you really have a problem with this, you should use referential integrity inside your database.
If you must call methods on nil which may or may not exist or throw some kind of error, you should use either a check a la
> nil.id if nil
=> nil
or Object#try (which is part of ActiveSupport nowadays I believe?), be warned - I reckon it's kind of a code smell.
> nil.try(:id)
=> nil
That being said, it is less of a smell than modifying NilClass to do something unexpected, think of what a new developer who had to work on your project would expect.
Won't this mean you will need to check for "id == 0" to confirm existence?
Not to mention unintended consequences of overiding base Ruby functionality - it becomes really hard to predict behaviour of other libraries and core Rails APIs when you mess with language internals. Not saying it won't just work, but it's hard to be sure.
I would leave the default - it works quite nicely as Ruby allows "if object.association" expressions.

Rails.cache.fetch, Symbols, & Memcached

I have a rails 2.3.4 app and a line that looks like:
temp = Rails.cache.fetch(:temp_id) { User.find_by_name('Temp').id }
and everything worked fine, until I decided to switch the caching layer to memcached by adding the following to my environment.rb:
config.cache_store = :mem_cache_store
Now the line which used to work fine gives me the following error:
undefined method 'length' for :temp_id:Symbol
/usr/local/lib/ruby/gems/1.8/gems/activesupport-2.3.4/lib/active_support/vendor/memcache-client-1.7.4/memcache.rb:645:in 'get_server_for_key'
I understand the error, but I would imagine this common case would have been quickly discovered by a rails test case, so I am wondering if I am doing something wrong. Otherwise, I'm sure I can monkeypatch this issue to convert the symbol to a string.
Thanks
Just use string keys if you can. All the documentation examples use string keys. Although it's not explicitly mentioned as far as I can see, other keys are not supported.
The key arguments are passed directly to the cache implementation, so the different caching flavours may disagree on whether or not they accept anything other than strings.
Because the caches are external with the exception of in-memory cache, I'm not sure that supporting symbols would be useful apart from preventing cases like yours. The key will actually be written to some output somewhere (it's not just internal to your Ruby app), so conceptually the key should be a string.
Edit in reaction to comment: yes, it is of course possible and perfectly reasonable in this case to create a monkey patch to circumvent having to change all calls. What you're suggesting is this (copied into the answer for readability):
class MemCache
def get_server_for_key_with_symbols(key, options = {})
key = key.to_s if key.is_a? Symbol
get_server_for_key_without_symbols(key, options)
end
alias_method_chain :get_server_for_key, :symbols
end
I would also consider just doing a project wide search-and-replace for \.fetch(:\w+) and replace it with \.fetch("$1") (repeat for read and write if necessary). This should probably cover 95% of all cases and a subsequent run of your test suite should catch the rest of the errors.
In general: While the documentation of Rails is pretty good these days, a lot of assumptions are unfortunately still implicit. It's generally a good idea to take a good look at the examples that are given in the documentation, and use the same style. The documented examples are always how the framework was intended to be used.
FWIW, it's canonically Rails.cache.read and Rails.cache.write.

Resources