Why doesn't the Digest gem show all the class as constants? - ruby

I am using the digest module for some task.
As always, ruby-doc has a very nice documentation.
When I want to get the classes from a module or class:
module A
class B end
C = Class.new(B)
D ||= 1
end
A.const_set(:E, Math::E)
p A.constants # => [:B, :C, :D, :E]
p A.constants.select { |x| A.const_get(x).kind_of?(Class) } # => [:B, :C]
But in Digest module, something ridiculous is happening:
require 'digest' # => true
p Digest.constants # => [:Class, :Base, :REQUIRE_MUTEX, :Instance]
p Digest::SHA1 # => Digest::SHA1
p Digest.constants # => [:Class, :Base, :SHA1, :REQUIRE_MUTEX, :Instance]
p Digest::SHA2 # => Digest::SHA2
p Digest.constants # => [:Class, :SHA2, :Base, :SHA1, :SHA256, :REQUIRE_MUTEX, :SHA512, :SHA384, :Instance]
As you can see the constant method doesn't return SHA1 class (on line 2). Then when I write Digest::SHA1 the program loads up the SHA1 class. Then when you run call the constants method again, it returns the SHA1 in the array.
What's going on here? Is there a way not to load all the classes in my program the same way? Is this an efficient design?

In the case of Digest, this behaviour is intentional. Depending on the hashing algorithm, different libraries are required. This is counterintuitive, but it's also expected behaviour as you can see here: https://github.com/ruby/ruby/blob/master/ext/digest/lib/digest.rb#L8-L25.
To change that, Ruby Core would need according adoption.
I hope you find this helpful.

The magic is in the const_missing singleton method in the Digest module. #knugje has pointed out a very useful link in his answer which makes in clearer.
ri documentation about const_missing says:
Invoked when a reference is made to an undefined constant in mod
So here's the trick:
module X
def self.const_missing(name)
name
end
end
p X.constants # => []
p X::Y # => :Y
p X.constants # => []
So the code doesn't define the constant in the module. So we can use a fairly simple trick:
module X
define_singleton_method(:const_missing) { |n| const_set(n, n.id2name) }
end
p X.constants # => []
p X::Y # => "Y"
p X::Z # => "Z"
p X.constants # => [:Y, :Z]
p X::Z # "Z"
That's how const_missing is used here. But Instead of const_set, what digest does is loading up a file with the name and do some exception handling and checking if the constants exist on the file.
Personally speaking, this may create confusion. For example, when you have no document to offline and online documents, you can't just simply run Digest.constants to list the available constants.
As knugie pointed out, you can read about the const_missing implementation in the Digest module here.

Related

Sorting/accessing through an array of nested hashes

I have an array of hashes with names and ages:
array = [ {"bill" => 12}, {"tom" => 13}, {"pat" => 14} ]
I realize that, by calling the first method, this happens:
array.first # => {"bill" => 12}
Without defining a class, I'd like to do:
array.first.name # => "bill"
How can I do that?
Doing:
def name
array[0].keys
end
will define a private method, which can't be called on the receiver.
You have:
h = array.first #=> {"bill" => 12}
h.class #=> Hash
so if you want to create a method first, such that:
h.name #=> "bill"
you must define name on the class of its receiver (h), which is Hash. #shivam has shown you how to that, but (as #AndrewMarshall points out in a comment) that it pollutes the class Hash. A better way is to use Refinements.
Refinements was an experimental addition to v2.0, then modified and made permanent in v2.1. It provides a way to avoid "monkey-patching" by providing "a way to extend a class locally".
We can do that as follows. First, refine Hash within a module M:
module M
refine Hash do
def name
keys.first
end
end
end
The refinement has no effect until the keyword using is invoked on the module:
h = {"bill" => 12}
puts h.name
#-> undefined method `name' for {"bill"=>12}:Hash (NoMethodError)
After activating the refinement we can invoke name on h:
using M
h.name
#-> bill
The refinement applies to the remainder of the file, but does not apply to to code in other files. Suppose we were to add the following to the present file:
class A
def greeting(h)
puts "Hello, #{h.name}"
end
end
Then:
A.new.greeting({"bill" => 12})
#-> Hello, bill
Since your Array array consists of Hashes, when you do array.first a Hash is returned {"bill" => 12}.
You can now define a method Hash#name that can be applied to array.first
(a Hash)
Here:
class Hash
def name
self.keys.first
end
end
array.first.name
# => "bill"
#to print all names
array.each{|a| puts a.name}
# bill
# tom
# pat
# or collect them in an Array
array.map(&:name)
# => ["bill", "tom", "pat"]

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.

