Make single ActiveRecord query through several associated models - ruby-on-rails

I have the following models and associations:
class Organization
has_many :buildings
end
class Building
has_many :counters
end
class Counter
has_many :counter_records
end
class CounterRecord
belongs_to :counter
end
I would like to get something like this
organization.counter_records(dimension_day: start_date...end_date)
([dimension_day: start_date...end_date] - it's condition).
How do I get counters records of organization through all these models?

Look into Activerecord Querying guide.
Specifically you're interested in joins:
Organization.joins(buildings: { counters: :counter_records })
.where(counter_records: { dimension_day: start_date...end_date })
.group('organizations.id')
You can create a method:
class Organization
def filter_counter_records(start_date, end_date)
self.class
.where(id: id)
.joins(buildings: { counters: :counter_records })
.where(counter_records: { dimension_day: start_date...end_date })
.group('organizations.id')
end
end
Now the following is possible:
organization = Organization.first
organization.filter_counter_records(start_date, end_date)
But more idiomatic/conventional option would be using associations:
class Organization
has_many :buildings
has_many :counters, through: :buildings
has_many :counter_records, through: :counters
end
Now you can just go with
organization = Organization.first
organization.counter_records.where(dimension_day: start_date..end_date)
The last step here would be setting up the scope in CounterRecord:
class CounterRecord
scope :by_date_range, ->(start_date, end_date) { where(dimension_day: start_date..end_date) }
end
And now
organization = Organization.first
organization.counter_records.by_date_range(start_date, end_date)

Related

How to prepend to order clause with scope

Is there any way to prepend to the existing order section of an active record query?
I the following associations defined on my Location model:
class Location < ActiveRecord::Base
has_many :location_parking_locations, -> { by_votes }
has_many :parking_locations, through: :location_parking_locations
end
In the LocationParkingLocation model, the by_votes scope is defined:
class LocationParkingLocation < ActiveRecord::Base
belongs_to :location
belongs_to :parking_location
scope :by_votes, -> { order("upvotes - downvotes ASC, upvotes + downvotes DESC, id ASC") }
end
I would like to add a scope to the ParkingLocation model that adds an additional scope to the query, but I want that scope to be prepended to the existing order section of the query. The scope looks like this:
class ParkingLocation < ActiveRecord::Base
has_many :location_parking_locations
has_many :locations, through: :location_parking_locations
scope :with_pending, -> { order(creation_pending: :desc) }
end
My hope is to call location.parking_locations.with_pending, and get back a collection of parking_locations, ordered by votes, but with any pending parking locations at the beginning of the collection. Is this possible?
Since a scope is a lambda, you can use arguments, for example:
scope :your_scope, -> (sort_order = :desc) { order(name: sort_order) }
Just set it up as you need and call it with a (possibly optional) argument:
your_model.your_scope(:desc)

Rails Active Record different association results into one object

I have a Project model.
Project model has "all_users" instance method which returns all users of the project.
class Project < ActiveRecord::Base
has_many :memberships
has_many :users, through: :memberships, source: :member, source_type: 'User'
has_many :teams, through: :memberships, source: :member, source_type: 'Team'
scope :all_users, -> (project) {
User.where(%{
(users.id in (select member_id from memberships where project_id = #{project.id} and member_type = 'User')) OR
(users.id in (select user_id from teams_users where team_id IN (select member_id from memberships where project_id = #{project.id} and member_type = 'Team')))
})
}
def all_users
Project.all_users(self).order(:name)
end
end
A user has many projects.
I want to make an instance method in User model to return all users of instance's all projects. Such as:
class User < ActiveRecord::Base
has_many :memberships, as: :member, dependent: :destroy
has_many :projects, through: :memberships
def colleagues
colleagues_of_user = []
projects.each do |project|
project.all_users.each do |user|
colleagues_of_user << user
end
end
teams.each do |team|
team.projects.each do |project|
project.all_users.each do |user|
colleagues_of_user << user
end
end
end
colleagues_of_user.uniq
end
end
The problem is; i want to concatenate all "project.all_users" into one object but i can't. I have to turn them into an array (to_a). But i want the results (colleagues_of_user) in one object ("ActiveRecord::Relation").
UPDATE: Another point that should be noted is;
colleagues_of_user could be:
1. Any user that is member of any projects of the current user.
2. Any user that is member of current user's teams' projects.
I have updated "colleagues" method regarding these notes. How to get all results into one ActiveRecord::Relation object? (Not an array)
Since you want colleagues_of_user to be ActiveRecord::Relation rather than an Array, I think you could do it like this:
def colleagues
colleague_ids = projects_colleague_ids + teams_projects_colleague_ids
colleagues_of_user = User.where(id: colleague_ids.flatten.uniq )
end
private
def projects_colleague_ids(projects = nil)
projects ||= self.projects
projects.includes(:users).collect{ |project| project.all_users.pluck(:id) }.flatten.uniq
end
def teams_projects_colleague_ids
teams.includes(projects: :users).collect do |team|
projects_colleague_ids( team.projects )
end.flatten.uniq
end
I think something like this should work:
def colleagues
projects.map(&:all_users)
end
You can try this with eager loading also.
Project.includes(users).map(&:all_users)
Thanks

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)

Rails 3 joining a related model with it's default scope

I'm trying to query across models with the following setup
Class Scorecard < AR::Base
default_scope where(:archived => false)
belongs_to :user
has_many :scorecard_metrics
end
Class ScorecardMetric < AR::Base
belongs_to :scorecard
end
Class User < AR::Base
has_many :scorecards
end
I am trying to query from scorecard metrics with a named scope that joins scorecard and I want it to include the default scope for scorecard, my current implementation (which works) looks like this
# on ScorecardMetric
scope :for_user, lambda {
|user| joins(:scorecard).
where("scorecards.user_id = ? and scorecards.archived = ?", user.id, false)
}
This just seems messy to me, is there any way to join and include the default scope of the joined association?
Looks like I found the answer I was looking for, I just did this
scope :for_user, lambda { |user| joins(:scorecard).where('scorecards.user_id = ?', user.id) & Scorecard.scoped }
which is much nicer without the duplicated logic

How to apply conditions when accessing records using a has_many through relationship in Rails?

I have the following models:
class Campaign < ActiveRecord::Base
has_many :campaign_keywords
has_many :leads, :through => :campaign_keywords
end
class CampaignKeyword < ActiveRecord::Base
belongs_to :campaign
has_many :leads
end
class Lead < ActiveRecord::Base
belongs_to :campaign_keyword
end
I am trying to build a function in the "Campaign" model that will only return leads which belong to a given campaign_keyword.
My attempt is:
def leads?(campaign_keyword_id = -1)
self.leads :conditions => ['campaign_keyword_id = #{campaign_keyword_id}']
end
but this does not work, the conditions are ignored.
Can you see a solution to this?
Create a named_scope for your Lead model, like so:
class Lead < ActiveRecord::Base
belongs_to :campaign_keyword
named_scope :with_keyword, lambda { |keyword| { :conditions => { :campaign_keyword => keyword } } }
end
Now, when you want to get leads for a particular campaign keyword, you would do so like this:
def leads_for_campaign(keyword)
self.leads.with_keyword(keyword)
end
This is much nicer and more re-usable, because the Lead model itself now knows how to find leads for a specific campaign.
For more of an idea of what you can do with named_scopes, check out http://apidock.com/rails/ActiveRecord/NamedScope/ClassMethods/named_scope
Try this:
def leads?(campaign_keyword_id = -1)
self.leads.all :conditions => ['campaign_keyword_id = #{campaign_keyword_id}']
end
I would rewrite your query as follows:
def leads?(campaign_keyword_id = -1)
self.leads.all :conditions => ['campaign_keyword_id = ?', campaign_keyword_id]
end
OR
self.leads.find_all_by_compaign_keyword_id(campaign_keyword_id)

Resources