Rails get associations on condition - ruby-on-rails

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

Related

Rails: Order activerecord object by attribute under has_many relation

class Post
has_many :commments
end
class Comment
belongs_to :post
end
I wish to display a list of posts ordered by date of post creation (submitted_at). I also want some post xyz to appear at the top if it has some new comment posted and yet to be reviewed by moderator. We will determine this by a boolean attribute/field at comments level (moderated = 1/0)
I tried
Posts.join(:comments)
.distinct
.order("submitted_at DESC, comments.moderated")
but this excludes posts that have no comments and results aren't sorted as expected. I am sure that we can do this at ruby level, but looking for a way to do this using AR.
For the join, use this:
Posts.join("LEFT JOIN comments ON comments.post_id = posts.id")
Which will include the ones with no comments.
Your sorting seems to suggest you want a count of moderated comments, in which case, try this:
.order("submitted_at DESC, COUNT(comments.moderated)")
Although, you may need to use group in some way too.

Rails query with group and count must include zeros

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}

Scope topic query when ordering by comments

I'm trying to set the order for Topic index by most recent Comment. This is working:
Topic.joins(:comments).order("comments.created_at desc")
But it lists the Topics more than one time.
Is there a way to limit the times each topic is displayed to one?
Ok, what's happening is this: When you join the comments, you get one database row for each comment, which (if there are multiple comments on a Topic), means multiple copies of each topic record.
To fix this, you'll need to use grouping, so that there's only one result per topic. The thing to group by is the id of the model you're returning (topics.id). Now, there's more to it - because there are still multiple comments per result, there are also multiple created_at values for each, and thus (in order to sort by it, as you do) you need to tell the database which one to use.
This is done with an aggregation function of some sort. I'm guessing you want the most recent comment to be the one that determines the order. If that's true, the code will be something like this:
Topic.joins(:comments).select('topics.*, max(comments.created_at) as last_comment').group('topics.id').order('last_comment desc')
The custom select includes the usual data that you need (everything about the Topic object - topics.*) and also an aggregate function (max(), which as the name suggests returns the largest of the possible values. ) used on the creation date of the comments. More recent dates are larger, so this will be the most recent comment's creation datestamp. That result is aliased as last_comment (using as), so you can refer to it in the .order call.
The most elegant way I found to solve the problem is to add touch to the polymorphic association in the comment model:
belongs_to :commentable, :polymorphic => true, touch: true
If you're not using a polymorphic association, you can use:
belongs_to :topic, touch: true
Then, all I had to do was change the default scope in the topic model to updated_at
default_scope order: 'topics.updated_at DESC'
Adding touch to comment means that every time a comment is added to topic, it updates the updated_at column in topics.
In the controller I am using:
#topics = Topic.order(sort_column + " " + sort_direction).paginate(:per_page => 20, :page => params[:page])
Topic.all or something else could work there as well.
Thanks to my super friend Alain for pointing this out to me.

Rails way to COUNT the nr of records in my case

I am using Rails v2.3.2.
I have a model called UsersCar:
class UsersCar < ActiveRecord::Base
belongs_to :car
belongs_to :user
end
This model mapped to a database table users_cars, which only contains two columns : user_id, car_id.
I would like to use Rails way to count the number of car_id where user_id=3. I konw in plain SQL query I can achieve this by:
SELECT COUNT(*) FROM users_cars WHERE user_id=3;
Now, I would like to get it by Rails way, I know I can do:
UsersCar.count()
but how can I put the ...where user_id=3 clause in Rails way?
According to the Ruby on Rails Guides, you can pass conditions to the count() method. For example:
UsersCar.count(:conditions => ["user_id = ?", 3])
will generates:
SELECT count(*) AS count_all FROM users_cars WHERE (user_id = 3)
If you have the User object, you could do
user.cars.size
or
user.cars.count
Another way would be to do:
UserCar.find(:user_id => 3).size
And the last way that I can think of is the one mentioned above, i.e. 'UserCar.count(conditions)'.
With the belogngs to association, you get several "magic" methods on the parent item to reference its children.
In your case:
users_car = UsersCar.find(1) #=>one record of users_car with id = 1.
users_car.users #=>a list of associated users.
users_car.users.count #=>the amount of associated users.
However, I think you are understanding the associations wrong, based on the fact that your UsersCar is named awkwardly.
It seems you want
User has_and_belongs_to_many :cars
Car has_and_belongs_to_manu :users
Please read abovementioned guide on associations if you want to know more about many-to-many associations in Rails.
I managed to find the way to count with condition:
UsersCar.count(:condition=>"user_id=3")

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)

Resources