on programming Rails for fun and profit...

How to Convert a Ruby Object to Hash

Let’s say you have a Product object with properties @name = "Table" & @price = 10. What’s the best way in Rails to convert this object to the Hash { name: "Table", price: 10 }?

Why?

Earlier this evening, I was trying to write an integration test to post some data to the server. The ActiveRecord model I was trying to create had many properties, so my params object grew quite a bit.

post records_path, params: { record: { attr_one: "", attr_two: "", ..., attr_n: "" } }

However, I was using FactoryBot for testing, and could easily create a throwaway object using a factory.

record = build(:record)

Now only if there was a simple way to transform this record object to a hash with the property names as keys and their values as the hash values.

It turns out, there are a few ways to achieve this.

The as_json Method

The as_json method converts a model to a hash, containing the attributes with their names as keys and the values of the attributes as values

user = User.find(1)
user.as_json
# => { "id" => 1, "name" => "Konata Izumi", "age" => 16 }

Additionally, you can include the model itself as the root of the object.

user = User.find(1)
user.as_json(root: true)
# => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16 } }

This method also gives you more control over the resulting JSON representation. For example, you can use the :only and :except options to select which attributes you want to include and skip, respectively. For more details, refer the documentation.

Using this method, I simplified my code to:

post records_path, params: build(:record).as_json(root: true)

The attributes Method

The ActiveRecord#attributes is another method you can use. Given an active record, attributes returns a hash of all the attributes with their names as keys and the values of the attributes as values.

class Person < ActiveRecord::Base
end

person = Person.create(name: 'Francesco', age: 22)
person.attributes
# => {"id"=>3, "created_at"=>Sun, 21 Oct 2012 04:53:04, "updated_at"=>Sun, 21 Oct 2012 04:53:04, "name"=>"Francesco", "age"=>22}

The attributes method returns a hash where all the keys are strings. If you want, it’s easy to symbolize the hash using the aptly named symbolize_keys method or its alias to_options.

hash = { 'name' => 'Rob', 'age' => '28' }

hash.symbolize_keys
# => {:name=>"Rob", :age=>"28"}

Using these methods, I refactored my code to:

post records_path, params: { record: build(:record).attributes.to_options }

The Ruby Way

If you are looking for a plain Ruby solution, you can do this using metaprogramming. For this, you will need two methods:

  1. instance_variables returns an array of object’s instance variables.
  2. instance_variable_get returns the value of the instance variable, given the variable name.

With that, you could do something like this:

hash = Hash.new
record.instance_variables.each do |v| 
  hash[v.to_s.delete("@")] = record.instance_variable_get(v) 
end

What do you think?

Subscribe to Akshay's Blog

Sign up now to get access to the library of members-only issues.
Jamie Larson
Subscribe