Rails querying database [duplicate] - ruby-on-rails

This question already has answers here:
Rails where condition using NOT NIL
(5 answers)
Closed 8 years ago.
I am trying to create an instance Object of all Users that have one or more Video in my controller, so there is one User that has many videos. So I have a User.rb model that has_many :videos, and my Video.rb model that belongs_to :user. I want to select all the users that have atleast one video.
I am really new at querying SQL so I'm not sure how to join tables or if I even need to.
ex:
#users = User.where(user.video not nil?)

I want to select all the users that have atleast one video.
All you need to do is use joins method
#users = User.joins(:videos)
It will retrieve all users which have associated video(s) i.e., users without any associated videos would not be part of the results and query formed would be:
SELECT users.* FROM users
INNER JOIN videos ON videos.user_id = users.id
As #engineersmnky pointed out in this comment, if you are planning to iterate over the users retrieved in #users and then again go through the videos of each user then it would be a better idea to go for eager loading rather than joins to avoid the n+1 queries problem. In that case your code should be:
#users = User.includes(:videos).where("videos.id IS NOT NULL")
and for Rails 4 and above
#users = User.includes(:videos).where.not(videos: {id: nil})

Something like this
User.includes(:videos).where("videos.user_id is not null")

Related

Rails get associations on condition

I have a problem - I do not know how to get associated records only if condition is met.
I have Posts model and Comments, Post has_many :comments, Comment belongs_to :post.
Now, I want to retrieve All of the Posts, but only with specific comments (lets say with user_id = 1).
How can I achieve that?
Query like
Post.includes(:comments).where("comments.user_id = ?", "1") will retrieve only some Posts, I want all of them, but only with comments with user_id equal to 1.
I guess I should use LEFT JOIN of some sort, maybe something like
posts.joins("LEFT JOIN comments ON posts.id comments.post_id")
but I am not sure how to put condition restricting right table results.
It can be achieved as below
Post.includes(:comments).where("comments.user_id = ?", "1").references(:comments)
For more information go here

Find and destroy dependent records

Over a period of time my Rails app has had various rewrites, and in some cases incorrect model associations.
Currently my User model has_many :posts and its destroy method correctly removes all dependent Posts, but at times when things were improperly written this did not happen. I'm now left with a handful of Post records that cause errors all over because their User does not exist.
What would be the most efficient way to manually filter through all Post records, check if its User actually exists, and if not destroy that Post?
I'm imagining something like:
Post.all.select{ |post| post.user.nil? }.destroy
But that seems incredibly inefficient for thousands of records. I'd love to know the best way to do this. Thank you!
any reason why you cannot do it directly on the database?
delete from posts where user_id not in (select id from users);
Fastest way would probably be to do it directory in the db console, but if you've got other dependent relationships and activerecord callbacks that you need to get fired, you could do something like:
Post.where("id in (select p.id from posts p left outer join users u on p.user_id = u.id where u.id is null)").destroy_all
Delete the orphan posts using
If you want to destroy the dependent associations with Post and run the callbacks
Post.where("user_id NOT IN (?)", User.pluck(:id)).destroy_all
If you just want to delete the posts
Post.where("user_id NOT IN (?)", User.pluck(:id)).delete_all
Here is one good post about finding and deleting orphan records

Ruby on rails activerecord joins - select fields from multiple tables

models:
#StatusMessage model
class StatusMessage < ActiveRecord::Base
belongs_to :users
default_scope :order => "created_at DESC"
end
#User Model
class User < ActiveRecord::Base
has_many :status_messages
end
In controller I want to join these two tables and get fields from both table. for example I want email field from User and status field from StatusMessage. When I use :
#status = User.joins(:status_messages)
Or
#status = User.includes(:status_messages)
It gives me only the user table data.
How can I implement this requirement?
You need to use includes here. It preloads data so you won't have another SQL query when you do #user.status_messages.
And yes you can't really see the effect: you need to check your logs.
First of all, I don't think it is possible (and reasonable) what you want to do. The reason for that is that the relation between User and StatusMessage is 1:n, that means that each user could have 0 to n status messages. How should these multitudes of attributes be included in your user model?
I think that the method joints in class ActiceRecord has a different meaning, it is part of the query interface. See the question LEFT OUTER joins in Rails 3
There are similar questions on the net, here is what I have found that matches most:
Ruby on Rails: How to join two tables: Includes (translated for your example) in the user a primary_status_message, which is then materialized in the query for the user. But it is held in one attribute, and to access the attributes of the status_message, you have to do something like that: #user.primary_status_message.status
When you use #status = User.includes(:status_messages) then rails eagerley loads the data of all the tables.
My point is when you use this User.includes(:status_messages) it will loads the data of status_messages also but shows only users table data then if you want first user status_messages then you have to #user.first.status_messages

Rails - count query while joining three tables to one

I have a fairly straightforward query that I can't seem to get right....
My model:
User - has many Friendships (with other users)
User - has many submissions
User - has many comments
User - has many votes
I need a count that represents
All current_user's friends, whose submissions, comments or votes created_at dates are > current_user.last_refresh_date
Right now, I am building up an array by iterating over friendships and adding all submissions, comment and votes. I then re-iterate this built-up array while comparing the dates to determine if the count should be incremented. Not the most ideal solution.
Edit:
#TobiasCohen
Efficient solution. Thanks!
Followup:
I wish to add yet one more count to the present query. I need to count all new comments & votes on the current_user.submissions that are not part of the original count (ie. not a friend).
Psuedo-code :
current_user.submissions.join(:comments, :votes, :friends).where('last_activity >? AND friend_id != ?', current_user.last_refresh_date, current_user.id).count
I can't quite get the query correct (new to complex queries via active record).
I was going to make it a separate query and then add it to the original count. Can it be absorbed into one query instead of two?
I think you'd get the best results by adding a cache column on User, let's call it :last_activity_at, then update this with an after_create callback on Submission, Comment and Vote.
class Submission < ActiveRecord::Base
belongs_to :user
after_create :update_user_last_activity_at
private
def update_user_last_activity_at
user.update_attribute :last_activity_at, Time.now
end
end
You could then fetch users simply with:
current_user.friends.where('last_activity_at > ?', current_user.last_refresh_date)

ActiveRecord Order the list of a model by associated model two levels

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)

Resources