I am using Ruby on Rails 3.2.13 and I would like to remove hash keys which corresponding hash value is blank. That is, if I have the following hash
{ :a => 0, :b => 1, :c => true, :d => "", :e => " ", :f => nil }
then the resulting hash should be (note: 0 and true are not considered blank)
{ :a => 0, :b => 1, :c => true }
How can I make that?
If using Rails you can try
hash.delete_if { |key, value| value.blank? }
or in case of just Ruby
hash.delete_if { |key, value| value.to_s.strip == '' }
There are a number of ways to accomplish this common task
reject
This is the one I use most often for cleaning up hashes as its short, clean, and flexible enough to support any conditional and doesn't mutate the original object. Here is a good article on the benefits of immutability in ruby.
hash.reject {|_,v| v.blank?}
Note: The underscore in the above example is used to indicate that we want to unpack the tuple passed to the proc, but we aren't using the first value (key).
reject!
However, if you want to mutate the original object:
hash.reject! {|_,v| v.blank?}
select
Conversely, you use select which will only return the values that return true when evaluated
hash.select {|_,v| v.present? }
select!
...and the mutating version
hash.select {|_,v| v.present? }
compact
Lastly, when you only need to remove keys that have nil values...
hash.compact
compact!
You have picked up the pattern by now, but this is the version that modifies the original hash!
hash.compact!
With respect to techvineet's solution, note the following when value == [].
[].blank? => true
[].to_s.strip == '' => false
[].to_s.strip.empty? => false
Related
I am trying to make a query that:
Finds/Gets the object (Coupon.code)
Checks if the coupon is expired (expires_at)
Checks if the coupon has been used up. (coupons_remaining)
I got some syntax using a newer version of ruby but it isnt working with my version 2.2.1 The syntax I have is
def self.get(code)
where(code: normalize_code(code)).
where("coupon_count > ? OR coupon_count IS NULL", 0).
where("expires_at > ? OR expires_at IS NULL", Time.now).
take(1)
end
This throws an error of wrong number of arguments (2 for 1) which is because my rails doesn't seem to recognize the 2 arguments ("coupon_count > ? OR coupon_count IS NULL", 0) so I have tried to change it but when I change them to something like this (which in my heart felt horribly wrong)
def self.get(code)
where(code: normalize_code(code)).
where(coupon_count: self.coupon_count > 0 || self.coupon_count.nil? ).
where(expires_at: self.expires_at > Time.now || self.expires_at.nil? ).
take(1)
end
I get undefined method `coupon_count' for Coupon:Class
I am short on ideas can someone help me get the syntax for this get method in my model? By the way if it matters I am using mongoid 5.1.0
I feel your pain. Combining OR and AND in MongoDB is a bit messy because you're not really working with a query language at all, you're just building a hash. Similar complications apply if you might apply multiple conditions to the same field. This is also why you can't include SQL-like snippets like you can with ActiveRecord.
For example, to express:
"coupon_count > ? OR coupon_count IS NULL", 0
you need to build a hash like:
:$or => [
{ :coupon_count.gt => 0 },
{ :coupon_count => nil }
]
but if you try to add another OR to that, you'll overwrite the existing :$or key and get confusion. Instead, you need to be aware that there will be multiple ORs and manually avoid the duplicate by saying :$and:
:$and => [
{
:$or => [
{ :coupon_count.gt => 0 },
{ :coupon_count => nil }
]
}, {
:$or => [
{ :expires_at.gt => Time.now },
{ :expires_at => nil }
]
}
]
Then adding the code condition is straight forward:
:code => normalize_code(code),
:$and => [ ... ]
That makes the whole thing a rather hideous monstrosity:
def self.get(code)
where(
:code => normalize_code(code),
:$and => [
{
:$or => [
{ :coupon_count.gt => 0 },
{ :coupon_count => nil }
]
}, {
:$or => [
{ :expires_at.gt => Time.now },
{ :expires_at => nil }
]
}
]
).first
end
You could also use find_by(that_big_mess) instead of where(that_big_mess).first. Also, if you expect the query to match multiple documents, then you probably want to add an order call to make sure you get the one you want. You could probably use the and and or query methods instead of a single hash but I doubt it will make things easy to read, understand, or maintain.
I try to avoid ORs with MongoDB because the queries lose their little minds fast and you're left with some gibbering eldritch horror that you don't want to think about too much. You're usually better off precomputing parts of your queries with generated fields (that you have to maintain and sanity check to make sure they are correct); for example, you could add another field that is true if coupon_count is positive or nil and then update that field in a before_validation hook when coupon_count changes.
You've defined a class method, so self in this circumstance references the Coupon class rather than a Coupon instance.
Try the following:
scope :not_expired, -> { where("expires_at > ? OR expires_at IS NULL", Time.now) }
scope :previously_used, -> { where("coupon_count > 0 OR coupon_count IS NULL") }
def self.get(code)
previously_used.not_expired.find_by!(code: normalize_code(code))
end
I am writing integration tests for rails and I want to compare the object created with the JSON object sent. The object returned is not exactly the same as the one sent, (i.e.) it has keys that the object sent doesn't have because I am using active model serializers to pull associations in the returned object. Basically, I just want to compare all the same keys between both objects to see if its the same. Let me know if there is a clean efficient code snippet that does this for me!
TL;DR
"Clever" test code is rarely useful. Each test should be as simple as possible, and it should be testing the behavior of some object rather than its composition. There are always ways to be clever, though.
Using Array Intersection
One unreadably-clever way to do this is to use Array#& to find the intersection of the keys, and then look for equality between the values. This will work on a relatively flat hash. For example:
hash1 = {:key1=>"value1", :key2=>"value2", :key3=>"value3", :key4=>"value4"}
hash2 = {:key1=>"value1", :key2=>"value2", :key5=>"value5"}
Array(hash1.keys & hash2.keys).map { |k| hash1[k] == hash2[k] }.uniq
#=> [true]
If you're using RSpec to test, you could say something like:
it 'has some matching key/value pairs' do
# ... populate hash1
# ... populate hash2
Array(hash1.keys & hash2.keys).
map { |k| hash1[k] == hash2[k] }.uniq.should == [true]
end
Of course, if the expectation is false, then you won't really know why, or which key/value pair was wrong. This is just one of the many reasons that you should always use fixed inputs and outputs for testing, rather than trying to do dynamic comparisons.
You could use Hash#slice, which is an Active Support core extension.
For example, if the keys you want to check are :a, :b, :c, but the result contains :a, :b, :c, :d, slice will reduce the result to just contain the keys you care about:
expected = { :a => 1, :b => 2, :c => 3 }
result = { :a => 1, :b => 2, :c => 3, :d => 4 }
result.slice(:a, :b, :c) == expected
# => true
If you get a NoMethodError: undefined method 'slice' exception, you need to require active_support/core_ext/hash/slice
First, find the keys that both hashes contain, and then compare the value for those keys:
hash1 = {:key1 => "value1", :key2 => "value2", :key3 => "value3", :key4 => "value4"}
hash2 = {:key1 => "value1", :key2 => "value2", :key5 => "value5"}
hash1_keys = hash1.keys
hash2_keys = hash2.keys
comparable_keys = hash1_keys.select{|key| hash2_keys.include?(key)}
comparable_keys.each do |key|
hash1[key].should == hash2[key]
end
What I'm aiming to do is to create an object which is initialized with a hash and then query this object in order to get values from that hash.
To make things clearer here's a rough example of what I mean:
class HashHolder
def initialize(hash)
#hash = hash
end
def get_value(*args)
# What are my possibilities here?
end
end
holder = HashHolder.new({:a => { :b => { :c => "value" } } } )
holder.get_value(:a, :b, :c) # should return "value"
I know I can perform iteration on the arguments list as in:
def get_value(*args)
value = #hash
args.each do |k|
value = value[k]
end
return value
end
But if I plan to use this method a lot this is going to degrade my performance dramatically when all I want to do is to access a hash value.
Any suggestions on that?
To update the answer since it's been a while since it was asked.
(tested in ruby 2.3.1)
You have a hash like this:
my_hash = {:a => { :b => { :c => "value" } } }
The question asked:
my_hash.get_value(:a, :b, :c) # should return "value"
Answer: Use 'dig' instead of get_value, like so:
my_hash.dig(:a,:b,:c) # returns "value"
Since the title of the question is misleading (it should be something like: how to get a value inside a nested hash with an array of keys), here is an answer to the question actually asked:
Getting ruby hash values by an array of keys
Preparation:
my_hash = {:a => 1, :b => 3, :d => 6}
my_array = [:a,:d]
Answer:
my_hash.values_at(*my_array) #returns [1,6]
def get_value(*args)
args.inject(#hash, &:fetch)
end
In case you want to avoid iteration at lookup (which I do not feel necessary), then you need to flatten the hash to be stored:
class HashHolder
def initialize(hash)
while hash.values.any?{|v| v.kind_of?(Hash)}
hash.to_a.each{|k, v| if v.kind_of?(Hash); hash.delete(k).each{|kk, vv| hash[[*k, kk]] = vv} end}
end
#hash = hash
end
def get_value(*args)
#hash[args]
end
end
If you know the structure of the hash is always in that format you could just do:
holder[:a][:b][:c]
... returns "value".
I am using Ruby on Rails 3.1.0 and I would like to check if an hash is "completely" included in another hash and return a boolean value.
Say I have those hashes:
hash1 = {
:key1 => 'value1',
:key2 => 'value2',
:key3 => 'value3'
}
hash2 = {
:key1 => 'value1',
:key2 => 'value2',
:key3 => 'value3',
:key4 => 'value4',
:key5 => 'value5',
...
}
I would like to check if the hash1 is included in the hash2 even if in the hash2 there are more values than hash1 (in the above case the response that I am looking for should be true)? Is it possible to do that by using "one only code line"\"a Ruby method"?
That will be enough
(hash1.to_a - hash2.to_a).empty?
The easiest way I can think of would be:
hash2.values_at(*hash1.keys) == hash1.values
the more elegant way is to check the equality when one hash merge another.
e.g. rewrite Hash include? instance method for this.
class Hash
def include?(other)
self.merge(other) == self
end
end
{:a => 1, :b => 2, :c => 3}.include? :a => 1, :b => 2 # => true
There is a way:
hash_2 >= hash_1
Alternatively:
hash_1 <= hash_2
More info in this post: https://olivierlacan.com/posts/proposal-for-a-better-ruby-hash-include/
The most efficient and elegant solution I've found - with no intermediary arrays, or redundant loops.
class Hash
alias_method :include_key?, :include?
def include?(other)
return include_key?(other) unless other.is_a?(Hash)
other.all? do |key, value|
self[key] == value
end
end
end
Since Ruby 2.3 you can use built-in Hash#<= method.
Since Ruby 2.3, you can do this:
hash1 <= hash2
I am not sure if I understand the inclusion idea in hash.
To see if it has the same keys(usual problem).
All keys in hash1 are included in hash2:
hash1.keys - hash2.keys == []
Then if you want to compare those values do as suggested in the previous post:
hash1.values - hash2.values_at(*hash1.keys) == []
If I already have a hash, can I make it so that
h[:foo]
h['foo']
are the same? (is this called indifferent access?)
The details: I loaded this hash using the following in initializers but probably shouldn't make a difference:
SETTINGS = YAML.load_file("#{RAILS_ROOT}/config/settings.yml")
You can just use with_indifferent_access.
SETTINGS = YAML.load_file("#{RAILS_ROOT}/config/settings.yml").with_indifferent_access
If you have a hash already, you can do:
HashWithIndifferentAccess.new({'a' => 12})[:a]
You can also write the YAML file that way:
--- !map:HashWithIndifferentAccess
one: 1
two: 2
after that:
SETTINGS = YAML.load_file("path/to/yaml_file")
SETTINGS[:one] # => 1
SETTINGS['one'] # => 1
Use HashWithIndifferentAccess instead of normal Hash.
For completeness, write:
SETTINGS = HashWithIndifferentAccess.new(YAML.load_file("#{RAILS_ROOT}/config/settings.yml"))
You can just make a new hash of HashWithIndifferentAccess type from your hash.
hash = { "one" => 1, "two" => 2, "three" => 3 }
=> {"one"=>1, "two"=>2, "three"=>3}
hash[:one]
=> nil
hash['one']
=> 1
make Hash obj to obj of HashWithIndifferentAccess Class.
hash = HashWithIndifferentAccess.new(hash)
hash[:one]
=> 1
hash['one']
=> 1