I'm trying to refactor a scope within a Concern so that the scope gets most of its logic by calling different class methods, depending on an arg.
With "public" class methods, that works fine.
But I want those class methods to be private, so they are not mistaken for scopes and called by other developers. (because this will lead to unexpected results in the app.)
I tried a suggestion here and every other way I could find to declare these class methods private, but when I do that, they are inaccessible from within the scope.
module OrderingScopes
extend ActiveSupport::Concern
included do
# This scope can handle methods like :method and :reverse_method
scope :order_by, lambda { |method|
method = method.dup.to_s
reverse = method.sub!(/^reverse_/, "")
scope = :"order_by_#{method}";
# This check fails
return all unless private_methods(false).include?(scope)
scope = send(scope)
scope = scope.reverse_order if reverse
# Disambiguate grouped result order by adding an order by :id
scope = scope.order(arel_table[:id].desc)
scope
}
private_class_method :order_by_method_name
end
class_methods do
def order_by_method_name
end
end
end
Also tried:
class_methods do
private
def order_by_method_name
end
end
In both cases the scope fails:
> Model.order_by(:method_name)
included do
scope :order_by, lambda { |method|
method = method.dup.to_s
reverse = method.sub!(/^reverse_/, "")
scope = :"order_by_#{method}";
debugger
> private_methods(false).include?(:order_by_method_name)
false
> respond_to?(:order_by_method_name)
false
Is there a way to do this?
Rails automatically defines scope methods on a relation object and delegates to your model's class method, but only if method is public.
You can work around this by copying how rails defines the delegate method:
scope :order_by, lambda { |method|
scoping { model.send(:"order_by_#{method}") }
}
https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-scoping
Note, that within a scope
you're working with an ActiveRecord::Relation
object. You can use model
attribute reader to get your model class. Since private class methods are not copied into the relation class you have to run your check on the model itself:
# private_methods(false).include?(scope)
model.private_methods(false).include?(scope)