How to avoid nilClass errors - ruby-on-rails

If I try to check for values in my database, I sometimes get errors related to the fact that there were no hits from my query.
So I started using .present? to check if the query returned any result, before doing the original check.
Is there a smoother way to avoid nilClass errors than this?
temp = Event.where(basket_id: basket.basket_id).where(event_type: ["operatorJoined", "operatorDisconnected"]).last
if temp.present? && temp.event_type == "operatorJoined"

You could be writing:
if temp && temp.event_type == "operatorJoined"
... or you might like to look at the Sandy Metz presentation "Nothing is Something" to learn more about avoiding this problem by using the Null Object pattern.
I would really hope this code would be something like:
temp = Basket.events.operator_join_or_disc.last
if temp && temp.operator_joined?

Ruby 2.3 introduced a safe call operator (I've seen it called safe navigation operator quite a lot, as well) &. which is similar to the try! method in rails:
class A
attr_accessor :the_other_a
def do_something
puts "doing something on #{object_id}"
end
end
a = A.new
a.do_something # => shows 'doing something on 70306921519040'
a.the_other_a.do_something # => NoMethodError: undefined method `do_something' for nil:NilClass
a.the_other_a&.do_something # => nil
a.the_other_a = A.new
a.the_other_a&.do_something # => shows 'doing something on 70306910376860'
a.the_other_a&.do_something_else # => NoMethodError: undefined method `do_something_else' for #<A:0x007fe334d62738>
a.the_other_a.try(:do_something_else) # => nil
a.the_other_a.try!(:do_something_else) # => NoMethodError: undefined method `do_something_else' for #<A:0x007fe334d62738>
So, in your example something like this should work:
temp = Event.where(basket_id: basket.basket_id).where(event_type: ["operatorJoined", "operatorDisconnected"]).last
if temp&.event_type == "operatorJoined"
However, present? just checks for !blank?, so if the variable (temp in this example) could be false, '', ' ', [], or {} (among any other things that would return true for blank?) then temp.present? && temp.something_else would not be the same as temp&.something_else. Doesn't apply in this situation, since it's the result of the ActiveRecord query, but something to keep in mind.

Related

RUBY: defined?(klass) seeing a local-variable instead of constant

I am running into an issue, where I need to check if a class exists. However, I am passing the class to a variable and trying to check it from there.
My issue is I need to pass the actual constant for defined?() to work, but I'm passing a variable, so instead of seeing a constant, it sees a method or variable.
obj is a rails model instance, for example, a specific User, or a specific Car.
def present(obj, presenter_class=nil, view_context=nil)
klass = presenter_class || "#{obj.class}Presenter".constantize
if defined?(klass) == 'constant' && klass.class == Class
klass.new(obj, view_context)
else
warn("#{self}: #{klass} is not a defined class, no presenter used")
obj
end
end
Pry Output:
[1] pry(ApplicationPresenter)> defined?(klass)
=> "local-variable"
I tried the below, but I get a method back...
[18] pry(ApplicationPresenter)> defined?("UserPresenter".constantize)
=> "method"
How can I fix this issue?
Well, apparently Object#defined? does not the thing that you hoped it would do.
tests whether or not expression refers to anything recognizable (literal object, local variable that has been initialized, method name visible from the current scope, etc.). The return value is nil if the expression cannot be resolved. Otherwise, the return value provides information about the expression.
Your goal looks like you are rebuilding what the draper gem is doing with .decorate... Don't forget that most of the gems are open source and you can use that for trying things on your own. See for example the decorator_class method from them
decorator_name = "#{prefix}Decorator"
decorator_name_constant = decorator_name.safe_constantize
return decorator_name_constant unless decorator_name_constant.nil?
They use the method safe_constantize and this apparently returns nil when the constant is not available.
2.6.5 :007 > class UserPresenter; end;
=> nil
2.6.5 :008 > 'UserPresenter'.safe_constantize
=> UserPresenter
2.6.5 :009 > 'ForgottenPresenter'.safe_constantize
=> nil
To me that looks exactly like what you need, and it also safer than using constantize
def present(obj, presenter_class=nil, view_context=nil)
klass = presenter_class || "#{obj.class}Presenter".safe_constantize
if klass != nil
klass.new(obj, view_context)
else
warn("#{self}: #{klass} is not a defined class, no presenter used")
obj
end
end

method.to_proc doesn't return from enclosed function

I was trying to DRY up a Rails controller by extracting a method that includes a guard clause to return prematurely from the controller method in the event of an error. I thought this may be possible using a to_proc, like this pure Ruby snippet:
def foo(string)
processed = method(:breaker).to_proc.call(string)
puts "This step should not be executed in the event of an error"
processed
end
def breaker(string)
begin
string.upcase!
rescue
puts "Well you messed that up, didn't you?"
return
end
string
end
My thinking was that having called to_proc on the breaker method, calling the early return statement in the rescue clause should escape the execution of foo. However, it didn't work:
2.4.0 :033 > foo('bar')
This step should not be executed in the event of an error
=> "BAR"
2.4.0 :034 > foo(2)
Well you messed that up, didn't you?
This step should not be executed in the event of an error
=> nil
Can anyone please
Explain why this doesn't work
Suggest a way of achieving this effect?
Thanks in advance.
EDIT: as people are wondering why the hell I would want to do this, the context is that I'm trying to DRY up the create and update methods in a Rails controller. (I'm trying to be agressive about it as both methods are about 60 LoC. Yuck.) Both methods feature a block like this:
some_var = nil
if (some complicated condition)
# do some stuff
some_var = computed_value
elsif (some marginally less complicated condition)
#error_msg = 'This message is the same in both actions.'
render partial: "show_user_the_error" and return
# rest of controller actions ...
Hence, I wanted to extract this as a block, including the premature return from the controller action. I thought this might be achievable using a Proc, and when that didn't work I wanted to understand why (which I now do thanks to Marek Lipa).
What about
def foo(string)
processed = breaker(string)
puts "This step should not be executed in the event of an error"
processed
rescue ArgumentError
end
def breaker(string)
begin
string.upcase!
rescue
puts "Well you messed that up, didn't you?"
raise ArgumentError.new("could not call upcase! on #{string.inspect}")
end
string
end
After all this is arguably a pretty good use case for an exception.
It seems part of the confusion is that a Proc or lambda for that matter are distinctly different than a closure (block).
Even if you could convert Method#to_proc to a standard Proc e.g. Proc.new this would simply result in a LocalJumpError because the return would be invalid in this context.
You can use next to break out of a standard Proc but the result would be identical to the lambda that you have now.
The reason Method#to_proc returns a lambda is because a lambda is far more representative of a method call than a standard Proc
For Example:
def foo(string)
string
end
bar = ->(string) { string } #lambda
baz = Proc.new {|string| string }
foo
#=> ArgumentError: wrong number of arguments (given 0, expected 1)
bar.()
#=> ArgumentError: wrong number of arguments (given 0, expected 1)
baz.()
#=> nil
Since you are converting a method to a proc object I am not sure why you would also want the behavior to change as this could cause ambiguity and confusion. Please note that for this reason you can not go in the other direction either e.g. lambda(&baz) does not result in a lambda either as metioned Here.
Now that we have explained all of this and why it shouldn't really be done, it is time to remember that nothing is impossible in ruby so this would technically work:
def foo(string)
# place assignment in the guard clause
# because the empty return will result in `nil` a falsey value
return unless processed = method(:breaker).to_proc.call(string)
puts "This step should not be executed in the event of an error"
processed
end
def breaker(string)
begin
string.upcase!
rescue
puts "Well you messed that up, didn't you?"
return
end
string
end
Example

