I find myself repeatedly looking for a clear definition of the differences of nil?, blank?, and empty? in Ruby on Rails. Here's the closest I've come:
blank? objects are false, empty, or a whitespace string. For example, "", " ", nil, [], and {} are blank.
nil? objects are instances of NilClass.
empty? objects are class-specific, and the definition varies from class to class. A string is empty if it has no characters, and an array is empty if it contains no items.
Is there anything missing, or a tighter comparison that can be made?
.nil? can be used on any object and is true if the object is nil.
.empty? can be used on strings, arrays and hashes and returns true if:
String length == 0
Array length == 0
Hash length == 0
Running .empty? on something that is nil will throw a NoMethodError.
That is where .blank? comes in. It is implemented by Rails and will operate on any object as well as work like .empty? on strings, arrays and hashes.
nil.blank? == true
false.blank? == true
[].blank? == true
{}.blank? == true
"".blank? == true
5.blank? == false
0.blank? == false
.blank? also evaluates true on strings which are non-empty but contain only whitespace:
" ".blank? == true
" ".empty? == false
Rails also provides .present?, which returns the negation of .blank?.
Array gotcha: blank? will return false even if all elements of an array are blank. To determine blankness in this case, use all? with blank?, for example:
[ nil, '' ].blank? == false
[ nil, '' ].all? &:blank? == true
I made this useful table with all the cases:
blank?, present? are provided by Rails.
Just extend Julian's table:
Ref: empty?blank?nil?傻傻分不清楚
Quick tip: !obj.blank? == obj.present?
Can be handy/easier on the eyes in some expressions
Everything that is nil? is blank?
Everything that is empty? is blank?
Nothing that is empty? is nil?
Nothing that is nil? is empty?
tl;dr -- only use blank? & present? unless you want to distinguish between "" and " "
One difference is that .nil? and .empty? are methods that are provided by the programming language Ruby, whereas .blank? is something added by the web development framework Rails.
A special case is when trying to assess if a boolean value is nil:
false.present? == false
false.blank? == true
false.nil? == false
In this case the recommendation would be to use .nil?
Just a little note about the any? recommendation: He's right that it's generally equivalent to !empty?. However, any? will return true to a string of just whitespace (ala " ").
And of course, see the 1.9 comment above, too.
Don't forget any? which is generally !empty?. In Rails I typically check for the presence of something at the end of a statement with if something or unless something then use blank? where needed since it seems to work everywhere.
nil? is a standard Ruby method that can be called on all objects and returns true if the object is nil:
b = nil
b.nil? # => true
empty? is a standard Ruby method that can be called on some objects such as Strings, Arrays and Hashes and returns true if these objects contain no element:
a = []
a.empty? # => true
b = ["2","4"]
b.empty? # => false
empty? cannot be called on nil objects.
blank? is a Rails method that can be called on nil objects as well as empty objects.
Everybody else has explained well what is the difference.
I would like to add in Ruby On Rails, it is better to use obj.blank? or obj.present? instead of obj.nil? or obj.empty?.
obj.blank? handles all types nil, '', [], {}, and returns true if values are not available and returns false if values are available on any type of object.
Though there n-number of answers available to this question but I liked the way its being explained here so posted one more answer :-)
Look at the data metric below - Its self explanatory with the various data-types used across available methods for it.
Reference: https://blog.appsignal.com/2018/09/11/differences-between-nil-empty-blank-and-present.html
exists? method can be used to check whether the data exists in the database or not. It returns boolean values either true or false.
Rails 4
an alternative to #corban-brook 's 'Array gotcha: blank?' for checking if an arrays only holds empty values and can be regarded as blank? true:
[ nil, '' ].all? &:blank? == true
one could also do:
[nil, '', "", " ",' '].reject(&:blank?).blank? == true
nil? can be used on any object. It determines if the object has any value or not, including 'blank' values.
For example:
example = nil
example.nil? # true
"".nil? # false
Basically nil? will only ever return true if the object is in fact equal to 'nil'.
empty? is only called on objects that are considered a collection. This includes things like strings (a collection of characters), hashes (a collection of key/value pairs) and arrays (a collection of arbitrary objects). empty? returns true is there are no items in the collection.
For example:
"".empty? # true
"hi".empty? # false
{}.empty? # true
{"" => ""}.empty? # false
[].empty? # true
[nil].empty? # false
nil.empty? # NoMethodError: undefined method `empty?' for nil:NilClass
Notice that empty? can't be called on nil objects as nil objects are not a collection and it will raise an exception.
Also notice that even if the items in a collection are blank, it does not mean a collection is empty.
blank? is basically a combination of nil? and empty? It's useful for checking objects that you assume are collections, but could also be nil.
Related
I have a database object in active record. If I call object.find(1).present? it returns false, but it exists. Calling !object.find(1).nil? returns true.
Why is this? I thought !nil == present?.
nil? and present? are not opposites.
Many things are both not present? and not nil?, such as an empty string or empty array.
"".present? # => false
"".nil? # => false
[].present? # => false
[].nil? # => false
To better answer your question lets look at the implementation:
def present?
!blank?
end
We don't see nil? mentioned here, just blank?, so lets check out blank? as well:
def blank?
respond_to?(:empty?) ? !!empty? : !self
end
So essentially, if the object responds_to empty? it will call out to that for the result. Objects which have an empty? method include Array, Hash, String, and Set.
Further Reading
The Blank/Present Source in ActiceSupport
A concise explanation of nil v. empty v. blank in Ruby on Rails
Here's my Rails class
class SkinnyEmployee
include ActiveModel::Validations
attr_accessor :uid, :name
validates :uid, :presence => true
def initialize(id, name)
#uid = id
#name = name
end
def ==(other)
puts "Calling =="
raise ArgumentError.new("other is nil or bad in "+self.to_s) if other.nil? or !other.instance_of?(SkinnyEmployee)
return (self.class == other.class && self.uid == other.uid)
end
alias :eql? :==
end
I have a hash of SkinnyEmployee objects. E.g.,
skinny_hash = {SkinnyEmployee.new("123", "xyz") => 1, SkinnyEmployee.new("456", "abc") => 2}
I have another SkinnyEmployee object that I want to look up. E.g.,
entry = SkinnyEmployee.new("456", "abc")
When I do
skinny_hash.keys.index(entry)
I get 1, as expected. But when I do
skinny_hash.has_key?(entry)
I get false.
Why is that? Doesn't has_key? also use == or eql? to find whether a key exists in a hash?
Thanks much for the help!
First, this drove me nuts. What you're doing looked absolutely correct to me, and, as you already know, doesn't work.
I can take you part of the way to a solution:
http://ruby-doc.org/core-2.0.0/Object.html#method-i-hash
quoting:
Generates a Fixnum hash value for this object. This function must have the property that a.eql?(b) implies a.hash == b.hash.
The hash value is used along with eql? by the Hash class to determine if two objects reference the same hash key. Any hash value that exceeds the capacity of a Fixnum will be truncated before being used.
I added:
def hash
1
end
to your SkinnyEmployee Class, and has_key? started returning true. Obviously that's not a solution, but I'm thinking it at least puts you on the path to one.
You have overwritten the eql? method used by Array#index but not the hash method used by Hash#has_key?.
From Ruby docs for Object#hash
Generates a Fixnum hash value for this object. This function must have the property that a.eql?(b) implies a.hash == b.hash.
The Object#hash and Object#eql? methods return equal if and only if the objects occupy the same space in memory. Some classes like Array overwrite both methods to return true if the compared array's have same elements.
For your case you can define the hash method like:
def hash
"#{self.class}_#{self.uid}".hash
end
This would satisfy the docs criteria for hash method given above.
That is happening because the object you are using as a key and they one you are using to search the key are different.
Every time you call SkinnyEmployee.new it will create a new, different, object. For example
employee_1 = SkinnyEmployee.new("123", "xyz")
employee_2 = SkinnyEmployee.new("123", "xyz")
employee_1 == employee_1 #=> true
employee_2 == employee_2 #=> true
employee_2 == employee_1 #=> false
If you call object_id on both employee_1 and employee_2 you will notice that it gives you different id's.
Using has_key? will check for the exact same object, and that won't be the case if you use SkinnyEmployee.new("456", "abc").
You would need a way to retrieve the exact same object, store it in a variable or in the DB, you are using as a key and use it as an attribute for has_key? for it to work.
Hope this can help you.
Basically just a check to make sure a url param was set. How I'd do it in PHP:
if(isset($_POST['foo']) && isset($_POST['bar'])){}
Is this the rough/best equivalent to isset() in RoR?
if(!params['foo'].nil? && !params['bar'].nil?) end
The closer match is probably #present?
# returns true if not nil and not blank
params['foo'].present?
There are also a few other methods
# returns true if nil
params['foo'].nil?
# returns true if nil or empty
params['foo'].blank?
You can also use defined?
See example from: http://www.tutorialspoint.com/ruby/ruby_operators.htm
foo = 42
defined? foo # => "local-variable"
defined? $_ # => "global-variable"
defined? bar # => nil (undefined)
Many more examples at the linked page.
Yes. .nil? is the equivalent of isset() in that case when checking the existence of a key in a Hash.
You should use Hash's key? method, which returns true if the given key is present in the receiver:
if(params.key?('foo') && params.key?('bar')) end
I think the most important thing, when you migrating from PHP to ROR, is the understanding of the fact that in Ruby everything is true except false and nil
So, your code:
if(!params['foo'].nil? && !params['bar'].nil?){}
is equivalent for:
if(params['foo'] && params['bar']) end
and this is full equivalent for your PHP code.
I'm submitting a parameter show_all with the value true. This value isn't associated with a model.
My controller is assigning this parameter to an instance variable:
#show_all = params[:show_all]
However, #show_all.is_a? String, and if #show_all == true always fails.
What values does Rails parse as booleans? How can I explicitly specify that my parameter is a boolean, and not a string?
UPDATE: Rails 5:
ActiveRecord::Type::Boolean.new.deserialize('0')
UPDATE: Rails 4.2 has public API for this:
ActiveRecord::Type::Boolean.new.type_cast_from_user("0") # false
PREVIOUS ANSWER:
ActiveRecord maintains a list of representations for true/false in https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/column.rb
2.0.0-p247 :005 > ActiveRecord::ConnectionAdapters::Column.value_to_boolean("ON")
2.0.0-p247 :006 > ActiveRecord::ConnectionAdapters::Column.value_to_boolean("F")
This is not part of Rails' public API, so I wrapped it into a helper method:
class ApplicationController < ActionController::Base
private
def parse_boolean(value)
ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value)
end
end
and added a basic test:
class ApplicationControllerTest < ActionController::TestCase
test "parses boolean params" do
refute ApplicationController.new.send(:parse_boolean, "OFF")
assert ApplicationController.new.send(:parse_boolean, "T")
end
end
I wanted to comment on zetetic answer but as I can't do that yet I'll post this as an answer.
If you use
#show_all = params[:show_all] == "1"
then you can drop ? true : false because params[:show_all] == "1" statement itself will evaluate to true or false and thus ternary operator is not needed.
This question is rather old, but since I came across this issue a couple of times, and didn't like any of the solutions proposed, I hacked something myself which allows to use multiple strings for true such as 'yes', 'on', 't' and the opposite for false.
Monkey patch the class String, and add a method to convert them to boolean, and put this file in /config/initializers as suggested here: Monkey Patching in Rails 3
class String
def to_bool
return true if ['true', '1', 'yes', 'on', 't'].include? self
return false if ['false', '0', 'no', 'off', 'f'].include? self
return nil
end
end
Notice that if the value is none of the valid ones either for true or false, then it returns nil. It's not the same to search for ?paid=false (return all records not paid) than ?paid= (I don't specify if it has to be paid or not -- so discard this).
Then, following this example, the logic in your controller would look like this:
Something.where(:paid => params[:paid].to_bool) unless params[:paid].try(:to_bool).nil?
It's pretty neat, and helps to keep controllers/models clean.
#show_all = params[:show_all] == "1" ? true : false
This should work nicely if you're passing the value in from a checkbox -- a missing key in a hash generates nil, which evaluates to false in a conditional.
EDIT
As pointed out here, the ternary operator is not necessary, so this can just be:
#show_all = params[:show_all] == "1"
You could change your equality statement to:
#show_all == "true"
If you want it to be a boolean you could create a method on the string class to convert a string to a boolean.
I think the simplest solution is to test "boolean" parameters against their String representation.
#show_all = params[:show_all]
if #show_all.to_s == "true"
# do stuff
end
Regardless of whether Rails delivers the parameter as the String "true" or "false" or an actual TrueClass or FalseClass, this test will always work.
You could just do
#show_all = params[:show_all].downcase == 'true'
It's worth noting that if you're passing down a value to an ActiveModel in Rails > 5.2, the simpler solution is to use attribute,
class Model
include ActiveModel::Attributes
attribute :show_all, :boolean
end
Model.new(show_all: '0').show_all # => false
As can be seen here.
Before 5.2 I use:
class Model
include ActiveModel::Attributes
attribute_reader :show_all
def show_all=(value)
#show_all = ActiveModel::Type::Boolean.new.cast(value)
end
end
Model.new(show_all: '0').show_all # => false
Another approach is to pass only the key without a value. Although using ActiveRecord::Type::Boolean.new.type_cast_from_user(value) is pretty neat, there might be a situation when assigning a value to the param key is redundant.
Consider the following:
On my products index view by default I want to show only scoped collection of products (e.g. those that are in the stock). That is if I want to return all the products, I may send myapp.com/products?show_all=true and typecast the show_all parameter for a boolean value.
However the opposite option - myapp.com/products?show_all=false just makes no sense since it will return the same product collection as myapp.com/products would have returned.
An alternative:
if I want to return the whole unscoped collection, then I send myapp.com/products?all and in my controller define
private
def show_all?
params.key?(:all)
end
If the key is present in params, then regardless of its value, I will know that I need to return all products, no need to typecast value.
You can add the following to your model:
def show_all= value
#show_all = ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value)
end
You could convert all your boolean params to real booleans like this:
%w(show_all, show_featured).each do |bool_param|
params[bool_param.to_sym] = params[bool_param.to_sym] == "true"
end
In this solution, nil parameters would become false.
While not explicitly what the question is about I feel this is appropriately related; If you're trying to pass true boolean variables in a rails test then you're going to want the following syntax.
post :update, params: { id: user.id }, body: { attribute: true }.to_json, as: :json
I arrived at this thread looking for exactly this syntax, so I hope it helps someone looking for this as well. Credit to Lukom
def role?(role)
return !!self.roles.find_by_name(role.to_s.camelize)
end
Can you help me understand what's happening in the code above? I'm new to Rails/Ruby.
Thanks
It's negation (!) operator repeated twice.
Note that only ruby objects evaluating to false (in boolean expression) are nil and false itself.
Therefore,
some_role will be true, !some_role is false and !!some_role is true again.
nil is false, !nil is true and !!nil is false.
So, this is a "clever" way to check whether role returned from find_by_name is nil or not. (And therefore whether role with such name exists or not)
I guess, I don't have to tell you that doing this is bad for readability. You can always check if result is nil by normal means, like result.nil? or result == nil.
This is more readable. No need for the 'self' or 'return'. 'present?' is the opposite of 'nil?' so no negation is required.
def role?(role)
roles.find_by_name(role.to_s.camelize).present?
end