Brad
Brad

Reputation:

Ruby on Rails Advanced JSON Serialization

I'm looking to render an index of all articles along with a full article via JSON in my rails app, but I'm having a little trouble figuring out how to do it.

Here is my controller now:

if params[:id]
    @article = Article.find(params[:id])
else
    @article = Article.published.not_draft.by_recent.first
end

respond_to do |format|

format.js { render :json => @article.to_json(
:except => [ :created_at, :updated_at, :draft, :id, :publish ], 
:include => {
    :comments => {
        :only => [:body]
    }
}),
:callback => params[:callback]}
end

What I'd like to do in the response is add an index of all articles, like so:

@index = Article.find(:all, :select => 'id, title')

The only way I've been able to do it, is put both the index and article into a hash or array and then put that to JSON.

@response = { :item => @article, :index => @index }

Full code with both:

@index = Article.find(:all, :select => 'id, title')

if params[:id]
    @article = Article.find(params[:id])
else
    @article = Article.published.not_draft.by_recent.first
end

@response = { :item => @article, :index => @index }

respond_to do |format|

format.js { render :json => @response.to_json(), :callback => params[:callback]}

end

This would be fine, except now I cannot specify :include or :except and get it to render properly.

Upvotes: 18

Views: 36019

Answers (7)

Kevin Bullaughey
Kevin Bullaughey

Reputation: 2576

I would recommend overloading the attributes method to return an alternat hash that will be automatically used in to_json output.

class Article
   def attributes
     { ... } # define your hash that you want to return at the '...'
   end
end

To me this seems much simpler than mucking around with to_json directly.

Upvotes: 0

Guoqiang Huang
Guoqiang Huang

Reputation: 169

Thanks for the question, I am able to customize my json format for a model with several associations.

render json: @posts.to_json(

:except => [ :created_at, :updated_at, :user_id ],

:include => {

:user => {:only => [:email, :phone]},

:location => {:only => [:title, :lat, :lon, :street, :city, :state, :zipcode]},

:uploads => {:only => [:image]}

}
)

Upvotes: 0

Corin
Corin

Reputation: 2467

(Please accept an answer)

I think that the link that nirvdrum gave holds your answer. I only answer because nobody has mentioned encode_json.

In your case you should only be dealing with as_json. Either by building a hash (with various calls to as_json) and sending that to render :json => ... (without the call to to_json) or by simply implementing as_json on your model and letting rails do all the work. (But I suspect you'll need the former.)

If you really need some fancy js in your rendered response then you can implement encode_json in your classes (again, not to_json). For example:

class JsEmptyClosure
  def encode_json(*args)
    "jQuery[\"noop\"] || function(){}"
  end
  def as_json(*args) self end
end

This will now respond to to_json with valid js (but note it's not actually json).

Upvotes: 1

nirvdrum
nirvdrum

Reputation: 2319

You hint at the solution in your question. You most likely want to build up a hash to render to JSON. The preferred way of doing this now is by providing an implementation for the as_json method. as_json provides a formal means of customizing to_json output by building up a hash containing the data you wish to encode.

A more thorough treatment of how as_json and to_json interact can be found on Jonathan Julian's weblog.

Upvotes: 28

DavidNorth
DavidNorth

Reputation: 450

to_json has a :method option that includes the result of any method you name, you could define a method on that model that returns the additional data you want in your JSON.

Upvotes: 1

Brad
Brad

Reputation:

Just to be clear the code above works with the :include and :except. And by works I mean it doesn't throw an error. The problem is it includes comments for every article in the index. I'm only looking to include comments for the :item and not any of the articles listed in the index.

I couldn't get nesting to work as a hash or an OpenStruct object.

Nesting on :include throws an error, nesting on :except doesn't throw an error, but nothing is filtered out, :created_at, etc. still show up.

...

@response = { :item => @article, :index => @index }

format.js { render :json => @response.to_json(
:except => {:item => [ :created_at, :updated_at, :draft, :id, :publish ]}, 
:include => { :item => {
        :comments => {
                :only => [:body]
        }
}}),
:callback => params[:callback]}
end

Upvotes: 2

MarkusQ
MarkusQ

Reputation: 21950

You should be able to nest the :include, :except, etc. like so:

:except => {:item => [ :created_at, :updated_at, :draft, :id, :publish ]}...

If that doesn't work, make it an object (e.g. an OpenStruct) instead of a hash.

-- Markus

Upvotes: 1

Related Questions