simon_says 0.2.0 → 0.3.0.alpha.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6c3b6bd7983eb136f044c2653430143493637871
4
- data.tar.gz: a66d00fe12a96f48e8865a61aaf61f145feb5007
3
+ metadata.gz: c6bf629f417e1bd66bf820ee52c333aacfe0e25a
4
+ data.tar.gz: 340919dff4421094ec54d4b75e0446b54f522a10
5
5
  SHA512:
6
- metadata.gz: 284e3a2a74a36d1ae52aa78849a347f38071b4c5838dc43b60cdba794fd9bed021054941b44b44d28744511a3af57eb6764108ae087b7bf1d17e96b03f1f1e8a
7
- data.tar.gz: dea0bc94572281d6b505ef4a3eb2ed0fb502e916483e122cf19f34444dfaf12947e374065495f2b2edd3c8a0e370b45a5454a1075c56daec896f642a02ae451e
6
+ metadata.gz: c2f679703c9bd5fb566ee181498131d069c6ad5e3de83957cb2e8a8a3777a6c194589a399a58e4272906c1e8e89f309101ad23103f1b5e515e7d747edc0c9055
7
+ data.tar.gz: 1aae317daf655c2dcbcf425ef2eeeff492212003a4256501f72c5278239aeb8f57b861051a407d8f35af5467e8f2f10466695e9a5eeaec887c3d050c02129ef4
data/README.md CHANGED
@@ -12,7 +12,7 @@ Rails that works great with devise!
12
12
 
13
13
  ### Installation
14
14
 
15
- SimonSays can be installed via your Gemfile or using Ruby gems directly.
15
+ SimonSays can be installed via your Gemfile.
16
16
 
17
17
  ```ruby
18
18
  gem 'simon_says'
@@ -22,21 +22,23 @@ gem 'simon_says'
22
22
 
23
23
  SimonSays consists of two parts:
24
24
 
25
- 1. A [Roleable](#roleable) concern which provides a way to define access roles
26
- on User models or on join through models.
27
- 2. An [Authorizer](#authorizer) concern which provides a declarative API
28
- to controllers for finding and authorizing model resources.
25
+ 1. A [Roleable](#roleable) module mixin which provides a way to define
26
+ roles on User models or on join through models.
27
+ 2. An [Authorizer](#authorizer) module mixin which provides a
28
+ declarative API to controllers for finding and authorizing resources.
29
29
 
30
30
  #### Roleable
31
31
 
32
32
  First, we need to define some roles on a model. Roles are stored as an
33
33
  integer and [bitmasking](https://en.wikipedia.org/wiki/Mask_(computing))
34
- is used to determine the roles assigned for that model. SimonSays
34
+ is used to determine the roles assigned for given record. SimonSays
35
35
  provides a generator for creating a new migration for this required
36
36
  attribute:
37
37
 
38
38
  ```bash
39
+ rails g model User # if and only if this model does not yet exist
39
40
  rails g active_record:simon_says User
41
+ rails db:migrate
40
42
  ```
41
43
 
42
44
  Now we can define some roles in our User model. For example:
@@ -51,14 +53,16 @@ end
51
53
  # > User.new.roles
52
54
  # => []
53
55
 
54
- # > u = User.new(roles: %i[add edit])
55
- #
56
+ # > u = User.create(roles: %i[add edit])
57
+ # => #<User ...>
56
58
  # > u.roles
57
59
  # => [:add, :edit]
58
60
  # > u.has_add?
59
61
  # => true
60
62
  # > u.has_delete?
61
63
  # => false
64
+ # > u.update roles: %i[delete add edit]
65
+ # > u.save # save record with roles_mask of 7
62
66
  ```
63
67
 
64
68
  The attribute name can be customized by using the `:as` option as seen
@@ -103,11 +107,20 @@ end
103
107
  # => [:download, :edit, :delete]
