Reputation: 944
I want to return all the Thing model objects with just the associations without the asscoiation_id
, is there a better way to do this without include
and except
?
# Thing.rb
belongs_to :object_a
belongs_to :object_b
# create_thing.rb
def change
create_table :things, id: false do |t|
t.string :id, limit: 36, primary_key: true
t.string :object_a_id, foreign_key: true
t.string :object_b_id, foreign_key: true
t.timestamps
end
end
# things_controller.rb
render json: Thing.all, include: [:object_a, :object_b]
output => {
id: ....
object_a_id: 'object_a_id',
object_b_id: 'object_b_id',
object_a: {
id: object_a_id
...
},
object_b: {
id: object_b_id
...
}
I know I can do this to get what I want but I wondered if there is a DRY way to do this without all the include and except.
render json: Thing.all, include: [:object_a, :object_b], except: [:object_a_id, :object_b_id]
output => {
id: ....
object_a: {
id: object_a_id
...
},
object_b: {
id: object_b_id
...
}
Upvotes: 1
Views: 837
Reputation: 1135
A DRY approach is inside your models, you can define a attributes
method and have it return the shape of the object that you want the render function to use.
# thing.rb
def attributes
# Return the shape of the object
# You can use symbols if you like instead of string keys
{
'id' => id, # id of the current thing
'other_fields' => other_fields, # add other fields you want in the serialized result
'object_a' => object_a, # explicitly add the associations
'object_b' => object_b
}
end
The associations object_a
and object_b
should get serialized as normal. You can repeat the same approach for them by adding an attributes
method in their respective classes if you want to limit/customize their serialized result.
So when render json:
is called on a single, or a collection of, thing model(s), the shape of the json objects returned will be as defined in the method above.
One caveat is your key names in the hash being returned in attributes
has to match the name of the method (or association name). I'm not too sure why. But the workaround that I've used when needing to add a key with a different name than its corresponding column is to make a method in the model of the key name I want to use.
For example, say your Thing
model has a column name
, but in your json result you want the key name that corresponds to that column to be called name_of_thing
. You'd do the following:
def name_of_thing
name
end
def attributes
{
'name_of_thing' => name_of_thing,
# other fields follow
# ...
}
end
The attributes
method can support conditional based on fields in the model.
# thing.rb
def attributes
result = {}
result['id'] = id
# add other fields
# For example, if association to object_a exists
if object_a
result.merge!({
'object_a' => object_a
})
end
# return result
result
end
If you want to make your method render different fields in different places, one thing you can do is override the as_json
method, which can work better for these cases because this method accepts options in a parameter.
# thing.rb
def as_json(options = {})
result = {}
result['id'] = id
# add other fields to result
if(options[:show_only_ids])
result.merge!({
'object_a_id' => object_a_id,
'object_b_id' => object_b_id
})
else
result.merge!({
'object_a' => object_a,
'object_b' => object_b
})
end
# return result
result
end
Then you need to modify your controller (or wherever you're calling the serialization of the Thing
model) to pass in the appropriate option when necessary.
# thing_controller.rb
render json: Thing.all.as_json({ show_only_ids: true })
When rendering, you don't always have to explicitly specify the as_json
. The render function will call it anyway by default. You only need to explicitly make that call when you want to pass in options.
Upvotes: 1