Nil and boolean in Ruby - ruby-on-rails

Can someone explain the reasoning being this? Just spent 30 mins trying to figure out why my boolean method returned nil and found out that in Ruby:
2.2.1 :001 > nil && true
=> nil
2.2.1 :002 > nil && false
=> nil
Since nil is a falsey value, I would have expected the output of nil && true to be false. Also it seems to go against the idea that conditional operators should return a boolean value.
What is the rationale behind this?
It makes sense that the boolean operator is not commutative:
nil && false != false && nil
For others seeing this, my issue was that in rails I had a statement like:
def some_method?
object.attr && object.attr > something
end
But when object.attr is nil, the function will be nil. Which is fine in most cases but when chaining boolean methods together, not so much. I just changed it to this instead:
def some_method?
object.attr.present? && object.attr > something
end
I could do the same thing in vanilla Ruby with:
def some_method?
!!object.attr && object.attr > something
end

The statement goes through the conditions in order, will stop when a falsy result is obtained and return the value of the last evaluation performed.
In contrary to && which stops at a falsy value, || will stop at a truthy value instead.

If first operand is falsy (nil or false), then, second operand of && will not be evaluated, as falsy && whatever will be always falsy

Problem
You list two related problems:
You are confused by a Boolean expression returning nil instead of false.
You are experiencing surprising behavior when chaining methods when one of the methods may evaluate to nil, false, or some other object that doesn't respond to the next method in the chain.
Nil And False
In your examples, you are chaining methods that may sometimes be called on nil. In Ruby, nil is a falsey value (e.g. it isn't "true"), but it is not actually false, either. Consider:
nil == false
#=> false
They aren't even descended from the same class.
nil.class
#=> NilClass
false.class
#=> FalseClass
It is often best to think of nil as a special value meaning "undefined" or "not applicable". It may also be useful to think of nil as similar to the special database value of null. Nil (like null) isn't true, false, or empty, and understanding this will help you avoid many confusing exceptions.
Short-Circuit Evaluation
When you evaluate nil && false you're really evaluating two separate expressions:
(nil) && (false)
The expression nil is not true, and therefore short-circuits and never evaluates the expression false. As a result, the expression nil && false will correctly return nil rather than false. This is a source of surprise for some people, but is expected behavior for experienced Rubyists.
Solutions
In addition to short-circuit expressions or post-expression conditionals such as:
# Send :to_s unless condition evaluates to false or nil.
object.attr.to_s if object.attr
you should consider the Rails Object#try method, or the Ruby 2.3.0 safe navigation operator.
Use Object#try or Safe Navigation Operator
Object#try
If you're using Rails, the Object#try method is a common way to avoid invoking methods on nil, or other objects that don't #respond_to? the chained method. For example:
# Invoke #to_s without raising an exception. May return nil.
object.attr.try(:to_s)
This is similar, but not exactly equivalent, to:
object.attr && object.attr.to_s
Ruby 2.3.0 introduces the safe navigation operator (&) which brings similar functionality to Ruby's core.
The & Safe Navigation Operator
Ruby 2.3.0 introduced a new safe navigation operator. This appears to be an actual operator, not a method. This operator lets you chain methods as long as the expression on the left is truthy. For example:
object.attr&.to_s
Because it chains methods, and because comparison operators like > are really methods rather than parser tokens, you can now do things like:
1&.> 0
#=> true
1&.> 2
#=> false
Whether or not you find object.attr&. > something more elegant, readable, or useful than other constructs is a matter of opinion, but it's certainly an option in many cases. YMMV.

It's good to have an understanding of variables/values "truthiness":
First, looking at Ruby's idea of true and false:
if true
'is true' # => "is true"
else
'is false' # =>
end
if false
'is true' # =>
else
'is false' # => "is false"
end
A shorter form of that if/else uses the ternary:
true ? true : false # => true
false ? true : false # => false
Continuing with that:
nil ? true : false # => false
0 ? true : false # => true
1 ? true : false # => true
Notice that nil, when used as a conditional acts like false, whereas 0 is true. (Perl treats false and 0 as false values in tests which is a confusing point when moving to Ruby.) 1 is true, and basically, in Ruby, only nil and false are false and everything else is true.
! performs a "not", reversing the truthiness of the value:
!true ? true : false # => false
!false ? true : false # => true
!nil ? true : false # => true
!0 ? true : false # => false
!1 ? true : false # => false
And !! "nots" the value twice, which we use as a way to quickly convert something from a non-true/false value to a true/false:
!!true ? true : false # => true
!!false ? true : false # => false
!!nil ? true : false # => false
!!0 ? true : false # => true
!!1 ? true : false # => true
Knowing how these work in Ruby makes it easier to understand other people's code as you'll see ! and !! often.
All that leads back to using && and ||. Using || ("or"), only one side has to be true to return a true result:
true || true # => true
false || false # => false
true || false # => true
false || true # => true
With && ("and") both sides have to be true to get a true result:
true && true # => true
false && false # => false
true && false # => false
false && true # => false
Don't confuse || and or, and && and and. Both !! and or do similar things, and && and and, however their tests will occur at different times in an expression. That's order-of-precedence and is very important to know but is a different question. You can find plenty of information about that with simple searches.

Related

Is there a benefit to Rails' ".present?" method?

In Ruby on Rails, is there a difference between:
if obj
# ...
end
and:
if obj.present?
# ...
end
It seems they do the same thing and not using .present? would help keep the line of code shorter and potentially cleaner. I understand that .present? is the opposite of blank? but should we always be using it when trying to determine if an object is "truthy" or not?
Is there a performance difference of some kind?
The #present? method does a bit more in that it also returns false if the string was a real but empty string (i.e. "")
Which is useful as your forms might return empty strings instead of nils.
You can also use #presence which is a useful way of returning the value only if the value is #present?
name = params[:name].presence || 'ardavis'
The above wouldn't work if params[:name] was an empty string and you didn't use #presence
They don't do the same thing at all.
In Ruby everything except nil and false are truthy. This is amazingly sane compared to the type casting schenigans in other popular dynamic languages.
irb(main):003:0> !!""
(irb):3: warning: string literal in condition
=> true
irb(main):004:0> !!0
=> true
irb(main):005:0> !![]
=> true
irb(main):006:0> !!{}
=> true
irb(main):007:0> !!Object.new
=> true
irb(main):008:0> !!nil
=> false
irb(main):009:0> !!false
=> false
present? and presence are a part of ActiveSupport which can be used to test for nil and false but are actually more useful when dealing with user input:
irb(main):010:0> "".present?
=> false
irb(main):011:0> [].present?
=> false
irb(main):012:0> {}.present?
=> false
present? and presence are widely overused by Rails beginners that don't bother to learn Ruby first. Just use implicit truth checks (if foo) or foo.nil? if you just want to check if an argument is sent or not or if a variable is set.
And while .present? can be used on ActiveRecord collections but there are more idiomatically correct choices such as any? and none?.
If you are working with string only checking if the attribute or object exists will return true, but present method will return false.
Here are some examples:
# Double negation return the boolean value
!!""
=> true
"".present?
=> false
" ".present?
=> false
[].present?
=> false
nil.present?
=> false
true.present?
=> true
false.present?
=> false
{}.present?
=> false
person = {:firstName => "John", :lastName => "Doe"}
person.present?
=> true
5.present?
=> true

Rails 4 to 5 AR boolean deprecation

I have a model wish contains the bellow method called by before_validation :
def set_to_false
self.confirme ||= false
self.deny ||= false
self.favoris ||= false
self.code_valid ||= false
end
When I run my tests, I got the deprecation message
DEPRECATION WARNING: You attempted to assign a value which is not
explicitly true or false to a boolean column. Currently this value
casts to false. This will change to match Ruby's semantics, and will
cast to true in Rails 5. If you would like to maintain the current
behavior, you should explicitly handle the values you would like cast
to false. (called from cast_value at
./Ruby2.1.0/lib/ruby/gems/2.1.0/gems/activerecord-4.2.1/lib/active_record/type/boolean.rb:17)
I understand I have to cast but I couldn't find a simple and smart way to do it. Any help to remove this deprecation would be great.
Here's a simple booleanification trick that I use often, double negation:
before_validation :booleanify
def booleanify
self.confirm = !!confirm
self.deny = !!deny
...
end
In case you are not familiar with this trick, it'll convert all values to their boolean equivalents, according to ruby rules (nil and false become false, everything else becomes true)
'foo' # => "foo"
!'foo' # => false
!!'foo' # => true
!nil # => true
!!nil # => false

Custom validation that boolean field has a boolean and not any other type

Currently, my model and validation is this:
class Suya < ActiveRecord::Base
belongs_to :vendor
validates :meat, presence: true
validates_inclusion_of :spicy, :in => [true, false]
end
The problem is that when I run this test:
test "suya is invalid if spiciness is not a boolean" do
suya = Suya.new(meat: "beef", spicy: 1)
suya1 = Suya.new(meat: "beef", spicy: "some string")
assert suya.invalid?
refute suya1.valid?
end
I get a deprecation warning that says:
DEPRECATION WARNING: You attempted to assign a value which is not
explicitly true or false to a boolean column. Currently this value
casts to false. This will change to match Ruby's semantics, and will
cast to true in Rails 5.
So I think my validation is not doing what I think it should be doing. I think my validation checks the presence of the column value and if it IS or is converted to true or false. So I think my test fixtures both convert to false and therefore pass the test which I don't want. What can I do?
You can use custom validation like:
validate :check_boolean_field
def check_boolean_field
false unless self.spicy.is_a?(Boolean)
end
Rails performs type casting any time you assign a value to an attribute. This is a convenience thing. It's not really your text case's fault, it's just how Rails works. If the attribute is a Boolean it'll convert truthy-looking values (true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON') to true and anything else to false. For example:
suya.spicy = "asdf"
suya.spicy # => false
# Likewise for other attribute types:
# Assuming Suya has an `id` attribute that is an Integer
suya.id = "asdf"
suya.id # => 0 # Because "asdf".to_i # => 0
# Assuming Suya has a `name` attribute that is a String
suya.name = 1
suya.name # => "1" # Because 1.to_s # => "1"
So this is just how rails works. In your test case your values are being typecast into their respective attributes' types via mass-assignment.
You can either test out Rails's typecasting by assigning "some value" to your booleans or you can just use more obvious boolean values like true and false in your test cases.

How do I determine if a string is numeric?

I thought this would be easier to find, but I'm quite surprised that it isn't.
How on Earth do I test if a string is a number (including decimals) outside a Model?
e.g.
is_number("1") # true
is_number("1.234") # true
is_number("-1.45") # true
is_number("1.23aw") #false
In PHP, there was is_numeric, but I can't seem to find an equivalent in Ruby (or Rails).
So far, I've read the following answers, and haven't gotten any closer:
Ruby on Rails - Validate a Cost
Ruby/Rails - How can you validate against decimal scale?
invalid decimal becomes 0.0 in rails
You could borrow the idea from the NumericalityValidator Rails uses to validate numbers, it uses the Kernel.Float method:
def numeric?(string)
# `!!` converts parsed number to `true`
!!Kernel.Float(string)
rescue TypeError, ArgumentError
false
end
numeric?('1') # => true
numeric?('1.2') # => true
numeric?('.1') # => true
numeric?('a') # => false
It also handles signs, hex numbers, and numbers written in scientific notation:
numeric?('-10') # => true
numeric?('0xFF') # => true
numeric?('1.2e6') # => true
You could use Regular Expression.
!!("1" =~ /\A[-+]?[0-9]+(\.[0-9]+)?\z/) # true
!!("1.234" =~ /\A[-+]?[0-9]+(\.[0-9]+)?\z/) # true
!!("-1.45" =~ /\A[-+]?[0-9]+(\.[0-9]+)?\z/) # true
!!("1.23aw" =~ /\A[-+]?[0-9]+(\.[0-9]+)?\z/) # false
You can use it like this or make a method in a module or add this in the String class
class String
def is_number?
!!(self =~ /\A[-+]?[0-9]+(\.[0-9]+)?\z/)
end
end
You can use this site to test your expression : Rubular: a Ruby regular expression editor and tester
I can explain much more the expression if needed.
Hope this helps.

Validate Param Types in Rails

I've been looking all over the place and I'm wondering if I'm doing something wrong. And just to double check, I'll ask you guys!
So I'm receiving params in a Rails controller. One key, value pair is :status => true/false. However, I find that when I try to post status as a string like
:status => "THIS IS NOT A BOOLEAN"
and create my object in my controller, the :status attribute of my object becomes false.
Therefore, is there any clean way in rails to validate that my :status corresponds to a boolean?
Thanks!
This very strange method will to the trick
def is_boolean?(item)
!!item == item
end
params[:status] = 'some string'
is_boolean?(params[:status])
# => false
params[:status] = true
is_boolean?(params[:status])
# => true
A slightly more intuitive version would be
def is_boolean?(item)
item == false || item == true
end
Validation
The Rails way to do it is to validate in the model (from the docs):
#app/models/model.rb
Class Model < ActiveRecord::Base
validates :status, inclusion: { in: [true, false] }, message: "True / False Required!"
end
--
MVC
The reason for this is twofold:
DRY
MVC
If you want to keep your application DRY, you need to make sure you have only one reference to a validation throughout. Known as the "Single Source Of Truth", it means if you try and populate the model with other controllers / methods, you'll still invoke the same validation
Secondly, you need to consider the MVC (Model-View-Controller) pattern. MVC is a core aspect of Rails, and means you have to use your controller to collate data only - pulling & compiling data in the model. This is also true for validations -- always make sure you keep your validations with the data (IE in the model)
The above #Iceman solution is good if you are only doing it once place but you keep doing/repeating it in other places i suggest you to create to_bool method. i.e
class String
def to_bool
return true if self == true || self =~ (/(true|t|yes|y|1)$/i)
return false if self == false || self.blank? || self =~ (/(false|f|no|n|0)$/i)
raise ArgumentError.new("invalid value for Boolean: \"#{self}\"")
end
end
and put this method in intializer or in library. And, you can simply do this
Mymodel.new(status: params[:status].to_s.to_bool)
we are doing to_s just because to convert nil to '' incase the status key isn't in params .

Resources