104
108
  ```
105
109
 
106
- It is useful to note the dynamically generated `has_` methods as shown
107
- in the User model as well the `ROLES` constant which is used in the
108
- Permission example. Take a look at the `Roleable`
110
+ `Roleable` also creates two scopes that can be used to find records that
111
+ have a given set roles. Using the default attribute name, the two scopes
112
+ generated would be `with_roles` and `with_all_roles`. Both methods
113
+ accept one or more role symbols as its arguments. The first scope,
114
+ `with_roles`, will find any record with one or more the supplied roles.
115
+ The second scope, `with_all_roles` will only find record that have all
116
+ of the supplied roles.
117
+
118
+ It is useful to note the various dynamically generated methods as well
119
+ the `ROLES` constant, which is used in the Permission example. Take a
120
+ look at the `Roleable`
109
121
  [source code](https://github.com/SimplyBuilt/SimonSays/blob/master/lib/simon_says/roleable.rb)
110
- to see how features are dynamically generated with `has_roles`.
122
+ to see how methods and scopes are dynamically generated with
123
+ `has_roles`.
111
124
 
112
125
  #### Authorizer
113
126
 
@@ -71,11 +71,11 @@ module SimonSays
71
71
  args.compact!
72
72
  args.map!(&:to_sym)
73
73
 
74
- self[:#{name}_mask] = (args & #{const}).map { |i| 2 ** #{const}.index(i) }.sum
74
+ self.#{name}_mask = (args & #{const}).map { |i| 2 ** #{const}.index(i) }.sum
75
75
  end
76
76
 
77
77
  def #{name}
78
- #{const}.reject { |i| ((#{name}_mask || 0) & 2 ** #{const}.index(i)).zero? }
78
+ #{const}.reject { |i| ((#{name}_mask || 0) & 2 ** #{const}.index(i)).zero? }.tap(&:freeze)
79
79
  end
80
80
 
81
81
  def has_#{name}?(*args)
@@ -93,12 +93,40 @@ module SimonSays
93
93
  RUBY_EVAL
94
94
  end
95
95
 
96
- # Declare a scope for finding records with a given role set
97
- # TODO support an array roles (must match ALL)
98
- scope "with_#{name}", ->(role) {
99
- where("(#{name}_mask & ?) > 0", 2**roles.index(role.to_sym))
100
- }
96
+ # Try to declare ActiveRecord scopes or Sequel subsets for finding
97
+ # records with a given set of roles
98
+
99
+ scope_method = respond_to?(:scope) ? :scope : respond_to?(:subset) ? :subset : nil
100
+
101
+ if scope_method
102
+ send scope_method, "with_#{name}", ->(*args) {
103
+ clause = "#{name}_mask & ?"
104
+ values = Roleable.roles2ints(roles, *args)
105
+
106
+ query = where(clause, values.shift)
107
+ query = query.or(where(clause, values.shift)) until values.empty?
108
+ query
109
+ }
110
+
111
+ send scope_method, "with_all_#{name}", ->(*args) {
112
+ clause = "#{name}_mask & ?"
113
+ values = Roleable.roles2ints(roles, *args)
114
+
115
+ query = where(clause, values.shift)
116
+ query = query.where(clause, values.shift) until values.empty?
117
+ query
118
+ }
119
+ end
101
120
  end
102
121
  end
122
+
123
+ def self.roles2ints(defined_roles, *args)
124
+ values = args.map do |arg|
125
+ index = defined_roles.index(arg)
126
+ index ? 2 ** index : nil
127
+ end
128
+
129
+ values.tap(&:flatten!)
130
+ end
103
131
  end
104
132
  end
@@ -1,3 +1,3 @@
1
1
  module SimonSays
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0.alpha.1'
3
3
  end
@@ -132,15 +132,34 @@ class RoleableTest < ActiveSupport::TestCase
132
132
  test "named scope with_roles" do
133
133
  assert_equal [2, 1], [
134
134
  Membership.with_roles(:download).count,
135
- Membership.with_roles(:delete).count
135
+ Membership.with_roles(:edit, :delete).count,
136
136
  ]
137
137
  end
138
138
 
139
139
  test "named scope with_access" do
140
- assert_equal [2, 2, 2], [
141
- Admin.with_access(:marketing).count,
140
+ assert_equal [2, 3, 4], [
142
141
  Admin.with_access(:content).count,
143
- Admin.with_access(:support).count
142
+ Admin.with_access(:content, :marketing).count,
143
+ Admin.with_access(:content, :marketing, :support).count
144
+ ]
145
+ end
146
+
147
+ test "named scope with_all_roles" do
148
+ memberships(:mb1).update roles: %i[download fork edit]
149
+ memberships(:mb2).update roles: %i[download fork]
150
+
151
+ assert_equal [2, 0], [
152
+ Membership.with_all_roles(:download, :fork).count,
153
+ Membership.with_all_roles(:download, :fork, :edit, :delete).count
154
+ ]
155
+ end
156
+
157
+ test "named scope with_all_access" do
158
+ Admin.create(access: %i[content marketing])
159
+
160
+ assert_equal [2, 1], [
161
+ Admin.with_all_access(:content, :marketing).count,
162
+ Admin.with_all_access(:content, :marketing, :support).count
144
163
  ]
145
164
  end
146
165
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simon_says
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0.alpha.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Coyne
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2017-10-07 00:00:00.000000000 Z
13
+ date: 2018-10-26 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -231,9 +231,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
231
231
  version: '0'
232
232
  required_rubygems_version: !ruby/object:Gem::Requirement
233
233
  requirements:
234
- - - ">="
234
+ - - ">"
235
235
  - !ruby/object:Gem::Version
236
- version: '0'
236
+ version: 1.3.1
237
237
  requirements: []
238
238
  rubyforge_project:
239
239
  rubygems_version: 2.6.13