thefugal
thefugal

Reputation: 1214

Renaming model association attributes in Rails JSON serialization?

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

Answers (3)

Crowleg
Crowleg

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

zetetic
zetetic

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

icecream
icecream

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

Related Questions