Writing scope, join and order for a Model
Try,
scope :some_scope, joins(mesures: :targets)
Applying two scopes in one ActiveRecord query
You can chain scopes and includes.
scope :rpc, includes(:registry_patient_counts)
scope :msr, includes(measures: :measures_off_targets)
@programs = Program.rpc.msr.where(organization_id: 1).limit(2)
# Equivalent to
@programs = Program.includes(
[:registry_patient_counts, measures: :measures_off_targets])
.where(organization_id: 1)
.limit(2)
ActiveRecord 2 level deep scope
You need:
has_many :comments
has_many :users, :through => :comments
ActiveRelation that requires nested joins using scopes
I would probably use something like this. I kept scopes separate for clarity.
Models (renamed PrivateMessage -> Message and Recipient -> MessageCopy):
class User < ActiveRecord::Base
has_many :sent_messages, :class_name => "Message", :foreign_key => :sender_id
has_many :sent_message_copies, :through => :sent_messages, :source => :message_copies
has_many :received_messages, :through => :received_message_copies, :source => :message
has_many :received_message_copies, :class_name => "MessageCopy", :foreign_key => :recipient_id
end
class Message < ActiveRecord::Base
belongs_to :sender, :class_name => "User"
has_many :message_copies
has_many :recipients, :through => :message_copies
end
class MessageCopy < ActiveRecord::Base
belongs_to :message
belongs_to :recipient, :class_name => "User"
scope :unread, where(:read => false)
scope :undeleted, where(:deleted => false)
scope :sent_to, lambda { |recipient| where(:recipient_id => recipient.id) }
end
Schema (migrations would have taken too much space here):
ActiveRecord::Schema.define(:version => 20110503061008) do
create_table "message_copies", :force => true do |t|
t.boolean "read", :default => false
t.boolean "deleted", :default => false
t.integer "message_id"
t.integer "recipient_id"
end
create_table "messages", :force => true do |t|
t.string "title"
t.integer "sender_id"
end
create_table "users", :force => true do |t|
t.string "name"
end
end
--edit
Example query using joins returning messages
Message.joins(:message_copies).where(:message_copies => {:read => false, :deleted => false, :recipient_id => 3})
Message scope reusing scopes on other model
scope :non_deleted_by_recipient, lambda { |recipient|
joins(:message_copies).merge(MessageCopy.unread.undeleted.sent_to(recipient))
}
--edit2
This Railscast has nice examples of both joins and scopes:
- http://railscasts.com/episodes/215-advanced-queries-in-rails-3
deeply nested joins in activerecord
For 'eager loading' you use .include
; .join
is for performing INNER JOIN functionality. It seems likely that for your case you will be using both (the join to get the photo, and the includes to get the additional people). I make this point because .join
alone will not perform eager loading (and so it will make the queries afterward when the associated entities are accessed).
If you wish to eager-load nested associations, you can use the syntax outlined in the Ruby on Rails guide at http://guides.rubyonrails.org/active_record_querying.html#eager-loading-multiple-associations
Would something along these lines work?
Photo.joins(:appearances => :person).includes(:appearances => :person).limit(5)
Or do you need to start with the person?
Person.joins(:appearances => :photo).includes(:appearances => :photo => :appearaces => :person).limit(5)
Also, as a final (small) note: you have ":appearances" spelled incorrectly in your last line of code (it states ":appearaces"), which could cause issues.
EDIT:
After some minor testing, it appears that nesting the include associations in the
a => b => c => b => a
form doesn't work. However, the following form does:
a => [b => [c => [b => a]]]
Though, of course, it isn't quite as pretty.
Meaning, using the following code:
Person.includes(:appearances => [:photo => [:appearances => :person]]).find(38)
The following SQL was generated:
Person Load (0.3ms) SELECT `persons`.* FROM `persons` WHERE `persons`.`id` = 38 LIMIT 1
Appearance Load (0.3ms) SELECT `appearances`.* FROM `appearances` WHERE (`appearances`.person_id = 38)
Photo Load (0.3ms) SELECT `photos`.* FROM `photos` WHERE (`photos`.`id` = 1904)
Appearance Load (0.3ms) SELECT `appearances`.* FROM `appearances` WHERE (`appearances`.photo_id = 1904)
Person Load (0.3ms) SELECT `persons`.* FROM `persons` WHERE (`persons`.`id` IN (38,346))
Eager loading the additional Persons from all Photos that include Person with id #38
Finding values of an array in a loop
try the following
def patient_counts(program)
sorted = program.patient_counts.sort { |a, b| b.money <=> a.money }
sorted[0..4]
end
UPDATE: limiting to just 5 records fetched from the db
def patient_counts(program)
program.registry_patient_counts.limit(5).order('patient_count_percentage DESC')
end
Laravel, getting deep relationships
To find all the tenants in a given building, the easiest method would be to use JOIN
clauses.
I have assumed all of your relationships are hasMany
inversed by belongsTo
.
$tenants = Tenant::select('tenants.*')
->join('units', 'units.id', '=', 'tenant.unit_id')
->join('blocks', 'blocks.id', '=', 'units.block_id')
->join('buildings', 'buildings.id', '=', 'blocks.building_id')
->where('buildings.id', 123)
->get();
If this is something you'll use more than once, then I'd suggest creating a query scope on your Tenant
model.
class Tenant extends Eloquent
{
// ...
public function scopeInBuilding($query, $buildingId)
{
return $query->select('tenants.*')
->join('units', 'units.id', '=', 'tenant.unit_id')
->join('blocks', 'blocks.id', '=', 'units.block_id')
->join('buildings', 'buildings.id', '=', 'blocks.building_id')
->where('buildings.id', $buildingId);
}
// ...
}
And you can use it as follows:
$tenants = Tenant::inBuilding(123)->get();
Eloquent with nested whereHas
Update: the PR has been just merged to 4.2, so now it's possible to use dot nested notation in has
methods ( ->has('relation1.relation2)
->whereHas('relation1.relation2, ..
)
Your question remains a bit unclear or you misunderstand whereHas() method as it is used to filter models (users in this case) and get only those that have related models fitting search conditions.
It seems that you want to find Packages from the context of a given User, so no need to use whereHas method.
Anyway depending on the relations (1-1,1-m,m-m) this can be easy or pretty hard and not very efficient. As I stated, loading nested relations means that for every level of nesting comes another db query, so in this case you end up with 5 queries.
Regardless of the relations you can invert this chain like this, as it will be easier:
edit: This is not going to work atm as whereHas() doesn't process dot nested relations!
// given $user and $search:
$packages = Package::where('alias','like',"%$search%")
->whereHas('product.membership.club.user', function ($q) use ($user) {
$q->whereId($user->id);
})->get();
As you can see this is much more readable, still runs 5 queries.
Also this way you get $packages, which is a single Collection of the models you wanted.
While from the context of a user you would get something like this (depending on the relations again):
$user
|-club
| |-membership
| | |-product
| | | |-packages
| | |-anotherProduct
| | | |-packages
| | |-yetAnotherProduct
| | |-packages
| |-anotherMembership
.....
You get the point, don't you?
You could fetch the packages from the Collection, but it would be cumbersome. It's easier the other way around.
So the answer to your question would be simply joining the tables:
// Let's assume the relations are the easiest to handle: 1-many
$packages = Package::where('alias','like',"%$search%")
->join('products','packages.product_id','=','products.id')
->join('memberships','products.membership_id','=','memberships.id')
->join('clubs','memberships.club_id','=','clubs.id')
->where('clubs.user_id','=',$user->id)
->get(['packages.*']); // dont select anything but packages table
Of course you can wrap it in a nice method so you don't have to write this everytime you perform such search.
Performance of this query will be definitely much better than separate 5 queries shown above. Obviously this way you load only packages, without other related models.
Related Topics
How to Make an Infowindow Automatically Display as Open with Google-Maps-For-Rails
What Ruby Features Are Used in Chef Recipes
Error Occurs When Trying to Install Homebrew on a MAC for Ruby on Rails
Using Devise for More Than One Models in Rails App
Bug in Implemented Tagging System
Duplicates in 2 Dimensional Array
How to Send Notifications to the User Whose Post Received a Comment
Find a Unique Element in a Compound Array
Count Overlapping Regex Matches in Perl or Ruby
Definition of Method in Top Level
Can Ruby Tell If It Is Called from an Interactive Shell or Cron
Why Does the Script Affect Everything on My Rails 3 App Even When Cased in This Code
Devise 'Find_First_By_Auth_Conditions' Method Explanation
Create and Initialize Instances of a Class with Sequential Names