How to Convert a Ruby Object to Hash
Tags: ruby, rails
Published: 2022-05-02 13:55:03

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](https://guides.rubyonrails.org/testing.html#creating-articles-integration) 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. 

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

However, I was using [FactoryBot](https://github.com/thoughtbot/factory_bot) for testing, and could easily create a throwaway object using a factory. 

```ruby
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`](https://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html#method-i-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

```ruby
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. 

```ruby
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](https://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html#method-i-as_json). 

Using this method, I simplified my code to:

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

### The _attributes_ Method

The [`ActiveRecord#attributes`](https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods.html#method-i-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.

```ruby
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`](https://api.rubyonrails.org/classes/Hash.html#method-i-symbolize_keys) method or its alias `to_options`. 

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

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

Using these methods, I refactored my code to: 

```ruby
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: 

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

**What do you think?**