heartcombo/has_scope

apply_scopes on multiple resources

phlegx opened this issue · 5 comments

Hi!

I have an index view of accounts on AccountsController. My view shows two tables. One table shows the user list (subclass of Account) and a second one shows the student list (subclass of Account). Every table has an own search scope:

class AccountsController < ApplicationController

  has_scope :u_search,  :s_search, only: :index

  def index
    @users = apply_scopes(User).all
    @students = apply_scopes(Student).all
  end
...

On every subclass model I have implemented the scope:

class User < Account
  scope :u_search, -> search { ... }
end

class Student < Account
  scope :s_search, -> search { ... }
end

Problem

If parameter u_search is present, the scope is applied also on Student. But u_search scope don't exists on model Student. How can I remove or add scopes to apply_scopes related to the model? Any idea?

Idea

One possible solution can be a if: :scope_exists?.

class AccountsController < ApplicationController

  has_scope :u_search,  :s_search, only: :index, if: :scope_exists?

Solved

See following patch or pull request! The simplest way is that apply_scopes ignores non existing scopes in the target (accounts). E.g. scope u_search (user search) applied on students (scope u_search is not defined in students) is ignored. Same thing with s_search (satellite search) applied on users (scope s_search is not defined in users) is ignored.

I have write a small patch. This code overwrites the method call_scope_by_type(type, scope, target, value, options) of has_scope. This code checks first, if the target responses to the scope. If the scope method is not present in the target returns the target. In other words: this code don't catch an error if the scope is not found in the target.

See commit: phlegx@f643811
See repo: https://github.com/phlegx/has_scope

# config/initializers/has_scope.rb

module HasScope

  protected

  # Call the scope taking into account its type.
  def call_scope_by_type(type, scope, target, value, options) #:nodoc:
    block = options[:block]

    if type == :boolean && !options[:allow_blank]
      block ? block.call(self, target) : target.respond_to?(scope) ? target.send(scope) : target
    elsif value && options.key?(:using)
      value = value.values_at(*options[:using])
      block ? block.call(self, target, value) : target.respond_to?(scope) ? target.send(scope, *value) : target
    else
      block ? block.call(self, target, value) : target.respond_to?(scope) ? target.send(scope, value) : target
    end
  end
end

Sorry, I don't think has scope has been designed to apply to two resources on the same page as you are trying to use, and I'm not sure that just adding respond to checks is a good thing because we may no longer give feedback in case the developer adds a typo while coding a scope.

Maybe we need to give it more thought if we actually want to allow this multiple scopes to happen. Thanks.

OK. I think scopes on multiple resources can be very useful, in many cases.

+1 I have the same problem. Is it been fixed ?

I did an ugly hack like this:

has_scope :u_search do |controller, scope, value|
  scope.class.to_s == 'User::ActiveRecord_Relation' ? scope.u_search : scope
end

Not fully tested obviously, worked on my case.