Tom Harrison
Tom Harrison

Reputation: 14018

Rails: overriding as_json for dynamic value -- is there a smarter way?

I want to output a list of affiliate links, each tagged to identify the current user. It would be simple in HTML, but we're writing an API, so the output is JSON.

I have it working, but it seems overly complicated. Is this the best approach?

My model, AffiliateLink contains a field (the raw HTML of the link) that I'll transform and output on the fly by adding a token. I have a model method that produces the replacement -- it is non-trivial because we use multiple affiliates and each has a special transformation rule that this method knows about:

def link_with_token(user_token)
  # some gnarly code that depends on a lot of stuff the model knows
  # that returns a proper link
end

To get my correct link html in JSON I have done these things:

...

def set_link_html(token)
  self.link_html = link_with_tracking_token(token)
end

...

def as_json(options = {})
  super(:methods => :link_html, :except => :html_code)
end

...

def index
  @links = Admin::AffiliateLink.all  # TODO, pagination, etc.

  respond_to do |format|
    format.html # index.html.erb
    format.json do
      @links.each do |link|
        link.set_link_html(account_tracking_token)
      end
      render json: @links
    end
  end
end

This seems like a lot of stuff to do just to get my teensy-weensy transformation done. Helpful suggestions (relating to this problem and not to other aspects of the code, which is in flux now) are welcome.

Upvotes: 12

Views: 7936

Answers (1)

anxiety
anxiety

Reputation: 1709

1) A quick solution to your problem (as demonstrated here):

affiliate_links_controller.rb

def index
  @links = Admin::AffiliateLink.all  # TODO, pagination, etc.

  respond_to do |format|
    format.html # index.html.erb
    format.json do
      render json: @links.to_json(:account_tracking_token => account_tracking_token)
    end
  end
end

AffiliateLink.rb

# I advocate reverse_merge so passed-in options overwrite defaults when option
# keys match.
def as_json(options = {})
  json = super(options.reverse_merge(:except => :html_code))
  json[:link_with_token] = link_with_token(options[:account_tracking_token])
  json
end

2) A more hardcore solution, if you're really writing an API:

  1. See this article describing your problem.
  2. See the gem that the authors made as a solution.
  3. See this railscast on using the gem.

3) And lastly, the convenient solution. If you have a convenient model relation, this is clean:

Pretending AffiliateLink belongs_to :user. And assuming user_token is an accessible attribute of User.

AffiliateLink.rb

# have access to user.user_token via relation
def link_with_token
  # some gnarly code that depends on a lot of stuff the model knows
  # that returns a proper link
end

def as_json(options = {})
  super(options.reverse_merge(:methods => :link_with_token, :except => :html_code))
end

affiliate_links_controller.rb

def index
  @links = Admin::AffiliateLink.all  # TODO, pagination, etc.

  respond_to do |format|
    format.html # index.html.erb
    format.json do
      render json: @links
    end
  end
end

Upvotes: 18

Related Questions