rails to_json unexpected behavior for include_root_in_json - ruby-on-rails

I have a model like this
class Parent < ActiveRecord::Base
:has_many :kids
end
class Kid < ActiveRecord::Base
:has_many :grandkids
:belongs_to :parent
end
I can generate json like this:
the_parent.to_json( :methods => [:kids] )
=> { "parent" : { "kids" : [ { "kid" : { "name" => "kid0" .... and so on. Just what I want. Each object looks like a hash with a single key - which is the model name - and the value is an attribute hash. Great.
But I get into trouble when I try to serialize the whole tree, like this:
the_parent.to_json( :include => { :kids => { :include => :grandkids } } )
=> { "parent" : { "kids" : [ { "name" => "kid0" ...
which is missing the model names in the "kids" array. The same thing happens at the next level with the grandkids. I'm going to parse this somewhere else, and it would help to have certainty about the object name (as opposed to relying on convention using the relationship name). The docs advertise this:
ActiveRecord::Base.include_root_in_json = true
Which I found it to have no effect. My guess is the different behavior has something to do with the difference between the :method and :include options, but I can't wrangle the :method syntax to get the nesting I need, and I'm not sure if that will work even if it compiles.
Any ideas? Thanks, Dan

As a workaround, I'm overriding to_json in my model like this:
def to_json(args)
super( :methods => [:kids] )
end

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])

Return associated model attribute as current model attribute

I have an association between ProcessingStatus and Order that looks like this:
Order belongs_to ProcessingStatus
ProcessingStatus has_one Order
When I return an Order, I want to be able to get the ProcessingStatus 'name' attribute as the 'status' attribute of my JSON response.
So, right now, for the following GET call at /orders/73,
render :json => #order.to_json(:only => [:id], :include => {:processing_status => {:only => [:name]}})
I get this:
{
"id": 73,
"processing_status": {
"name": 'processing'
}
}
And I am looking for a way to get this:
{
"id": 73,
"status": 'processing'
}
Anyway on doing that?
You could define a method on your model that returns the status as the value of processing_status.name and then include that in your json:
class Order < ActiveRecord::Base
def status
self.processing_status.try(:name)
end
end
And then include that in your to_json call:
#order.to_json(only: :id, methods: :status)
Alternatively you could add the status to a hash that gets converted to json:
#order.as_json(only: :id).merge(status: #order.processing_status.try(:name)).to_json
I've used .try(:name) in case processing_status is nil but you might not need that. The behaviour is slightly different in the two cases as the first will not include status in the json and the second will include "status":null.

Controller Chokes on Nested Model's Attributes

I'm using Rails 4 + Ruby 2
Nested models in Rails are a huge pain. There, I said it.
Okay, so I have an entry model with approaches nested inside.
# --- entry.rb ---
has_many :approaches, :dependent => :destroy
accepts_nested_attributes_for :approaches, :reject_if => lambda { |a| a[:approach_type].blank? }, :allow_destroy => true
The approach parameters come over to the controller's create method like this (from the log):
{"utf8"=>"✓",
"entry"=>{
"aircraft_registration"=>"N384HA",
"flight_date"=>"2013-12-10",
"departure"=>"KSAD",
...
"approaches"=>{
"1386633324306"=>{
"approach_type"=>"GLS",
"holding"=>"false",
"quantity"=>"2",
"airport"=>"FFS",
"runway"=>"12L",
"updated_flag"=>"true"
},
"1386633813852"=>{
"approach_type"=>"TACAN",
"holding"=>"false",
"quantity"=>"1",
"airport"=>"DFD",
"runway"=>"12L",
"updated_flag"=>"true"
}
},
}
For testing purposes, I do blanket param allowance:
params.require(:entry).permit!
...and I get this error from Satan himself:
ActiveRecord::AssociationTypeMismatch - Approach(#70114475640640) expected,
got Array(#70114477494560)
It seems that the problem is that the Entry model doesn't like the approaches being an array (which actually looks like a hash with id numbers, but what do I know).
My Question
How should the params look on a nested model when they come over from the view to the controller?
I'm trying to narrow down whether I have mis-formatted data coming from my form, or a problem in my controller.
I'm new to Rails, so please be gentle. :)
To answer your question, a nested model should come through something like this:
params = {
:entry => {
:approaches_attributes => [
{:approach_one_attr => ...},
{:approach_two_attr => ...}
]
}
}
Looks to me like the problem here is with the form in your view not the controller.

Rails 3 respond_to json, with custom attributes/methods

In a rails app I have an action that returns a json representation of a collection of different models. It looks something like this:
respond_to :json
def index
#cars = Car.all
#vans = Van.all
respond_with({
:cars => #cars,
:vans => #vans
})
end
However, I want to customise the attributes and methods that are passed to the json object. A bit like:
respond_with({
:cars => #cars.to_json(:only => [:make, :model], :methods => [:full_name]),
:vans => #vans
})
Doing the above, causes the json representation of the "cars" to be escaped as one big string, like:
{
"cars":"[{\"car\":{\"make\":\"Ford\" ... etc
"vans": [{"van":{"make":"Citreon" ... vans not escaped
}
Obviously I'm approaching this the wrong way. Can anyone point me in the right direction?
Since you're nesting the to_json in another Hash, I think you need to use as_json (which returns a Hash instead of a String) instead:
respond_with({
:cars => #cars.as_json(:only => [:make, :model], :methods => [:full_name]),
:vans => #vans
})

MongoDB/MongoMapper Modifiers on Embedded Documents

Need some help with how to use atomic modifiers on an embedded document.
To illustrate, let's assume I've got a collection that looks like this.
Posts Collection
{
"_id" : ObjectId("blah"),
"title" : "Some title",
"comments" : [
{
"_id" : ObjectId("bleh"),
"text" : "Some comment text",
"score" : 0,
"voters" : []
}
]
}
What I'm looking to do with MongoMapper/MongoDB is perform an atomic update on a specific comment within a post document.
Something like:
class Comment
include MongoMapper::EmbeddedDocument
# Other stuff...
# For the current comment that doesn't have the current user voting, increment the vote score and add that user to the voters array so they can't vote again
def upvote!(user_id)
collection.update({"comments._id" => post_id, "comments.voters" => {"$ne" => user_id}},
{"$inc" => {"comments.score" => 1}, "$push" => {"comments.voters" => user_id}})
end
end
That's basically what I have now and it isn't working at all (nothing gets updated). Ideally, I'd also want to reload the document / embedded document but it seems as though there may not be a way to do this using MongoMapper's embedded document. Any ideas as to what I'm doing wrong?
Got this working for anyone that's interested. Two things I was missing
Using $elemMatch to search objects within an array that need to satisfy two conditions (such as _id = "" AND voters DOES NOT contain the user_id)
Using the $ operator on the $inc and $push operations to ensure I'm modifying the specific object that's referenced by my query.
def upvote!(user_id)
# Use the Ruby Mongo driver to make a direct call to collection.update
collection.update(
{
'meanings' => {
'$elemMatch' => {
'_id' => self.id,
'voters' => {'$ne' => user_id}
}
}
},
{
'$inc' => { 'meanings.$.votes' => 1 },
'$push' => { 'meanings.$.voters' => user_id }
})
end

Resources