Reputation: 1214
I have an existing Rails 3 application, to which I am adding a JSON API. We have a Vendor
ActiveRecord model, and an Employee
ActiveRecord model. An Employee
belongs to a Vendor
. In the API, we want to include an Employee
's Vendor
in the JSON serialization. For example:
# Employee as JSON, including Vendor
:employee => {
# ... employee attributes ...
:vendor => {
# ... vendor attributes ...
}
}
That's easy enough. However, I have a business requirement that the public API not expose the internal model names. That is, to the outside world, it needs to seem as though the Vendor
model is actually called Business
:
# Employee as JSON, including Vendor as Business
:employee => {
# ... employee attributes ...
:business => {
# ... vendor attributes ...
}
}
This is easy to do for the top-level object. That is, I could call @employee.as_json(:root => :dude_who_works_here)
to rename Employee
to DudeWhoWorksHere
in the JSON. But what about for included associations? I have tried a few things without success:
# :as in the association doesn't work
@employee.as_json(:include => {:vendor => {:as => :business}})
# :root in the association doesn't work
@employee.as_json(:include => {:vendor => {:root => :business}})
# Overriding Vendor's as_json doesn't work (at least, not in a association)
# ... (in vendor.rb)
def as_json(options)
super(options.merge({:root => :business}))
end
# ... elsewhere
@employee.as_json(:include => :vendor)
The only other idea I have is to manually rename the key, something like this:
# In employee.rb
def as_json(options)
json = super(options)
if json.key?(:vendor)
json[:business] = json[:vendor]
json.delete(:vendor)
end
return json
end
But that seems inelegant. I'm hoping there's a cleaner, more Rails-y way to do what I want. Any ideas?
Upvotes: 11
Views: 10617
Reputation: 1
I ran into the same issue, so I modified the as_json
to also run a recursive method to scan through the options, and switch the associations' names for the alias names. Anyhow, it works for my app, might could work for yours.
def self.test_serializer
as_json(
{
:include => {
:contact_info => {as: :contact_info_attributes}
}
}
)
end
def as_json(options={})
data = super(options)
data = as_json_associations_alias_fix(options, data)
return data
end
def as_json_associations_alias_fix options, data, opts = {}
if options[:include].present?
options[:include].each do |include_key, include_hash|
data_key = include_key.to_s
if include_hash[:as].present?
alias_name = include_hash[:as]
data[alias_name.to_s] = data[include_key.to_s]# if !data.is_a?(Array)
data.delete(include_key.to_s)# if !data.is_a?(Array)
data_key = alias_name.to_s
end
# Handle to-one association here, else handle the to-many association.
if !data[data_key].is_a?(Array)
data[data_key] = as_json_associations_alias_fix(include_hash, data[data_key])
else
data[data_key].each_with_index do |value,i|
data[i] = as_json_associations_alias_fix(include_hash, value)
end
end
end
end
return data
end
Upvotes: 0
Reputation: 47548
Here's a sneaky way of doing it. Create a model called Business mapped to the vendors table:
class Business < ActiveRecord::Base
set_table_name "vendors"
end
Now add a belongs_to on Employee:
class Employee < ActiveRecord::Base
belongs_to :vendor
belongs_to :business, :foreign_key => :vendor_id
...
end
Call to_json
on Employee
passing in the "virtual" association as an option:
Employee.first.to_json(:only=>:name,:include=>:business)
# "{\"employee\":{\"name\":\"Curly\",\"business\":{\"name\":\"Moe's Garage\"}}}"
Upvotes: -1
Reputation: 1425
Trying to generate complex JSON with the built-in options for as_json is clumsy. I would override as_json
in your models, and not bother with calling super
at all. Make up your own option keys for as_json
to control what you want to include in the Hash.
# employee.rb
def as_json(options = {})
json = {:name => name, ...} # whatever info you want to expose
json[:business] = vendor.as_json(options) if options[:include_vendor]
json
end
Upvotes: 14