Let's say I have two models:
Campaign and Qualification. Campaign has_one :qualification and Qualification belongs_to :campaign.
Let's say that Qualification has a male_gender boolean attribute, what's the most effective way to find all campaigns that have a qualification with male_gender == true
One way would be to loop through qualification with male_gender indexed. Create an array of campaign_ids, and then get campaigns with those ids, but this seems not very effective.
Ideally I'd like to do something like Campaign.where("qualification.male_gender: true")
Is that possible? Is there a better way to do this?
You certainly can do this. Using a join you can reference the other table in your where query:
Campaign.joins(:qualification).where(qualifications: {male_gender: true})
Rich Peck edit
You could also put this into a scope:
#app/models/campaign.rb
class Campaign < ActiveRecord::Base
scope :male_gender, -> { joins(:qualification).where(qualifications: {male_gender: true}) }
end
... which would allow you to call #campaigns = Campaign.male_gender
Related
Here is an example:
Let says I have a Student object which has a has_many relationship with ReportCard objects. ReportCard objects have a boolean field called "graded" that flags they have been graded. So it looks like:
class Student < ActiveRecord
has_many :report_cards
end
class ReportCard < ActiveRecord
# graded :boolean (comes from DB)
belongs_to :student
end
Now, let's say you want to create a default scope so that if a student has no graded ReportCards, you want to see all of them, but if they have at least one graded ReportCard, you only want to see the graded ones. Finally, let's say you order them by "semester_number".
Using this scope on ReportCard works correctly:
scope :only_graded_if_possible, ->(student) { where(graded: true, student: student).order(:semester_number).presence || order(:semester_number) }
But I want it to be the default scope for Student so I tried:
class Student < ActiveRecord
has_many :report_cards, ->{ where(graded: true).order(:semester_number).presence || order(:semester_number) }
end
but this does not work. It won't return any report_cards if there is a single graded report_card in the whole db. Looking at the queries that are run, first it runs something like:
SELECT report_cards.* FROM report_cards WHERE reports_cards.graded = t ORDER BY semester_number ASC
I think this must be the present? check part of the presence query and notice it does not filter on Student at all! So if there is a single report_card that is graded, the check passes and then it runs the following query to get what to return:
SELECT report_cards.* FROM reports_card WHERE report_card.student_id = 'student id here' AND report_card.graded = t ORDER BY semester_number
This query actually would be correct if the student had a graded report card but it is always empty if he does not.
I assume that possibly the filtering on Student is added afterwards. So I tried to somehow to get it to filter student right off the bat:
has_many :report_cards, ->{ where(graded: true, student: self).order(:semester_number).presence || order(:semester_number) }
This does not work either because it appears that "self" in this scope is not the Student object like I'd assume, but a list of all the report_card ids. Here is the query this one runs:
SELECT report_cards.* FROM report_cards WHERE report_cards.graded = t AND report_cards.student_id IN (SELECT report_cards.id FROM report_cards) ORDER BY semester_number ASC
That isn't even close to correct. How can I get this to work?
I think what it really comes down to is someone being able to pass "self" (meaning the current Student object) as a parameter into the scope being applied in the "has_many". Maybe that isn't possible.
You can pass object to has_many scope as a parameter to lambda
has_many :report_cards, -> (student) { ... }
Try this:
class Student < ActiveRecord::Base
has_many :report_cards, ->{ where(graded: true).order(:semester_number).presence || unscoped.order(:semester_number) }
end
I am using this in my project where I have a model which is associated with users:
has_many :users, -> { only_deleted }
And in the Users model create a scope of only_deleted, returning your users which are deleted.
I need to grab all users that have an application. User is part of my core engine, which is used by many other engines. I'd like to keep User unaware of what is using it, which is why I don't want to add has_many :training_applications in my User model.
Here are the classes
module Account
class User < ActiveRecord::Base
end
end
module Training
class TrainingApplication < ActiveRecord::Base
belongs_to :user, class: Account::User
end
end
The following obviously won't work because User has no concept of TrainingApplication:
Account::User.joins(:training_application).distinct
Is there an elegant way to return a distinct collection of User objects that are associated with a TrainingApplication?
What I landed on as a quick solution is
Account::User.where(id: Training::TrainingApplication.all.pluck(:user_id))
but I'm thinking that there's a better solution.
In case there is no way you can add a has_many :training_applications association to the User, the following should be suitable solutions:
You could type up a joins string yourself:
t1 = Account::User.table_name
t2 = Training::TrainingApplication.table_name
Account::User.
joins("INNER JOINS #{t2} ON #{t2}.user_id = #{t1}.id").
group("#{t1}.id")
For the sake of variety, let me cover the subquery method as well:
Account::User.where("id IN (SELECT user_id FROM #{t2})")
I would go with the joins method but I believe both solutions will be faster than your current implementation.
I have a Gift model:
class Gift
include Mongoid::Document
include Mongoid::Timestamps
has_many :gift_units, :inverse_of => :gift
end
And I have a GiftUnit model:
class GiftUnit
include Mongoid::Document
include Mongoid::Timestamps
belongs_to :gift, :inverse_of => :gift_units
end
Some of my gifts have gift_units, but others have not. How do I query for all the gifts where gift.gift_units.size > 0?
Fyi: Gift.where(:gift_units.exists => true) does not return anything.
That has_many is an assertion about the structure of GiftUnit, not the structure of Gift. When you say something like this:
class A
has_many :bs
end
you are saying that instance of B have an a_id field whose values are ids for A instances, i.e. for any b which is an instance of B, you can say A.find(b.a_id) and get an instance of A back.
MongoDB doesn't support JOINs so anything in a Gift.where has to be a Gift field. But your Gifts have no gift_units field so Gift.where(:gift_units.exists => true) will never give you anything.
You could probably use aggregation through GiftUnit to find what you're looking for but a counter cache on your belongs_to relation should work better. If you had this:
belongs_to :gift, :inverse_of => :gift_units, :counter_cache => true
then you would get a gift_units_count field in your Gifts and you could:
Gift.where(:gift_units_count.gt => 0)
to find what you're looking for. You might have to add the gift_units_count field to Gift yourself, I'm finding conflicting information about this but I'm told (by a reliable source) in the comments that Mongoid4 creates the field itself.
If you're adding the counter cache to existing documents then you'll have to use update_counters to initialize them before you can query on them.
I tried to find a solution for this problem several times already and always gave up. I just got an idea how this can be easily mimicked. It might not be a very scalable way, but it works for limited object counts. The key to this is a sentence from this documentation where it says:
Class methods on models that return criteria objects are also treated like scopes, and can be chained as well.
So, get this done, you can define a class function like so:
def self.with_units
ids = Gift.all.select{|g| g.gift_units.count > 0}.map(&:id)
Gift.where(:id.in => ids)
end
The advantage is, that you can do all kinds of queries on the associated (GiftUnits) model and return those Gift instances, where those queries are satisfied (which was the case for me) and most importantly you can chain further queries like so:
Gift.with_units.where(:some_field => some_value)
I'm working on implementing a tagging system and I'm having problem querying for tagged objects with a scope.
For example, I would like to find all the user's items with a certain tag. With a class method I can currently find all the objects:
def self.tagged_with(name)
Tag.find_by_name(name).items
end
However, this has a problem. If I were to do something like: current_user.items.tagged_with(name) won't this existing method return ALL the items and not just items owned by the current_user? I suppose this is a simply querying issue but I can't figure out how to change a class method into something called on a collection. I have tried going the opposite way, to get a the collection through the tags, something like... tag.items.where(:user_id => current_user.id) but in this case, it's a many-to-many relationship and I haven't been able to get on thumb on this either.
What's the proper way to restrict a query like this?
Create an association on your User class that points to your Tag class.
class User < ActiveRecord::Base
has_many :tags
end
Then you can do:
current_user.tags.where(...)
If you don't already have an association in place, you'll need to create a migration to have the tags table reference your users table with a foreign key.
I think this will help you:
class Account < ActiveRecord::Base
has_many :people do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by_first_name_and_last_name(first_name, last_name)
end
end
end
person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
person.first_name # => "David"
person.last_name # => "Heinemeier Hansson"
So, basically you can define your method tagged_with directly into the association!
This example is took from the documentations ActiveRecord::Associations
Here is the classes I have:
Model Organization
has_many Students
Model Student
has_many Classes
belongs_to Organization
Model Class
a field named : price
belongs_to Student
scope :top_expensive_classes, joins(:students).order('price DESC')
Now I want to list the top 10 expensive classes
At least the first problem I have is that in the params I have the organization_id to filter based on that But I write my controller like this which does NOT work because it thinks it should find organization_id in the Class model but it is in the Student model.
#results = Class.top_expensive_classes.where(organization_id: params[:id]).limit(RESULT_SET_COUNT)
So I was wondering if there is a way to fix this? I think I should introduce a new join somewhere? but couldn't figure it out.
There's a typo in your scope: joins:(:programs) should be joins(:programs)
To fetch based on the organization id in Student you may be able to do this:
#results = Class.top_expensive_classes
.joins(student: :organization)
.where(organization: {id: params[:id]})