Reputation: 6445
I have a class that takes an array of scopes and applies them iteratively:
class AssignableLearningObjectives::Collector
def initialize(user:, only_self_assignable: false, scopes: [])
@user = user
@only_self_assignable = only_self_assignable
@scopes = scopes
end
.....
def available_objectives
objectives = assignable_objectives.or(manager_assigned_objectives).or(global_objectives).distinct
return objectives unless scopes.any?
scopes.each{ |scope| objectives = objectives.send(scope) }
objectives
end
My issue is with
scopes.each{ |scope| objectives = objectives.send(scope) }
objectives
Is there a better way of doing this? I was hoping for a rails method apply_scopes or something like that, however can't find anything like that.
My concern is the scopes are sent from the controller, and it is possible for the user to submit a request with a scope of 'destroy_all' or something equally fun.
Is there an easy way for me to let rails handle this? Or will I need to manually check each scope before I apply it to the collection?
Thanks in advance
EDIT:
I'm happy to validate each scope individually if I have to, but even that's causing issues. There is a method in rails which was dropped in 3.0.9 which I could use, Model.scopes :
https://apidock.com/rails/v3.0.9/ActiveRecord/NamedScope/ClassMethods/scopes
however that's deprecated. Is there any method I can call on a class to list its scopes? I can't believe the feature was there in rails 3 and removed completely...
Upvotes: 1
Views: 1132
Reputation: 434665
From the fine guide:
14 Scopes
[...]
To define a simple scope, we use the scope method inside the class, passing the query that we'd like to run when this scope is called:class Article < ApplicationRecord scope :published, -> { where(published: true) } end
This is exactly the same as defining a class method, and which you use is a matter of personal preference:
class Article < ApplicationRecord def self.published where(published: true) end end
So scope
is mostly just a fancy way of creating a class method that is supposed to have certain behavior (i.e. return a relation) and any class method method that returns a relation is a scope. scope
used to be something special but now they're just class methods and all the class methods are copied to relations to support chaining.
There is no way to know if method Model.m
is a "real" scope that will return a relation or some random class method without running it and checking what it returns or manually examining its source code. The scopes
method you seek is gone and will never come back.
You could try to blacklist every class method that you know is bad; this way lies bugs and madness.
The only sane option is to whitelist every class method that you know is good and is something that you want users to be able to call. Then you should filter the scopes
array up in the controller and inside AssignableLearningObjectives::Collector
. I'd check in both places because you could have different criteria for what is allowed depending on what information is available and what path you're taking through the code; slightly less DRY I suppose but efficiency and robustness aren't friends.
You could apply the scope whitelist in the AssignableLearningObjectives::Collector
constructor or in available_objectives
.
If you want something prettier than:
scopes.each{ |scope| objectives = objectives.send(scope) }
objectives
then you could use inject
:
def available_objectives
objectives = assignable_objectives....
scopes.inject(objectives) { |objectives, scope| objectives.send(scope) }
end
Upvotes: 1