Rails: include nested through association - ruby-on-rails

I have a Store model, which has an id and other fields.
I have a Product model which has an id and other field
To join them I use Amount model. Here are the models:
class Product < ApplicationRecord
has_many :amounts
has_many :stores, through: :amounts
end
class Store < ApplicationRecord
has_many :amounts
has_many :products , through: :amounts
end
class Amount < ApplicationRecord
belongs_to :store
belongs_to :product
end
Amount has in_stock property. I want my json to look like this:
{stores: [
{"id": 1,
"name": "New Store",
"products": ["id": 1, "name": "Toothbrush", "in_stock": 4000]}
]}
I tried render json: {stores: stores}.to_json(include: :products), I also tried a nested include but it didn't work as well, it showed output for all amounts

Based on your comments on my original answer (which shows in stock comes from the Amount model) you can include amounts in the includes relation which would be a cleaner solution and more performant...
output = Store.all.includes(amounts: :product).map do |store|
{ id: store.id, name: store.name,
products: store.amounts.map do |amount|
{ id: amount.product.id, name: amount.product.name, in_stock: amount._in_stock }
end
}
end.to_json

output = Store.all.includes(:products).map do |store|
{ id: store.id, name: store.name,
products: store.products.map do |product|
{ id: product.id, name: product.name, in_stock: product.in_stock }
end
}
end.to_json
This builds an array of store hashes, each store hash has an array of products, and the hash is converted to json.

Related

How to save a nested one-to-many relationship in API-only Rails?

