eager loading nested object rails - ruby-on-rails

I have models like below
Customer.rb
has_many :appointments
has_many :addresses
has_many :contacts
Address.rb
belongs_to :customer
Contact.rb
belongs_to :customer
Appointment.rb
belongs_to :customer
I have defined API's to return customers like below but with one extra attribute i.e appointment_id.
customers: [
{
appointment_id: 'xxxxxxx'
..
..
..
addresses: [{...}, {...}]
contacts: [{...},{...}]
},
{
..
},
.....
]
The above api is defined in a way that I pass #customers (which is array of customers along with their nested objects address, contacts). Problem is How should write active record query to return so so data.
Current Approach:
// I got list of appointment id's and I should return corresponding customers data as shown in above api.
cust_ids = Appointment.where(blah blah blah).pluck(:customer_id)
#customers = Customer.where(appointment_id: cust_ids).includes(:addresses, :contacts)
What I want?
My above approach doesnt have appointment_id in #customers object. How should I get it? Do I need to join table
along with includes. ??

Add inverse of to association definitions to avoid n+1 during preload
# customer.rb
has_many :appointments, inverse_of: :customer
# appointment
belongs_to :customer, inverse_of: :appointment
Now you can fetch your appointments from the DB and construct JSON
# in the controller
appointments = Appointment.where(blah blah blah)
.preload(customer: [:contacts, :addresses])
customers = appointments.map do |appointment|
appointment.customer
.as_json(include: [:contacts, :addresses])
.merge(appointment_id: appointment.id)
end
render json: { customers: customers }

Here is what I can think of
appointment_ids = Appointment.where(blah blah blah).pluck(:id)
customers = Customer.includes(:addresses, :contacts, :appointments).where(appointments: { id: appointment_ids })
Now when you will write
customers.first.appointments you will only get those appointments which satisfy the first condition.
And According to your say, it will have only one appointment per customer per day.
So you can do customers.first.appointments.first.id

Use select in the query:
cust_ids = Appointment.where(blah blah blah).pluck(:customer_id)
#customers = Customer.where(appointment_id: cust_ids).includes(:addresses, :contacts).select("customers.*")

Related

Rails join through 2 other tables

