How to override to_json in Rails? - ruby-on-rails

Update:
This issue was not properly explored. The real issue lies within render :json.
The first code paste in the original question will yield the expected result. However, there is still a caveat. See this example:
render :json => current_user
is NOT the same as
render :json => current_user.to_json
That is, render :json will not automatically call the to_json method associated with the User object. In fact, if to_json is being overridden on the User model, render :json => #user will generate the ArgumentError described below.
summary
# works if User#to_json is not overridden
render :json => current_user
# If User#to_json is overridden, User requires explicit call
render :json => current_user.to_json
This all seems silly to me. This seems to be telling me that render is not actually calling Model#to_json when type :json is specified. Can someone explain what's really going on here?
Any genii that can help me with this can likely answer my other question: How to build a JSON response by combining #foo.to_json(options) and #bars.to_json(options) in Rails
Original Question:
I've seen some other examples on SO, but I none do what I'm looking for.
I'm trying:
class User < ActiveRecord::Base
# this actually works! (see update summary above)
def to_json
super(:only => :username, :methods => [:foo, :bar])
end
end
I'm getting ArgumentError: wrong number of arguments (1 for 0) in
/usr/lib/ruby/gems/1.9.1/gems/activesupport-2.3.5/lib/active_support/json/encoders/object.rb:4:in `to_json
Any ideas?

You are getting ArgumentError: wrong number of arguments (1 for 0) because to_json needs to be overridden with one parameter, the options hash.
def to_json(options)
...
end
Longer explanation of to_json, as_json, and rendering:
In ActiveSupport 2.3.3, as_json was added to address issues like the one you have encountered. The creation of the json should be separate from the rendering of the json.
Now, anytime to_json is called on an object, as_json is invoked to create the data structure, and then that hash is encoded as a JSON string using ActiveSupport::json.encode. This happens for all types: object, numeric, date, string, etc (see the ActiveSupport code).
ActiveRecord objects behave the same way. There is a default as_json implementation that creates a hash that includes all the model's attributes. You should override as_json in your Model to create the JSON structure you want. as_json, just like the old to_json, takes an option hash where you can specify attributes and methods to include declaratively.
def as_json(options)
# this example ignores the user's options
super(:only => [:email, :handle])
end
In your controller, render :json => o can accept a string or an object. If it's a string, it's passed through as the response body, if it's an object, to_json is called, which triggers as_json as explained above.
So, as long as your models are properly represented with as_json overrides (or not), your controller code to display one model should look like this:
format.json { render :json => #user }
The moral of the story is: Avoid calling to_json directly, allow render to do that for you. If you need to tweak the JSON output, call as_json.
format.json { render :json =>
#user.as_json(:only => [:username], :methods => [:avatar]) }

If you're having issues with this in Rails 3, override serializable_hash instead of as_json. This will get your XML formatting for free too :)

For people who don't want to ignore users options but also add their's:
def as_json(options)
# this example DOES NOT ignore the user's options
super({:only => [:email, :handle]}.merge(options))
end
Hope this helps anyone :)

Override not to_json, but as_json.
And from as_json call what you want:
Try this:
def as_json
{ :username => username, :foo => foo, :bar => bar }
end

Related

how do I return model parent values when I make a query from the database

So I have a model called Image that belongs_to :user. Each user has a first and last name.
I have a flash app that I am returning a json object back to of Images.
the service I will be calling on the Images controller would look something like this
def getimages
#images = Image.all
render :json => #images
end
My json would look something like this
[{"image":{"created_at":"2011-01-22T19:04:30Z","img_path":"assets/img/bowl_93847566_3_0.png","updated_at":"2011-01-22T19:04:30Z","id":9,"user_id":3}}]
what I would like to do is also include the users first and last name with in the image object that gets passed back.
once I have an image object I am able to do something like image.user.first_name but I am not clear how I would return something like an array of image objects and include the user along with it.
what would be great is if I could get my array of images to look like the following.
[{"image":{"created_at":"2011-01-22T19:04:30Z","img_path":"assets/img/bowl_93847566_3_0.png","updated_at":"2011-01-22T19:04:30Z","id":9,"user_id":3, "first_name":"Matthew", "last_name":"Wallace"}}]
I am thinking this may include adding some kind of model method or somthing that I am not familiar with.
What would be the best practice for achieving this?
You could:
render :json => #images.to_json(:include => :users)
See http://apidock.com/rails/ActiveRecord/Serialization/to_json (and http://apidock.com/rails/Array/to_json shows it works on Arrays). Finally, http://apidock.com/rails/ActionController/Base/render describes using to_json in a json render as optional and not required, which implies it should cause no harm (I couldn't see another way to pass the required options in).
Perhaps cleaner json:
render :json => #images.to_json(:include => { :user => { :only => [:first_name, :last_name] } })
Besides the answer provided by #apneadiving, you can also override the Image's to_json method and return a string containing whatever JSON you need.

Rails rendering JSON data with Model Root

I've got some data in Rails that I want to render as JSON data. What I'm doing right now is simply finding all instances of a Model and calling render :json=>data.
data = Data.find(:all)
render :json => data
However, Rails is including the model name in each JSON object. So my JSON data ends up looking like this:
[{modelname:{propertyName: 'value',...}},{modelname:{propertyName: 'value2',...}}]
instead of this:
[{propertyName:'value',...},{propertyName:'value2',...}]
The modelname is always the same and I don't want it to be there.
I changed the option to render the root in the JSON data in one of the Rails initializers but that affects everything that I want rendered as JSON, which I don't want to do for this project.
In this case, I want to be able to do this on a case-by-case basis.
How can I do this? Thanks in advance.
With Rails 3, you can use active_model_serializers gem1
that allows you to specify rootless rendering of an object like this:
render :json => data, :root => false
I did not find a way to do this by passing options to the to_json method (and I don't believe there is such an option). You have more alternative to do this, any class that inherits from ActiveRecord::Base will have include_root_in_json.
Do something like this.
Data.include_root_in_json = false
data = Data.find(:all)
render :json => data
Hope this gets you going.
Ok let's try this then.
DataController < ApplicationControlle
private
def custom_json(data)
Data.include_root_in_json = false
data.to_json
Data.include_root_in_json = true
data
end
end
Then your redirect would look like this
data = Data.find(:all)
render :json => custom_json(data)
It's pretty silly code I wish I could think of something else entirely. Let me ask you this: What is it about having the model name included in the json data ?
With Rails 3, I found this way better to do. Override the as_json in your model and do as follows:
def as_json(options = {})
super(options.merge :methods => [:some_method_that_you_want_to_include_result], :include => {:child_relation => {:include => :grand_child_relation } })
end

Ruby to_json :methods arguments

I am using the to_json method on an object, and trying to get the :methods argument to work. I have a method on my model (Drop) called is_favorited_by_user?. This method takes an argument of the current_user, and then checks to see if the drop is favorited by the user. How can I pass this argument through the to_json method.
render :json => #drops.to_json(:methods => :is_favorited_by_user?(current_user))
You can add current_user attribute to your model and set it before executing to_json
attr_accessor :current_user
def is_favorited_by_user?(user=nil)
user ||= current_user
# rest of your code
end
#drops.current_user = current_user
render :json => #drops.to_json(:methods => :is_favorited_by_user?)
The methods to_json takes aren't intended to be ones that take arguments, but just "getter" methods. (Typically the ones created based on your db attributes.)
See examples here, none pass arguments:
http://apidock.com/rails/ActiveRecord/Serialization/to_json
If you want to do something custom, you'll need to create a method that builds up the hash of info you want, then convert that hash to json.

Pass method chains to to_json

I know you can pass methods the values of which you want to be available to json objects like so:
# user.rb
def name
first_name + last_name
end
# some controller
render :json => #user.to_json(:methods => :name)
But if I want to massage the value returned from the method a bit (with a text helper say) is there a way to do that? I guess another way to ask this is does #to_json support arbitrary attributes? If not, why not? Has anyone else ran into this before?
You can use "render :json" to specify arbitrary attributes in the JSON output. Here is an example:
render :json => { :arbitraryAttribute => arbitrary_method_to_call(), :user => #user.to_json }
The above code would generate JSON like the following:
{
"arbitraryAttribute":"returnValueOfMethodCall",
"user":{ the result of #user.to_json }
}

How do I expose data in a JSON format through a web service using Rails?

Is there an easy way to return data to web service clients in JSON using Rails?
Rails resource gives a RESTful interface for your model. Let's see.
Model
class Contact < ActiveRecord::Base
...
end
Routes
map.resources :contacts
Controller
class ContactsController < ApplicationController
...
def show
#contact = Contact.find(params[:id]
respond_to do |format|
format.html
format.xml {render :xml => #contact}
format.js {render :json => #contact.json}
end
end
...
end
So this gives you an API interfaces without the need to define special methods to get the type of respond required
Eg.
/contacts/1 # Responds with regular html page
/contacts/1.xml # Responds with xml output of Contact.find(1) and its attributes
/contacts/1.js # Responds with json output of Contact.find(1) and its attributes
http://wiki.rubyonrails.org/rails/pages/HowtoGenerateJSON
Rails monkeypatches most things you'd care about to have a #to_json method.
Off the top of my head, you can do it for hashes, arrays, and ActiveRecord objects, which should cover about 95% of the use cases you might want. If you have your own custom objects, it's trivial to write your own to_json method for them, which can just jam data into a hash and then return the jsonized hash.
There is a plugin that does just this,
http://blog.labnotes.org/2007/12/11/json_request-handling-json-request-in-rails-20/
And from what I understand this functionality is already in Rails. But go see that blog post, there are code examples and explanations.
ActiveRecord also provides methods to interact with JSON. To create JSON out of an AR object, just call object.to_json. TO create an AR object out of JSON you should be able to create a new AR object and then call object.from_json.. as far as I understood, but this did not work for me.

Resources