Scoping is a complementary consideration to authorization. Rather than checking “can this user see this thing?”, scoping takes a list of items filters it to the subset which is appropriate for the current viewer and context.
For similar features, see Pundit scopes and Cancan’s .accessible_by
.
scope:
optionFields accept a scope:
option to enable (or disable) scoping, for example:
field :products, [Types::Product], scope: true
# Or
field :all_products, [Types::Product], scope: false
For list and connection fields, scope: true
is the default. For all other fields, scope: false
is the default. You can override this by using the scope:
option.
.scope_items(items, ctx)
methodType classes may implement .scope_items(items, ctx)
. This method is called when a field has scope: true
. For example,
field :products, [Types::Product] # has `scope: true` by default
Will call:
class Types::Product < Types::BaseObject
def self.scope_items(items, context)
# filter items here
end
end
The method should return a new list with only the appropriate items for the current context
.
If you know that any items returned from .scope_items
should be visible to the current client, you can skip the normal .authorized?(obj, ctx)
checks by configuring reauthorize_scoped_objects(false)
in your type definition. For example:
class Types::Product < Types::BaseObject
# Check that singly-loaded objects are visible to the current viewer
def self.authorized?(object, context)
super && object.visible_to?(context[:viewer])
end
# Filter any list to only include objects that are visible to the current viewer
def self.scope_items(items, context)
items = super(items, context)
items.visible_for(context[:viewer])
end
# If an object of this type was returned from `.scope_items`,
# don't call `.authorized?` with it.
reauthorize_scoped_objects(false)
end