Why is it possible to change a frozen constant? - ruby

Why is it possible to do the following? I would not expect it to be.
CAD={:hey => {a: [1], b: [2]}}.freeze
CAD.frozen? #=> true
p=CAD[:hey][:a] #=> [1]
p << nil #=> [1, nil]
CAD #=> {:hey=>{:a=>[1, nil], :b=>[2]}}
UPDATE
I found a solution, thanks to the answer: http://blog.flavorjon.es/2008/08/freezing-deep-ruby-data-structures.html

Only the hash object represented by CAD is frozen, but not the other objects referenced on the hash like CAD[:hey][:a].
> CAD={:hey => {a: [1], b: [2]}}.freeze
=> {:hey=>{:a=>[1], :b=>[2]}}
> CAD.frozen?
=> true
> CAD[:hey].frozen?
=> false
> CAD[:hey][:a].frozen?
=> false

Related

How can I place a new key value pair to the top of a hash?

I have some hash:
> my_hash = {b: "foo", c: "bar"}
I add a new key and it goes to the end:
> my_hash[:a] = "baz"
=> "baz"
> my_hash
=> {b: "foo", c: "bar", a: "baz"}
But I want it to go to the front:
> my_hash
=> {a: "baz", b: "foo", c: "bar"}
Thanks in advance
Theoretically hash elements' order is not important, but who am I to judge your reasons you might have valid reasons we don't know about. You can use Hash#merge for that:
> {a: "baz"}.merge(my_hash)
=> {:a=>"baz", :b=>"foo", :c=>"bar"}
> my_hash = {a: "baz"}.merge(my_hash)
=> {:a=>"baz", :b=>"foo", :c=>"bar"}
> my_hash
=> {:a=>"baz", :b=>"foo", :c=>"bar"}

How to tell if there are other keys in a hash other than specified ones?

Say I have a hash:
a = {b: "asdfgh", c: "qwerty", d: "dvorak"}
And I want to be able to tell if there are keys other than the ones I specify in it, something like this:
a.has_other_keys?(:b, :c, :d)
=> false
a.has_other_keys?(:c, :d)
=> true
But I don't want it to return false if there are LESS keys than specified:
a.has_other_keys?(:b, :c, :d, :e)
=> true
Is there an easy way to do this in ruby?
Rails has an except/except! method that returns the hash with those keys removed. If you're already using Rails, You can use it
class Hash
# Returns a hash that includes everything but the given keys.
# hash = { a: true, b: false, c: nil}
# hash.except(:c) # => { a: true, b: false}
# hash # => { a: true, b: false, c: nil}
#
# This is useful for limiting a set of parameters to everything but a few known toggles:
# #person.update(params[:person].except(:admin))
def except(*keys)
dup.except!(*keys)
end
# Replaces the hash without the given keys.
# hash = { a: true, b: false, c: nil}
# hash.except!(:c) # => { a: true, b: false}
# hash # => { a: true, b: false }
def except!(*keys)
keys.each { |key| delete(key) }
self
end
end
Using above you can do:
bundle :002 > a = {b: "asdfgh", c: "qwerty", d: "dvorak"}
=> {:b=>"asdfgh", :c=>"qwerty", :d=>"dvorak"}
bundle :006 > a.except(:b)
=> {:c=>"qwerty", :d=>"dvorak"}
bundle :007 > a.except(:b).length
=> 2
bundle :008 > a.except(:b, :c, :d, :e).length
=> 0
In plain Ruby, You can do something like following:
2.2.2 :010 > a.select{|x| ![:b, :c, :d, :e].include?(x)}
=> {}
2.2.2 :011 > a.select{|x| ![:b, :c, :d, :e].include?(x)}.length
=> 0
EDIT: I just noticed Cary Swoveland's answer uses this same technique. His answer was here first.
If this doesn't have to be a high performance solution, you might consider using Array arithmetics (which a nice Ruby feature).
For example:
Your hash:
a = {b: "asdfgh", c: "qwerty", d: "dvorak"}
And our known keys:
known_keys = [:b, :c]
The rest of the keys:
other_keys = a.keys - known_keys
other_keys.empty?
For a true/false statement, we can do something like this:
(a.keys - [:b, :c]).empty?
def subset_of_keys?(h, other_keys)
(other_keys - h.keys).empty?
end
h = { b: "asdfgh", c: "qwerty", d: "dvorak"}
subset_of_keys? h, [:d, :b]
#=> true
subset_of_keys? h, [:b, :w, :d]
#=> false
See Array#-.
You can check whether sets constructed from the keys are the same:
require 'set'
class Hash
def has_other_keys?(*keys)
Set.new(keys) != Set.new(self.keys)
end
end

