Loading Relations - ruby-on-rails

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
}
}

Related

Optimizing database query in Rails 5

I have 4 models
module Vehicle
has_many :routes
end
module Route
has_many :route_users
belongs_to :vehicle
end
module RouteUser
belongs_to :route
belongs_to :user
end
module User
belongs_to :route_user
end
My goal is to return the most recent driver (a user) through the aggregate.rb; to be specific, I need user id, first_name, last_name.
attributes = {
id: vehicle.id,
... other attributes ...
}
attributes.merge!(
driver: {
id: vehicle.routes.last.route_users.last.user.id,
first_name: vehicle.routes.last.route_users.last.user.first_name,
last_name: vehicle.routes.last.route_users.last.user.last_name
}
) if vehicle.routes.present? && vehicle.routes.last.route_users.present?
As you can see, .merge! loads a bunch of information and dramatically slows down the aggregate.rb return. Is there any way to optimize this return to make it faster? Am I missing something?
You can improve your User model to make the query easier.
class User < ApplicationRecord
has_many :route_users
has_many :routes, through: :route_users
has_many :vehicles, through: :routes
end
And when the query is big from one side, the best way is two invert the logic, and make a query from user, example:
First fetch last_user_drive, and after that, use his fields to merge into attributes
last_user_driver = User.joins(routes: :vehicle).where(vehicle: {id: vehicle.id}).order('routes.created_at').last
...
attributes.merge!(
driver: {
id: last_user_driver.id,
first_name: last_user_driver.first_name,
last_name: last_user_driver.last_name
}
) if last_user_driver.present?

eager loading nested object 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.*")

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 } }

ActiveRecord join with select Rails 4

I have the following AR relations,
class Restaurant < ActiveRecord::Base
has_many :deals
end
class Deal < ActiveRecord::Base
belongs_to :restaurant
acts_as_taggable_on :tags
end
What I want to do is to find the deals with given tags, and return the related restaurants.
I have the following scope in my Restaurant model
scope :food_type, ->(food_types){
select("*")
joins(:deals).merge(Deal.tagged_with([food_types], any: true, wild: true ))
}
But the issue is, when I call Restaurant.food_type(<tags>) it returns ActiveRecord relation with Restaurant objects with Deal data
Ex: #<Restaurant id: 383, name: "sample deal name", note: "sample deal note", created_at: "2014-03-18 22:36:27", updated_at: "2014-03-18 22:36:27"
I even used select in the scope , without any luck. How can I return Restaurant attributes from the above scope?

How can I populate a join (many-to-many) table inside seeds.rb for ruby on rails?

I currently have a model for team.rb and user.rb, which is a many to many relationship. I have created the join table teams_users but I am not sure how to populate this table in my seeds.rb?
For example, I have :
user = User.create({ first_name: 'Kamil', last_name: 'Bo', email: 'bo#gmail.com'})
team = Team.create([{ name: 'Spot Forwards', num_of_games: 10, day_of_play: 4}])
But the following does not work???
TeamsUsers.create({ team_id: team.id, user_id: user.id })
I get a message :
uninitialized constant TeamsUsers
This isn't optimized but
user.team_ids = user.team_ids < team.id
user.save
or if this is the first team
user.team_ids = [team.id]
user.save
ALso start using has_many :through. then you will have a TeamUser model. it's a life saver if the join table needs more attributes
Pick a side to work from and then, as #drhenner suggests, use the _ids property to create the association. For example, working with the User model, create the teams first, then the users, assigning them to teams as you go:
teams = Team.create([
{ name: 'Team 1' },
{ name: 'Team 2' },
{ name: 'Team 3' },
# etc.
])
User.create([
{ name: 'User 1', team_ids: [teams[0].id, teams[2].id] },
{ name: 'User 2', team_ids: [teams[1].id, teams[2].id] },
{ name: 'User 3', team_ids: [teams[0].id, teams[1].id] },
# etc.
])
From comment above:
You can have multiple relationships configured on a has_many :through relationship. It's up to you which ones you want to implement. These are all the possibilities:
class Team < ApplicationRecord
has_many :memberships
has_many :users, through: :memberships
end
class Membership < ApplicationRecord
belongs_to :team
belongs_to :user
end
class User < ApplicationRecord
has_many :memberships
has_many :teams, through: :memberships
end
So, when dealing with the Team model, you can use: team.memberships and team.users;
when dealing with the User model, you can use: user.memberships and user.teams;
and if dealing with the join model, you can use: membership.team and membership.user.
You can omit the relationship references to the join model if you don't use it—especially if you're treating the relationship between Team and User like a standard has_and_belongs_to_many relationship:
class Team < ApplicationRecord
has_many :users, through: :memberships
end
class User < ApplicationRecord
has_many :teams, through: :memberships
end
This gives you team.users and user.teams.
Using Rails, say you have tables foos and bars in a many-to-many relationship using table foos_bars, you can seed their associations like this:
bar1 = Bar.find(1)
bar2 = Bar.find(2)
foo1 = Foo.find(1) # for example
foo1.bars << bar1
foo1.bars << bar2
foo1.save
This will update the joins table foos_bars with associations <foo_id:1, bar_id:1>, <foo_id:1, bar_id:2>
Hope this helps.

Resources