Rails - Nested includes on Active Records? - ruby-on-rails

I have a list of events that I fetch.
I'm trying to include every user associated to this event and every profile associated to each user. The Users get included but not their profiles.
How Would i do this
Event.includes(:users [{profile:}])
The docs don't seem to be clear:
http://guides.rubyonrails.org/active_record_querying.html

I believe the following should work for you.
Event.includes(users: :profile)
If you want to include an association (we'll call it C) of an already included association (we'll call it B), you'd use the syntax above. However, if you'd like to include D as well, which is also an association of B, that's when you'd use the array as given in the example in the Rails Guide.
A.includes(bees: [
:cees,
:dees
])
You could continue to nest includes like that (if you actually need to). Say that A is also associated with Z, and that C is associated to E and F.
A.includes({
bees: [{
cees: [:ees, :effs]
}, :dees]
}, :zees)
And for good fun, we'll also say that E is associated to J and X, and that D is associated to Y.
A.includes({
bees: [{
cees: [{
ees: [:jays, :exes]
}, :effs]
},
{
dees: :wise
}
]
}, :zees)

Please refer to Joe Kennedy's solution.
Here's a more readable example (for future me).
includes(
:product,
:package, {
campaign: [
:external_records,
{ account: [:external_records] },
{ agency: [:external_records] },
:owner,
:partner,
]
}
)

If anyone is doing a respond_to block to generate nested json, you can do something like:
respond_to do |f|
f.json do
render json: event.to_json(include: {users: {include: :profile} }), status: :ok
end
end

Related

ActiveModel::Serializer - How can I catch and handle errors for individual resources during collection serialization?

