Ruby case statement oddity - ruby

I'm seeing something peculiar in Ruby that I don't understand, wondering if someone could show me what I'm missing. Here's some pry output:
[77] pry(main)> c.fields['iparties'].type
=> Array
[78] pry(main)> c.fields['iparties'].type == Array
=> true
[79] pry(main)> case c.fields['iparties'].type
[79] pry(main)* when Array
[79] pry(main)* puts "Array!"
[79] pry(main)* else
[79] pry(main)* puts "Something else?"
[79] pry(main)* end
Something else?
=> nil
c is a Mongoid object, and obviously I'm taking a look at the type of one of the model's fields. The field is an Array, as shown in line 77 and confirmed in the comparison on line 78. However, when I run it through a "when" statement, it does not match on the Array comparator. If I change the case definition to be
case c.fields['iparties'].type.to_s
and I change the Array match to
when 'Array'
it works as expected and the output is "Array!".
I don't understand why it doesn't work as it's shown here though, so if someone could explain it to me, I'd really appreciate it.

It's because case uses ===:
Array == Array #=> true
Array === Array #=> false
As
Array.class #=> Class
Class.ancestors #=> [Class, Module, Object, Kernel, BasicObject]
so Module#=== applies:
mod === obj → true or false. Returns true if obj is an instance of mod or one of mod’s descendants.

Take the .type out of your case and it will work. The magic of the when is that when Array means if Array === val, and the === method on a class returns true if the argument is an instance of that class!

Related

Why is ruby acting like passing by reference when using gsub function in Ruby? [duplicate]

This question already has answers here:
Ruby 'pass by value' clarification [duplicate]
(3 answers)
Closed 4 years ago.
Given the following two methods:
[53] pry(main)> def my_method
[53] pry(main)* leti = 'leti'
[53] pry(main)* edit(leti)
[53] pry(main)* leti
[53] pry(main)* end
=> :my_method
[54] pry(main)> def edit(a_leti)
[54] pry(main)* a_leti.gsub!('e', '3')
[54] pry(main)* a_leti
[54] pry(main)* end
=> :edit
[55] pry(main)> my_method
=> "l3ti"
Can someone explain why I am getting the value edited inside the edit method and not the original value ('leti'). I though Ruby was passed by value. In fact, if instead of using the function gsub I use a simple assignment, I get the original value. Does the gsub! make it by reference?
Thank you!
In Ruby: Objects like strings are passed by reference. Variables with objects like strings are in fact references to those strings. Parameters are passed by value. However, for strings, these are references to those strings.
So here is the classic example:
irb(main):004:0* a = "abcd"
=> "abcd"
irb(main):005:0> b = a
=> "abcd"
irb(main):006:0> b << "def"
=> "abcddef"
irb(main):007:0> a
=> "abcddef"
irb(main):008:0> b
=> "abcddef"
If you do not wish to modify the original string, you need to make a copy of it:
Three ways (of many) to do this are:
b = a.dup
b = a.clone
b = String.new a
Using dup
irb(main):009:0> a = "abcd"
=> "abcd"
irb(main):010:0> b = a.dup
=> "abcd"
irb(main):011:0> b << "def"
=> "abcddef"
irb(main):012:0> a
=> "abcd"
irb(main):013:0> b
=> "abcddef"
BTW: For myself, this effect is the number one cause of defects in my own code.

Detecting if a key-value pair exists within a hash