Ruby - explain code snippet hash sorter

I use this code for sorting Hash;
I've got no idea how its works.
please explain to me:
def foo(hash)
Hash[hash.sort]
end
irb(main):001:0> h = {1=>'z', 3=>'x', 2=>'y'}
=> {1=>"z", 3=>"x", 2=>"y"}
irb(main):002:0> Hash[h.sort]
=> {1=>"z", 2=>"y", 3=>"x"}
irb(main):003:0>
Enumerable#sort reutrns an sorted array of key-value pairs:
h = {b: 1, a: 2}
h.sort
# => [[:a, 2], [:b, 1]]
Hash::[] create a new hash base on the argument:
Hash[h.sort]
# => {:a=>2, :b=>1}
BTW, if you use Ruby 2.1+, you can use Array#to_h instead:
h.sort.to_h
# => {:a=>2, :b=>1}

How to test order-conscious equality of hashes

Ruby 1.9.2 introduced order into hashes. How can I test two hashes for equality considering the order?
Given:
h1 = {"a"=>1, "b"=>2, "c"=>3}
h2 = {"a"=>1, "c"=>3, "b"=>2}
I want a comparison operator that returns false for h1 and h2. Neither of the followings work:
h1 == h2 # => true
h1.eql? h2 # => true
Probably the easiest is to compare the corresponding arrays.
h1.to_a == h2.to_a
You could compare the output of their keys methods:
h1 = {one: 1, two: 2, three: 3} # => {:one=>1, :two=>2, :three=>3}
h2 = {three: 3, one: 1, two: 2} # => {:three=>3, :one=>1, :two=>2}
h1 == h2 # => true
h1.keys # => [:one, :two, :three]
h2.keys # => [:three, :one, :two]
h1.keys.sort == h2.keys.sort # => true
h1.keys == h2.keys # => false
But, comparing Hashes based on key insertion order is kind of strange. Depending on what exactly you're trying to do, you may want to reconsider your underlying data structure.

Convert Ruby hash into array without calling `Hash#to_a`

The Savon gem I am using is giving me back a single object or an array, and I have no way to know which it will be until the SOAP response comes back.
For convenience I would like to have a nil response converted to [], a single response converted to [obj] and an array stay as an array. This can easily be done with Kernel#Array, thus:
> Array nil
=> []
> Array 1
=> [1]
> Array [1,2,3]
=> [1, 2, 3]
However, because Kernel#Array calls to_a, it fails for Hash which overrides to_a:
> Array({a: 1})
=> [[:a, 1]]
> Array([{a: 1}, {b: 2}])
=> [{:a=>1}, {:b=>2}]
On line 2 above I would like to see [{:a=>1}].
If you are using ActiveSupport, you can do the following:
> Array.wrap({a: 1})
=> [{:a, 1}]
> Array.wrap([{a: 1}, {b: 2}])
=> [{:a=>1}, {:b=>2}]
>> [nil].compact.flatten(1)
=> []
>> [1].compact.flatten(1)
=> [1]
>> [{a: 1, b: 2}].compact.flatten(1)
=> [{:a=>1, :b=>2}]
Currently I am able to bypass Hash#to_a with my own straight_to_a method:
def straight_to_a(o)
o.kind_of?(Array) ? o : [o].compact
end
Thus:
> straight_to_a nil
=> []
> straight_to_a 1
=> [1]
> straight_to_a( {a: 1} )
=> [{:a=>1}]
I'm hoping there's an easier way?
Another poster suggested the use of Active Support. If you don't want to add an extra dependency to your project for just one method, here is the source code for Active Support's Array.wrap:
class Array
def self.wrap(object)
if object.nil?
[]
elsif object.respond_to?(:to_ary)
object.to_ary || [object]
else
[object]
end
end
end
You could easily add this code to your own utilities.rb or core_extensions.rb file and include that in your project.
Your solution seems ok, perhaps you can try something based on flatten, like
def straight_to_a *ary
ary.flatten(1)
end

Resources