Module: RolePlays::Mixin::ClassMethods
- Defined in:
- lib/role_plays/mixin.rb
Overview
The DSL a policy class answers, and the lookups it resolves a declaration with
Instance Method Summary collapse
- #action_handler(role, action) ⇒ Object
-
#action_predicates ⇒ Object
Module holding the generated predicates of this class, included on first use.
-
#context(*names) ⇒ Object
Declares the keywords the policy is built with, giving each one a reader that answers
nilwhen the caller left it out, so a handler can treat it as optional:. -
#context_readers ⇒ Object
Module holding the generated context readers of this class, included on first use.
-
#declare_role(role) ⇒ Object
Files a Role struct under its name, merging it into an already declared role of the same name so a later declaration extends or overrides the earlier one.
-
#define_action_predicates(*actions) ⇒ Object
Defines a
can_<action>?predicate per declared action, so a policy answers the samecan_create?/can_destroy?messages a hand written one does:. - #permitted_attributes_handler(role, label) ⇒ Object
-
#policy_context_keys ⇒ Object
The names this policy declares with
context. -
#policy_roles ⇒ Object
The roles this policy declares — nothing is inherited, so a role shared with another policy is composed in as a Role struct rather than picked up from a parent class.
-
#reserved_context_name?(name) ⇒ Boolean
Whether a keyword of this name would be unreachable because the policy answers it already.
-
#role(*roles, &block) ⇒ Object
Declares one or more roles, either with a block of actions or with a prebuilt Role struct.
- #scope_handler(role, label) ⇒ Object
Instance Method Details
#action_handler(role, action) ⇒ Object
236 237 238 |
# File 'lib/role_plays/mixin.rb', line 236 def action_handler(role, action) policy_roles[role]&.action_for(action) || policy_roles[ANY_ROLE]&.action_for(action) end |
#action_predicates ⇒ Object
Module holding the generated predicates of this class, included on first use
268 269 270 |
# File 'lib/role_plays/mixin.rb', line 268 def action_predicates @action_predicates ||= Module.new.tap { |generated| include generated } end |
#context(*names) ⇒ Object
Declares the keywords the policy is built with, giving each one a reader that answers nil
when the caller left it out, so a handler can treat it as optional:
context :user, :order, :order_relation
action(:create) { %i[title] + (order ? [:user_id] : []) }
Declaring is optional — any keyword new is given is readable by name anyway. What the
declaration adds is the nil for a keyword that was not passed: an undeclared name always
raises, so a typo in a handler cannot quietly answer nil.
282 283 284 285 286 287 288 289 290 |
# File 'lib/role_plays/mixin.rb', line 282 def context(*names) names.flatten.each do |name| name = name.to_sym policy_context_keys << name unless policy_context_keys.include?(name) next if method_defined?(name) || private_method_defined?(name) context_readers.define_method(name) { context[name] } end end |
#context_readers ⇒ Object
Module holding the generated context readers of this class, included on first use
298 299 300 301 302 303 |
# File 'lib/role_plays/mixin.rb', line 298 def context_readers @context_readers ||= Module.new.tap do |generated| generated.extend(ContextReaders) include generated end end |
#declare_role(role) ⇒ Object
Files a Role struct under its name, merging it into an already declared role of the same name so a later declaration extends or overrides the earlier one
223 224 225 226 227 228 |
# File 'lib/role_plays/mixin.rb', line 223 def declare_role(role) declared = policy_roles[role.role] policy_roles[role.role] = declared ? declared.merge(role) : role define_action_predicates(role.actions.keys) end |
#define_action_predicates(*actions) ⇒ Object
Defines a can_<action>? predicate per declared action, so a policy answers the same
can_create? / can_destroy? messages a hand written one does:
action :create # => can_create? == can?(:create)
action :destroy # => can_destroy? == can?(:destroy)
Called for every role declaration, so the predicates cover the actions of all roles —
each one still resolves the handler for the current role at call time. The methods live on
a module included into the policy, so a predicate written by hand on the class always wins.
257 258 259 260 261 262 263 264 265 |
# File 'lib/role_plays/mixin.rb', line 257 def define_action_predicates(*actions) actions.flatten.each do |action| action = action.to_sym predicate = :"can_#{action}?" next if action_predicates.method_defined?(predicate) action_predicates.define_method(predicate) { can?(action) } end end |
#permitted_attributes_handler(role, label) ⇒ Object
240 241 242 |
# File 'lib/role_plays/mixin.rb', line 240 def permitted_attributes_handler(role, label) policy_roles[role]&.permitted_attributes_for(label) || policy_roles[ANY_ROLE]&.permitted_attributes_for(label) end |
#policy_context_keys ⇒ Object
The names this policy declares with context
293 294 295 |
# File 'lib/role_plays/mixin.rb', line 293 def policy_context_keys @policy_context_keys ||= [] end |
#policy_roles ⇒ Object
The roles this policy declares — nothing is inherited, so a role shared with another policy is composed in as a Role struct rather than picked up from a parent class
232 233 234 |
# File 'lib/role_plays/mixin.rb', line 232 def policy_roles @policy_roles ||= {} end |
#reserved_context_name?(name) ⇒ Boolean
Whether a keyword of this name would be unreachable because the policy answers it already.
A reader generated by context does not count — answering it is what it is there for.
307 308 309 310 311 |
# File 'lib/role_plays/mixin.rb', line 307 def reserved_context_name?(name) return false unless method_defined?(name) || private_method_defined?(name) !instance_method(name).owner.is_a?(ContextReaders) end |
#role(*roles, &block) ⇒ Object
Declares one or more roles, either with a block of actions or with a prebuilt Role struct. Names given as a list or as an array share the block, which is evaluated once and filed under each of them, so roles with identical permissions are declared together:
role :user do ... end
role %i[user access_code] do ... end
208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/role_plays/mixin.rb', line 208 def role(*roles, &block) roles = roles.flatten raise ArgumentError, "role requires at least one role name" if roles.empty? shared = nil roles.each do |role_or_name| next declare_role(role_or_name) if role_or_name.is_a?(Role) shared ||= RoleBuilder.build(role_or_name, &block) declare_role(shared.new(role: role_or_name.to_sym)) end end |
#scope_handler(role, label) ⇒ Object
244 245 246 |
# File 'lib/role_plays/mixin.rb', line 244 def scope_handler(role, label) policy_roles[role]&.scope_for(label) || policy_roles[ANY_ROLE]&.scope_for(label) end |