I have bit complicated model design with many associations among themselves.
Here is model design
User Model
class User < ActiveRecord::Base
has_many :records
validates :email, presence: true
end
Record Model
class Record < ActiveRecord::Base
belongs_to :user
has_many :record_type_student
has_many :record_type_employee
has_many :record_type_other
end
RecordTypeStudent Model
class RecordTypeStudent < ActiveRecord::Base
belongs_to :record
belongs_to :user
belongs_to :source
end
Similar Model for other two RecordTypeOther and RecordTypeEmployee
I have Added index as well in RecordTypeStudent, Other and Employee from Record for fast retrieval.
Currently retreiving 1000 records including all three takes 2.4 seconds which I think is alot.
Here is how I am querying my records
first Query
#records = Record.where(:user_id => 1)
#r = []
#records.each do |m|
if !r.record_type_students.empty?
#r += r.record_type_students
end
if !r.record_type_other.empty?
#r += r.record_type_others
end
if !r.record_type_employees.empty?
#r += r.record_type_employees
end
end
The processing time is very low and it is only 1000 records so I am not sure is bad queries that I am doing or something else.
Application is using MySQl for data base
Completed 200 OK in 2338ms (Views: 0.5ms | ActiveRecord: 445.5ms)
It seems to me like you are creating a lot of unnecessary queries. Instead of pulling out the records and iterating over them (which should have been done with .find_each rather than .each), you can query the individual record types with the right records which will result in an IN clause and will be done on the database side. If I am understanding your schema correctly, you can get the same data as follows (AR 4.1.1):
record = Record.where(user_id: 1)
r = []
r += RecordTypeStudent.where(record: record).to_a
r += RecordTypeEmployee.where(record: record).to_a
r += RecordTypeOther.where(record: record).to_a
This will result in 3 queries total. You can make the code cleaner as follows:
r = [RecordTypeStudent, RecordTypeEmployee, RecordTypeOther].flat_map do |type|
type.where(record: Record.where(user_id: 1)).to_a
end
If you want to further drop the number of queries, you could get the data via UNION between those tables but I don't think that's necessary.
I guess it's just here in the question's text but those has_many in the Record model have to be specified with plural arguments, otherwise it won't work.
Related
I would like to specify each condition for Maintenance and Ability associated with a Person and retrieve each of them.
Normally, the query would be to retrieve the records that contain the two conditions, Person.maintenances and Person.abilities.
A = Person.maintenances.where(~~~~)
B = Person.abilities.where(~~~)
I want to get all of the above in a single query as a Person.
(What I want = A + B in a single query)
Also, I don't want to have the N+1 problem, but I would like to know how to prevent the query from being issued because the conditions of Person.maintenances and Person.abilities are separated.
class Person << ApplicationRecord
has_many :maintenances
has_many :abilities
end
class Maintenance << ApplicationRecord
belongs_to :person
end
class Ability << ApplicationRecord
belongs_to :person
end
※I'm using MySQL
Thanks.
A simple join should do the trick:
Person.joins(:maintenance).where(maintenances: {status: 0, example: "yes"}).joins(:ability).where(abilities: {........})
Or something like this, which should create the same result
Person.where(maintenances: Maintenance.where(person: self, ......), abilities: Ability.where(person: self, ......))
I kind of stuck at trying to generate statistics for my application. The relevant part of the application has the following structure:
class CarRegistration < ActiveRecord::Base
belongs_to :ride
belongs_to :car
...
end
class Car < ActiveRecord::Base
has_many :car_registration
...
end
class Ride < ActiveRecord::Base
belongs_to :passenger
belongs_to :driver
has_many :car_registration
...
end
class Driver < ActiveRecord::Base
has_many :cars
...
end
class Passenger < ActiveRecord::Base
has_many :cars
...
end
I am trying to get a list of rides, top drivers and and top passengers. I originally tried something like this:
#rides_finished = Ride.joins(:car_registration)
.select('rides.id')
.where("(car_registrations.ride_id = rides.id)
AND rides.status = 3
AND rides.driver_currency = ?
AND rides.passenger_currency = ?", currency, currency)
.distinct # against displaying one shipment multiple times
And then I tried:
#top_pasengers = #rides_finished.joins(:passenger)
.select('passengers.id, passengers.name, count(rides.passenger_id) AS count_all')
.where('rides.passenger_id IS NOT NULL')
.group('passengers.id')
.order('count_all DESC')
.limit(10)
But when I run these queries, I get
Mysql2::Error: Unknown column 'count_all' in 'order clause': ...
Any help how to get the needed numbers?
Thank you very much
Your question is a little confusing because your query uses Ride but there is no Ride in the model definitions listed. I've focussed purely on the example queries you listed.
I think it would be easier to start with a single query chain for 'top passengers':
Passenger
.select('passengers.*')
.select('count(1) as ride_count')
.joins(:rides)
.where(rides: { status: 3,
driver_currency: currency,
passenger_currency: currency })
.group('passengers.id')
.order('ride_count desc')
.limit(10)
That will get you an ActiveRecord::Relation of Passenger models that also respond to a ride_count call, e.g. you could use it like:
results.each do |p|
puts "#{p.name}: #{p.ride_count}'
end
If all that works, you should be able to adjust the query to get the top drivers.
To get the list of finished rides, I suggest a separate, simple query:
Ride.where(status: 3,
driver_currency: currency,
passenger_currency: currency)
Let me know if any of that produces an error.
Having a tough time with this one. I have a Job model, and a JobStatus model. A job has many statuses, each with different names (slugs in this case). I need an 'active' method I can call to find all jobs where none of the associated statuses has a slug of 'dropped-off'.
class Job < ActiveRecord::Base
belongs_to :agent
has_many :statuses, :class_name => "JobStatus"
validates :agent_id,
:pickup_lat,
:pickup_lng,
:dropoff_lat,
:dropoff_lng,
:description,
presence: true
class << self
def by_agent agent_id
where(agent_id: agent_id)
end
def active
#
# this should select all items where no related job status
# has the slug 'dropped-off'
#
end
end
end
Job Status:
class JobStatus < ActiveRecord::Base
belongs_to :job
validates :job_id,
:slug,
presence: true
end
The closest I've gotten so far is:
def active
joins(:statuses).where.not('job_statuses.slug = ?', 'dropped-off')
end
But it's still selecting the Job that has a dropped-off status because there are previous statuses that are not 'dropped-off'. If i knew the raw sql, I could probably work it into activerecord speak but I can't quite wrap my head around it.
Also not married to using activerecord, if the solution is raw SQL that's fine too.
Job.where.not(id: JobStatus.where(slug: 'dropped-off').select(:job_id))
will generate a nested subquery for you.
Not the cleanest method, but you could use two queries.
# Getting the ID of all the Jobs which have 'dropped-off' JobStatuses
dropped_off_ids = JobStatus.where(slug: 'dropped-off').pluck(:job_id)
# Using the previous array to filter the Jobs
Job.where.not(id: dropped_off_ids)
Try this:
def active
Job.joins(:statuses).where.not('job_statuses.slug' => 'dropped-off')
end
or this:
def active
Job.joins(:statuses).where('job_statuses.slug != ?', 'dropped-off')
end
I think you may want to reevaluate your data model somewhat. If the problem is that you're turning up old statuses when asking about Job, you likely need to have column identifying the current status for any job, i.e. job.statuses.where(current_status: true)
Then you can very easily grab only the rows which represent the current status for all jobs and are not "dropped-off".
Alternatively, if I'm misunderstanding your use case and you're just looking for any job that has ever had that status, you can just go backwards and search for the status slugs first, i.e.
JobStatus.where.not(slug: "dropped-off").map(&:job)
I have a pair of ActiveRecord objects that have a belongs_to ... has_many association, with the has_many association being custom-made. Example:
First AR object:
class Car < Vehicle
has_many :wheels, class_name: "RoundObject", foreign_key: :vehicle_id, conditions: "working = 1"
validates_presence_of :wheels
...
end
Second AR object:
class RoundObject < ActiveRecord::Base
belongs_to :vehicle
...
end
Please note that the above is not indicative of my app's function, simply to outline the association between my two AR objects.
The issue I'm having is that, when I reset the cache (and thus my Rails app re-caches all AR objects in the database), when it comes time for the RoundObject object to get re-cached, it makes multiple calls to the database, one for each unique vehicle_id associated with the collection of RoundObjects. The SQL commands being run are output to the console, so this is what my output looked like:
RoundObject Load (2.0ms) SELECT `round_objects`.* FROM `round_objects` WHERE `round_objects`.`vehicle_id` = 28 AND (active = 1)
RoundObject Load (1.0ms) SELECT `round_objects`.* FROM `round_objects` WHERE `round_objects`.`vehicle_id` = 29 AND (active = 1)
RoundObject Load (2.0ms) SELECT `round_objects`.* FROM `round_objects` WHERE `round_objects`.`vehicle_id` = 30 AND (active = 1)
My app has several other AR objects that use the built-in has_many association without any modifications, and I notice that they only hit the database once when resetting the cache. For instance:
Micropost Load (15.0ms) SELECT `microposts`.* FROM `microposts` INNER JOIN `posts` ON `posts`.`id` = `microposts`.`post_id` WHERE `microposts`.`active` = 1 AND `posts`.`active` = 1
My question is, how can I make my AR object only hit the database once on cache reset, while still maintaining the custom has_many association I need? Can I manually force a join on the SQL query being called, and will this help?
Thank you!
You can use includes method while calling your Vehicle object to include the RoundObject.
It will go like this:
Vehicle.where(conditions_for_getting_data).includes(:round_object)
I am having an issue with model associations. I want to pull a route's trip info by accessing the data from trips.route_id where routes.route_id would be the same value. Currently the SQL query is calling routes.id, instead using routes.route_id. Any help would be appreciated.
Route Table Structure
COLUMNS: id,route_id,route_short_name,route_long_name,route_desc,route_type
ROW: 1,2-36,2,"East 34th St",,3
The primary_key on this table is 'id'.
Models
class Route < ActiveRecord::Base
has_many :trips, :foreign_key => 'route_id'
end
class Trip < ActiveRecord::Base
belongs_to :route
end
Route Controller
class RouteController < ApplicationController
def show
#route = Route.where("'%s' = routes.route_short_name",params[:id]).first
end
end
Want to call #route.trips to pull trip information associated with said #route
LOG INFO
Started GET "/route/19" for 127.0.0.1 at Wed Nov 23 21:09:55 -0500 2011
Processing by RouteController#show as HTML
Parameters: {"id"=>"19"}
Route Load (0.4ms) SELECT `routes`.* FROM `routes` WHERE ('19' = routes.route_short_name) LIMIT 1
Trip Load (8.4ms) SELECT `trips`.* FROM `trips` WHERE `trips`.`route_id` = 15
Trip Load Explanation: 15 represents the id of the object returned from the "Route Load" query. I would like to use the routes.route_id value of the result instead of the id to build the "Trip Load" query.
Desired result:
Trip Load (8.4ms) SELECT 'trips'.* FROM 'trips' WHERE 'trips'.'route_id' = '2-36'
('2-36' value is referenced from Route Table Structure example)
If I understand your question properly, you need to change your models to something like this:
class Route < ActiveRecord::Base
has_many :trips, :primary_key => "route_id"
end
class Trip < ActiveRecord::Base
belongs_to :route, :primary_key => "route_id"
end