2

I am struggling to find a suitable solution for what should be a simple task. Essentially I have a category model which has many posts. The post belongs to the category.

I am displaying categories as part of a search form as well as a number of other places and do not want to display categories that have no associated posts. That seems kind of pointless.

I managed to solve this problem by adding the following method to my category model.

# Check if Category has associated results
def self.with_results
  includes(:posts).where.not(posts: { id: nil })
end

That works fine allowing me to filter categories that have no results. The slightly more confusing bit is when I try to use scopes.

My Post has several scopes such as frontend_visible which dictates whether it should be accessible from the frontend (non-admin).

scope :frontend_visible, -> { where(:state => ['approved', 'changes_pending_approval']) }

Equally I have other scopes to pull posts that are marked as private content only (members only).

The problem with my initial solution is that a category that contains posts which are not approved will not be shown, equally non-members will not be able to see posts that are marked as private although the category will still be shown.

The ideal solution would be something like:

Get all categories that have associated posts, if associated posts are not frontend visible, disregard category. If current_user can not access private content and all associated posts are marked private, disregard category.

I have the scopes but I am unsure how to use them from the associated model. Is this possible?

Ilya Lavrov
  • 2,810
  • 3
  • 20
  • 37
Torrm
  • 153
  • 1
  • 14
  • So you want all categories that have posts that are frontend visible? Category.includes(:posts).where(frontend_visible: true).where.not(posts: {id: nil}) ... If you're on Rails5 you don't need to load the associated table if you're just querying to see if it has an associated record: Category.where(frontend_visible: true).left_outer_joins(:posts).where(posts: { id: nil } ) – bkunzi01 Apr 04 '17 at 14:26
  • [here](http://guides.rubyonrails.org/active_record_querying.html#scopes) you can find a solution ... e.g. merging-scope or scope-of-scope ... – rfellons Apr 04 '17 at 14:40
  • @bkunzi01 Thank you, however neither of your examples work for me. I get the following error: PG::UndefinedColumn: ERROR: column categories.frontend_visible does not exist – Torrm Apr 04 '17 at 14:53
  • @rfellons Thank you. I did take a look through that guide earlier. I guess what I am struggling with is using the scope as a condition within an associated model. I can not find a clear example of that kind of usage – Torrm Apr 04 '17 at 14:55
  • Just spend some time learning active record. This isn't a difficult concept so if you read the docs you can easily make whatever query you need. – bkunzi01 Apr 04 '17 at 14:57
  • I will have another look through. I had tried something similar to your example prior to posting this question which of course did not work. I am quite comfortable with scopes and AR in general but I have never had to access a scope as a condition from an associated model which I suppose was the point of my question. – Torrm Apr 04 '17 at 15:35

1 Answers1

2

As i understand, you need to select categories with posts visible to an user. For that you need to do two things:

  1. Make mapping between user’s roles and post’s visible scopes
  2. Select categories for posts visible by user. In your case it’s easier to select that categories in two queries, without joins.

Try this code:

class Category < ApplicationRecord
  has_many :posts

  scope :with_posts_for_user, -> (user) do
    where(id: Post.categories_for_user(user))
  end
end

class Post < ApplicationRecord
  belongs_to :category

  scope :frontend_visible, -> { where(:state => ['approved', 'changes_pending_approval']) }
  scope :member_visible, -> { where(:state => ['approved', 'changes_pending_approval', 'private']) }

  scope :categories_for_user, -> (user) do
    (user.member? ? member_visible : frontend_visible).distinct.pluck(:category_id)
  end
end
Ilya Lavrov
  • 2,810
  • 3
  • 20
  • 37