Including virtual attributes when responding_to a JSON call - ruby-on-rails

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.

Related

Where do I put a rails method/scope that uses params?

I have a scope that uses RubyGeocoder method, near, to filter events by location using param[:searchCity]. The param gets the user's geolocation so it shows events only near them. I currently have it working in my events_controller index action, but I also need to call it on my home page.
Considering it's a filter that gets data from the database, I thought it would be best to go in the model, but I'm finding conflicting information on whether having a param in the model is ok or bad practice. Also, I can't get it to work in the model with the param present.
What's the best practice for something like this? Where should I place the scope, the model, controller, helper, or somewhere else?
Here's my code:
Model:
class Event < ActiveRecord::Base
# attr, validates, belongs_to etc here.
scope :is_near, self.near(params[:searchCity], 20, :units => :km, :order => :distance) #doesn't work with the param, works with a "string"
end
Controller:
def index
unless params[:searchCity].present?
params[:searchCity] = request.location.city
end
#events = Event.is_near
# below works in the controller, but I don't know how to call it on the home page
# #events = Event.near(params[:searchCity], 20, :units => :km, :order => :distance)
respond_to do |format|
format.html # index.html.erb
format.json { render json: #events }
end
end
The line I'm calling in my home page that gets how many events are in the area
<%= events.is_near.size %>
Edit: Using a lambda seems to be working. Is there any reason I shouldn't do it this way?
Model:
class Event < ActiveRecord::Base
scope :is_near, lambda {|city| self.near(city, 20, :units => :km, :order => :distance)}
end
Controller:
def index
#events = Event.is_near(params[:searchCity])
...
home.html.erb
<%= events.is_near(params[:searchCity]).size %>
Accessing the params in model is not possible. Params is something which is made to exist only at controller and view level.
So best way is to write some helper method in controller to perform this.
Class Mycontroller < ApplicationController
before_action fetch_data, :only => [:index]
def fetch_data
#data = Model.find(params[:id])#use params to use fetch data from db
end
def index
end

Mongoid virtual attributes in to_json

I'm trying to get some virtual (non-persisted) attributes to show up in the JSON representation of some Mongoid models, but can't seem to get it to work:
class MyModel
include Mongoid::Document
def virtual_attribute
#my_attribute || false
end
def virtual_attribute=(value)
#my_attribute=value
end
end
class MyController
def myaction
false_values=MyModel.where( whatever )
true_values=MyModel.where( something_else ).map{ |model| model.virtual_attribute=true }
#val['my_models']=false_values+true_values
render json: #val.to_json( :include => {:my_models => {:methods => %w(virtual_attribute)}} )
end
end
virtual_attribute doesn't appear in the json. What am I doing wrong?
Edit - ok, so I guess my actual problem is that I can't figure out how to invoke the virtual_attribute method on each of an array of objects that is nested in the root object.
to_json passes the options directly to the array and the objects. :include is only a Mongoid thing:
render json: #val.to_json(methods: :virtual_attribute)

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

Including a virtual attribute in the respond_with hash

I am trying to include a virtual attribute/method within a respond_to JSON hash.
The Model (employee.rb)
attr_reader :my_method
def my_method
return "foobar"
end
The Controller (employees_controller.rb)
respond_to :json
def index
#employees = Employee.all
respond_with(:data => #employees, :total => Employee.all.count)
end
It is important that I have "data" as the json root for the collection of "employees" and also to include the "total" within the hash. This works well and returns a nice JSON result of all the employees and the total value.
My qustion is: How do I include the virtual attribute "my_method" for each employee within the employees hash in the JSON response?
Thanks for your time!
This is what worked for me.
Employee.rb
def as_json(options={})
super.as_json(options).merge({:my_method => my_method})
end
Thanks for cmason for pointing me in the right direction. Any other solutions are welcome.
In Rails 3 one can use following
#yourmodel.to_json(methods: ['virtual_attr1', 'virtual_attr2']
Overwriting as_json in your model should do the trick:
def as_json(options={})
{ :methods=>[:my_method] }.merge(options)
end

Accessing virtual attribute from JSON

My setup: Rails 2.3.10, Ruby 1.8.7
I have experimented, without success, with trying to access a virtual attribute in a model from a JSON call. Let's say I have the following models and controller code
class Product
name,
description,
price,
attr_accessor :discounted_price
end
class Price
discount
end
class ProductsController
def show
#product = Product.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render :json => #product }
end
end
end
What I like is to have the JSON output also include Product.discounted_price which is calculated in real-time for each call, ie discounted_price = Price.discount * Product.price. Is there a way to accomplish this?
SOLUTION:
With the initial help from dmarkow, I figured it out, my actual scenario is more complex than the above example. I can do something like this, in the Product model, add a getter method
def discounted_price
...# do the calculation here
end
In the JSON call do this
store = Store.find(1)
store.as_json(:include => :products, :methods => :discounted_price)
You can run to_json with a :methods parameter to include the result of those method(s).
render :json => #product.to_json(:methods => :discounted_price)
Have a look at the gem RABL, as shown in this railscast:
http://railscasts.com/episodes/322-rabl?view=asciicast
RABL gives you fine grained control of the json you produce, including collections and children.

Resources