Davide Targa website

Programming, web development and open source

Symbolize Hash Keys in Ruby

In a Rails application YAML files are often used to manage configuration parameters. YAML is a human readable data serialization file format. Let’s say we have a YAML file called constants.yml that contains some configurations for our rails application like the following:

1
2
name: "Albert"
surname: "Einstein"

YAML files can be loaded to be used in a rails application through an initializer. So let’s create a new file inside config/initializers and call it load_constants.rb. The content of this file can be something like this:

1
CONSTANTS = YAML.load_file("#{RAILS_ROOT}/config/constants.yml")

To have something more dynamic, this can be improved to load all the files contained inside an array and to create a constant named like the file. The code to achieve this is the following:

1
2
3
4
5
CONFIG_FILES = ["constants", "config"]
CONFIG_FILES.each do |file_name|
  tmp = YAML.load_file(File.join("#{Rails.root}","config","#{file_name}.yml"))
  eval("#{file_name.upcase} = tmp")
end

Now inside the application we have access to the contant. To get the values inside of it we can write:

1
2
CONSTANTS["name"]
=> "Albert"

In my opinion, using strings as hash keys is a bit unconfortable, don’t you tink? I prefer using symbols instead.

A way to obtain this behavior is to extend the basic Ruby Hash class with a new metod. Let’s create a file called hash.rb in the lib directory. Inside it we can extend the Hash class with the symbolize_keys method. This is the content of the file:

1
2
3
4
5
6
7
8
class Hash
  def symbolize_keys!
    t=self.dup
    self.clear
    t.each_pair{|k,v| self[k.to_sym] = v}
    self
  end
end

In this way, we can modify the initializer we wrote before like the following:

1
2
3
4
5
CONFIG_FILES = ["constants", "config"]
CONFIG_FILES.each do |file_name|
  tmp = YAML.load_file(File.join("#{Rails.root}","config","#{file_name}.yml")).symbolize_keys!
  eval("#{file_name.upcase} = tmp")
end

Now we can have access to all the constants defined from within our application in this way:

1
2
CONSTANTS[:name]
=> "Albert"

The solution described before works only for one level nested YAML files. For example, if we have this YAML file, the above solution does not work:

1
2
3
4
5
6
7
physicist:
  name: "Albert"
  surname: "Einstein"

painter:
  name: "Leonardo"
  surname: "da Vinci"

To convert this YAML file with the symbolize_keys method, we need to modify it by adding a recursive call to itself if the current value is an Hash:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Hash
  def symbolize_keys!
    t=self.dup
    self.clear
    t.each_pair do |k,v|
      if v.kind_of?(Hash)
        v.symbolize_keys
      end
      self[k.to_sym] = v
      self
    end
    self
  end
end

Note that the method is a bang (!) method. This means that the object on which it’s invoked will be permanently modified.

Hope this helps :–)

Comments