In a rails application, I have a collection I'd like to render as json. Some of the items in the collection may not successfully serialize due to errors being raised. I'd like to catch those errors and handle them by reporting them to our error logging service, and also provide a dummy record in the json array to represent the unloadable record so that the client software may use it.
Example desired collection of hashes with 1 unloadable record:
[
{ id: 1, name: "foo", ... many other properties },
{ id: 2, name: "bar", ... many other properties },
{ unloadable_record: true, id: 3, name: "baz" }
]
I am currently able to achieve this by handling everything in the controller (which is more than I'd like):
def index
render json: safely_serialized_records
end
private
def safely_serialized_records
#records.map do |r|
begin
ActiveModelSerializers::SerializableResource.new(r, include: "**").as_json
rescue StandardError => e
report_unloadable_record(e, r)
{
unloadable_record: true,
id: r.id,
name: r.name
}
end
end
end
But there are downsides to this approach that I'd like to avoid: it puts too much of the view-rendering concern into the controller, the Rails logs no longer attribute the json serialization to the view portion of the response time (making the performance less straightforward to assess), and has that "I'm doing it wrong" feeling.
I tried a few other approaches that had to do with setting the serializer, each_serializer, and adapter options on the render call. I implemented custom classes to inherit from sensible bases (e.g. ActiveModelSerializers::Adapter::Attributes) and override serializable_hash with something like:
def serializable_hash(*args)
begin
super
rescue StandardError => e
handle_unloadable_record(e, serializer.object)
end
end
... but to no avail.
Is there a clean approach with AMS to achieve what I'm after?

Add method to json output with include in rails

I am getting info from my database
#teams = Team.select(:id,:name)
Then, I am rendering this info with an assosiation
render json: #teams, include: :players
This produce me the following input:
[
{
"id": 1,
"name": "Lyon Gaming",
"players": [
{
"name": "Jirall",
"position": "Superior",
},
{
"name": "Oddie",
"position": "Jungla",
}
]
}
]
How ever I need to sort the players in an specific order (not alphabetical)
I already have the sql statement to do it; how ever I dont know how to apply this method to each association.
I am looking something like:
render json: #teams, (include: :players, method: custom_order)
But this raise me an error.
Any ideas?
This should work:
render json: #teams, include: :players, methods: :custom_order
I'd like to add that if you will always want to return the object this way, you can add this to your model instead:
def as_json(options = nil)
super(include: :players, methods: :custom_order)
end
As Adam has mentioned, active_model_serializers is good to have if your render json's start getting ugly. If the complexity doesn't grow too much, I always recommend keeping the dependencies down.
https://github.com/rails-api/active_model_serializers
You need to read more about serializers in rails(This is good practice)
Here you have a nice article about this https://www.sitepoint.com/active-model-serializers-rails-and-json-oh-my/
Then you can make something like this
app/serializers/team_serializer.rb
class TeamSerializer < ActiveModel::Serializer
attributes :id, :name, :ordered_players
end
app/controllers/teams_controller.rb
def index
respond_with(Team.all.map(&TeamSerializer.method(:new)))
end

How can I render an index of objects to JSON in Rails, when I need to join 2 tables?

The title is likely bad at explaining what I need.
Here's what that need is...
I have two models. Walkthru and WalkthruComplete.
Walkthru has columns (:id, :name, :reward)
WalkthruComplete has columns (:id, :user_id, :walkthru_id)
If a user completes a walkthru/guide in my application, a new row will be written to WalkthruComplete, saying for instance that user_id 17 completed walkthru_id 2. They may be rewarded some points for this.
Now, I need to fetch all the available walkthrus. This is easy. However, I want to include more than Walkthru.(:id, :name, :reward). I also would like to include a column I make up on the fly called 'completed'. In my Walkthru#index, I will check to see if the particular user requesting the Walkthru#index has any WalkthruComplete rows. If they do, 'completed' would be set to YES for those particular walkthrus. If they don't, 'completed' would be set to NO.
The reason the user_id is not set to Walkthru is because some walkthrus are allows for any users, including non-signed in users. Others are only for signed in users, and reward points to those signed in users.
In the end I want something like...
{
"walkthrus": [
{
"id": 1,
"name": "Intro",
"reward": 0,
"completed": 0
},
{
"id": 2,
"name": "Widgets",
"reward": 10,
"completed": 1
},
{
"id": 3,
"name": "Gadgets",
"reward": 10,
"completed": 0
}
]
}
Basically you want to have a presentation layer for your JSON response. I'll make some assumptions about the code you have and outline what I would do to solve this.
Given a helper method in your model
class Walkthru < ActiveRecord::Base
has_many :walkthru_completions
def completed_status(user)
walkthru_completions.where(user_id: user.id).present? ? 1 : 0
end
end
And a decorator for your model:
class WalkthruDecorator
def initialize(user, walkthru)
#walkthru = walkthru
#user = user
end
def as_json(*args)
#walkthru.as_json(args).merge(completed: #walkthru.completed_status(#user))
end
end
Then in your controller, let's assume you have a #user representing the current user:
class WalkthruController < ActionController
def index
decorated_walkthrus = []
Walkthru.all.each do |walkthru|
decorated_walkthrus << WalkthruDecorator.new(#user, walkthru)
end
render json: decorated_walkthrus
end
end
This structure lets you define your JSON separately from your model.

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

Allow an array of hashes with a dynamic hash (hstore) inside

I'm stuck with strong_parameters and this array of hashes with a dynamic hash (hstore) inside.
The structure is the following:
{ contact_sources: [
{ id: 1, filled_fields: { randomstuff: 'randomdata', dunno: 123 } },
{ id: 2, filled_fields: { blah: 'blabla', dunno: 9043 } }
] }
So, my main attempt is the following:
params.permit(contact_sources: [{:filled_fields => []}, 'id'])
Which doesn't return filled_fields. Any suggestion on how to deal with it?
Update 1:
I have the following model:
class ContactSource < ActiveRecord::Base
# Fields: id:integer, filled_fields:hstore
end
In my action, I'm submitting multiple records at once (mass update), so I have an array of contact_source, but actually they don't belong to anything, it's just a mass update.
Looks like it's not possible to do it with "plain" strong_parameters syntax. The only option you have is to actually, after filtering, re-add those values with a loop. I know it's terrible but it's the only way right now. I submitted a bug to Rails actually.

Resources