How to change values overriding the 'to_json' method? - ruby-on-rails

I am using Ruby on Rails 3 and I would like to override the to_json method.
At this time I use the following code in order to avoid to export important information.
def to_json
super(
:except => [
:password
]
)
end
If I want change a value using that method, how I can do?
For example, I would like to capitalize the user name
:name => name.capitalize
on retrieving this
#user.to_json

If you want to render :json => #user in the controller for Rails 3, you can override as_json in the model:
class User < ActiveRecord::Base
def as_json(options={})
result = super({ :except => :password }.merge(options))
result["user"]["name"] = name.capitalize
result
end
end
Here's a good post about the differences between to_json and as_json.

Use the :methods option to to_json.
http://apidock.com/rails/ActiveRecord/Serialization/to_json

Related

Including virtual attributes when responding_to a JSON call

I have a post model that has a virtual attribute that I would like to set and then include in a response to a JSON call to my post#index action. I can't seem to get the virtual attribute to be included in the response.
class Post < ActiveRecord::Base
attr_accessible :height
attr_accessor :m_height
end
class PostsController < ApplicationController
respond_to :html, :json, :js
def index
story = Story.find(params[:story_id])
#posts = story.posts.where("posts.id >= ?", 100)
#posts.each do |post|
post.m_width = post.height * 200
end
results = { :total_views => story.total_views,
:new_posts => #posts }
respond_with(results)
end
end
I think that I must need something similar to #post.to_json(:methods => %w(m_width)), but I don't see how to use :methods in a respond_with
This seems to provide the answer. Implement a to_json and to_xml in your models, as appropriate, with definitions like:
There's a better answer implied here.
Following code stolen from the post:
def as_json(options={})
super(options.merge(:methods => [...], :only => [...], :include => [...])
end
to_json won't be called on your model in this case, from what I can tell in the source, but as_json will be, in the process of serialization.
So, here's what happens, in overview form:
You call respond_with with the results hash you've constructed.
Rails (ActionController) calls to_json on that.
to_json sends you over to JSON::Encoding which keeps calling as_json all the way down until everything is JSONified.
That's why there was the confusion about to_json and as_json in an earlier version of this answer.

Rails as_json with two unrelated models

Given two models and a controller:
Apples
class Apples < ActiveRecord::Base
belongs_to :not_oranges
...
def as_json(options={})
opts = {:include => [:not_oranges]}
super(options.reverse_merge! opts)
end
end
Oranges
class Oranges < ActiveRecord::Base
belongs_to :not_apples
...
def as_json(options={})
opts = {:include => [:not_apples]}
super(options.reverse_merge! opts)
end
end
Search Controller
class SearchController < ApplicationController
a = Apples.search params[:q]
o - Oranges.search params[:q]
#results = {
:apples => a,
:oranges => o
}
respond_to do |format|
format.json { render :json => #results }
end
As you can see, the two models are completely unrelated and both have different :include options in their as_json definitions.
All works as expected if the search query only hits apples or only hits oranges, but once both objects aren't empty I get:
undefined method `not_apples' for #<Oranges:0x00000004af8cd8>
Seems either the two as_json definitions are being merged, or Oranges.as_json is being overriden by Apples.as_json.
Is this expected behaviour? Is there any clean way around it without using something like RABL? I feel it would be overkill for my needs.
In pseudo code the code for hash as_json method looks like
def as_json(options={})
Hash[collect {|key,element| [key.to_s,element.as_json(options)]}]
end
But your element is modifying the options argument you pass to it. Hash is unaware of this and so passes the modified options hash to as json.
It's usually a good idea not to modify in place the arguments passed to you, except when it is very clear this is ok. I'd rewrite your method as
def as_json(options={})
defaults = {:include => :not_apples}
super(defaults.merge(options))
end

problem using 'as_json' in my model and 'render :json' => in my controller (rails)

I am trying to create a unique json data structure, and I have run into a problem that I can't seem to figure out.
In my controller, I am doing:
favorite_ids = Favorites.all.map(&:photo_id)
data = { :albums => PhotoAlbum.all.to_json,
:photos => Photo.all.to_json(:favorite => lambda {|photo| favorite_ids.include?(photo.id)}) }
render :json => data
and in my model:
def as_json(options = {})
{ :name => self.name,
:favorite => options[:favorite].is_a?(Proc) ? options[:favorite].call(self) : options[:favorite] }
end
The problem is, rails encodes the values of 'photos' & 'albums' (in my data hash) as JSON twice, and this breaks everything... The only way I could get this to work is if I call 'as_json' instead of 'to_json':
data = { :albums => PhotoAlbum.all.as_json,
:photos => Photo.all.as_json(:favorite => lambda {|photo| favorite_ids.include?(photo.id)}) }
However, when I do this, my :favorite => lambda option no longer makes it into the model's as_json method.......... So, I either need a way to tell 'render :json' not to encode the values of the hash so I can use 'to_json' on the values myself, or I need a way to get the parameters passed into 'as_json' to actually show up there.......
I hope someone here can help... Thanks!
Ok I gave up... I solved this problem by adding my own array methods to handle performing the operations on collections.
class Array
def to_json_objects(*args)
self.map do |item|
item.respond_to?(:to_json_object) ? item.to_json_object(*args) : item
end
end
end
class Asset < ActiveRecord::Base
def to_json_object(options = {})
{:id => self.id,
:name => self.name,
:is_favorite => options[:favorite].is_a?(Proc) ? options[:favorite].call(self) : !!options[:favorite] }
end
end
class AssetsController < ApplicationController
def index
#favorite_ids = current_user.favorites.map(&:asset_id)
render :json => {:videos => Videos.all.to_json_objects(:favorite => lambda {|v| #favorite_ids.include?(v.id)}),
:photos => Photo.all.to_json_objects(:favorite => lambda {|p| #favorite_ids.include?(p.id)}) }
end
end
I think running this line of code
render :json => {:key => "value"}
is equal to
render :text => {:key => "value"}.to_json
In other words, don't use both to_json and :json.

Conditional JSON output for ActiveRecord Model (Rails 3)

I am using ActiveRecord's as_json integration with ActiveSupport::JSON to render custom output in my controllers. A basic setup I have in my model looks something like this:
def as_json(options = {})
{ :guid => id,
:title => title,
:body => body,
:date => created_at }
end
I want to take this setup a step further and show select information depending upon options passed. My question is, when I call respond_with #model_instance or render :json => #model_instance am I able to pass options that the options argument in as_json receives? If not, should I just create and convert a unique hash in my controller?
Seems like you could just call .as_json and pass in the options, no?
render :json => #mymodel.as_json(:someoption =>" value")

globalize2 with xml/json support

I'm implementing a distributed application, server with rails and mobile clients in objective c (iPhone). To enable internationalization, I use the rails plugin 'globalize2' by joshmh.
However, it turned out that this plugin does not translate attributes when calling to_xml or to_json on an ActiveRecord. Does anyone know of a workaround / patch? Do you have any ideas how to fix this, where to alter globalize2?
Using:
Rails 2.3.5
globalize2: commit from 2010-01-11
With Globalize2 (and with model_translations as well) translated attribute in a model is not a real attribute but is a method. Thus and so when you execute to_json method you can use :methods, as Joris suggested, but in a simpler way:
class Post < ActiveRecord::Base
attr_accessible :title, :text
translates :title, :text
end
class PostsController < ApplicationController
def index
#posts = Post.all
respond_to do |format|
format.html
format.json { render :json => { :posts => #posts.to_json(:only => :id, :methods => :title) }}
format.js
end
end
end
Here I would like to receive only post id and title in json response. For additional information see to_json (Serialization) in Rails API.
I found this fork on github: http://github.com/leword/globalize2
But it looks like it is based on an older version.
I was looking for this myself, but solved my problem using the :methods option:
If you want to translate one attribute in #item, you can use:
class Item < ActiveRecord::Base
translates :name
def t_name
self.name
end
end
And in your controller:
render :text => #item.to_xml(:methods => [ :t_name ])
If your api path is something like /en/api/item.xml, you should get the english translation in the t_name attribute
For a belongs_to relation:
belongs_to :category
def category_name
self.category.name
end
And in your controller:
render :text => #item.to_xml(:methods => [ :category_name ])
Your use case is probably different. Above is a workaround that works for me.

Resources