Ruby-on-rails – Renaming model association attributes in Rails JSON serialization

activerecordjsonruby-on-railsruby-on-rails-3serialization

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?

Best Answer

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