I've got a model User with a related class (User has_many Posts, Post belongs_to User). I want to display Users in a list, with those who have posted most recently at the top. So basically I want to order the List of Users by the created_at date of their last post. How is the best way to query this in Rails 3?
Thanks!
I think I would just do this by pulling the Posts, ordering by created_at and doing a group by on the user_id.
Maybe try something like this.
class User < ActiveRecord::Base
has_many :posts
has_one :last_post, :order => 'created_at DESC', :class_name => "Post"
scope :sort_by_last_post_desc, :include => :last_post, :order => ('posts.created_at DESC')
end
NOTE: not tested
Related
I have the classic Post with a has_many relationship on comments. I want to fetch all of the approved posts as well as the related comments which also have been approved. I have an approved scope in place for each but I am not sure how to enable it on the comments.
This is what I have so far which returns all the comments for approved postings. What is the best way without having to specify this condition on the has_many which will limit my use of this association for other queries.
Post.approved.includes(:comments)
Your example is used in the documentation. You should use another association:
class Post < ActiveRecord::Base
has_many :approved_comments, :class_name => 'Comment', :conditions => ['approved = ?', true]
end
Post.includes(:approved_comments)
I have a rails model let say
User with a has many relationship with Post. Post contains an attribute named position that stores the position (acts_as_list gem). The position stores the position on the user Post list (the order is not due to creation/modification of the post).
Moreover, the Post has timestamps stored (t.timestamps in the migration)
So I have defined the following in User.rb:
has_many :post, :order => "position", :dependent => :destroy
However, when i do a User.first.posts The posts are not ordered by position.
I've cheked and the sql being queries contains the following:
ORDER BY post.created_at DESC, position
So the created_at is still being used.
How can I sort by position and not use the created_at attribute?
Thanks in advance!
SOLUTION
I had a default scope degined in Post that was creating the undesired order by.
Problem solved
You can use a default scope:
class Post < ActiveRecord::Base
scope :by_position, order("position ASC")
default_scope by_position
#...
end
Then #user.posts should return a list ordered by the position (ASC).
Also, the following should be working for you: (plural on post)
has_many :posts, :dependent => :destroy, :order => "posts.position ASC"
I have a Post model that has_many :comments. The question is: how can I build a query using ActiveRecord Query Interface that retrieves all posts and the last comment of each posts?
All I have right now is the following, but it doesn't filter the comments and I have no idea of what to do next:
Post.includes(:comments)
Well, the simplest thing comes to mind, which may not be the best is creating a scope.
class Post < ActiveRecord::Base
# has_many or maybe a has_one ? I don't have time to fire up console again to check, sorry!
has_many :last_comment, :class_name => 'Comment', :limit => 1, :order => 'comments.created_at DESC'
end
And then use
Post.includes(:last_comment)
Be sure you have indexes in your SQL.
I have this working to a degree, but I am looking for some input on how to query for the siblings in a one to many relationship to see if there is a more elegant way of accomplishing this.
Consider the following classes
class Post < ActiveRecord::Base
has_many :post_categories
has_many :categories, :through => :post_categories
end
class Category < ActiveRecord::Base
has_many :post_categories
has_many :posts, :through => :post_categories
end
A post by definition can have multiple categories, what I would need this for is to show a "related posts" area on the site. Like I mentioned previously I do have a working version which is to simply do the following:
Post.find(id, :include => {:categories => :posts})
Looking at the logs the application then has to do five queries to get the end data that I am looking for.
Any thoughts are appreciated!
The only problem I see you having with what you have already is that you probably don't want to return all posts which share a category with a post.
#post = Post.find params[:id]
#related_posts = Posts.find(:all, :joins => :post_categories,
:select => "posts.*, count(post_categories) post_category_count",
:conditions => {:post_categories => {:category => #post.categories}},
:group => "posts.id", :order => "post_category_count desc")
This will return the most relevant posts first, ie. those which have the most shared categories, and you can either add a limit or paginate in order to limit results returned.
If you need support for large object trees, you might want to look at awesome nested set thought it may be overkill for this problem.
I'm implementing a Blog with Post and votable Comments.
When loading a Post, I want to eagerly load all votes by the current user for the Post's Comments.
Something like this (which doesn't work):
#post.comments.all(:joins => :votes, :conditions => ['votes.user_id = ?', current_user.id])
Each Comment has a method called rated_by?
def rated_by?(actor)
votes.find_by_user_id(actor.id)
end
The problem is that ActiveRecord will run a query for each rated_by? call, even though my #post.comments finder joined all the relevant votes.
I had a look at the act_as_rateable plugin but it has the same problem, running a query for each record, not using joins.
Double Secret Edit: I was answering another question and came across something that should work for you. It's a bit of a crazy hack involving the Thread.current global hash. And probably not advised at all, but it works.
It involves creating a second has_many votes association on Comments
class Comment < ActiveRecord::Base
has_many :votes
belongs_to :post
has_many :current_user_votes, :class_name => "Vote",
:conditions => '`#{Vote.table_name}`.user_id = \
#{Thread.current[:current_user].id}'
end
It also requires you to set Thread.current[:current_user] = current_user in the controller where you're going to be calling these methods.
Then you should be able to do
#post.comments.find(:all, :include => :current_user_votes)
To get a list of comments, that have eager loaded only the :current_user_votes. All in one query. If you're getting multiple posts at once, you can do this.
Post.find(:all, :include => { :comments => :current_user_votes},
:conditions => ...)
Which will populate a list of posts, and eager load their comments which in turn will each have their current_user_votes eager loaded.
Original Answer (preserved for posterity)
I don't think it's possible to select all of one model eager load only the relevant associations in one query.
The best you're going to get is pretty much what you've done. Select all of one model and then for each them load only the relevant association with a named scope or finder.
This statement that doesn't work is only selecting comments the user has voted on.
#post.comments.all(:joins => :votes,
:conditions => ['votes.user_id = ?', current_user.id])
This statement selects the same set of comments, but also eager loads all votes for the comments it selects.
#post.comments.all(:include => :votes,
:conditions => ['votes.user_id = ?', current_user.id])
Really what you're going to have to do is call rated_by? on each comment. You might be able to minimize database impact by using a named scope. But I honestly don't think it's going to make an improvement.
If you're so worried about hitting the database so hard you could do something like this:
class Post < ActiveRecord::Base
has_many :comments
has_many :votes, :through => :comments
...
end
class Vote < ActiveRecord::Base
belongs_to :comments
...
named_scope :made_by_user, lambda {|user|
{:conditions => {:user_id => user}}
}
end
#users_votes = #post.votes.made_by_use(current_user)
#comments = #post.comments.find(:all, :include => :votes)
#comments.each{|comment|
user_voted_this_on_this_comment = comment.votes & #user_votes
...
}
Honestly I don't think it's worth the effort.
P.S. There's a Ruby convention regarding methods names that end in a question mark should always return a boolean value.
you need to use
:include => :votes
joins doesn't load your data, it just join the query in the db.