Rails - HABTM association, checking exists? of any object in array

I want to check the existence of any HABTM relationships in an array and return true if any exist.
At present, the only way I can see to do this is:
result = false
[1,2,3,4].each do |i|
if user.parents.exists?(i)
result = true
break
end
end
I tried passing in an array as follows, but I get an exception
result = true if user.parents.exists?([1,2,3,4])
NoMethodError: undefined method `include?` for 1:Fixnum
Is there a better way of doing this?
[1,2,3,4].inject(false) {|res, i| res ||= user.parents.exists?(i)}
Pretty much the same logic, just more ruby-ish code using inject syntax.
UPDATE:
Haven't tested it. but this might also work:
user.parents.exists?(:id => [1,2,3,4])

Equivalent of .try() for a hash to avoid "undefined method" errors on nil? [duplicate]

This question already has answers here:
How to avoid NoMethodError for nil elements when accessing nested hashes? [duplicate]
(4 answers)
Closed 7 years ago.
In Rails we can do the following in case a value doesn't exist to avoid an error:
#myvar = #comment.try(:body)
What is the equivalent when I'm digging deep into a hash and don't want to get an error?
#myvar = session[:comments][#comment.id]["temp_value"]
# [:comments] may or may not exist here
In the above case, session[:comments]try[#comment.id] doesn't work. What would?
You forgot to put a . before the try:
#myvar = session[:comments].try(:[], #comment.id)
since [] is the name of the method when you do [#comment.id].
The announcement of Ruby 2.3.0-preview1 includes an introduction of Safe navigation operator.
A safe navigation operator, which already exists in C#, Groovy, and
Swift, is introduced to ease nil handling as obj&.foo. Array#dig and
Hash#dig are also added.
This means as of 2.3 below code
account.try(:owner).try(:address)
can be rewritten to
account&.owner&.address
However, one should be careful that & is not a drop in replacement of #try. Take a look at this example:
> params = nil
nil
> params&.country
nil
> params = OpenStruct.new(country: "Australia")
#<OpenStruct country="Australia">
> params&.country
"Australia"
> params&.country&.name
NoMethodError: undefined method `name' for "Australia":String
from (pry):38:in `<main>'
> params.try(:country).try(:name)
nil
It is also including a similar sort of way: Array#dig and Hash#dig. So now this
city = params.fetch(:[], :country).try(:[], :state).try(:[], :city)
can be rewritten to
city = params.dig(:country, :state, :city)
Again, #dig is not replicating #try's behaviour. So be careful with returning values. If params[:country] returns, for example, an Integer, TypeError: Integer does not have #dig method will be raised.
The most beautiful solution is an old answer by Mladen Jablanović, as it lets you to dig in the hash deeper than you could with using direct .try() calls, if you want the code still look nice:
class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc}
end
end
You should be careful with various objects (especially params), because Strings and Arrays also respond to :[], but the returned value may not be what you want, and Array raises exception for Strings or Symbols used as indexes.
That is the reason why in the suggested form of this method (below) the (usually ugly) test for .is_a?(Hash) is used instead of (usually better) .respond_to?(:[]):
class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc.is_a?(Hash)}
end
end
a_hash = {:one => {:two => {:three => "asd"}, :arr => [1,2,3]}}
puts a_hash.get_deep(:one, :two ).inspect # => {:three=>"asd"}
puts a_hash.get_deep(:one, :two, :three ).inspect # => "asd"
puts a_hash.get_deep(:one, :two, :three, :four).inspect # => nil
puts a_hash.get_deep(:one, :arr ).inspect # => [1,2,3]
puts a_hash.get_deep(:one, :arr, :too_deep ).inspect # => nil
The last example would raise an exception: "Symbol as array index (TypeError)" if it was not guarded by this ugly "is_a?(Hash)".
The proper use of try with a hash is #sesion.try(:[], :comments).
#session.try(:[], :comments).try(:[], commend.id).try(:[], 'temp_value')
Update: As of Ruby 2.3 use #dig
Most objects that respond to [] expect an Integer argument, with Hash being an exception that will accept any object (such as strings or symbols).
The following is a slightly more robust version of Arsen7's answer that supports nested Array, Hash, as well as any other objects that expect an Integer passed to [].
It's not fool proof, as someone may have created an object that implements [] and does not accept an Integer argument. However, this solution works great in the common case e.g. pulling nested values from JSON (which has both Hash and Array):
class Hash
def get_deep(*fields)
fields.inject(self) { |acc, e| acc[e] if acc.is_a?(Hash) || (e.is_a?(Integer) && acc.respond_to?(:[])) }
end
end
It can be used the same as Arsen7's solution but also supports arrays e.g.
json = { 'users' => [ { 'name' => { 'first_name' => 'Frank'} }, { 'name' => { 'first_name' => 'Bob' } } ] }
json.get_deep 'users', 1, 'name', 'first_name' # Pulls out 'Bob'
say you want to find params[:user][:email] but it's not sure whether user is there in params or not. Then-
you can try:
params[:user].try(:[], :email)
It will return either nil(if user is not there or email is not there in user) or otherwise the value of email in user.
As of Ruby 2.3 this gets a little easier. Instead of having to nest try statements or define your own method you can now use Hash#dig (documentation).
h = { foo: {bar: {baz: 1}}}
h.dig(:foo, :bar, :baz) #=> 1
h.dig(:foo, :zot) #=> nil
Or in the example above:
session.dig(:comments, #comment.id, "temp_value")
This has the added benefit of being more like try than some of the examples above. If any of the arguments lead to the hash returning nil then it will respond nil.
#myvar = session.fetch(:comments, {}).fetch(#comment.id, {})["temp_value"]
From Ruby 2.0, you can do:
#myvar = session[:comments].to_h[#comment.id].to_h["temp_value"]
From Ruby 2.3, you can do:
#myvar = session.dig(:comments, #comment.id, "temp_value")
Another approach:
#myvar = session[:comments][#comment.id]["temp_value"] rescue nil
This might also be consider a bit dangerous because it can hide too much, personally I like it.
If you want more control, you may consider something like:
def handle # just an example name, use what speaks to you
raise $! unless $!.kind_of? NoMethodError # Do whatever checks or
# reporting you want
end
# then you may use
#myvar = session[:comments][#comment.id]["temp_value"] rescue handle
When you do this:
myhash[:one][:two][:three]
You're just chaining a bunch of calls to a "[]" method, an the error occurs if myhash[:one] returns nil, because nil doesn't have a [] method. So, one simple and rather hacky way is to add a [] method to Niclass, which returns nil: i would set this up in a rails app as follows:
Add the method:
#in lib/ruby_extensions.rb
class NilClass
def [](*args)
nil
end
end
Require the file:
#in config/initializers/app_environment.rb
require 'ruby_extensions'
Now you can call nested hashes without fear: i'm demonstrating in the console here:
>> hash = {:foo => "bar"}
=> {:foo=>"bar"}
>> hash[:foo]
=> "bar"
>> hash[:doo]
=> nil
>> hash[:doo][:too]
=> nil
Andrew's answer didn't work for me when I tried this again recently. Maybe something has changed?
#myvar = session[:comments].try('[]', #comment.id)
The '[]' is in quotes instead of a symbol :[]
Try to use
#myvar = session[:comments][#comment.id]["temp_value"] if session[:comments]

ROR + Error nil.empty? while running code

My "Project" Table have invoice as integer attribute, Here I put nil object to this attribute in DB. During evaluation nil.empty? occurs.
Code written at HAML extentions
- #project.each do |proj|
=proj.invoice if !proj.invoice.blank? || !proj.invoice.empty? || !proj.invoice.nil?
- #project_invoice=proj.invoice
=#project_invoice=0 if proj.invoice.blank? || proj.invoice.empty? || proj.invoice.nil
I receive this error while running code.
NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.empty?
There's a few standard tests provided by Ruby and rails that can help, but you usually don't need to use all of them at once:
# Rails provided Object#blank? method
nil.blank? # => true
false.blank? # => true
''.blank? # => true
[ ].blank? # => true
# Ruby provided Object#nil? method
nil.nil? # => true
false.nil? # => false
''.nil? # => false
[ ].nil # => false
# Ruby class-specific #empty? method
nil.empty? # => error
false.empty? # => error
''.empty? # => true
[ ].empty? # => true
In your case the test you're probably looking for is actually a different one altogether. The opposite of blank? is present? and it comes in very handy for situations like this. You can even collapse down both of your inverted logical tests into a simple ternary query:
- #project_invoice = proj.present? ? proj.invoice : 0
More verbosely it looks like this:
- if (proj.present?)
#project_invoice = proj.invoice
- else
#project_invoice = 0
The present method verifies that the variable represents a non-nil, non-blank value of some sort.
The second condition has a misspelled variable name. It should be proj, not projt.
That would cause your issue.
if the invoice column is nill, then !proj.invoice.blank? evaluates to false, and the next test is done, !projt.invoice.empty?
since invoice is nil, you have nil.empty? which is an error, as empty? can not run on nil.
ruby-1.9.2-p0 > !nil.blank? || !nil.empty?
NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.empty?
I think you are doing an overkill, since an integer should not be an array. I think you should just shorten it to only test blank?, as that catches an empty array too.
Is this is the code you are using. If this is the same code, I see a spelling mistake in the
second line.
Can you try with
=proj.invoice if !proj.invoice.blank? || !proj.invoice.empty? || !proj.invoice.nil?

Resources