I cannot find a way to determine if a key-value pair exists in a hash.
h4 = { "a" => 1, "d" => 2, "f" => 35 }
I can use Hash#has_value? and Hash#has_key? to find information about a key or a value individually, but how can I check if a pair exists?
Psuedo-code of what I'm after:
if h4.contains_pair?("a", 1)
Just use this:
h4['a'] == 1
It seems excessive to me, but you could monkey-patch Hash with a method like so:
class Hash
def contains_pair?(key, value)
key?(key) && self[key] == value
end
end
I confess to starting down a road and then wondering where it might take me. This may not be the best way of determining if a key/value pair is present in a hash (how could one improve on #Jordan's answer?), but I learned something along the way.
Code
def pair_present?(h,k,v)
Enumerable.instance_method(:include?).bind(h).call([k,v])
end
Examples
h = { "a"=>1, "d"=>2, "f"=>35 }
pair_present?(h,'a',1)
#=> true
pair_present?(h,'f',36)
#=> false
pair_present?(h,'hippopotamus',2)
#=> false
Discussion
We could of course convert the hash to an array and then apply Array#include?:
h.to_a.include?(['a', 1])
#=> true
but that has the downside of creating a temporary array. It would be nice if the class Hash had such an instance method, but it does not. One might think Hash#include? might be used for that, but it just takes one argument, a key.1.
The method Enumerable#include? does what we want, and of course Hash includes the Enumerable module. We can invoke that method in various ways.
Bind Enumerable#include? to the hash and call it
This was of course my answer:
Enumerable.instance_method(:include?).bind(h).call([k,v])
Use the method Method#super_method, which was introduced in v2.2.
h.method(:include?).super_method.call(['a',1])
#=> true
h.method(:include?).super_method.call(['a',2])
#=> false
Note that:
h.method(:include?).super_method
#=> #<Method: Object(Enumerable)#include?>
Do the alias_method/remove_method merry-go-round
Hash.send(:alias_method, :temp, :include?)
Hash.send(:remove_method, :include?)
h.include?(['a',1])
#=> true
h.include?(['a',2])
#=> false
Hash.send(:alias_method, :include?, :temp)
Hash.send(:remove_method, :temp)
Convert the hash to an enumerator and invoke Enumerable#include?
h.to_enum.include?(['a',1])
#=> true
h.to_enum.include?(['a',2])
#=> false
This works because the class Enumerator also includes Enumerable.
1 Hash#include? is the same as both Hash#key? and Hash#has_key?. It makes me wonder why include? isn't used for the present purpose, since determining if a hash has a given key is well-covered.
How about using Enumerable any?
h4 = { "a" => 1, "d" => 2, "f" => 35 }
h4.any? {|k,v| k == 'a' && v == 1 }

using a string or key-val pair as a method argument

Is there a better way to write this? basically I want to add an argument to a hash. if the argument is a key-val pair, then id like to add it as is. if the argument is a string i'd like to add it as a key with a nil value. the below code works, but is there a more appropriate (simple) way?
2nd question, does calling an each method on an array with two arguments |key, val| automatically convert an array to a hash as it appears to?
#some_hash = {}
def some_method(input)
if input.is_a? Hash
input.each {|key, val| #some_hash[key] = val}
else
input.split(" ").each {|key, val| #some_hash[key] = val}
end
end
some_method("key" => "val")
This gives the result as instructed in the question, but it works differently from the code OP gave (which means that the OP's code does not work as it says):
#some_hash = {}
def some_method(input)
case input
when Hash then #some_hash.merge!(input)
when String then #some_hash[input] = nil
end
end
some_method("foo" => "bar")
some_method("baz")
#some_hash # => {"foo" => "bar", "baz" => nil}
Second question
An array is never automatically converted to a hash. What you are probably mentioning is the fact that the elements of an array within an array [[:foo, :bar]] can be referred to separately in:
[[:foo, :bar]].each{|f, b| puts f; puts b}
# => foo
# => bar
That is due to destructive assignment. When necessary, Ruby takes out the elements of an array as separate things and tries to adjust the number of variables. It is the same as:
f, b = [:foo, :bar]
f # => :foo
b # => :bar
Here, you don't get f # => [:foo, :bar] and b # => nil.

Hash use array as key in ruby

I have a hash that uses array as its key. When I change the array, the hash can no longer get the corresponding key and value:
1.9.3p194 :016 > a = [1, 2]
=> [1, 2]
1.9.3p194 :017 > b = { a => 1 }
=> {[1, 2]=>1}
1.9.3p194 :018 > b[a]
=> 1
1.9.3p194 :019 > a.delete_at(1)
=> 2
1.9.3p194 :020 > a
=> [1]
1.9.3p194 :021 > b
=> {[1]=>1}
1.9.3p194 :022 > b[a]
=> nil
1.9.3p194 :023 > b.keys.include? a
=> true
What am I doing wrong?
Update:
OK. Use a.clone is absolutely one way to deal with this problem.
What if I want to change "a" but still use "a" to retrieve the corresponding value (since "a" is still one of the keys) ?
The #rehash method will recalculate the hash, so after the key changes do:
b.rehash
TL;DR: consider Hash#compare_by_indentity
You need to decide if you want the hash to work by array value or array identity.
By default arrays .hash and .eql? by value, which is why changing the value confuses ruby. Consider this variant of your example:
pry(main)> a = [1, 2]
pry(main)> a1 = [1]
pry(main)> a.hash
=> 4266217476190334055
pry(main)> a1.hash
=> -2618378812721208248
pry(main)> h = {a => '12', a1 => '1'}
=> {[1, 2]=>"12", [1]=>"1"}
pry(main)> h[a]
=> "12"
pry(main)> a.delete_at(1)
pry(main)> a
=> [1]
pry(main)> a == a1
=> true
pry(main)> a.hash
=> -2618378812721208248
pry(main)> h[a]
=> "1"
See what happened there?
As you discovered, it fails to match on the a key because the .hash value under which it stored it is outdated [BTW, you can't even rely on that! A mutation might result in same hash (rare) or different hash that lands in the same bucket (not so rare).]
But instead of failing by returning nil, it matched on the a1 key.
See, h[a] doesn't care at all about the identity of a vs a1 (the traitor!). It compared the current value you supply — [1] with the value of a1 being [1] and found a match.
That's why using .rehash is just band-aid. It will recompute the .hash values for all keys and move them to the correct buckets, but it's error-prone, and may also cause trouble:
pry(main)> h.rehash
=> {[1]=>"1"}
pry(main)> h
=> {[1]=>"1"}
Oh oh. The two entries collapsed into one, since they now have the same value (and which wins is hard to predict).
Solutions
One sane approach is embracing lookup by value, which requires the value to never change. .freeze your keys. Or use .clone/.dup when building the hash, and feel free to mutate the original arrays — but accept that h[a] will lookup the current value of a against the values preserved from build time.
The other, which you seem to want, is deciding you care about identity — lookup by a should find a whatever its current value, and it shouldn't matter if many keys had or now have the same value.
How?
Object hashes by identity. (Arrays don't because types that .== by value tend to also override .hash and .eql? to be by value.) So one option is: don't use arrays as keys, use some custom class (which may hold an array inside).
But what if you want it to behave directly like a hash of arrays? You could subclass Hash, or Array but it's a lot of work to make everything work consistently. Luckily, Ruby has a builtin way: h.compare_by_identity switches a hash to work by identity (with no way to undo, AFAICT). If you do this before you insert anything, you can even have distinct keys with equal values, with no confusion:
[39] pry(main)> x = [1]
=> [1]
[40] pry(main)> y = [1]
=> [1]
[41] pry(main)> h = Hash.new.compare_by_identity
=> {}
[42] pry(main)> h[x] = 'x'
=> "x"
[44] pry(main)> h[y] = 'y'
=> "y"
[45] pry(main)> h
=> {[1]=>"x", [1]=>"y"}
[46] pry(main)> x.push(7)
=> [1, 7]
[47] pry(main)> y.push(7)
=> [1, 7]
[48] pry(main)> h
=> {[1, 7]=>"x", [1, 7]=>"y"}
[49] pry(main)> h[x]
=> "x"
[50] pry(main)> h[y]
=> "y"
Beware that such hashes are counter-intuitive if you try to put there e.g. strings, because we're really used to strings hashing by value.
Hashes use their key objects' hash codes (a.hash) to group them. Hash codes often depend on the state of the object; in this case, the hash code of a changes when an element has been removed from the array. Since the key has already been inserted into the hash, a is filed under its original hash code.
This means you can't retrieve the value for a in b, even though it looks alright when you print the hash.
You should use a.clone as key
irb --> a = [1, 2]
==> [1, 2]
irb --> b = { a.clone => 1 }
==> {[1, 2]=>1}
irb --> b[a]
==> 1
irb --> a.delete_at(1)
==> 2
irb --> a
==> [1]
irb --> b
==> {[1, 2]=>1} # STILL UNCHANGED
irb --> b[a]
==> nil # Trivial, since a has changed
irb --> b.keys.include? a
==> false # Trivial, since a has changed
Using a.clone will make sure that the key is unchanged even when we change a later on.
As you have already said, the trouble is that the hash key is the exact same object you later modify, meaning that the key changes during program execution.
To avoid this, make a copy of the array to use as a hash key:
a = [1, 2]
b = { a.clone => 1 }
Now you can continue to work with a and leave your hash keys intact.

Ruby Style: How to check whether a nested hash element exists

Consider a "person" stored in a hash. Two examples are:
fred = {:person => {:name => "Fred", :spouse => "Wilma", :children => {:child => {:name => "Pebbles"}}}}
slate = {:person => {:name => "Mr. Slate", :spouse => "Mrs. Slate"}}
If the "person" doesn't have any children, the "children" element is not present. So, for Mr. Slate, we can check whether he has parents:
slate_has_children = !slate[:person][:children].nil?
So, what if we don't know that "slate" is a "person" hash? Consider:
dino = {:pet => {:name => "Dino"}}
We can't easily check for children any longer:
dino_has_children = !dino[:person][:children].nil?
NoMethodError: undefined method `[]' for nil:NilClass
So, how would you check the structure of a hash, especially if it is nested deeply (even deeper than the examples provided here)? Maybe a better question is: What's the "Ruby way" to do this?
The most obvious way to do this is to simply check each step of the way:
has_children = slate[:person] && slate[:person][:children]
Use of .nil? is really only required when you use false as a placeholder value, and in practice this is rare. Generally you can simply test it exists.
Update: If you're using Ruby 2.3 or later there's a built-in dig method that does what's described in this answer.
If not, you can also define your own Hash "dig" method which can simplify this substantially:
class Hash
def dig(*path)
path.inject(self) do |location, key|
location.respond_to?(:keys) ? location[key] : nil
end
end
end
This method will check each step of the way and avoid tripping up on calls to nil. For shallow structures the utility is somewhat limited, but for deeply nested structures I find it's invaluable:
has_children = slate.dig(:person, :children)
You might also make this more robust, for example, testing if the :children entry is actually populated:
children = slate.dig(:person, :children)
has_children = children && !children.empty?
With Ruby 2.3 we'll have support for the safe navigation operator:
https://www.ruby-lang.org/en/news/2015/11/11/ruby-2-3-0-preview1-released/
has_children now could be written as:
has_children = slate[:person]&.[](:children)
dig is being added as well:
has_children = slate.dig(:person, :children)
Another alternative:
dino.fetch(:person, {})[:children]
You can use the andand gem:
require 'andand'
fred[:person].andand[:children].nil? #=> false
dino[:person].andand[:children].nil? #=> true
You can find further explanations at http://andand.rubyforge.org/.
One could use hash with default value of {} - empty hash. For example,
dino = Hash.new({})
dino[:pet] = {:name => "Dino"}
dino_has_children = !dino[:person][:children].nil? #=> false
That works with already created Hash as well:
dino = {:pet=>{:name=>"Dino"}}
dino.default = {}
dino_has_children = !dino[:person][:children].nil? #=> false
Or you can define [] method for nil class
class NilClass
def [](* args)
nil
end
end
nil[:a] #=> nil
Traditionally, you really had to do something like this:
structure[:a] && structure[:a][:b]
However, Ruby 2.3 added a feature that makes this way more graceful:
structure.dig :a, :b # nil if it misses anywhere along the way
There is a gem called ruby_dig that will back-patch this for you.
def flatten_hash(hash)
hash.each_with_object({}) do |(k, v), h|
if v.is_a? Hash
flatten_hash(v).map do |h_k, h_v|
h["#{k}_#{h_k}"] = h_v
end
else
h[k] = v
end
end
end
irb(main):012:0> fred = {:person => {:name => "Fred", :spouse => "Wilma", :children => {:child => {:name => "Pebbles"}}}}
=> {:person=>{:name=>"Fred", :spouse=>"Wilma", :children=>{:child=>{:name=>"Pebbles"}}}}
irb(main):013:0> slate = {:person => {:name => "Mr. Slate", :spouse => "Mrs. Slate"}}
=> {:person=>{:name=>"Mr. Slate", :spouse=>"Mrs. Slate"}}
irb(main):014:0> flatten_hash(fred).keys.any? { |k| k.include?("children") }
=> true
irb(main):015:0> flatten_hash(slate).keys.any? { |k| k.include?("children") }
=> false
This will flatten all the hashes into one and then any? returns true if any key matching the substring "children" exist.
This might also help.
dino_has_children = !dino.fetch(person, {})[:children].nil?
Note that in rails you can also do:
dino_has_children = !dino[person].try(:[], :children).nil? #
Here is a way you can do a deep check for any falsy values in the hash and any nested hashes without monkey patching the Ruby Hash class (PLEASE don't monkey patch on the Ruby classes, such is something you should not do, EVER).
(Assuming Rails, although you could easily modify this to work outside of Rails)
def deep_all_present?(hash)
fail ArgumentError, 'deep_all_present? only accepts Hashes' unless hash.is_a? Hash
hash.each do |key, value|
return false if key.blank? || value.blank?
return deep_all_present?(value) if value.is_a? Hash
end
true
end
Simplifying the above answers here:
Create a Recursive Hash method whose value cannot be nil, like as follows.
def recursive_hash
Hash.new {|key, value| key[value] = recursive_hash}
end
> slate = recursive_hash
> slate[:person][:name] = "Mr. Slate"
> slate[:person][:spouse] = "Mrs. Slate"
> slate
=> {:person=>{:name=>"Mr. Slate", :spouse=>"Mrs. Slate"}}
slate[:person][:state][:city]
=> {}
If you don't mind creating empty hashes if the value does not exists for the key :)
You can try to play with
dino.default = {}
Or for example:
empty_hash = {}
empty_hash.default = empty_hash
dino.default = empty_hash
That way you can call
empty_hash[:a][:b][:c][:d][:e] # and so on...
dino[:person][:children] # at worst it returns {}
Given
x = {:a => {:b => 'c'}}
y = {}
you could check x and y like this:
(x[:a] || {})[:b] # 'c'
(y[:a] || {})[:b] # nil
Thks #tadman for the answer.
For those who want perfs (and are stuck with ruby < 2.3), this method is 2.5x faster:
unless Hash.method_defined? :dig
class Hash
def dig(*path)
val, index, len = self, 0, path.length
index += 1 while(index < len && val = val[path[index]])
val
end
end
end
and if you use RubyInline, this method is 16x faster:
unless Hash.method_defined? :dig
require 'inline'
class Hash
inline do |builder|
builder.c_raw '
VALUE dig(int argc, VALUE *argv, VALUE self) {
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
self = rb_hash_aref(self, *argv);
if (NIL_P(self) || !--argc) return self;
++argv;
return dig(argc, argv, self);
}'
end
end
end
You can also define a module to alias the brackets methods and use the Ruby syntax to read/write nested elements.
UPDATE: Instead of overriding the bracket accessors, request Hash instance to extend the module.
module Nesty
def []=(*keys,value)
key = keys.pop
if keys.empty?
super(key, value)
else
if self[*keys].is_a? Hash
self[*keys][key] = value
else
self[*keys] = { key => value}
end
end
end
def [](*keys)
self.dig(*keys)
end
end
class Hash
def nesty
self.extend Nesty
self
end
end
Then you can do:
irb> a = {}.nesty
=> {}
irb> a[:a, :b, :c] = "value"
=> "value"
irb> a
=> {:a=>{:b=>{:c=>"value"}}}
irb> a[:a,:b,:c]
=> "value"
irb> a[:a,:b]
=> {:c=>"value"}
irb> a[:a,:d] = "another value"
=> "another value"
irb> a
=> {:a=>{:b=>{:c=>"value"}, :d=>"another value"}}
I don't know how "Ruby" it is(!), but the KeyDial gem which I wrote lets you do this basically without changing your original syntax:
has_kids = !dino[:person][:children].nil?
becomes:
has_kids = !dino.dial[:person][:children].call.nil?
This uses some trickery to intermediate the key access calls. At call, it will try to dig the previous keys on dino, and if it hits an error (as it will), returns nil. nil? then of course returns true.
You can use a combination of & and key? it is O(1) compared to dig which is O(n) and this will make sure person is accessed without NoMethodError: undefined method `[]' for nil:NilClass
fred[:person]&.key?(:children) //=>true
slate[:person]&.key?(:children)

Resources