role_plays 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +869 -0
- data/Rakefile +12 -0
- data/lib/role_plays/base_struct.rb +14 -0
- data/lib/role_plays/mixin.rb +392 -0
- data/lib/role_plays/types.rb +10 -0
- data/lib/role_plays/version.rb +5 -0
- data/lib/role_plays.rb +10 -0
- data/sig/role_plays.rbs +4 -0
- metadata +74 -0
data/Rakefile
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry-struct"
|
|
4
|
+
|
|
5
|
+
require_relative "types"
|
|
6
|
+
|
|
7
|
+
module RolePlays
|
|
8
|
+
# Base of the value objects a policy is built from. They are values on purpose: a declaration is
|
|
9
|
+
# read many times and changed by no one, so extending a role returns a new struct rather than
|
|
10
|
+
# mutating the one another policy may have been given.
|
|
11
|
+
class BaseStruct < Dry::Struct
|
|
12
|
+
transform_keys(&:to_sym)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_struct"
|
|
4
|
+
require_relative "types"
|
|
5
|
+
|
|
6
|
+
module RolePlays
|
|
7
|
+
# Declarative, role based authorization DSL.
|
|
8
|
+
#
|
|
9
|
+
# class OrderPolicy
|
|
10
|
+
# include RolePlays::Mixin
|
|
11
|
+
#
|
|
12
|
+
# context :user, :order, :order_relation
|
|
13
|
+
#
|
|
14
|
+
# role :user do
|
|
15
|
+
# action :create, -> { true }
|
|
16
|
+
# action :destroy do
|
|
17
|
+
# order.user_id == user.id
|
|
18
|
+
# end
|
|
19
|
+
# action :edit do
|
|
20
|
+
# order.user_id == user.id && order.completed?
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# permitted_attributes %i[title description]
|
|
24
|
+
# permitted_attributes :list, %i[page per_page]
|
|
25
|
+
# permitted_attributes(:create) do
|
|
26
|
+
# %i[title description] + (order ? [:user_id] : [])
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# scope :list, -> { order_relation.where(sent: true) }
|
|
30
|
+
# scope -> { order_relation.where(user_id: user.id) }
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# # Roles listed together share the block, each one getting the same declarations
|
|
34
|
+
# role %i[user access_code] do
|
|
35
|
+
# action :list, -> { true }
|
|
36
|
+
#
|
|
37
|
+
# scope -> { order_relation.where(user_id: user.id) }
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# role :admin do
|
|
41
|
+
# end
|
|
42
|
+
#
|
|
43
|
+
# role :any do
|
|
44
|
+
# action :list, -> { true }
|
|
45
|
+
# end
|
|
46
|
+
# end
|
|
47
|
+
#
|
|
48
|
+
# policy = OrderPolicy.new(role: :user, user: current_user, order: order,
|
|
49
|
+
# order_relation: Order.completed)
|
|
50
|
+
#
|
|
51
|
+
# policy.can?(:destroy) # => true / false
|
|
52
|
+
# policy.permitted_attributes(:list) # => %i[page per_page]
|
|
53
|
+
# policy.scope(:list) # => Order.completed.where(sent: true)
|
|
54
|
+
#
|
|
55
|
+
# Every declared action also gets a `can_<action>?` predicate — `can_create?`, `can_destroy?` — so
|
|
56
|
+
# a policy can replace a hand written one without touching its callers.
|
|
57
|
+
#
|
|
58
|
+
# `role:` is the only argument the policy asks for. Every other keyword is arbitrary: it is kept as
|
|
59
|
+
# context and answered as a reader, so a policy is given what it actually talks about — `user`,
|
|
60
|
+
# `order`, `order_relation` — rather than a fixed record/relation/options triple. `context`
|
|
61
|
+
# declares those names, which makes them optional: a declared name reads as `nil` when the caller
|
|
62
|
+
# leaves it out, while an undeclared one raises, so a typo in a handler is not read as `nil`.
|
|
63
|
+
#
|
|
64
|
+
# The role is a symbol such as :user or :provider_location, selected from the authenticated user by
|
|
65
|
+
# the caller — the policy is told which role it answers for instead of resolving it from a user
|
|
66
|
+
# itself, so the same rules answer for a request, a background job or a spec.
|
|
67
|
+
#
|
|
68
|
+
# Roles are stored per class as RolePlays::Mixin::Role structs. An action is looked up on the
|
|
69
|
+
# current role first and falls back to the :any role, so shared permissions can be declared once.
|
|
70
|
+
# Unknown role/action pairs are denied. Nothing is inherited: a policy declares the roles it
|
|
71
|
+
# answers for, and a role shared between policies is passed around as a Role struct built with
|
|
72
|
+
# RoleBuilder.
|
|
73
|
+
#
|
|
74
|
+
# Permitted attributes and scopes are declared the same way, keyed by a label (:default when
|
|
75
|
+
# omitted) and resolved with the same role/:any fallback. An unknown attributes label yields an
|
|
76
|
+
# empty list, an unknown scope label yields `nil` — narrowing a relation to nothing is left to the
|
|
77
|
+
# caller, the only side that knows which relation the policy was built around.
|
|
78
|
+
#
|
|
79
|
+
# A policy is built once and never mutated afterwards. Action, attribute and scope bodies take no
|
|
80
|
+
# arguments and are evaluated against the policy instance, which gives them access to +role+, to
|
|
81
|
+
# every context keyword and to any helper method defined on the policy class itself.
|
|
82
|
+
# The DSL is one module on purpose — the declarations, the lookups and the readers they feed are
|
|
83
|
+
# one contract, so it is read top to bottom rather than split across files.
|
|
84
|
+
module Mixin
|
|
85
|
+
# Role every other role falls back to for actions it does not declare itself.
|
|
86
|
+
ANY_ROLE = :any
|
|
87
|
+
|
|
88
|
+
# Label a `permitted_attributes` declaration is filed under when none is given.
|
|
89
|
+
DEFAULT_ATTRIBUTES_LABEL = :default
|
|
90
|
+
|
|
91
|
+
# Label a `scope` declaration is filed under when none is given.
|
|
92
|
+
DEFAULT_SCOPE_LABEL = :default
|
|
93
|
+
|
|
94
|
+
# Marks the modules holding the readers `context` generates, so a keyword named after one of
|
|
95
|
+
# them is recognised as declared instead of as shadowing a method of the policy.
|
|
96
|
+
module ContextReaders; end
|
|
97
|
+
|
|
98
|
+
# What one role may do: the actions it declares, the attributes it may submit per label and the
|
|
99
|
+
# scopes it may read per label. Built by RoleBuilder from a `role` block, or by hand to share a
|
|
100
|
+
# role between policies.
|
|
101
|
+
class Role < BaseStruct
|
|
102
|
+
attribute :role, Types::Strict::Symbol
|
|
103
|
+
attribute :actions, Types::Hash.map(Types::Strict::Symbol, Types.Interface(:call)).default({}.freeze)
|
|
104
|
+
attribute :permitted_attributes,
|
|
105
|
+
Types::Hash.map(Types::Strict::Symbol, Types.Interface(:call)).default({}.freeze)
|
|
106
|
+
attribute :scopes, Types::Hash.map(Types::Strict::Symbol, Types.Interface(:call)).default({}.freeze)
|
|
107
|
+
|
|
108
|
+
def action_for(name)
|
|
109
|
+
actions[name]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def permitted_attributes_for(label)
|
|
113
|
+
permitted_attributes[label]
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def scope_for(label)
|
|
117
|
+
scopes[label]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Later declarations win, so a policy can extend or override a role it was given
|
|
121
|
+
def merge(other)
|
|
122
|
+
new(actions: actions.merge(other.actions),
|
|
123
|
+
permitted_attributes: permitted_attributes.merge(other.permitted_attributes),
|
|
124
|
+
scopes: scopes.merge(other.scopes))
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Collects `action`, `permitted_attributes` and `scope` declarations of a single `role` block
|
|
129
|
+
class RoleBuilder
|
|
130
|
+
def self.build(role, &block)
|
|
131
|
+
builder = new
|
|
132
|
+
builder.instance_eval(&block) if block
|
|
133
|
+
|
|
134
|
+
Role.new(role: role.to_sym, actions: builder.actions, permitted_attributes: builder.attributes,
|
|
135
|
+
scopes: builder.scopes)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
attr_reader :actions, :attributes, :scopes
|
|
139
|
+
|
|
140
|
+
def initialize
|
|
141
|
+
@actions = {}
|
|
142
|
+
@attributes = {}
|
|
143
|
+
@scopes = {}
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def action(name, handler = nil, &block)
|
|
147
|
+
callable = handler || block
|
|
148
|
+
raise ArgumentError, "action :#{name} requires a lambda or a block" if callable.nil?
|
|
149
|
+
raise ArgumentError, "action :#{name} handler must not take required arguments" if callable.arity.positive?
|
|
150
|
+
|
|
151
|
+
@actions[name.to_sym] = callable
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Declares the attributes a role may submit, keyed by label (:default when omitted).
|
|
155
|
+
# The attributes can be listed literally or computed by a lambda or a block:
|
|
156
|
+
#
|
|
157
|
+
# permitted_attributes %i[title description]
|
|
158
|
+
# permitted_attributes :list, %i[page per_page]
|
|
159
|
+
# permitted_attributes(:create) { own_order? ? %i[title user_id] : %i[title] }
|
|
160
|
+
def permitted_attributes(label = DEFAULT_ATTRIBUTES_LABEL, attributes = nil, &block)
|
|
161
|
+
unless label.is_a?(Symbol)
|
|
162
|
+
attributes = label
|
|
163
|
+
label = DEFAULT_ATTRIBUTES_LABEL
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
attributes ||= block
|
|
167
|
+
raise ArgumentError, "permitted_attributes :#{label} requires a list, a lambda or a block" if attributes.nil?
|
|
168
|
+
|
|
169
|
+
if attributes.respond_to?(:call) && attributes.arity.positive?
|
|
170
|
+
raise ArgumentError, "permitted_attributes :#{label} handler must not take required arguments"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
@attributes[label.to_sym] = attributes.respond_to?(:call) ? attributes : -> { attributes }
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Declares how a role narrows a relation, keyed by a label (:default when omitted).
|
|
177
|
+
# The body reads the keyword the policy carries its relation in:
|
|
178
|
+
#
|
|
179
|
+
# scope -> { order_relation.where(user_id: user.id) }
|
|
180
|
+
# scope :list, -> { order_relation.where(sent: true) }
|
|
181
|
+
# scope(:report) { own_orders.where(created_at: period) }
|
|
182
|
+
def scope(label = DEFAULT_SCOPE_LABEL, handler = nil, &block)
|
|
183
|
+
unless label.is_a?(Symbol)
|
|
184
|
+
handler = label
|
|
185
|
+
label = DEFAULT_SCOPE_LABEL
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
handler ||= block
|
|
189
|
+
raise ArgumentError, "scope :#{label} requires a lambda or a block" if handler.nil?
|
|
190
|
+
raise ArgumentError, "scope :#{label} handler must not take required arguments" if handler.arity.positive?
|
|
191
|
+
|
|
192
|
+
@scopes[label.to_sym] = handler
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def self.included(base)
|
|
197
|
+
base.extend(ClassMethods)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# The DSL a policy class answers, and the lookups it resolves a declaration with
|
|
201
|
+
module ClassMethods
|
|
202
|
+
# Declares one or more roles, either with a block of actions or with a prebuilt Role struct.
|
|
203
|
+
# Names given as a list or as an array share the block, which is evaluated once and filed
|
|
204
|
+
# under each of them, so roles with identical permissions are declared together:
|
|
205
|
+
#
|
|
206
|
+
# role :user do ... end
|
|
207
|
+
# role %i[user access_code] do ... end
|
|
208
|
+
def role(*roles, &block)
|
|
209
|
+
roles = roles.flatten
|
|
210
|
+
raise ArgumentError, "role requires at least one role name" if roles.empty?
|
|
211
|
+
|
|
212
|
+
shared = nil
|
|
213
|
+
roles.each do |role_or_name|
|
|
214
|
+
next declare_role(role_or_name) if role_or_name.is_a?(Role)
|
|
215
|
+
|
|
216
|
+
shared ||= RoleBuilder.build(role_or_name, &block)
|
|
217
|
+
declare_role(shared.new(role: role_or_name.to_sym))
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Files a Role struct under its name, merging it into an already declared role of the
|
|
222
|
+
# same name so a later declaration extends or overrides the earlier one
|
|
223
|
+
def declare_role(role)
|
|
224
|
+
declared = policy_roles[role.role]
|
|
225
|
+
|
|
226
|
+
policy_roles[role.role] = declared ? declared.merge(role) : role
|
|
227
|
+
define_action_predicates(role.actions.keys)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# The roles this policy declares — nothing is inherited, so a role shared with another policy
|
|
231
|
+
# is composed in as a Role struct rather than picked up from a parent class
|
|
232
|
+
def policy_roles
|
|
233
|
+
@policy_roles ||= {}
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def action_handler(role, action)
|
|
237
|
+
policy_roles[role]&.action_for(action) || policy_roles[ANY_ROLE]&.action_for(action)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def permitted_attributes_handler(role, label)
|
|
241
|
+
policy_roles[role]&.permitted_attributes_for(label) || policy_roles[ANY_ROLE]&.permitted_attributes_for(label)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def scope_handler(role, label)
|
|
245
|
+
policy_roles[role]&.scope_for(label) || policy_roles[ANY_ROLE]&.scope_for(label)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Defines a `can_<action>?` predicate per declared action, so a policy answers the same
|
|
249
|
+
# `can_create?` / `can_destroy?` messages a hand written one does:
|
|
250
|
+
#
|
|
251
|
+
# action :create # => can_create? == can?(:create)
|
|
252
|
+
# action :destroy # => can_destroy? == can?(:destroy)
|
|
253
|
+
#
|
|
254
|
+
# Called for every `role` declaration, so the predicates cover the actions of all roles —
|
|
255
|
+
# each one still resolves the handler for the current role at call time. The methods live on
|
|
256
|
+
# a module included into the policy, so a predicate written by hand on the class always wins.
|
|
257
|
+
def define_action_predicates(*actions)
|
|
258
|
+
actions.flatten.each do |action|
|
|
259
|
+
action = action.to_sym
|
|
260
|
+
predicate = :"can_#{action}?"
|
|
261
|
+
next if action_predicates.method_defined?(predicate)
|
|
262
|
+
|
|
263
|
+
action_predicates.define_method(predicate) { can?(action) }
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Module holding the generated predicates of this class, included on first use
|
|
268
|
+
def action_predicates
|
|
269
|
+
@action_predicates ||= Module.new.tap { |generated| include generated }
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Declares the keywords the policy is built with, giving each one a reader that answers `nil`
|
|
273
|
+
# when the caller left it out, so a handler can treat it as optional:
|
|
274
|
+
#
|
|
275
|
+
# context :user, :order, :order_relation
|
|
276
|
+
#
|
|
277
|
+
# action(:create) { %i[title] + (order ? [:user_id] : []) }
|
|
278
|
+
#
|
|
279
|
+
# Declaring is optional — any keyword `new` is given is readable by name anyway. What the
|
|
280
|
+
# declaration adds is the `nil` for a keyword that was not passed: an undeclared name always
|
|
281
|
+
# raises, so a typo in a handler cannot quietly answer `nil`.
|
|
282
|
+
def context(*names)
|
|
283
|
+
names.flatten.each do |name|
|
|
284
|
+
name = name.to_sym
|
|
285
|
+
policy_context_keys << name unless policy_context_keys.include?(name)
|
|
286
|
+
next if method_defined?(name) || private_method_defined?(name)
|
|
287
|
+
|
|
288
|
+
context_readers.define_method(name) { context[name] }
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# The names this policy declares with `context`
|
|
293
|
+
def policy_context_keys
|
|
294
|
+
@policy_context_keys ||= []
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Module holding the generated context readers of this class, included on first use
|
|
298
|
+
def context_readers
|
|
299
|
+
@context_readers ||= Module.new.tap do |generated|
|
|
300
|
+
generated.extend(ContextReaders)
|
|
301
|
+
include generated
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Whether a keyword of this name would be unreachable because the policy answers it already.
|
|
306
|
+
# A reader generated by `context` does not count — answering it is what it is there for.
|
|
307
|
+
def reserved_context_name?(name)
|
|
308
|
+
return false unless method_defined?(name) || private_method_defined?(name)
|
|
309
|
+
|
|
310
|
+
!instance_method(name).owner.is_a?(ContextReaders)
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
attr_reader :role, :context
|
|
315
|
+
|
|
316
|
+
# The role is the name a `role` declaration was filed under, `nil` standing for no role at all,
|
|
317
|
+
# which leaves only the :any declarations. It is the only argument the policy asks for — every
|
|
318
|
+
# other keyword is kept as context and read back by name:
|
|
319
|
+
#
|
|
320
|
+
# OrderPolicy.new(role: :user, user: current_user, order: order, order_relation: Order.all)
|
|
321
|
+
def initialize(role:, **context)
|
|
322
|
+
reject_shadowing_context!(context)
|
|
323
|
+
|
|
324
|
+
@role = role&.to_sym
|
|
325
|
+
@context = context.freeze
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def can?(action)
|
|
329
|
+
handler = self.class.action_handler(role, action.to_sym)
|
|
330
|
+
return false if handler.nil?
|
|
331
|
+
|
|
332
|
+
!!instance_exec(&handler)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def cannot?(action)
|
|
336
|
+
!can?(action)
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# Attributes the current role may submit for the given label, always as an array
|
|
340
|
+
# ready to be handed to ActionController::Parameters#permit
|
|
341
|
+
def permitted_attributes(label = DEFAULT_ATTRIBUTES_LABEL)
|
|
342
|
+
handler = self.class.permitted_attributes_handler(role, label.to_sym)
|
|
343
|
+
return [] if handler.nil?
|
|
344
|
+
|
|
345
|
+
wrap_attributes(instance_exec(&handler))
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# The relation narrowed to what the current role may see under the given label, or `nil` when
|
|
349
|
+
# neither that role nor :any declares the label. The policy carries its relation in a keyword of
|
|
350
|
+
# its own naming, so saying "nothing is visible" is left to the caller:
|
|
351
|
+
#
|
|
352
|
+
# OrderPolicy.new(role:, order_relation: Order.all).scope(:list) || Order.none
|
|
353
|
+
def scope(label = DEFAULT_SCOPE_LABEL)
|
|
354
|
+
handler = self.class.scope_handler(role, label.to_sym)
|
|
355
|
+
return nil if handler.nil?
|
|
356
|
+
|
|
357
|
+
instance_exec(&handler)
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
private
|
|
361
|
+
|
|
362
|
+
# Reads a context keyword — `new(role: :user, order:)` answers `order` — so handlers can name
|
|
363
|
+
# what they need. An unknown name still raises NameError, so a typo is not read as `nil`.
|
|
364
|
+
def method_missing(name, *args)
|
|
365
|
+
return super unless args.empty? && context.key?(name)
|
|
366
|
+
|
|
367
|
+
context[name]
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def respond_to_missing?(name, include_private = false)
|
|
371
|
+
context.key?(name.to_sym) || super
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# A context keyword only reaches a handler through its reader, so one named after a method the
|
|
375
|
+
# policy already answers — `can?`, `context`, a helper of its own — would be silently unreadable
|
|
376
|
+
def reject_shadowing_context!(context)
|
|
377
|
+
shadowed = context.keys.select { |key| self.class.reserved_context_name?(key) }
|
|
378
|
+
return if shadowed.empty?
|
|
379
|
+
|
|
380
|
+
raise ArgumentError, "#{self.class} already defines #{shadowed.map { ":#{_1}" }.join(", ")}, " \
|
|
381
|
+
"so the value passed under that name could never be read"
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# A declared list is handed straight to `permit`, so a single attribute — or a nested hash —
|
|
385
|
+
# reads as the one element list it stands for rather than being splatted
|
|
386
|
+
def wrap_attributes(attributes)
|
|
387
|
+
return [] if attributes.nil?
|
|
388
|
+
|
|
389
|
+
attributes.is_a?(::Array) ? attributes : [attributes]
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
end
|
data/lib/role_plays.rb
ADDED
data/sig/role_plays.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: role_plays
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Aliexandr Andrade
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: dry-struct
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.6'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.6'
|
|
26
|
+
description: 'RolePlays turns a policy into one readable class per resource, built
|
|
27
|
+
and asked explicitly the way Pundit and Action Policy policies are, with its rules
|
|
28
|
+
declared rather than defined method by method as in CanCanCan: every role declares
|
|
29
|
+
the actions it may take, the attributes it may submit and the scopes it may read,
|
|
30
|
+
all resolved with a shared :any fallback. The policy is told the role name rather
|
|
31
|
+
than a user, so the same rules answer for a request, a background job or a spec.'
|
|
32
|
+
email:
|
|
33
|
+
- veimar.94@gmail.com
|
|
34
|
+
executables: []
|
|
35
|
+
extensions: []
|
|
36
|
+
extra_rdoc_files: []
|
|
37
|
+
files:
|
|
38
|
+
- CHANGELOG.md
|
|
39
|
+
- LICENSE.txt
|
|
40
|
+
- README.md
|
|
41
|
+
- Rakefile
|
|
42
|
+
- lib/role_plays.rb
|
|
43
|
+
- lib/role_plays/base_struct.rb
|
|
44
|
+
- lib/role_plays/mixin.rb
|
|
45
|
+
- lib/role_plays/types.rb
|
|
46
|
+
- lib/role_plays/version.rb
|
|
47
|
+
- sig/role_plays.rbs
|
|
48
|
+
homepage: https://github.com/Alexander-Andrade/role_plays
|
|
49
|
+
licenses:
|
|
50
|
+
- MIT
|
|
51
|
+
metadata:
|
|
52
|
+
allowed_push_host: https://rubygems.org
|
|
53
|
+
homepage_uri: https://github.com/Alexander-Andrade/role_plays
|
|
54
|
+
source_code_uri: https://github.com/Alexander-Andrade/role_plays
|
|
55
|
+
changelog_uri: https://github.com/Alexander-Andrade/role_plays
|
|
56
|
+
rdoc_options: []
|
|
57
|
+
require_paths:
|
|
58
|
+
- lib
|
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
60
|
+
requirements:
|
|
61
|
+
- - ">="
|
|
62
|
+
- !ruby/object:Gem::Version
|
|
63
|
+
version: 3.2.0
|
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
requirements: []
|
|
70
|
+
rubygems_version: 4.0.8
|
|
71
|
+
specification_version: 4
|
|
72
|
+
summary: Role based authorization policies, one per resource as in Pundit or Action
|
|
73
|
+
Policy, declared with a DSL in the spirit of CanCanCan.
|
|
74
|
+
test_files: []
|