ordering by child's value in one to many relationship - ruby-on-rails

posts table
int id
comments table
int id
int post_id
datetime created_at
i need to order posts by post's comments created_at. I tried something like that but i can't select distinct post ids.Any help will be appreciated.
Post.all(:joins => :comments, :order => 'comments.created_at')
I want to see the post which was commented lately at the top.

The condition you are passing is invalid.
The easiest way you can accomplish what you are trying to do, is by adding a column to your posts table - say last_commented_at.
In your Comment model you add a callback
class Comment < ActiveRecord::Base
belongs_to :post
after_save :update_posts_last_commented_attribute
def update_posts_last_commented_attribute
post.update_attribute(:last_commented_at, updated_at)
end
end
Then you can load your Posts by calling
Post.order("last_commented_at DESC" )
to show the posts first, that have recently been commented.

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.

Get last comment of all posts in one category

Comment belongs to Post.
Post belongs to Category.
How would I get a collection of every lastly updated comment for each post, all belonging to one single category?
I've tried this but it just gives me one post:
category.posts.joins(:comments).order('updated_at DESC').first
Update
What I want is to fetch one commment per post, the last updated comment for each post.
Rails doesn't do this particularly well, especially with Postgres which forbids the obvious solution (as given by #Jon and #Deefour).
Here's the solution I've used, translated to your example domain:
class Comment < ActiveRecord::Base
scope :most_recent, -> { joins(
"INNER JOIN (
SELECT DISTINCT ON (post_id) post_id,id FROM comments ORDER BY post_id,updated_at DESC,id
) most_recent ON (most_recent.id=comments.id)"
)}
...
(DISTINCT ON is a Postgres extension to the SQL standard so it won't work on other databases.)
Brief explanation: the DISTINCT ON gets rid of all the rows except the first one for each post_id. It decides which row the first one is by using the ORDER BY, which has to start with post_id and then orders by updated at DESC to get the most recent, and then id as a tie-breaker (usually not necessary).
Then you would use it like this:
Comment.most_recent.joins(:post).where("posts.category_id" => category.id)
The query it generates is something like:
SELECT *
FROM comments
INNER JOIN posts ON (posts.id=comments.post_id)
INNER JOIN (
SELECT DISTINCT ON (post_id) post_id,id FROM comments ORDER BY post_id,updated_at DESC,id
) most_recent ON (most_recent.id=comments.id)
WHERE
posts.category_id=#{category.id}
Single query, pretty efficient. I'd be ecstatic if someone could give me a less complex solution though!
If you want a collection of every last updated Comment, you need to base your query on Comment, not Category.
Comment.joins(:post).
where("posts.category_id = ?", category.id).
group("posts.id").
order("comments.updated_at desc")
What you're basically asking for is a has_many :through association.
Try setting up your Category model something like this:
class Category < ActiveRecord::Base
has_many :posts
has_many :comments, through: :posts
end
Then you can simply do this to get the last 10 updated comments:
category.comments.order('updated_at DESC').limit(10)
You could make this more readable with a named scope on your Comment model:
class Comment < ActiveRecord::Base
scope :recently_updated, -> { order('updated_at DESC').limit(10) }
end
Giving you this query to use to get the same 10 comments:
category.comments.recently_updated
EDIT
So, a similar solution for what you actually wanted to ask for, however it requires you to approach your associations from the Comment end of things.
First of all, set up an association on Comment so that it has knowledge of its Category:
class Comment < ActiveRecord::Base
belongs_to :post
has_one :category, through: :post
end
Now you can query your comments like so:
Comment.order('updated_at desc').joins(:post).where('posts.category' => category).group(:post_id)
Somewhat long-winded, but it works.
.first is grabbing only one for you. The first one to be exact. So drop the .first. So instead do:
category.posts.joins(:comments).order('updated_at DESC')

How to retrive same name attributes of joined table in active record rails 4

I have following association
class Article < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :article
end
comments and articles table both contains column name text. I want to join both the table and would like to to get all the attributes from articles table including text attribute and text attribute from comments table.
In sql i would do something like this
select comments.text as comment_text, articles.* from articles,comments
I want to achieve same o/p using active record query.
I tried following thing but it didn't work
Article.joins(:comments).select("articles.*", "comments.text as comment_text")
Above query only return text attribute of comment and not the text attribute of articles table. What am I doing wrong?
I think what you looking for is how to do a eager loading.
Article.includes(:comments)
Try this:
Article.includes(:comments).select("articles.*, comments.text as comment_text")

Get a user's posts ordered by most recent comment on post

If I have the following relationship:
User
has_many :posts
Post
has_many :comments
belongs_to :user
Comment
belongs_to :post
How do I get a user's posts ordered by the most recent comment on each post? I'm using Rails with Postgres. Ideally, I'd like to use joins to do this.
You might find it easier and more efficient in the long run to have your Post object's updated_at updated when a comment is created. If you don't like the idea of that, you could add a comment_added_at attribute and have that updated instead.
This is easy to achieve with the :touch option on the association in the child:
Comment
belongs_to :post, :touch => true # This will update the Post.updated_at attribute
belongs_to :post, :touch => :comment_added_at # This updates the specified attribute instead
Then you can simply order by :updated_at or :comment_added_at and save a recursive DB query.
If you REALLY want to do it with joins, try this:
User.find(1).posts.joins(:comments).order('comments.created_at desc').group(:id)
I have this in my app to have it ordered by the most recent post listed on my home page. Maybe it will help you set the order of the most recent comment on the posts.
#posts = Post.select([:id, :title]).order("created_at desc").limit(6)
You may want to do something like the below but not sure if it will quite work for you.
#posts = post.comments.select([:id, :title]).order("created_at desc").limit(6)
You can use below code as your reference:
User.includes(:comments).where("posts.user_id =? ", post_owner_user_id).order("comments.created_at desc")
I hope it will give you the desired result.

Display first record in rails

Assuming I have a comments model and a posts model,
What code can I use in a view to display the first comment of the post being linked?
assuming:
post = Post.find(params[:id])
and post model contains:
has_many :comments
then you can:
comments = post.comments
first_comment = comments.first
Post.find(123).comments.first
Where 123 is your post ID.
#post.comments.first will normally work. (#post should be set up in your controller method)
However it is good to realise that 'first' means first in the association, which is normally ordered by id. Since ids autoincrement, this happens to be the same as 'first added' or 'earliest comment'. But it doesn't have to be.
If your association for comments specified a different ordering, then first will use this, e.g. if your association looked like this:
has_many :comments, :order=>'rating desc'
Then (assuming the 'rating' field is set up somehow to be some value that represents the average rating) post.comments.first would give you the highest rated comment, not the first to be added.
In that case, assuming your comments model has timestamps, then you'd need to do something like
#post.comments.find(:first, :order=>'created_at asc')

Resources