Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

  • add attr_accessor :link_html to model
  • add an instance method to set the new accessor

...

def set_link_html(token)
  self.link_html = link_with_tracking_token(token)
end
  • override as_json in the model, replacing the original html_code with link_html

...

def as_json(options = {})
  super(:methods => :link_html, :except => :html_code)
end
  • iterate over the collection returned in the controller method to do the transformation

...

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.

like image 661
Tom Harrison Avatar asked Aug 04 '12 20:08

Tom Harrison


1 Answers

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
like image 147
anxiety Avatar answered Oct 18 '22 03:10

anxiety