Rails as_json includes with conditions - ruby-on-rails

I have problems to restrict an as_json include by a dynamic attribute:
#pirates_ships = #current_account.pirates.as_json(:include => {:ships => {:only => [:id, :name]}}, :only => [:id, :last_name])
This for sure gives me all pirates with or without their ships.
But I also need to restrict the ships by e.g. ships.ocean_id
I tried resolving it by includes with conditions:
pirates.includes(:ships).where("ships.ocean_id = ?", #ocean.id).as_json(...)
The restriction works, but now all pirates without a ship are lost.
Also no luck with my own JOIN Syntax.
Any ideas?
Ahoy
UPDATE
My solution so far is to manually eager load. This way I can have my dynamic conditions:
#pirates = #current_account.pirates
#ships = #current_account.ships.where({:pirate_id.in => #pirates, :ocean_id => #ocean.id})
render :json => { :pirates => #pirates.as_json(...), :ships => #ships.as_json(...) }
My Ajax callback can now iterate over :pirates and add for each pirate his ships if any.
(I use a JS template engine clientside to generate the view from the JSON response)
Not very elegant, but performance is important in my case.
I'am still open for better ideas.
I tried dynamic has_many :ships, :conditions => ...
but that's a bit fiddly.

I think your best bet would be altering the #pirates_ships hash after generating it from as_json (I tried multiple variations of includes, etc. and couldn't get anything to work).
#pirates_ships = #current_account.pirates.as_json(:include => :ships)
#pirates_ships.each do |pirate|
pirate[:ships].delete_if{ |ship| ship.ocean_id != #ocean.id }
end
# Now, #pirates_ships should contain ALL pirates, as well as ships for #ocean

Related

How to apply WHERE filter when calling to_json preserving the :includes

I have the followin Ruby + Rails code
render :json => enterprise.to_json(:include => { :v3_passengers => { :include => [:cost_center, :restrictions]}})
And I need to apply a WHERE filter using one of the fields of the v3_passengers model before rendering it as json (for example "where v3_passenger.id = 2345")
I have tried this
render :json => enterprise.includes(:v3_passengers).where(enterprise_country: Thread.current['CurrentBehaviour'].COUNTRY).includes(:cost_center, :restrictions).to_json
But is not working, I have looked arround whitout any look in how to achieve this.
UPDATE
This are how the models are related
class Enterprise < ActiveRecord::Base
has_many :v3_passengers
class V3Passenger < GlobalDB
has_many :restrictions
belongs_to :cost_center
1. First you need to filter by joins or includes:
foo = enterprise.joins(:v3_passengers).where(v3_passengers: {enterprise_country: Thread.current['CurrentBehaviour'].COUNTRY})
or (prefered includes, since you are going to need v3_passengers )
foo = enterprise.includes(:v3_passengers).where(v3_passengers: {enterprise_country: Thread.current['CurrentBehaviour'].COUNTRY})
2. Then include the other nodes you need in the to_json:
foo.to_json(include: [v3_passengers: { include: [:cost_center, :restrictions] } ])
Final Result:
render :json => enterprise.joins(:v3_passengers).where(v3_passengers: {enterprise_country: Thread.current['CurrentBehaviour'].COUNTRY}).to_json(include: [v3_passengers: { include: [:cost_center, :restrictions] } ])
The problem is that:
model.includes(:other_model).to_json
Isn't the same as:
model.to_json(include: :other_model)
So your first attempt is giving you all the fields of Enterprise, V3Passenger, Restriction and CostCenter in the output. Your second attempt is just giving you fields of Enterprise.
One potential fix is:
enterprise.joins(:v3_passengers).where("v3_passengers.id=?",2345).to_json(include: :v3_passengers)
(Including the other tables of course.)
This will give you JSON for all the Enterprises with v3_passengers.id=2345, including JSON for all their V3Passengers (even the V3Passengers who don't have id 2345).
If you only want to include V3Passengers who match the where clause then you need to add a scoped association to the model:
has_many :v3_passengers_where_id_2345, -> { where id: 2345 }
And then use that association when doing the JSON conversion:
enterprise.joins(:v3_passengers).where("v3_passengers.id=?",2345).to_json(include: :v3_passengers_where_id_2345)
This will give you JSON for enterprises who have v3_passengers.id=2345, including only their V3Passengers who have id 2345.
The second shot is close to working variant.
render :json => enterprise.v3_passengers.where(enterprise_country: Thread.current['CurrentBehaviour'].COUNTRY).includes(:cost_center, :restrictions).to_json
Try to use some relation.
For more clear answer add your key models listings, Enterprice and passengers models.
if you need the enterprise attributes in the resulting json:
render :json => enterprise.joins(:v3_passengers).where("v3_passengers.enterprise_country = ?", Thread.current['CurrentBehaviour'].COUNTRY).to_json(include: [v3_passengers: { include: [:cost_center, :restrictions] } ])
if you just need the passengers:
render :json => enterprise.v3_passengers.where(enterprise_country: Thread.current['CurrentBehaviour'].COUNTRY).includes(:cost_center, :restrictions).to_json( include: [:cost_center, :restrictions])

Add virtual attribute to json output

Let's say I have an app that handles a TODO list. The list has finished and unfinished items. Now I want to add two virtual attributes to the list object; the count of finished and unfinished items in the list. I also need these to be displayed in the json output.
I have two methods in my model which fetches the unfinished/finished items:
def unfinished_items
self.items.where("status = ?", false)
end
def finished_items
self.items.where("status = ?", true)
end
So, how can I get the count of these two methods in my json output?
I'm using Rails 3.1
The serialization of objects in Rails has two steps:
First, as_json is called to convert the object to a simplified Hash.
Then, to_json is called on the as_json return value to get the final JSON string.
You generally want to leave to_json alone so all you need to do is add your own as_json implementation sort of like this:
def as_json(options = { })
# just in case someone says as_json(nil) and bypasses
# our default...
super((options || { }).merge({
:methods => [:finished_items, :unfinished_items]
}))
end
You could also do it like this:
def as_json(options = { })
h = super(options)
h[:finished] = finished_items
h[:unfinished] = unfinished_items
h
end
if you wanted to use different names for the method-backed values.
If you care about XML and JSON, have a look at serializable_hash.
With Rails 4, you can do the following -
render json: #my_object.to_json(:methods => [:finished_items, :unfinished_items])
Hope this helps somebody who is on the later / latest version
Another way to do this is add this to your model:
def attributes
super.merge({'unfinished' => unfinished_items, 'finished' => finished_items})
end
This would also automatically work for xml serialization.
http://api.rubyonrails.org/classes/ActiveModel/Serialization.html
Be aware though, you might want use strings for the keys, since the method can not deal with symbols when sorting the keys in rails 3. But it is not sorted in rails 4, so there shouldn't be a problem anymore.
just close all of your data into one hash, like
render json: {items: items, finished: finished, unfinished: unfinished}
I just thought I'd provide this answer for anyone like myself, who was trying to integrate this into an existing as_json block:
def as_json(options={})
super(:only => [:id, :longitude, :latitude],
:include => {
:users => {:only => [:id]}
}
).merge({:premium => premium?})
Just tack .merge({}) on to the end of your super()
This will do, without having to do some ugly overridings. If you got a model List for example, you can put this in your controller:
render json: list.attributes.merge({
finished_items: list.finished_items,
unfinished_items: list.unfinished_items
})
As Aswin listed above, :methods will enable you to return a specific model's method/function as a json attribute, in case you have complex assosiations this will do the trick since it will add functions to the existing model/assossiations :D it will work like a charm if you dont want to redefine as_json
Check this code, and please notice how i'm using :methods as well as :include [N+Query is not even an option ;)]
render json: #YOUR_MODEL.to_json(:methods => [:method_1, :method_2], :include => [:company, :surveys, :customer => {:include => [:user]}])
Overwritting as_json function will be way harder in this scenario (specially because you have to add the :include assossiations manually :/
def as_json(options = { })
end
If you want to render an array of objects with their virtual attributes, you can use
render json: many_users.as_json(methods: [:first_name, :last_name])
where first_name and last_name are virtual attributes defined on your model

ActiveRecord Include, how to use in nested records?

I currently have the following:
#threads = current_user.threads.includes(:user, :thread_members)
I then take threads and do the following:
#threads.each do |thread|
thread_members = thread.thread_members_active(current_user)
#threadList << {
:id => thread.id,
:uuid => thread.uuid,
:user_id => thread.user.id,
:last_activity_at => thread.last_activity_at,
:user_count => thread_members.length,
:user_photos => thread_members.collect { |thread_member|
{
:id => thread_member.user.id,
:photo => thread_member.user.photo(:thumb),
:name => thread_member.user.full_name
}
},
:caption => thread.caption
}
end
The issue here is that every EACH loop, rails is hitting the DB for the same basic records. Rails sees to be caching as I see CACHE in the log but it's mighty messy. Leaves me wishing I could do some type of includes so there wasn't so many db requests.
Any ideas on how this can be optimized? Something around including all the users in one db hit?
Thanks
If you don't want any DB queries in the loop, you have to define everything that's used there in the named associations that are included, so instead of a thread_members_active method you'd define a thread_members_active association which has the same behavior. Note that the association also needs to use includes on user. Can't give you more right now, but maybe that helps a bit.
Edit: Check out the "Eager loading of associations" part of this doc:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

Rails3-amf and bringing an association to amf

i am changing from WebORB to Warhammerkids Rails3-amf - great choice, even tehre are some open issues. One those issues is, how can I get records from an association to the array, which is sent back to the Flex-Application.
In WebORB the method of the controller looks like:
def getClients(clientFilter,myMandant)
clients = Client.find(:all, :conditions => {:account_id => myMandant}, :include => [:addresses, :contacts, :proofs])
end
in Rails3-amf I have a similar construction:
def getClients()
#clients = Client.find(:all, :conditions => {:account_id => params[1]}, :include => [:addresses, :contacts, :proofs])
respond_with(#clients) do |format|
format.amf { render :amf => #clients}
end
With this code I get back all correctly typed Client Objects as an Array, but without the records from the ":include" argument.
How can I handle this ??
I also tried another way with:
....
respond_with(#clients) do |format|
format.amf { render :amf => #clients.to_amf(:include => [:addresses, :contacts, :proofs])}
....
With this try I got an error message" undefined method to_amf for #.
Thanks for any help.
I don't know about rail3-amf, but you might find it worthwhile having a look at restfulx - https://github.com/dima/restfulx/tree/rails3
It consists of a library for rails, and a library for flex. It supports data transfer through json, xml, or amf.
The actionscript api for working with models is very nice too:
var user:User = new User();
user.first_name = "Ed";
user.create();
It can also keep track of rails associations etc.:
trace(user.account.title);
See more usage here - https://github.com/dima/restfulx_framework/wiki/Working-with-RestfulX-Models

Adding further attributes to a ActiveRecord serialization?

I have my json serialization working fine
render :json => "#{current_object.serialize(:json, :attributes => [:id, :name])}
But I also want to add further data to the json before it gets set back to the client. Mainly the auth_token.
Googled around like crazy but I can not find what option serialize will take to allow me to append/merge my other data into the JSON.
Hopting to find something like this...
current_object.serialize(:json, :attriubtes => [:id, name], :magic_option => {:form_authenticity_token => "#{form_authenticity_token}"})
You want the :methods key, which works like :attributes, but will include the results of the methods given. In your case:
current_object.to_json(
:attributes => [:id, :name],
:methods => [:form_authenticity_token]
)
For what it's worth, in a recent Rails I hacked together what you want like this:
sr = ActiveRecord::Serialization::Serializer.new(your_object, some_serialization_options).serializable_record
sr['extra'] = my_extra_calculation(some_parameters)
format.json { render :json => sr }
Where your_object is what you want to serialize, some_serialization_options are your standard :include, :only, etc parameters, and my_extra_calculation is whatever you want to do to set the value.
Jimmy
Hacked it in and moving on...
current_object.serialize(:json, :attributes => [:id, :name]).gsub(/\}$/, ", \"form_authenticity_token\": \"#{form_authenticity_token}\"}")

Resources