Bidirectional Hash table in Ruby

I need a bidirectional Hash table in Ruby. For example:
h = {:abc => 123, :xyz => 789, :qaz => 789, :wsx => [888, 999]}
h.fetch(:xyz) # => 789
h.rfetch(123) # => abc
h.rfetch(789) # => [:xyz, :qaz]
h.rfetch(888) # => :wsx
Method rfetch means reversed fetch and is only my proposal.
Note three things:
If multiple keys map at the same value then rfetch returns all of them, packed in array.
If value is an array then rfetch looks for its param among elements of the array.
Bidirectional Hash means that both fetch and rfetch should execute in constant time.
Does such structure exists in Ruby (including external libraries)?
I thought about implementing it using two one-directional Hashes synchronized when one of them is modified (and packing it into class to avoid synchronization problems) but maybe I could use an already existing solution?
You could build something yourself pretty easily, just use a simple object that wraps two hashes (one for the forward direction, one for the reverse). For example:
class BiHash
def initialize
#forward = Hash.new { |h, k| h[k] = [ ] }
#reverse = Hash.new { |h, k| h[k] = [ ] }
end
def insert(k, v)
#forward[k].push(v)
#reverse[v].push(k)
v
end
def fetch(k)
fetch_from(#forward, k)
end
def rfetch(v)
fetch_from(#reverse, v)
end
protected
def fetch_from(h, k)
return nil if(!h.has_key?(k))
v = h[k]
v.length == 1 ? v.first : v.dup
end
end
Look ups will behave just like normal hash lookups (because they are normal hash lookups). Add some operators and maybe decent to_s and inspect implementations and you're good.
Such a thing works like this:
b = BiHash.new
b.insert(:a, 'a')
b.insert(:a, 'b')
b.insert(:a, 'c')
b.insert(:b, 'a')
b.insert(:c, 'x')
puts b.fetch(:a).inspect # ["a", "b", "c"]
puts b.fetch(:b).inspect # "a"
puts b.rfetch('a').inspect # [:a, :b]
puts b.rfetch('x').inspect # :c
puts b.fetch(:not_there).inspect # nil
puts b.rfetch('not there').inspect # nil
There's nothing wrong with building your tools when you need them.
There is no such structure built-in in Ruby.
Note that Hash#rassoc does something similar, but it returns only the first match and is linear-time:
h = {:abc => 123, :xyz => 789, :qaz => 789, :wsx => [888, 999]}
h.rassoc(123) # => [:abc, 123]
Also, it isn't possible to fullfill your requirements in Ruby in a perfectly safe manner, as you won't be able to detect changes in values that are arrays. E.g.:
h = MyBidirectionalArray.new(:foo => 42, :bar => [:hello, :world])
h.rfetch(:world) # => :bar
h[:bar].shift
h[:bar] # => [:world]
h.rfetch(:world) # => should be nil, but how to detect this??
Computing a hash everytime to detect a change will make your lookup linear-time. You could duplicate the array-values and freeze them, though (like Ruby does for Hash keys that are strings!)
What you seem to need is a Graph class, which could have a different API than a Hash, no? You can check out rgl or similar, but I don't know how they're implemented.
Good luck.
There is a Hash#invert method (http://www.ruby-doc.org/core-2.1.0/Hash.html#method-i-invert) to achieve this. It won't map multiple values to an array though.
Try this:
class Hash
def rfetch val
select { |k,v| v.is_a?(Array) ? v.include?(val) : v == val }.map { |x| x[0] }
end
end
If you're not doing lots of updates to this hash, you might be able to use inverthash.

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)

Best way to convert strings to symbols in hash

What's the (fastest/cleanest/straightforward) way to convert all keys in a hash from strings to symbols in Ruby?
This would be handy when parsing YAML.
my_hash = YAML.load_file('yml')
I'd like to be able to use:
my_hash[:key]
Rather than:
my_hash['key']
In Ruby >= 2.5 (docs) you can use:
my_hash.transform_keys(&:to_sym)
Using older Ruby version? Here is a one-liner that will copy the hash into a new one with the keys symbolized:
my_hash = my_hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
With Rails you can use:
my_hash.symbolize_keys
my_hash.deep_symbolize_keys
Here's a better method, if you're using Rails:
params.symbolize_keys
The end.
If you're not, just rip off their code (it's also in the link):
myhash.keys.each do |key|
myhash[(key.to_sym rescue key) || key] = myhash.delete(key)
end
For the specific case of YAML in Ruby, if the keys begin with ':', they will be automatically interned as symbols.
require 'yaml'
require 'pp'
yaml_str = "
connections:
- host: host1.example.com
port: 10000
- host: host2.example.com
port: 20000
"
yaml_sym = "
:connections:
- :host: host1.example.com
:port: 10000
- :host: host2.example.com
:port: 20000
"
pp yaml_str = YAML.load(yaml_str)
puts yaml_str.keys.first.class
pp yaml_sym = YAML.load(yaml_sym)
puts yaml_sym.keys.first.class
Output:
# /opt/ruby-1.8.6-p287/bin/ruby ~/test.rb
{"connections"=>
[{"port"=>10000, "host"=>"host1.example.com"},
{"port"=>20000, "host"=>"host2.example.com"}]}
String
{:connections=>
[{:port=>10000, :host=>"host1.example.com"},
{:port=>20000, :host=>"host2.example.com"}]}
Symbol
if you're using Rails, it is much simpler - you can use a HashWithIndifferentAccess and access the keys both as String and as Symbols:
my_hash.with_indifferent_access
see also:
http://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html
Or you can use the awesome "Facets of Ruby" Gem, which contains a lot of extensions to Ruby Core and Standard Library classes.
require 'facets'
> {'some' => 'thing', 'foo' => 'bar'}.symbolize_keys
=> {:some=>"thing", :foo=>"bar}
see also:
http://rubyworks.github.io/rubyfaux/?doc=http://rubyworks.github.io/facets/docs/facets-2.9.3/core.json#api-class-Hash
Even more terse:
Hash[my_hash.map{|(k,v)| [k.to_sym,v]}]
Since Ruby 2.5.0 you can use Hash#transform_keys or Hash#transform_keys!.
{'a' => 1, 'b' => 2}.transform_keys(&:to_sym) #=> {:a => 1, :b => 2}
http://api.rubyonrails.org/classes/Hash.html#method-i-symbolize_keys
hash = { 'name' => 'Rob', 'age' => '28' }
hash.symbolize_keys
# => { name: "Rob", age: "28" }
If you are using json, and want to use it as a hash, in core Ruby you can do it:
json_obj = JSON.parse(json_str, symbolize_names: true)
symbolize_names: If set to true, returns symbols for the names (keys) in a JSON object. Otherwise strings are returned. Strings are the default.
Doc: Json#parse symbolize_names
Here's a way to deep symbolize an object
def symbolize(obj)
return obj.inject({}){|memo,(k,v)| memo[k.to_sym] = symbolize(v); memo} if obj.is_a? Hash
return obj.inject([]){|memo,v | memo << symbolize(v); memo} if obj.is_a? Array
return obj
end
I really like the Mash gem.
you can do mash['key'], or mash[:key], or mash.key
A modification to #igorsales answer
class Object
def deep_symbolize_keys
return self.inject({}){|memo,(k,v)| memo[k.to_sym] = v.deep_symbolize_keys; memo} if self.is_a? Hash
return self.inject([]){|memo,v | memo << v.deep_symbolize_keys; memo} if self.is_a? Array
return self
end
end
params.symbolize_keys will also work. This method turns hash keys into symbols and returns a new hash.
In Rails you can use:
{'g'=> 'a', 2 => {'v' => 'b', 'x' => { 'z' => 'c'}}}.deep_symbolize_keys!
Converts to:
{:g=>"a", 2=>{:v=>"b", :x=>{:z=>"c"}}}
So many answers here, but the one method rails function is hash.symbolize_keys
This is my one liner for nested hashes
def symbolize_keys(hash)
hash.each_with_object({}) { |(k, v), h| h[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v }
end
In case the reason you need to do this is because your data originally came from JSON, you could skip any of this parsing by just passing in the :symbolize_names option upon ingesting JSON.
No Rails required and works with Ruby >1.9
JSON.parse(my_json, :symbolize_names => true)
You could be lazy, and wrap it in a lambda:
my_hash = YAML.load_file('yml')
my_lamb = lambda { |key| my_hash[key.to_s] }
my_lamb[:a] == my_hash['a'] #=> true
But this would only work for reading from the hash - not writing.
To do that, you could use Hash#merge
my_hash = Hash.new { |h,k| h[k] = h[k.to_s] }.merge(YAML.load_file('yml'))
The init block will convert the keys one time on demand, though if you update the value for the string version of the key after accessing the symbol version, the symbol version won't be updated.
irb> x = { 'a' => 1, 'b' => 2 }
#=> {"a"=>1, "b"=>2}
irb> y = Hash.new { |h,k| h[k] = h[k.to_s] }.merge(x)
#=> {"a"=>1, "b"=>2}
irb> y[:a] # the key :a doesn't exist for y, so the init block is called
#=> 1
irb> y
#=> {"a"=>1, :a=>1, "b"=>2}
irb> y[:a] # the key :a now exists for y, so the init block is isn't called
#=> 1
irb> y['a'] = 3
#=> 3
irb> y
#=> {"a"=>3, :a=>1, "b"=>2}
You could also have the init block not update the hash, which would protect you from that kind of error, but you'd still be vulnerable to the opposite - updating the symbol version wouldn't update the string version:
irb> q = { 'c' => 4, 'd' => 5 }
#=> {"c"=>4, "d"=>5}
irb> r = Hash.new { |h,k| h[k.to_s] }.merge(q)
#=> {"c"=>4, "d"=>5}
irb> r[:c] # init block is called
#=> 4
irb> r
#=> {"c"=>4, "d"=>5}
irb> r[:c] # init block is called again, since this key still isn't in r
#=> 4
irb> r[:c] = 7
#=> 7
irb> r
#=> {:c=>7, "c"=>4, "d"=>5}
So the thing to be careful of with these is switching between the two key forms. Stick with one.
Would something like the following work?
new_hash = Hash.new
my_hash.each { |k, v| new_hash[k.to_sym] = v }
It'll copy the hash, but you won't care about that most of the time. There's probably a way to do it without copying all the data.
a shorter one-liner fwiw:
my_hash.inject({}){|h,(k,v)| h.merge({ k.to_sym => v}) }
How about this:
my_hash = HashWithIndifferentAccess.new(YAML.load_file('yml'))
# my_hash['key'] => "val"
# my_hash[:key] => "val"
This is for people who uses mruby and do not have any symbolize_keys method defined:
class Hash
def symbolize_keys!
self.keys.each do |k|
if self[k].is_a? Hash
self[k].symbolize_keys!
end
if k.is_a? String
raise RuntimeError, "Symbolizing key '#{k}' means overwrite some data (key :#{k} exists)" if self[k.to_sym]
self[k.to_sym] = self[k]
self.delete(k)
end
end
return self
end
end
The method:
symbolizes only keys that are String
if symbolize a string means to lose some informations (overwrite part of hash) raise a RuntimeError
symbolize also recursively contained hashes
return the symbolized hash
works in place!
The array we want to change.
strings = ["HTML", "CSS", "JavaScript", "Python", "Ruby"]
Make a new variable as an empty array so we can ".push" the symbols in.
symbols = [ ]
Here's where we define a method with a block.
strings.each {|x| symbols.push(x.intern)}
End of code.
So this is probably the most straightforward way to convert strings to symbols in your array(s) in Ruby. Make an array of strings then make a new variable and set the variable to an empty array. Then select each element in the first array you created with the ".each" method. Then use a block code to ".push" all of the elements in your new array and use ".intern or .to_sym" to convert all the elements to symbols.
Symbols are faster because they save more memory within your code and you can only use them once. Symbols are most commonly used for keys in hash which is great. I'm the not the best ruby programmer but this form of code helped me a lot.If anyone knows a better way please share and you can use this method for hash too!
If you would like vanilla ruby solution and as me do not have access to ActiveSupport here is deep symbolize solution (very similar to previous ones)
def deep_convert(element)
return element.collect { |e| deep_convert(e) } if element.is_a?(Array)
return element.inject({}) { |sh,(k,v)| sh[k.to_sym] = deep_convert(v); sh } if element.is_a?(Hash)
element
end
Starting on Psych 3.0 you can add the symbolize_names: option
Psych.load("---\n foo: bar")
# => {"foo"=>"bar"}
Psych.load("---\n foo: bar", symbolize_names: true)
# => {:foo=>"bar"}
Note: if you have a lower Psych version than 3.0 symbolize_names: will be silently ignored.
My Ubuntu 18.04 includes it out of the box with ruby 2.5.1p57
ruby-1.9.2-p180 :001 > h = {'aaa' => 1, 'bbb' => 2}
=> {"aaa"=>1, "bbb"=>2}
ruby-1.9.2-p180 :002 > Hash[h.map{|a| [a.first.to_sym, a.last]}]
=> {:aaa=>1, :bbb=>2}
This is not exactly a one-liner, but it turns all string keys into symbols, also the nested ones:
def recursive_symbolize_keys(my_hash)
case my_hash
when Hash
Hash[
my_hash.map do |key, value|
[ key.respond_to?(:to_sym) ? key.to_sym : key, recursive_symbolize_keys(value) ]
end
]
when Enumerable
my_hash.map { |value| recursive_symbolize_keys(value) }
else
my_hash
end
end
I like this one-liner, when I'm not using Rails, because then I don't have to make a second hash and hold two sets of data while I'm processing it:
my_hash = { "a" => 1, "b" => "string", "c" => true }
my_hash.keys.each { |key| my_hash[key.to_sym] = my_hash.delete(key) }
my_hash
=> {:a=>1, :b=>"string", :c=>true}
Hash#delete returns the value of the deleted key
Facets' Hash#deep_rekey is also a good option, especially:
if you find use for other sugar from facets in your project,
if you prefer code readability over cryptical one-liners.
Sample:
require 'facets/hash/deep_rekey'
my_hash = YAML.load_file('yml').deep_rekey
In ruby I find this to be the most simple and easy to understand way to turn string keys in hashes to symbols :
my_hash.keys.each { |key| my_hash[key.to_sym] = my_hash.delete(key)}
For each key in the hash we call delete on it which removes it from the hash (also delete returns the value associated with the key that was deleted) and we immediately set this equal to the symbolized key.
Similar to previous solutions but written a bit differently.
This allows for a hash that is nested and/or has arrays.
Get conversion of keys to a string as a bonus.
Code does not mutate the hash been passed in.
module HashUtils
def symbolize_keys(hash)
transformer_function = ->(key) { key.to_sym }
transform_keys(hash, transformer_function)
end
def stringify_keys(hash)
transformer_function = ->(key) { key.to_s }
transform_keys(hash, transformer_function)
end
def transform_keys(obj, transformer_function)
case obj
when Array
obj.map{|value| transform_keys(value, transformer_function)}
when Hash
obj.each_with_object({}) do |(key, value), hash|
hash[transformer_function.call(key)] = transform_keys(value, transformer_function)
end
else
obj
end
end
end

Resources