Search code examples
ruby-on-railsactiverecordscopesassociated-object

Rails querying an associated models scope


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?


Solution

  • 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