NoMethodError: undefined method `to_d' for nil:NilClass - ruby-on-rails

NoMethodError: undefined method `to_d' for nil:NilClass
this seems to be inconsistent with other "to_"
eg with Rails 3.2.14, and ruby 1.9.3p362:
1.9.3p362 :055 > nil.to_f
=> 0.0
1.9.3p362 :056 > nil.to_d
NoMethodError: undefined method `to_d' for nil:NilClass
1.9.3p362 :057 > nil.to_s
=> ""
1.9.3p362 :058 > nil.to_i
=> 0
it means that when ever i might want to convert to big decimal that i first have to make sure the value is not nil and assign it a 0 value anyway... so... comments on what is the best way to make this consistent? and should i consider this a bug?

to_d isn't part of core Ruby. It's part of the BigDecimal package; specifically you get it when you require "bigdecimal/util". It monkey-patches itself into some of the core classes, but as you've discovered, not all of them.
If you just want nil.to_d to return nil (which seems like the only sensible thing for it to return), you can monkey-patch it yourself:
class NilClass
def to_d
nil
end
end
irb(main):015:0> nil.to_d
=> nil
If you want it to return an actual BigDecimal with value 0, then return BigDecimal.new(0) instead, but I think nil should be nil.

or
(nil.try(:to_d) || 0) + value

You can always use #try, which will return nil if the receiver is nil, or call the method if the receiver is not nil
nil.try :to_d
#=> nil
1.try :to_d
#=> #<BigDecimal:7ffe8e39c1e8,'0.1E1',9(36)>

you're probably better off converting to a string first if that's what you need
BigDecimal.new(amount.to_s)

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

How to avoid nilClass errors

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.

rails : undefined method `>' for nil:NilClass

I'm a beginner in rails and I need to compare 2 DateTime on my controller :
#b = Book.find(params[:book][:id])
if #b.expiry_date > DateTime.now
... something here ...
end
But I get this error :
undefined method `>' for nil:NilClass
Anyone have an idea why ?
Operators are methods in ruby, so your undefined method '>' for nil:NilClass error indicates that #b.expiry_date is nil on this line: if #b.expiry_date > DateTime.now.
You can use a short-circuiting logic to only evaluate the condition if #b.expiry_date is present.
if #b.expiry_date && (#b.expiry_date > DateTime.now)
The if expressions is only true if both sides of the && are also true, so(#b.expiry_date > DateTime.now) won't be executed if the first condition, #b.expiry_date, is false or nil.
Otherwise, you'll need to add logic/validations to ensure the existence of expiry_date.
Just add a check that the record isn't nil in your if statement as shown:
#b = Book.find(params[:book][:id])
if !#b.expiry_date.nil? && #b.expiry_date > DateTime.now
... something here ...
end
If all Books are supposed to have expiry dates you should add a validation that insists an expiry date is included when creating/updating a Book record in your Book Model!

Unique undefined method error rails

I have a really unique case of the 'undefined method error' in Rails. I have a Task Order model that has the attributes "obligatedAmount" and "awardAmount". When creating a new Task Order, one of my business rules is the "obligatedAmount" cannot be greater than the "awardAmount". So ensure this, I made a custom validation:
validate :check_amount_obilgated
validates_presence_of :awardAmount
validates_presence_of :obligatedAmount
def check_amount_obilgated #cannot be greater than contract award amount
if obligatedAmount > awardAmount
errors.add(:obligatedAmount, "The Obligated Amount cannot be greater than the Award Amount")
end
end
This works fine. HOWEVER, if I make a new Task Order and I leave the "obligatedAmount" OR the "awardAmount"empty, I Rails takes me to the error page with the error snippet:
undefined method `>' for nil:NilClass'
def check_amount_obilgated #cannot be greater than contract award amount
if obligatedAmount > awardAmount
errors.add(:obligatedAmount, "The Obligated Amount cannot be greater than the Award Amount")
end
end
So I guess the issue is that if one or both values are missing, the ">" operator cannot work. However, I put in the validates_presence_of :awardAmount and :obligatedAmount... is there any way I can get the validations to kick in first or is there any way around this error? Please let me know. Thank you!!
Use to_i to convert nil to zero
def check_amount_obilgated #cannot be greater than contract award amount
if obligatedAmount.to_i > awardAmount.to_i
errors.add(:obligatedAmount, "The Obligated Amount cannot be greater than the Award Amount")
end
end
So the explaination is pretty straightforward. The > operator is defined on the Fixnum class. NilClass does not have > defined, so it will throw an undefined method error. If nil is passed to the valid call, you'll get a comparison error as nil can't be coerced implicitly to a Fixnum.
A quick check in irb shows the errors you can expect if nil shows up in either the right-hand or left-hand operand:
2.1.2 :001 > 1 > nil
ArgumentError: comparison of Fixnum with nil failed
from (irb):1:in `>'
from (irb):1
from /Users/hungerandthirst/.rvm/rubies/ruby-2.1.2/bin/irb:11:in `<main>'
2.1.2 :002 > nil > 1
NoMethodError: undefined method `>' for nil:NilClass
from (irb):2
from /Users/hungerandthirst/.rvm/rubies/ruby-2.1.2/bin/irb:11:in `<main>'
A simple cast to_i on both operands will result in the values being zero when nil and you will always be able to run the comparison.
2.1.2 :005 > nil.to_i
=> 0
So in your code, do:
obligatedAmount.to_i > awardAmount.to_i

params[...] comparison raises "undefined method `>' for nil:NilClass" error in ruby on rails

I want to check below:
if params[:hidval] > "0"
OR
if !params[:hidval] < "1"
But it gave me error below:
undefined method `>' for nil:NilClass
How do I check above conditions in ruby on rails?
The error message says it all. Make sure, the param actually exists. You try to compare nothing (NilClass) with a number. (which is actually a string, that will be your next problem)
Probably correct would be:
if params[:hidval].present? && params[:hidval] > 0
# Do something ...
end
As of Ruby 2.3, you can use the Safe Navigation Operator &. to streamline this:
irb(main):028:0> 5 > 0
=> true
irb(main):029:0> nil > 0
NoMethodError: undefined method '>' for nil:NilClass
from (irb):29
irb(main):030:0> 5 &.> 0
=> true
irb(main):031:0> nil &.> 0
=> nil
irb(main):032:0> 5 &.> 99
=> false
This will typically get you what you want (e.g. conditional branching) since nil is falsey. But keep that in mind if you actually depend on an exact false value.
&. means to only call the trailing method if the leading object is not null. It makes a lot more sense in a chained method call like:
user&.deactivate!
But, since the greater-than operator > is actually a method in Ruby, it works here as well.
Personally, I'm not convinced the painful aesthetics of params[:hidval] &.> "0" makes this worthwhile...but that's just a question of style.
Try this code
if params[:hidval].present?
if params[:hidval].to_i > 0
# Greater than 0 condition
else
# Less than equal to 0 condition
end
end
Re-reading this question, I realize I missed the mark on my first answer. It side-stepped the problem without addressing it.
The real problem you saw this error is that you probably put ! in the wrong place.
If you meant, if it is not less than "1", then you should write:
if !( params[:hidval] < "1" )
This compares hidval and "1" and THEN negates the result.
What you posted:
if !params[:hidval] < "1"
...first negates hidval (usually resulting in false) and then compares false (or possibly true) to see if it is less than "1". Not only does comparing false to a string not make much sense, you'll actually get you this error:
NoMethodError (undefined method `<' for false:FalseClass)

Resources