Combined order with association in Rails 3 - ruby-on-rails

I have two models, post and comment. Posts has many comments and comments belongs to posts.
What I'm trying to do, is to have a list of posts that is ordered by creation date, unless it has comments. Then it needs to take the creation date of the latest comment.
Now I know that I can include associations like this:
Post.find(:all, :include => [:comments], :order => "comments.created_at DESC, posts.created_at DESC")
But this will order all posts with comments first and then the posts without comments. I don't want either ones ordered separately, but combined.
The solution also needs to be compatible with a paginate gem like will_paginate.
Edit
I have it now working with the following code:
posts_controller.rb
#posts = Post.order('updated_at DESC').page(params[:page]).per_page(10)
comments_controller.rb
#post = Post.find(params[:post_id])
#post.update_attributes!(:updated_at => Time.now)

I'd recommend:
Add a last_commented_at date field to your Post model. Whenever someone comments on the post update that field. It de-normalizes your db structure a bit, but your query will be faster and more strait-forward.

Postgres
Post.paginate(:include => [:comments],
:order => "COALESCE(comments.created_at, posts.created_at)",
:page => 1, :per_page => 10)
MySQL
Post.paginate(:include => [:comments],
:order => "IFNULL(comments.created_at, posts.created_at)",
:page => 1, :per_page => 10)

Related

Sunspot rails: include associated models when calling .results and how to correct syntax for it

A similar question has already been asked
Sunspot rails: include associated models when calling .results
search = Sunspot.search(ArticlePost, Post, User, Group) do
fulltext query
with(:api_search_shared, true)
paginate :page => page, :per_page => 100
end
what i want to do is include a few other tables with the query something like that:
include [{:user => [:user_job_title, :user_departments], :group => []}]
How would you go about go about putting the include in for when you are searching multiple models?
This is an example of a Single one:
Event.search(:include => [:user]) do...
this solution works for me :
search_in = [Post, Tweet]
search = Sunspot.search search_in do
search_in.each{|m|data_accessor_for(m).include = [:user]}
[...]
end
Hope this solution help.
Have a nice day :)

Rails child object query leads to N+1 problem

while writing some application for personal use. I find out the child query is not as great as it look.
For instance, I have 2 object
Category has_many Files
File belongs_to Category
File.category will access its parent category. But this lead to the famous N+1 problem.
For example, I want my homepage to list out 20 newest files and its corresponding category using something like this
for file in #files
%p
= file.name
= file.category.name
How should I solve this problem?
#files = File.find(:all, :limit => 20, :order => "created at desc", :include => :category)
In your find if you say :include => :category then this will eager load the categories for you and avoid a separate query to retrieve each category's name. So for your example of the 20 most recent files you could do:
#files = File.find :all, :limit => 20, :include => :category,
:order => 'created at desc'

RoR top five ratings

I have a model that has ratings in it for an post.
I want to display a list of the top 5 ratings for a given post, but I am completely lost on where to start. I figure the find method might have something useful, but I'm unsure. I've even considered looping through each record getting its size, adding it to a hash, sorting the hash, etc., but that seems too complicated.
Does anyone know how I might accomplish something like this?
Thank you
Edit: I found this to get all the posts that have the rating of agree:
Post.find(:all, :include => :post_ratings, :condtions => ['post_ratings.agree = ?', true])
The only problem is I can't figure out how to get the top five ratings from this query.
Might be worth giving a little more of an example of the code you're working with but I'll answer with a few assumptions.
If you have:
class Post
has_many :post_ratings
end
class PostRating
belongs_to :post
# Has a 'rating' attribute
end
You can find the top five post ratings with:
p = Post.find(:first) # For example
p.post_ratings.find(:all, :limit => 5, :order => 'rating desc')
To get the top five post ratings overall you can do:
PostRating.find(:all, :limit => 5, :order => 'rating desc')
UPDATE:
Following your edit it seems you have an 'agree' and a 'disagree' column. Not sure how that works in combination so I'll stick with the 'agree' column. What you'll need to do is count the ratings with agree flagged. Something like:
count_hsh PostRating.count(:group => 'post_id',
:order => 'count(*) desc',
:conditions => { :agree => true },
:limit => 5)
This will return you a hash mapping the post id to the count of agree ratings. You can then use that post_id to locate the posts themselves. The ratings are provided by the counts so the individual ratings are (I think) of no use though you could access them by calling post.post_ratings.
So, to get the top five posts:
#top_five_posts = []
count_hsh.each_pair do |post_id, ratings|
p = Post.find(post_id)
p[:rating_count] = ratings
#top_five_posts << p
end
This is probably more verbose than it could be but is hopefully illustrative. The p[:rating_count] is a virtual attribute which isn't in the database but will allow you to access the .rating_count method on the posts in your view if you wish to.
Assuming the same Post and PostRating from Shadwell's answer:
class Post
has_many :post_ratings
end
class PostRating
belongs_to :post
# Has a 'rating' attribute
end
To get the top five ratings for all Posts:
post_ratings.find(:all, :limit => 5, :order => 'rating desc')
To get the top five ratings for a specific Post you can:
p = Post.find(:first)
post_ratings.find_all_by_post_id(p.id, :limit => 5, :order => 'rating desc')
To find all posts sorted by average rating, you can use ActiveRecord::Calculations.
PostRating.average(:rating, :group => :post_id, :include => :post, :order => 'average desc')