In my Rails (api only) learning project, I have 2 models, Group and Album, that have a one-to-many relationship. When I try to save the group with the nested (already existing) albums, I get the following error, ActiveRecord::RecordNotFound (Couldn't find Album with ID=108 for Group with ID=). I'm using the jsonapi-serializer gem. Below is my current set up. Any help is appreciated.
Models
class Group < ApplicationRecord
has_many :albums
accepts_nested_attributes_for :albums
end
class Album < ApplicationRecord
belongs_to :group
end
GroupsController#create
def create
group = Group.new(group_params)
if group.save
render json: GroupSerializer.new(group).serializable_hash
else
render json: { error: group.errors.messages }, status: 422
end
end
GroupsController#group_params
def group_params
params.require(:group)
.permit(:name, :notes, albums_attributes: [:id, :group_id])
end
Serializers
class GroupSerializer
include JSONAPI::Serializer
attributes :name, :notes
has_many :albums
end
class AlbumSerializer
include JSONAPI::Serializer
attributes :title, :group_id, :release_date, :release_date_accuracy, :notes
belongs_to :group
end
Example JSON payload
{
"group": {
"name": "Pink Floyd",
"notes": "",
"albums_attributes": [
{ "id": "108" }, { "id": "109" }
]
}
}
If the albums already exist, then accepts_nested_attributes is not necessary.
You could save them like this:
Group.new(name: group_params[:name], notes: group_params[:notes], album_ids: group_params[:album_ids])
You will want to extract the album_ids as an array when passing it here.

Loading Relations

Goal: I would like to include all of a customers medical conditions as an array in the result of a customer.
for:
cust = Customer.includes(:conditions).find(1)
expected result:
#<Customer id: 1, first_name: "John", last_name: "Doe", conditions [...]>
actual result:
#<Customer id: 1, first_name: "John", last_name: "Doe">
code:
I have 2 classes and a 3rd join class (ConditionsCustomer).
class Customer < ApplicationRecord
has_many :conditions_customers
has_many :conditions, through: :conditions_customers
end
#join table. Contains 2 foreign_keys (customer_id, condition_id)
class ConditionsCustomer < ApplicationRecord
belongs_to :customer
belongs_to :condition
end
class Condition < ApplicationRecord
has_many :conditions_customers
has_many :customers, through: :conditions_customers
end
What's interesting is that I see 3 select queries getting fired (customer, join table and medical conditions table) so I know the includes is somewhat working but unfortunately customer returns without the medical conditions.
I've also tried using a join but I get an array of same customer over and over again.
Is there an easy way to do this with ActiveRecord? I would prefer not having to merge the record manually.
Not really possible via active record, as json offers some cool possibilities :
render json: customers,
include: {
conditions: {
only: [:attr1, :attr2], # filter returned fields
methods: [:meth1, :meth2] # if you need model methods
},
another_joined_model: {
except: [:password] # to exclude specific fields
}
}

Rails 5: includes for polymorphic with where conditions

I need to query my models and produce a record similar to this:
[{
"subscriber": {
"email": "user#example.com",
"subscriptions": [{
"confirmed": true,
"subscriptionable": {
"name": "Place XYZ",
"comments": [{
"author": "John",
"body": "Hello."
}]
}
}]
}
}]
My models look like this:
class Subscriber < ApplicationRecord
has_many :subscriptions
end
class Subscription < ApplicationRecord
belongs_to :subscriptionable, polymorphic: true
belongs_to :subscriber
end
class Place < ApplicationRecord
has_many :subscriptions, as: :subscriptionable
has_many :comments, as: :commentable
end
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
So far I'm able to produce the record I want by running this query:
Subscriber.includes(subscriptions: [subscriptionable: [:comments]])
The problem is I that, with the query above, I can't specify any where conditions. For example, this will fail:
Subscriber.includes(subscriptions: [subscriptionable: [:comments]])
.where(subscriptions: { confirmed: true })
> ActiveRecord::EagerLoadPolymorphicError: Cannot eagerly load the polymorphic association :subscriptionable
And the other issue is that I can't just get certain attributes, for example:
Subscriber.includes(subscriptions: [subscriptionable: [:comments]])
.pluck("subscribers.email")
> ActiveRecord::EagerLoadPolymorphicError: Cannot eagerly load the polymorphic association :subscriptionable
Edit
Maybe this will help clarify: what I would like to achieve is something in the lines of this SQL query:
SELECT subscriptions.name as sub_name, subscriptions.email as sub_email,
places.name as place_name, comments.author as com_author, comments.body as com_body, subscribers.token
FROM subscriptions
JOIN subscribers
ON subscriptions.subscriber_id = subscribers.id
JOIN places
ON subscriptionable_id = places.id
JOIN comments
ON places.id = commentable_id
WHERE subscriptions.confirmed
AND subscriptionable_type = 'Place'
AND commentable_type = 'Place'
AND comments.status = 1
AND comments.updated_at >= '#{1.week.ago}'

Include properties for ActiveModelSerializer only if called within has many

I have a rails app with the following models.
class Project
has_many :project_clips
has_many :clips, through: :project_clips
end
class Clip
has_many :project_clips
has_many :projects, through: :project_clips.
end
class ProjectSerializer < ActiveModel::Serializer
attributes :id, :name
has_many :clips
end
class ClipSerializer < ActiveModel::Serializer
attributes :id, :name
end
I was wondering if it's possible to display the values of the associated project_clip, if the clip has been called within the context of project.
Let's say the ProjectClip model, has a field called priority. I want the results to show up like this.
{ projects: { "id": 1, "name": "ipsum", "clips": [{ "id": 1, "name": "lorem", "priority": "high"}] } }
I don't want the values of project_clips to be included, just a few properties when returning the data for projects.
If I'm getting your question right, you can do something like:
res = project.clips.collect{ |clip| [clip, clip.project_clips] }
or if you want to return hashes and not objects, you can do:
res = project.clips.collect{ |clip| [clip.attributes, clip.project_clips.collect{|pc| pc.attributes}] }

Rails Active Model Serializer - has_many and accessing the parent record

I'm trying to build a JSON representation of some Rails models using Active Model Serializer, where some models embed others. For example, I have Event and Attendees, Event has_and_belongs_to_many Attendees.
class EventSerializer < ActiveModel::Serializer
attributes :name
has_many :attendees, serializer: AttendeeSerializer
end
class AttendeeSerializer < ActiveModel::Serializer
attributes :name
end
This would result in JSON like { name: 'Event One', attendees: [{ name: 'Alice' }, { name: 'Bob' }] }.
Now, I'd like to add what the attendees have said about the event. Let's say, Comment belongs_to Event, belongs_to Attendee. I'd like to include said comments in the serialized output of event, so it would become { name: 'Event One', attendees: [{ name: 'Alice', comments: [{ text: 'Event One was great!'}] }, { name: 'Bob', comments: [] }] }.
I could have
class AttendeeSerializer < ActiveModel::Serializer
attributes :name
has_many :comments
end
but that would select all the comments by this attendee for all the events - not what I want. I'd like to write this, but how do I find the particular event for which I'm doing serialization? Can I somehow access the 'parent' object, or maybe pass options to a has_many serializer?
class AttendeeSerializer < ActiveModel::Serializer
attributes :name
has_many :comments
def comments
object.comments.where(event_id: the_event_in_this_context.id)
end
end
Is this something that can be done, or should I just build the JSON in another way for this particular use case?
I'd do things manually to get control:
class EventSerializer < ActiveModel::Serializer
attributes :name, :attendees
def attendees
object.attendees.map do |attendee|
AttendeeSerializer.new(attendee, scope: scope, root: false, event: object)
end
end
end
class AttendeeSerializer < ActiveModel::Serializer
attributes :name, :comments
def comments
object.comments.where(event_id: #options[:event].id).map do |comment|
CommentSerializer.new(comment, scope: scope, root: false)
end
end
end

Resources