How to paginate an eager loaded association model - ruby-on-rails

I have the following code :
Post.where("user_id IN [1, 2, 3, 4, 5, 6]").includes(:authors, :comments).paginate(page: params[:page], per_page: 30)
what i want here is to eager load just 8 comments per post using will_paginate, is this possible ? and how ?

Not a tested answer
I don't see that possible from there, but:
Comment.joins(:posts).includes(:posts).where(posts: { user_id: [1,2,3,4,5,6] })
I am not sure if joins and includes can be called together.
This would give you a relation for comments you can continue working on, and you will have eager loaded posts:
#comments = Comment.joins(:post).includes(:post).where(posts: { user_id: [1,2,3,4,5,6] })
#comments.paginate(...)
If you want to get the posts from #comments I would do this:
class Comment < ActiveRecord::Base
def self.post_ids
all.map(&:post_id)
end
def self.posts
Post.where(id: post_ids)
end
end
Then use and paginate it:
#posts = #comments.posts.paginate(...)

Related

Secondary sorting of a column using will_paginate

Rails 6
will_paginate
In my model, I have the following:
def index
#books = Book.all.paginate page: params[:page], per_page: params[:per_page]
end
which is going to give me an array of objects, each with the following attributes:
name
author_id
So, in my view, I can do the following:
th= sortable "name", "Name"
th= sortable "author_id", "Author ID"
But what I really want to do, is show the Author Name, which I can get from the book object, as follows:
book.author.name
As book belongs to author
How do Have a column in the table, with the author's name, and make that column sortable?
Did you tried something like this?
controllers/books_controller.rb
def index
books_with_author = Book.all.map { |book|
book.attributes.merge(author_name: book.author.name)
}
#books = books_with_author.paginate page: params[:page], per_page: params[:per_page]
end
Also you can define to your model for future use
models/book.rb
def author_name
self.author.name
end

Next element in array of active record objects

Ruby on Rails Next/Previous Post
I have an index page - posts#index which has links to the show pages of each post. Each post references a particular topic and may have been created at different times, so the id's of the posts aren't necessarily sequential for all the posts in that topic. I want to be able to go to the next & previous posts for a particular topic.
In my PostsController I've got instance variables for all the posts for a topic and the particular topic being shown
def index
#topic = Topic.find(params[:topic_id])
#posts = #topic.posts.all
end
def show
#topic = Topic.find(params[:topic_id])
#posts = #topic.posts.all
#post = #topic.posts.find(params[:id])
end
#posts is an array of active record objects
#posts => #<ActiveRecord::AssociationRelation [
#<Post id: 73, topic_id: 3>,
#<Post id: 74, topic_id: 3>,
#<Post id: 76, topic_id: 3>,
#<Post id: 77, topic_id: 3>,
#<Post id: 91, topic_id: 3>]
If this was an array of ruby objects I could use #posts.index(#post) to find the id of a particular element of the array and then do either index + 1 or index - 1 to traverse the array of posts, but .index is a different method in active record. If I'm on the show page for any given post in a particular topic, Post id: 76 for example, how can I go to link_to Post id: 74 (next) and link_to Post id: 77 (previous) in a way that work for any post in a particular topic?
You can simply do the following:
def show
#topic = Topic.find(params[:topic_id])
posts = #topic.posts.order('posts.id ASC')
#post = posts.find(params[:id])
#previous_post = posts.where('posts.id < ?', #post.id).first
#next_post = posts.where('posts.id > ?', #post.id).first
end
Try something like this:
Post.where("id > ?", 74).order(:id).first
Or, if you have some topics (not sure about your model), but it will look similar to this:
some_topic.posts.where("id > ?", 74).order(:id).first
Here the 74 is an ID of the current post.
There are a few gems to help. I have created one which is quit flexible because it works dynamically with any collection and its order (which is not the case when it's hardcoded like : Book.where("id > ?", 74).order(:id))
The gem is called nexter and it's basic usage is like this :
#books = Book.includes(:author).bestsellers.
order("genre", "authors.name", "published_at desc")
nexter = Nexter.wrap( #books, #books.find(params[:id]) )
nexter.previous
nexter.next
See the README for more info.

How can I sort records ordered by datetime?

#comments has 10 records sorted by id ASC now.
I wanted to reverse order simply, so I coded #comments = #comments.reverse
But I get this error message
ActionView::Template::Error (undefined method `total_count'
<%= page_entries_info(#comments, :entry_name => 'comment').html_safe %>
If I take off reverse and leave it as #comments = #comments, there won't be any problem.
Why? and How can I sort it ordered by created_at DESC?
#comments = Comment.where(:user_id => user_ids, :commentable_type => commentable)
if params[:page].blank?
params[:page] = ((#comments.count - 1)/10) + 1
#comments = #comments.page(params[:page]).per(10)
else
#comments = #comments.page(params[:page]).per(10)
end
#comments = #comments.reverse
You're receiving
ActionView::Template::Error (undefined method `total_count'
because #comments.reverse returns a plain array. You need a Relation object that has the pagination functionality. The best way to accomplish your sorting needs is to create a scope in your comments model:
class Comment < ActiveRecord::Base
scope :reversed, -> { order 'created_at DESC' }
end
Then instantiate your comments by calling the scope:
#comments = Comment.reversed
You can call the page method on that #comments collection and the rest should work. You can chain your where like you had it before, so it would look like:
Comment.reversed.where(:user_id => user_ids, :commentable_type => commentable)
#comments = Comment.where(:user_id => user_ids, :commetable_type => commentable).order("created_at DESC").page(params[:page]).per(10)
ALso, your page calculation is bad can can result in float/fractional numbers. If params[:page] is blank, just default to page 1 for the first page.

Ruby .each block doesn't add up all objects

For some reason it seems like .each block iterate only once, when I write lets say 3 or 4 comments, #total_comments displays only 1, instead of 3 or 4.
The relationships are:
user has many posts
post has many comments
Can anybody help please?
controller
#total_comments = 0
#posts = current_user.posts
#posts.each do |post|
#comments = post.comments
#new_comments = #comments.count -#comments.where("created_at < ?",1.day.ago).count
#total_comments += #new_comments
end
view
<%= #total_comments %>
To get the count of all comments you want something like this
#total_comments = Comment.count(
:conditions => ["post.user_id = ? and comment.created_at >= ?",
current_user.id, 1.day.ago],
:include => :post
)
Although without knowing your data model it's difficult to say exactly.
As discussed with #m_x, a neater and more rails-3/ruby 1.9 esque version of above
#total_comments = Comment
.joins( post: :user )
.where( users: {id: current_user.id} )
.where( "created_at < ?", 1.day.ago )
.count
use this instead :
#total_comments = Comment
.joins( post: :user )
.where( users: {id: current_user.id} )
.where( "comments.created_at < ?", 1.day.ago )
.count
EDIT
i definitely need some sleep. as #Ryan pointed out this is much simpler :
#total_comments = current_user.comments.where( "created_at < ?", 1.day.ago ).count
... but the OP has to add this to his User model :
has_many :comments, through: :posts
It looks like you forget to save you comments - some comments are not saved to database that's why you're loosing some of comments.
Also you could use autosave option to automatically save your variables every time when you're changing them.

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