Multiple conditions with will_paginate

I am using will_paginate for pagination but I can't seem to use more than one condition at a time. For example, if I want to have a sql query that ends in "Where office_id = 5", then it's pretty straight forward, I can do that. but what if I want to do "Where office_id = 5 AND primary_first = 'Mark'"? I can't do that. I have no idea how to enter multiple conditions. Can you help??
Below is an example of my code:
def self.search(search, page, office_id)
paginate :per_page => 5, :page => page,
:conditions => ['office_id', "%#{office_id}"], # + ' and primary_first like ?', "%#{params[:search]}%"],
#:conditions => ['primary_first', "%#{search}%"],
:order => 'created_at'
end
Thank you for your help!
If I understand correctly what you want, this should work:
def self.search(search, page, office_id)
paginate :per_page => :page => page,
:conditions => ["office_id LIKE ? and primary_first LIKE ?", "%#{office_id}", "%#{search}%"]
:order => :created_at
end
try :conditions => ["office_id = ? and primary_first = ?", office_id, search]
or using a dynamic finder:
paginate_by_office_id_and_primary_first(office_id, search, :per_page => 5, :page => page, :order => 'created_at')
These will only work for equivalency, however the db is defining it. If you need to use LIKE, see Jens Fahnenbruck's answer

Grab only the latest comment in Rails

In a typical User - Post - Comment model in Rails, every user can create a Post and also can create Comment, question is how to grab every user latest comment on specific post.
Example:
Post A have 3 user making comment
User 1 have comment 1, 2, 3, 4, 5, 6
User 2 have comment 1, 2, 3, 4
User 3 have comment 1, 2
So the view I want is just the latest comment for every user:
Post A have 3 user making comment
User 1 latest comment that is 6
User 2 latest comment that is 4
user 3 latest comment that is 2
How to do it ?
thanks
Something like this:
post.comments.for_user(current_user).last
add a named_scope in your model
class Comment
named_scope :for_user, lambda{ |user| {:conditions=>{:user_id => user.id}}
end
That should do the trick.
If you rather do it in rails,
messages_by_users = post.messages.group_by(&:user)
messages_by_users.each do |key, value|
messages_by_users[key] = value.last
end
I have had to get this kind of data and usually I end up doing two queries. In my case I have Blogs and their Posts and I wanted a list of the 3 most recent blog posts with the restriction that the blogs are unique, I dont want 2 posts from the same blog. I ended up doing something like this (MySQL):
q = <<-EOQ
SELECT id,pub_date FROM
(
SELECT id,blog_id,pub_date
FROM posts
ORDER BY pub_date DESC
LIMIT 40
)
t
GROUP BY blog_id
ORDER BY pub_date DESC
LIMIT #{num_posts}
EOQ
post_ids = Post.connection.select_values(q)
Post.find(:all, :include => [:blog], :conditions => ["id IN (?)", post_ids], :order => "posts.pub_date DESC")
So in your case you might have something like:
q = <<-EOQ
SELECT id FROM
(
SELECT id,post_id
FROM comments
ORDER BY id DESC
LIMIT 40
)
t
GROUP BY post_id
ORDER BY id DESC
LIMIT 10
EOQ
post_ids = Post.connection.select_values(q)
Post.find(:all, :include => [:blog], :conditions => ["id IN (?)", post_ids], :order => "posts.id DESC")
Assuming that your database is assigning sequential IDs to the comments, you can do this:
class Comment
named_scope :most_recent, lambda {
lastest_comments = Comment.maximum :id, :group => "user_id, post_id"
{ :conditions => [ "comment_id in ?", lastest_comments.map(&:last) ] }
}
end
This gives you a two-query method that you can use in a variety of ways. The named_scope above pulls back the most recent comments for all users on all posts. This might be a problem if your database is gigantic, but you can certainly add conditions to make it more specific.
As it stands, it is a flexible method that allows you to do the following:
Comment.most_recent.find_by_user #user #-> the most recent comments on all posts by a user
#user.comments.most_recent #-> same as above
Comment.most_recent.find_by_post #post #-> the most recent comments on a single post by all users
#post.comments.most_recent #-> same as above
Comment.most_recent.find_by_user_and_post #user, #post #-> the specific most recent comment by a certain user on a certain post
#post.comments.most_recent.find_by_user #user #-> you get the idea

Resources