I am trying to join tables to get an object.
I have these models:
class Company < ApplicationRecord
has_many :users
end
class Claim < ApplicationRecord
has_many :uploads, dependent: :destroy
validates :number, uniqueness: true
belongs_to :user, optional: true
end
class User < ApplicationRecord
belongs_to :company
has_many :claims
end
Basically I want to select all claims that belong to users that belong to a company.
Somethings I have tried:
(This works but is terrible and not the rails way)
#claims = []
#company = Company.find(params[:id])
#users = #company.users
#users.each do |u|
u.claims.each do |c|
#claims.push(c)
end
end
#claims = #claims.sort_by(&:created_at)
if #claims.count > 10
#claims.shift(#claims.count - 10)
end
#claims = #claims.reverse
This is close but doesn't have all the claim data because its of the user:
#claims = User.joins(:claims, :company).where("companies.id = users.company_id").where("claims.user_id = users.id").where(company_id: params[:id]).order("created_at DESC").limit(10)
I tried this but keep getting an error:
#claims = Claim.joins(:user, :company).where("companies.id = users.company_id").where("claims.user_id = users.id").where(company_id: params[:id]).order("created_at DESC").limit(10)
error: ActiveRecord::ConfigurationError (Can't join 'Claim' to association named 'company'; perhaps you misspelled it?)
Any ideas what I should do or change?
Based on your relations, you should use
Claim.joins(user: :company)
Because the Company is accessible through the relation Claim <> User.
If you wanted to join/preload/include/eager load another relation, let's say if Claim belongs_to :insurance_company, then you would add it like this:
Claim.joins(:insurance_company, user: :company)
Similar questions:
Join multiple tables with active records
Rails 4 scope to find parents with no children
That being said, if you want to
select all claims that belong to users that belong to a company
Then you can do the following:
Claim
.joins(:user) # no need to join on company because company_id is already on users
.where(company_id: params[:id])
.order(claims: { created_at: :desc })
.limit(10)
Tada!

ActiveRecord query on has_many :through in Rails 4

This is my Organization Model:
class Organization < ActiveRecord::Base
has_many :users
has_many :shipments, :through => :users
This is my Shipment Model:
class Shipment < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
I'm trying to access shipments for all organizations but two.
This code works but only returns shipments for the FIRST organization that's returned by my .where.not calls. I would like to join the shipments for all Organizations returned by my .where.not calls.
Organization.where.not(name: "Admin Org").where.not(name: "Test Organization").first.shipments
Thanks!
We need to do the query on Shipment model to fetch all the shipments of all the organizations except those two, which we will filter out using where.not caluse.
So the query would be like this:
#shipments = Shipment.joins(:organization).where.not(organization: {name: "Admin Org"}).where.not(organization: {name: "Test Organization"})
And a bit cleaner:
#shipments = Shipment.joins(:organization).where.not(organization: {name: ["Admin Org", "Test Organization"]})
I ended up creating an array and using two .each blocks to dump shipments into it:
array = []
Organization.where.not(name: ["Admin Org", "Test Organization"]).each { |x| x.shipments.each { |z| array << z } }

Association not working

I have three models:
Department
class Department < ActiveRecord::Base
has_many :patients, :dependent => :destroy
has_many :waitingrooms, :dependent => :destroy
end
Waitingroom with fields patient_id:integer and department_id:integer
class Waitingroom < ActiveRecord::Base
belongs_to :patient
end
Patient with department_id:integer
class Patient < ActiveRecord::Base
belongs_to :department
has_many :waitingrooms
end
I save a waitingroom after a patient was in the waitingroom! So now i tried to retrieve the patients who where in the the waitingroom of the department:
def index
#waited = #current_department.waitingrooms.patients
end
Somehow it didnt worked it returned this error:
undefined method `patients' for #<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Waitingroom:0x374c658>
But this worked: What did i wrong? Thanks!
def index
#waited = #current_department.waitingrooms
end
You can't invoke an association on a collection. You need to invoke it on a specific record. If you want to get all the patients for a set of waiting rooms, you need to do this:
def index
rooms = #current_department.waitingrooms
#waited = rooms.map { |r| r.patients }
end
If you want a flat array, you could (as a naive first pass) use rooms.map { |r| r.patients }.flatten.uniq. A better attempt would just build a list of patient ids and fetch patients once:
#waited = Patient.where(id: rooms.pluck(:patient_id).uniq)

How To Get Additional Attributes From Has Many Through

I am using Rails 3 beta 4.
I have the following models:
class Player < ActiveRecord::Base
has_many :players_items, :dependent => :destroy
has_many :items, :through => :players_items
end
class PlayersItem < ActiveRecord::Base
belongs_to :player
belongs_to :item
end
class Item < ActiveRecord::Base
has_many :players_items, :dependent => :destroy
has_many :players, :through => :players_items
end
In the players_controller
def items
#player = Player.find(params[:id])
#player_items = #player.items
end
I have the following attributes
--Items Model--
Item_id:Integer
Name:String
Cost:Integer
Description:Text
--PlayersItem Model--
Item_id:Integer
Player_id:Integer
Total:Integer
Traded:Integer
I am trying to print out all the items associated with a player and for each item print out the "Name", "Cost", "Description", "Total", and "Traded" values.
When I call #player_items in the items.html.erb, I can only access the attributes associated with the Item Model and not any of the attributes associated with PlayersItem model.
I am trying to access the attributes from both the items model and players_items model in the same "call" similar to SQL Join Statement like this
SELECT * FROM players_items INNER JOIN items ON players_items.item_id=items.id
WHERE players_items.player_id = "#player"
Is this possible?
#player = Player.order("created_at").last
#player.players_items.each do |item|
puts "#{item.player}: #{item.description} cost:#{item.cost}"
end
Has many through is a little weird. Think of it as a model whose name should (ideally) be descriptive of the relationship between the two other models. So maybe if equipment is being distributed to the players you could call the join model distributions or loans or something. The players_items is the naming convention for join tables which aren't going to be addressed directly.
I hope that helps!
Used in the controller
#player = Player.find(params[:id], :include => [:items,:players_items])
And in the view
#player.players_items.each do |player|
puts "#{player.name}: #{player.player.item.description} cost:#{player.item.cost}"
end

Rails: order using a has_many/belongs_to relationship

I was wondering if it was possible to use the find method to order the results based on a class's has_many relationship with another class. e.g.
# has the columns id, name
class Dog < ActiveRecord::Base
has_many :dog_tags
end
# has the columns id, color, dog_id
class DogTags < ActiveRecord::Base
belongs_to :dog
end
and I would like to do something like this:
#result = DogTag.find(:all, :order => dog.name)
thank you.
In Rails 4 it should be done this way:
#result = DogTag.joins(:dog).order('dogs.name')
or with scope:
class DogTags < ActiveRecord::Base
belongs_to :dog
scope :ordered_by_dog_name, -> { joins(:dog).order('dogs.name') }
end
#result = DogTags.ordered_by_dog_name
The second is easier to mock in tests as controller doesn't have to know about model details.
You need to join the related table to the request.
#result = DogTag.find(:all, :joins => :dog, :order => 'dogs.name')
Note that dogs is plural in the :order statement.

Resources