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:
-
instance_variables
returns an array of object's instance variables. -
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?
Top comments (0)