4

I have a Post model. Having a scope which finds the enabled posts.A post can belong to a category. I also have a Category model, with categories ranked. Category has many posts. I want to display first distinct 20 posts which belong to distinct categories(which are the top 20 categories) and then display the rest of the post in descending order of their published_time.

This is what I have

Post model:

class Post < ActiveRecord::Base
belongs_to :categories
scope :by_category, ->(category) { joins(categories).where(categories: { id: category.id }) }
scope :enabled, where(disabled: false)
scope :recent, order('published_at DESC')

Category model

class Category < ActiveRecord::Base
has_many :feeds
scope :most_popular, order('rank ASC')

Home Controller

def index
  Category.most_popular.limit(20).each do |cat|
    @posts= Post.enabled.recent.by_category(cat).page(1).per(30)
  end

in view file I am rendering the attributes of the post that i receive using @posts. But as obvious, it returns only the post of the last category found in the loop. Basically it doesn't append. I tried using << to append.. as in -

@posts <<  Post.enabled.recent.by_category(cat).page(1).per(30)

But it gives no method << for nil:nil class

I tried making @posts as an array, but then it does not take page and per of kaminari into play.

I tried making @posts as ActiveRecord::Relation object using new, it gave argument error.

I tried making @posts as object of Post but then it says undefined method << for Post, since ofcourse, << is not a method for my model class. I also followed some SO posts but it didn't seem to fit in my step.

Basically, my insight into achieving this was appending records into the model object and then displaying the object. I even doubt, if my approach is good enough. There could be more efficient way of doing this, which I may be missing out in RoR.

Community
  • 1
  • 1
inquisitive
  • 3,738
  • 6
  • 30
  • 56

2 Answers2

3

You could do something like this:

def index
  posts_ids = []
  Category.most_popular.limit(20).each do |cat|
    post_ids << Post.enabled.recent.by_category(cat).map(&:id)
  end
  @posts = Post.find( post_ids ).page(1).per(30)
end
NM Pennypacker
  • 6,704
  • 11
  • 36
  • 38
  • Thanks for the reply. This actually gave me a way of how can I implement. But this code gave me an error, `undefined method page for Array`, on searching I came to know that this is because find returns an array of object and to apply pagination on array different method is used for kaminari http://stackoverflow.com/questions/6934623/undefined-method-page-for-array0xafd0660. I used Kaminari.paginate_array(post_ids).page(params[:page]).per(params[:per_page]). Putting it as comment for others to know. I merged your idea and the next answer to achieve the desired results. Thanks. – inquisitive Jan 05 '15 at 06:28
  • @inquisitive The correct solution is to use `where(id: post_ids)` instead of `find`. You might also want to fix `page(1)` to `page(params[:page])` – Tashows Sep 24 '18 at 18:12
  • ```post_ids.push *(Post.enabled.recent.by_category(cat).map(&:id)) ``` – Albert.Qing Jul 22 '20 at 15:20
1

Let me define your problem to ensure that I understand it correctly. In your view, you want the latest post of each category first. And then you want all posts ordered by their recentness.

I would create two instance variables in the controller to be later used in the view.

def index
  enabled_posts = Post.enabled.recent

  @category_posts = enabled_posts.joins(:categories).group("categories.id")

  exclude_post_ids = @category_posts.pluck("posts.id")

  @posts = enabled_posts.where("id NOT IN (?)", exclude_post_ids)
end

The above should be convenient if you're using two different sections to display @category_posts and the remaining posts. But if you're using a single section and you want all the posts ordered in a single variable, then simply change your controller code to the following:

def index
  enabled_posts = Post.enabled.recent

  category_posts = enabled_posts.joins(:categories).group("categories.id")

  exclude_post_ids = @category_posts.pluck("posts.id")

  remaining_posts = enabled_posts.where("id NOT IN (?)", exclude_post_ids)

  @posts = category_posts + remaining_posts
end
SHS
  • 7,651
  • 3
  • 18
  • 28
  • Yes, you understood my idea correctly, for getting the category posts, I could not understand the join you have used.I just wanted one post from each category and then the rest, this code gave me an idea, how I can get posts fetched from two different queries. I merged your answer and the first answer to get the desired results. Thanks – inquisitive Jan 05 '15 at 06:31
  • Since, both the answers have helped me in some way or the other, and we can not mark both of them as answer, I am marking the previous answer as solving the problem. FCFS. Upvoting both. Thanks.. – inquisitive Jan 05 '15 at 06:44
  • While I was running the query remaining_posts = enabled_posts.where(" id NOT IN (?)", exclude_post_ids), it takes a long time, I think i need to find in batches or so.. – inquisitive Jan 05 '15 at 09:24