Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails: Disable root in JSON for only specific controller actions?

I know how to disable the root element globally, a la Rails 3.1 include_root_in_json or by using ActiveRecord::Base.include_root_in_json = false, but I only want to do this for a few JSON requests (not globally).

So far, I've been doing it like this:

@donuts = Donut.where(:jelly => true)
@coffees = Coffee.all
@breakfast_sandwiches = Sandwich.where(:breakfast => true)

dunkin_donuts_order = {}
dunkin_donuts_order[:donuts] = @donuts
dunkin_donuts_order[:libations] = @coffees
dunkin_donuts_order[:non_donut_food] = @breakfast_sandwiches

Donut.include_root_in_json = false
Coffee.include_root_in_json = false

render :json => dunkin_donuts_order

Donut.include_root_in_json = true
Coffee.include_root_in_json = true

There are about 5 cases where I have to do this, sometimes with more than one model, and it doesn't feel clean at all. I had tried putting this in around_filters, but exceptions were breaking the flow, and that was getting hairy as well.

There must be a better way!

like image 404
neezer Avatar asked Aug 30 '11 23:08

neezer


2 Answers

The answer is, unfortunately, yes and no.

Yes, what you've done above can arguably be done better. No, Rails won't let you add the root on a per-action basis. The JSON rendering just wasn't built with that sort of flexibility in mind.

That being said, here's what I'd do:

  1. Set include_root_in_json to false for those models which have root depending on the action (such as Donut and Coffee above).
  2. Override as_json to allow for greater flexibility. Here's an example:

    # in model.rb
    def as_json(options = nil)
        hash = serializable_hash(options)
        if options && options[:root]
            hash = { options[:root] => hash }
        else
            hash = hash
        end
    end
    

    This example will make it so that you can optionally pass a root but defaults to no root. You could alternatively write it the other way.

  3. Since you overrode as_json, you'll have to modify your render calls appropriately. So, for Donut, you'd do render :json => @donut.to_json.

Hope this helps!

like image 182
Logan Leger Avatar answered Nov 05 '22 08:11

Logan Leger


You can set include_root_in_json per model instance, and it won't affect the class's (see class_attribute in the rails api for a description of this behavior). So you can set a sensible default value on the class level, then set a different value on each instance in the relevant controllers.

Example:

@donuts = Donut.where(:jelly => true).each {|d| d.include_root_in_json = false }

For convenience, you can create a utility method that accepts an array of model instances and sets the value on all of them.

like image 31
Kelvin Avatar answered Nov 05 '22 09:11

Kelvin