I have two models: User and Lesson.
User has_many :lessons
Lesson belongs_to :user
I have to retrieve User who are teachers (they have at least one lesson). Which is an efficient way?
For now I used a where condition that is traslated in a "IN" in Sql, so slow, or n query where n is the number of lessons. Thank you :)
If you want teachers, and teachers are users that have at least one lesson, then do this:
User.joins(:lessons)
That will perform an inner join thus excluding users that don't have any lessons.
But since a user can have multiple lessons, the returned table will potentially have multiple users. One solution for this could be to group by the users' ids.
User.joins(:lessons).group("users.id")
Or call distinct
User.joins(:lessons).distinct
Related
I have three models with the following associations:
User has_many :owns, has_many :owned_books, :through => :owns, source: :book
Book has_many :owns
Own belongs_to :user, :counter_cache => true, belongs_to :book
I also have a page that tracks the top users by owns with the following query:
User.all.order('owns_count desc').limit(25)
I would now like to add a new page which can track top users by owns as above, but with a condition:
Book.where(:publisher => "Publisher #1")
What would be the most efficient way to do this?
I'm interesting if there is something special for this case, but my shot would be the following.
First, I don't see how polymorphic association can be applied here. You have just one object (user) that book can belong to. As I understand, polymorphic is for connecting book to several dif. objects (e.g. to User, Library, Shelf, etc.) (edit - initial text of question mentioned polymorphic associations, now it doesn't)
Second, I don't believe there is a way to cache counters here, as long as "Publisher #1" is a varying input parameter, and not a set of few pre-defined and known publishers (few constants).
Third, I would assume that amount of books by single Publisher is relatively limited. So even if you have millions of books in your table, amount of books per publisher should be hundreds maximum.
Then you can first query for all Publisher's books ids, e.g.
book_ids = Book.where(:publisher => "Publisher #1").pluck(:id)
And then query in owns table for top users ids:
Owns.select("user_id, book_id, count(book_id) as total_owns").where(book_id: book_ids).group(:user_id).order(total_owns: :desc).limit(25)
Disclaimer - I didn't try the statement in rails console, as I don't have your objects defined. I'm basing on group call in ActiveRecord docs
Edit. In order to make things more efficient, you can try the following:
0) Just in case, ensure you have indexes on Owns table for both foreign keys.
1) Use pluck for the second query as well not to create Own objects, although should not be a big difference because of limit(25). Something like this:
users_ids = Owns.where(book_id: book_ids).group(:user_id).order("count(*) DESC").limit(25).pluck("user_id")
See this question for reference.
2) Load all result users in one subsequent query and not N queries for each user
top_users = User.where(:id => users_ids)
3) Try joining User table in the first order:
owns_res = Owns.includes(:user).select("user_id, book_id, count(book_id) as total_owns").where(book_id: book_ids).group(:user_id).order("total_owns DESC").limit(25)
And then use owns_res.first.user
I know this question has been asked before but I have read many of the posts and tried applying them to my situation and I just can't get it working. I'm a beginner who could use some help. Here are my models:
class Action < ActiveRecord::Base
belongs_to :Student
belongs_to :Meeting
belongs_to :ClassSection
end
class Meeting < ActiveRecord::Base
belongs_to :floorplan
has_many :actions
belongs_to :class_section
end
I am trying to get a count of actions for a single student across multiple meetings, including meetings where he/she had 0 actions. So, if there are two rows in the meetings table, ID=1 and ID=2, and student 83 has a single action with meeting_id=1 in the actions table, my query, which will include where(:student_id=>83) somewhere, should return something like
1=>1
2=>0
I hope this makes sense. I know the answer involves outer joins but I am bad at implementing them in pure SQL and worse at doing them through ActiveRecord. FYI I am using MYSQL. Thanks in advance for whatever help you can provide.
Meeting
.joins('LEFT JOIN actions ON meeting_id = meetings.id')
.where(student_id: 83)
.group('meetings.id')
.count('actions.id')
Explanation
.joins is the left/outer join that you intuited that you needed. It means "include at least one row for every meeting, even if there are no actions".
.group needs to be on the meeting id, since this will always be present and different meetings should be grouped separately.
.count needs to be on actions id. COUNT does not count null records, so meetings with no actions will be counted as 0.
It's a little bit weird that for a count of actions you nee to start your query with Meeting, but that is necessary when you want to include 0 counts. Otherwise there would be no way for SQL to know what meetings were missing!
For reference, the generated SQL is:
SELECT
COUNT(actions.id) AS count_actions_id,
meetings.id AS meetings_id
FROM "meetings" LEFT JOIN actions ON meeting_id = meetings.id
GROUP BY meetings.id
I think this should work fine just by grouping
Meeting.where(student_id: 83).group(:actions).count
This will return the hash you want
{1=>1, 2=>0}
I have three models I'm working with: User, Deal, and Investment.
User
User has many :deals
User has many :investments
Deal
Deal has many :investments
Deal belongs to :user
Investment
Investment belongs to :user
Investment belongs to :deal
(these are the only associations I have set up between these models)
Lets say I have a User record 'u', and Deal has an attribute called funding_type_id.
I want to find all investments made by user 'u' where the investment.deal.funding_type_id == 3.
Or to be more clear: Investments are made on a deal by a user. I was the set of investments made by user 'u' on deals who's funding type id is 3.
I posted this awhile ago, but didn't receive any successful responses. I've made several attempts on my own since then, but all have been met with failure, so I'm back to SO. Hopefully I explained my question clearly. Thanks!
EDIT: My bad, misread the question -- early morning after a late night :)
Try this instead:
investments = Investments.joins(:deal).where(user_id: u.id, deals: { funding_type_id: 3 })
That should generate the following SQL (subbing in 1 for u.id):
SELECT "investments".* FROM "investments"
INNER JOIN "deals" ON "deals"."id" = "investments"."deal_id"
WHERE "investments"."id" = 1 AND "deals"."funding_type_id" = 3
Which should give you the rows you want.
If you set up a has_many :through association (see the Association Basics guide) between User and Deal, you can directly access all the deals belonging to a user:
# user.rb
has_many :deals, through: :investments
You can then get all the deal for a particular user using:
user_deals = User.deals
You can optionally put a where condition on that to limit it the way you want. Where u is the user you want the deals for:
deals = u.deals.where(funding_type_id: 3)
My application has a user model with some simple access level checks. This access level determines the scope of access to the other models in the database. To be precise, I have District, School, Teacher, Room and Student models. An Admin can see all records, a District can see all child schools, teachers, rooms and students, a principal can see all child teachers, rooms and students, a teacher can see all child rooms and students.
This is done by associating a User object with one or more levels of models.
belongs_to :district
belongs_to :school
belongs_to :teacher
So a school principal would have a district id and a school id, but its teacher id would be null.
Access to the children is controlled via functions like this:
def teachers
if is_admin?
Teacher.all
elsif is_district_head?
district.teachers
elsif is_principal?
school.teachers
else
[ teacher ]
end
end
This func is treated in code as if it were a plain old has_many relationship, where we can do stuff like:
current_user.teachers.find param[:teacher_id]
current_user.teachers.each {|t| puts t.id }
Whether the current_user is an admin or a teacher or anything in between, the correct amount of teachers is returned.
Except, that's sadly not the case. Only the actual has_many relationships work fully, my fake ones fail when I try to use .find or some function that's specific to the ActiveRecord collections created by has_many.
So, on to my question. How can I return an ActiveRecord collection object without explicitly calling the has_many function?
If you think I'm a bleedin' retard and I'm missing something godawfully obvious, please don't hesitate to enlighten me! I had thought this system was going great, until I had to use a .find off an administrator level user account. It was essentially running
Teacher.all.find :conditions => 'xyz'
...and that sadly returns an Enumerator object instead of an ActiveRecord for the Teacher model.
Basically you have four roles (Admin, District Head, Principal, and Teacher(?)). Each user has one of these roles, and you have a set of rules to determine the access privileges for each role. Correct?
This problem has been solved. I would urge you to consider an authorization system such as CanCan or Declarative Authorization. Both contain straightforward ways to handle the issues you are struggling with here. There's a learning curve, or course, but the time will be well spent.
I have a rails app which has users who post recipes.People can post comments to the recipes with a rating.I want to sort the list of users(index action) by the average of the ratings received by the recipes posted by them.
I am already sorting the list of recpes by using this:
#courses = #recipes.joins(:comments).group('recipes.id').order('AVG(comments.rating)').reverse_order
How can I do something similar(or not) for users?
I have tried to do this using joins, group etc by reading the rails guides and apidock but still can't figure this out.
try this, not sure if it's completely right. Hope it's helpful to you.
User.joins(recipes: [:comments]).group('users.id').order('AVG(comments.rating) DESC')
update
my Models are User, Quiz, Question. The part confusing me a lot is your definition of User. Are the users who posted courses, and the users who comment in the same table or separate table? The relationship I mocked is this, which is I think is almost exactly same as your models:
User has_many quizzes, and user has_many questions.
Quiz has_many questions, and belongs_to user.
Questions belongs_to a user, and belongs_to a quizz.
in this case, if I do #users = User.joins(quizzes: [:questions]).group('users.id').order('AVG(questions.easy_count) DESC'), the result I get is, the result #users is a list of user who owns quizzes ordered by the easy_count(in your case rating) questions belongs to the corresponding quiz.
explanation of the code
User.joins(quizzes: [:questions]) simple gives you all rows of users who has quizzes(in your case should be courses) which has questions(in your case should be comment). consider the below example:
user1 has quiz1
quiz1 has question1 and question2
the result you get from User.joins(quizzes: [:questions]) will return you two duplicated rows of user1(because user1 is related to two questions through quiz1)
then group('users.id') is going to group the result from User.joins(quizzes: [:questions]) by user.id(you want to get user list)
so so far, what you get is very simple, it's a list of users who has quizzes and those quizzes need to have at least one question, I'm pretty sure it is the owner user of the quiz, not the owner user of the questions.
then at the end you sort this list by average easy_count of questions(in your case, rating of comments)