i_am_i_can 3.0.0pre → 3.0.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 +4 -4
- data/Gemfile.lock +3 -1
- data/README.md +46 -23
- data/i_am_i_can.gemspec +3 -2
- data/lib/generators/i_am_i_can/setup_generator.rb +53 -16
- data/lib/generators/i_am_i_can/templates/initializers/i_am_i_can.erb +8 -0
- data/lib/generators/i_am_i_can/templates/migrations/i_am_i_can.erb +78 -0
- data/lib/generators/i_am_i_can/templates/models/permission.erb +9 -2
- data/lib/generators/i_am_i_can/templates/models/role.erb +15 -4
- data/lib/generators/i_am_i_can/templates/models/role_group.erb +12 -5
- data/lib/i_am_i_can/configs/config.rb +32 -0
- data/lib/i_am_i_can/configs/configs.rb +29 -0
- data/lib/i_am_i_can/configurable.rb +28 -0
- data/lib/i_am_i_can/dynamic_generate.rb +95 -0
- data/lib/i_am_i_can/permission/assignment.rb +3 -18
- data/lib/i_am_i_can/permission/definition.rb +1 -21
- data/lib/i_am_i_can/permission/helpers.rb +35 -8
- data/lib/i_am_i_can/permission.rb +50 -50
- data/lib/i_am_i_can/reflection.rb +25 -0
- data/lib/i_am_i_can/resource.rb +29 -0
- data/lib/i_am_i_can/role/assignment.rb +2 -16
- data/lib/i_am_i_can/role/definition.rb +4 -46
- data/lib/i_am_i_can/role/helpers.rb +43 -5
- data/lib/i_am_i_can/role.rb +17 -0
- data/lib/i_am_i_can/subject/permission_querying.rb +4 -4
- data/lib/i_am_i_can/subject.rb +24 -0
- data/lib/i_am_i_can/version.rb +1 -1
- data/lib/i_am_i_can.rb +52 -35
- metadata +32 -12
- data/lib/generators/i_am_i_can/templates/migrations/add_to_subject.erb +0 -5
- data/lib/generators/i_am_i_can/templates/migrations/permission.erb +0 -15
- data/lib/generators/i_am_i_can/templates/migrations/role.erb +0 -13
- data/lib/generators/i_am_i_can/templates/migrations/role_group.erb +0 -14
- data/lib/i_am_i_can/config.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2cda6740e5fdc39a1bf281df8ec61425d001171a
|
4
|
+
data.tar.gz: 263b17f70ce6ff0fdfdfff8338a85b798e43b500
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b6d4f29d6565b394bce3d01ddab46f2532d307b093318b5a9cc01e8ce1746edbc3dbe0616403a9a1ab96b05403ac02b933fb90c0f089d99e0746d9efb1040897
|
7
|
+
data.tar.gz: a545727532511dbb469b0057b5160764442896f9c10d0705a43ce13b40b1bb4c55d3b40be3579449804b653a904cbf5e48ecbcbb6d38472f0ff8bde4d6f45497
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
i_am_i_can (3.0.
|
4
|
+
i_am_i_can (3.0.0)
|
5
5
|
activerecord
|
6
6
|
activesupport
|
7
7
|
railties
|
@@ -53,6 +53,7 @@ GEM
|
|
53
53
|
minitest (5.11.3)
|
54
54
|
nokogiri (1.8.4)
|
55
55
|
mini_portile2 (~> 2.3.0)
|
56
|
+
pg (1.0.0)
|
56
57
|
pry (0.11.3)
|
57
58
|
coderay (~> 1.1.0)
|
58
59
|
method_source (~> 0.9.0)
|
@@ -109,6 +110,7 @@ DEPENDENCIES
|
|
109
110
|
bundler (~> 1.16)
|
110
111
|
database_cleaner
|
111
112
|
i_am_i_can!
|
113
|
+
pg
|
112
114
|
pry
|
113
115
|
rake (~> 10.0)
|
114
116
|
rspec (~> 3.0)
|
data/README.md
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
# IAmICan
|
2
2
|
|
3
|
+
[](https://badge.fury.io/rb/i_am_i_can)
|
3
4
|
[](https://travis-ci.org/zhandao/i_am_i_can)
|
4
5
|
[](https://codeclimate.com/github/zhandao/i_am_i_can/maintainability)
|
5
6
|
[](https://codeclimate.com/github/zhandao/i_am_i_can/test_coverage)
|
6
7
|
|
7
|
-
Concise and Natural DSL for `Subject - Role(Role Group) - Permission` Management.
|
8
|
+
Concise and Natural DSL for `Subject - Role(Role Group) - Permission - Resource` Management (RBAC like).
|
8
9
|
|
9
10
|
```ruby
|
10
11
|
# our Subject is People, and subject is he:
|
@@ -45,21 +46,32 @@ he.can? :perform, :magic # => true
|
|
45
46
|
# Cancel Assignment
|
46
47
|
he.falls_from :admin
|
47
48
|
Roles.which(name: :coder).cannot :fly
|
49
|
+
|
50
|
+
# Get allowed resources:
|
51
|
+
Resource.that_allow(user).to(:manage) # => Active::Relation
|
48
52
|
```
|
49
53
|
|
50
54
|
## Concepts and Overview
|
51
55
|
|
52
56
|
### Definition and uniqueness of nouns
|
53
57
|
|
58
|
+
0. Subject
|
59
|
+
- Someone who can be assigned roles, and who has permissions through the assigned roles.
|
60
|
+
- See wiki [RBAC](https://en.wikipedia.org/wiki/Role-based_access_control)
|
54
61
|
1. Role
|
55
|
-
-
|
56
|
-
-
|
57
|
-
|
58
|
-
|
59
|
-
-
|
60
|
-
|
61
|
-
|
62
|
-
-
|
62
|
+
- A job function that groups a series of permissions according to a certain dimension.
|
63
|
+
- Also see wiki [RBAC](https://en.wikipedia.org/wiki/Role-based_access_control)
|
64
|
+
- Uniquely identified by `name`
|
65
|
+
2. Role Group
|
66
|
+
- A group of roles that may have the same permissions.
|
67
|
+
- Uniquely identified by `name`
|
68
|
+
3. Permission
|
69
|
+
- An action, or an approval of a mode of access to a resource
|
70
|
+
- Also see wiki [RBAC](https://en.wikipedia.org/wiki/Role-based_access_control)
|
71
|
+
- Uniquely identified by `predicate( + object)` (name),
|
72
|
+
or we can say, `action( + resource)`
|
73
|
+
4. Resource
|
74
|
+
- Polymorphic association with permissions
|
63
75
|
|
64
76
|
|
65
77
|
### In one word:
|
@@ -90,13 +102,13 @@ Roles.which(name: :coder).cannot :fly
|
|
90
102
|
- the role or permission you want to assign **MUST** be defined before
|
91
103
|
- option :auto_define_before (before assignment) you may need in some cases
|
92
104
|
- class methods, like: `UserRoleGroup.have_permission :fly`
|
93
|
-
|
105
|
+
|
94
106
|
**Definition => Assignment => Querying**
|
95
|
-
|
107
|
+
|
96
108
|
### Two Concepts of this gem
|
97
109
|
|
98
|
-
1. Stored (save in database)
|
99
|
-
2. Local (variable value)
|
110
|
+
1. Stored (save in database) TODO
|
111
|
+
2. Local (variable value) TODO
|
100
112
|
|
101
113
|
[Feature List: needs you](https://github.com/zhandao/i_am_i_can/issues/2)
|
102
114
|
|
@@ -107,33 +119,40 @@ Roles.which(name: :coder).cannot :fly
|
|
107
119
|
```ruby
|
108
120
|
gem 'i_am_i_can'
|
109
121
|
```
|
110
|
-
|
122
|
+
|
111
123
|
2. Generate migrations and models by your subject name:
|
112
124
|
|
113
125
|
```bash
|
114
126
|
rails g i_am_i_can:setup <subject_name>
|
115
127
|
```
|
116
|
-
|
128
|
+
|
117
129
|
For example, if your subject name is `user`, it will generate
|
118
130
|
model `UserRole`, `UserRoleGroup` and `UserPermission`
|
119
|
-
|
120
|
-
3. run `rails db:migrate`
|
121
131
|
|
122
|
-
|
132
|
+
3. Add the code returned by the generator to your subject model, like:
|
123
133
|
|
124
134
|
```ruby
|
125
135
|
class User
|
126
|
-
|
136
|
+
has_and_belongs_to_many :stored_roles,
|
137
|
+
join_table: 'users_and_user_roles', foreign_key: 'user_role_id', class_name: 'UserRole', association_foreign_key: 'user_id'
|
138
|
+
|
139
|
+
act_as_subject
|
127
140
|
end
|
128
141
|
```
|
129
|
-
|
130
|
-
[here](#options) is some options you can pass to the declaration.
|
131
|
-
|
142
|
+
|
143
|
+
[here](#config-options) is some options you can pass to the declaration.
|
144
|
+
|
145
|
+
4. Run `rails db:migrate`
|
146
|
+
|
132
147
|
That's all!
|
133
148
|
|
134
149
|
## Usage
|
135
150
|
|
136
|
-
###
|
151
|
+
### Customization
|
152
|
+
|
153
|
+
1. association names TODO
|
154
|
+
|
155
|
+
### Config Options
|
137
156
|
|
138
157
|
TODO
|
139
158
|
|
@@ -423,6 +442,10 @@ user.is? :master # => true
|
|
423
442
|
user.can? :read, :book # => true
|
424
443
|
```
|
425
444
|
|
445
|
+
#### I. Resource Querying
|
446
|
+
|
447
|
+
TODO
|
448
|
+
|
426
449
|
## Development
|
427
450
|
|
428
451
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/i_am_i_can.gemspec
CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["zhandao"]
|
10
10
|
spec.email = ["x@skippingcat.com"]
|
11
11
|
|
12
|
-
spec.summary = 'Concise and Natural DSL for `Subject - Role(Role Group) - Permission` Management.'
|
13
|
-
spec.description = 'Concise and Natural DSL for `Subject - Role(Role Group) - Permission` Management.'
|
12
|
+
spec.summary = '(RBAC like) Concise and Natural DSL for `Subject - Role(Role Group) - Permission - Resource` Management.'
|
13
|
+
spec.description = '(RBAC like) Concise and Natural DSL for `Subject - Role(Role Group) - Permission - Resource` Management.'
|
14
14
|
spec.homepage = 'https://github.com/zhandao/i_am_i_can'
|
15
15
|
spec.license = 'MIT'
|
16
16
|
|
@@ -26,6 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.add_development_dependency "rspec", "~> 3.0"
|
27
27
|
spec.add_development_dependency 'rspec-rails'
|
28
28
|
spec.add_development_dependency 'database_cleaner'
|
29
|
+
spec.add_development_dependency 'pg'
|
29
30
|
spec.add_development_dependency 'simplecov'
|
30
31
|
spec.add_development_dependency 'pry'
|
31
32
|
|
@@ -12,45 +12,82 @@ module IAmICan
|
|
12
12
|
|
13
13
|
source_root File.expand_path('../templates', __FILE__)
|
14
14
|
|
15
|
+
# TODO: more readable tips
|
15
16
|
def questions
|
16
17
|
@ii_opts = { }
|
17
|
-
|
18
|
+
role_class = ask("Do you want to change the class name of the role model (defaults to [#{name_c}Role])? Press Enter or input your name:")
|
19
|
+
@ii_opts[:role_class] = role_class.blank? ? "#{name_c}Role" : role_class
|
20
|
+
pms_class = ask("Do you want to change the class name of the permission model (defaults to [#{name_c}Permission])? Press Enter or input your name:")
|
21
|
+
@ii_opts[:permission_class] = pms_class.blank? ? "#{name_c}Permission" : pms_class
|
22
|
+
if yes?('Do you want to use role group? y (default) / n')
|
23
|
+
group_class = ask("Do you want to change the class name of the role_group model (defaults to [#{name_c}RoleGroup])? Press Enter or input your name:")
|
24
|
+
@ii_opts[:role_group_class] = group_class.blank? ? "#{name_c}RoleGroup" : group_class
|
25
|
+
else
|
18
26
|
@ii_opts[:without_group] = true
|
19
27
|
end
|
20
|
-
|
28
|
+
|
29
|
+
unless yes?('Do yo want it to save role and permission to database by default? y (default) / n')
|
21
30
|
@ii_opts[:default_save] = false
|
22
31
|
end
|
23
|
-
if yes?('
|
32
|
+
# if @ii_opts[:default_save] != false && yes?('Don\'t you need **local** definition and assignment feature? y / n (default)')
|
33
|
+
# TODO
|
34
|
+
# end
|
35
|
+
if yes?('Do you want it to raise error when you are doing wrong definition or assignment? y / n (default)')
|
24
36
|
@ii_opts[:strict_mode] = true
|
25
37
|
end
|
26
|
-
if yes?('Do you want it to define the role/permission which is not defined when assigning to subject?')
|
38
|
+
if yes?('Do you want it to auto define the role/permission which is not defined when assigning to subject? y / n (default)')
|
27
39
|
@ii_opts[:auto_define_before] = true
|
28
40
|
end
|
29
41
|
end
|
30
42
|
|
31
43
|
def setup_migrations
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
44
|
+
migration_template 'migrations/i_am_i_can.erb', "db/migrate/#{name_u}_am_#{name_u}_can.rb"
|
45
|
+
end
|
46
|
+
|
47
|
+
def setup_initializer
|
48
|
+
template 'initializers/i_am_i_can.erb', "config/initializers/#{name_u}_am_#{name_u}_can.rb"
|
37
49
|
end
|
38
50
|
|
39
51
|
def setup_models
|
40
|
-
template 'models/role.erb', "app/models/#{
|
41
|
-
template 'models/role_group.erb', "app/models/#{
|
42
|
-
template 'models/permission.erb', "app/models/#{
|
52
|
+
template 'models/role.erb', "app/models/#{role_u}.rb"
|
53
|
+
template 'models/role_group.erb', "app/models/#{group_u}.rb" unless @ii_opts[:without_group]
|
54
|
+
template 'models/permission.erb', "app/models/#{permission_u}.rb"
|
43
55
|
end
|
44
56
|
|
45
|
-
def
|
46
|
-
|
47
|
-
puts
|
48
|
-
|
57
|
+
def tips
|
58
|
+
puts " Add the code below to #{name_c}:".green
|
59
|
+
puts <<~TIPS
|
60
|
+
has_and_belongs_to_many :stored_roles,
|
61
|
+
join_table: '#{subj_role_tb}', foreign_key: '#{role_u}_id', class_name: '#{role_c}', association_foreign_key: '#{name_u}_id'
|
62
|
+
|
63
|
+
act_as_subject
|
64
|
+
TIPS
|
49
65
|
end
|
50
66
|
|
51
67
|
def self.next_migration_number(dirname)
|
52
68
|
ActiveRecord::Generators::Base.next_migration_number(dirname)
|
53
69
|
end
|
70
|
+
|
71
|
+
def name_c; name.camelize end
|
72
|
+
def name_u; name.underscore end
|
73
|
+
def name_up; name_u.pluralize end
|
74
|
+
|
75
|
+
def role_c; @ii_opts[:role_class] end
|
76
|
+
def role_u; @ii_opts[:role_class].underscore end
|
77
|
+
def role_up; @ii_opts[:role_class].underscore.pluralize end
|
78
|
+
|
79
|
+
def group_c; @ii_opts[:role_group_class] end
|
80
|
+
def group_u; @ii_opts[:role_group_class]&.underscore end
|
81
|
+
def group_up; @ii_opts[:role_group_class]&.underscore&.pluralize end
|
82
|
+
|
83
|
+
def permission_c; @ii_opts[:permission_class] end
|
84
|
+
def permission_u; @ii_opts[:permission_class].underscore end
|
85
|
+
def permission_up; @ii_opts[:permission_class].underscore.pluralize end
|
86
|
+
|
87
|
+
def subj_role_tb; name_up + '_and_' + role_up end
|
88
|
+
def group_role_tb; group_up + '_and_' + role_up end
|
89
|
+
def role_pms_tb; role_up + '_and_' + permission_up end
|
90
|
+
def group_pms_tb; group_up + '_and_' + permission_up end
|
54
91
|
end
|
55
92
|
end
|
56
93
|
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
IAmICan::Configs.set_for(subject: '<%= name_c %>',
|
2
|
+
role: '<%= @ii_opts[:role_class] %>',
|
3
|
+
permission: '<%= @ii_opts[:permission_class] %>'<% unless @ii_opts[:without_group] %>,
|
4
|
+
role_group: '<%= @ii_opts[:role_group_class] %>'<% end %>) do |config|
|
5
|
+
<% @ii_opts.except(:role_class, :permission_class, :role_group_class).each do |k, v| %>
|
6
|
+
<%= "config.#{k} = " %><%= v %>
|
7
|
+
<% end %>
|
8
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
class <%= name_c %>Am<%= name_c %>Can < ActiveRecord::Migration::Current
|
2
|
+
def change
|
3
|
+
create_table :<%= role_up %>, force: :cascade do |t|
|
4
|
+
t.string :name, null: false
|
5
|
+
t.string :desc
|
6
|
+
|
7
|
+
t.timestamps
|
8
|
+
end
|
9
|
+
|
10
|
+
add_index :<%= role_up %>, :name,
|
11
|
+
unique: true, name: '<%= role_up %>_unique_index', using: :btree
|
12
|
+
<% unless @ii_opts[:without_group] %>
|
13
|
+
create_table :<%= group_up %>, force: :cascade do |t|
|
14
|
+
t.string :name, null: false
|
15
|
+
t.string :desc
|
16
|
+
|
17
|
+
t.timestamps
|
18
|
+
end
|
19
|
+
|
20
|
+
add_index :<%= group_up %>, :name,
|
21
|
+
unique: true, name: '<%= group_up %>_unique_index', using: :btree
|
22
|
+
<% end %>
|
23
|
+
create_table :<%= permission_up %>, force: :cascade do |t|
|
24
|
+
t.string :pred, null: false
|
25
|
+
t.string :obj_type
|
26
|
+
t.integer :obj_id
|
27
|
+
t.string :desc
|
28
|
+
|
29
|
+
t.timestamps
|
30
|
+
end
|
31
|
+
|
32
|
+
add_index :<%= permission_up %>, %i[ pred obj_type obj_id ],
|
33
|
+
unique: true, name: '<%= permission_up %>_unique_index', using: :btree
|
34
|
+
|
35
|
+
create_table :<%= subj_role_tb %>, id: false, force: :cascade do |t|
|
36
|
+
t.belongs_to :<%= name_u %>, null: false#, index: false
|
37
|
+
t.belongs_to :<%= role_u %>, null: false#, index: false
|
38
|
+
end
|
39
|
+
|
40
|
+
# add_index :<%= subj_role_tb %>, :<%= name_u %>_id, name: ':<%= subj_role_tb %>_index1'
|
41
|
+
# add_index :<%= subj_role_tb %>, :<%= role_u %>_id, name: ':<%= subj_role_tb %>_index2'
|
42
|
+
add_index :<%= subj_role_tb %>, [ :<%= name_u %>_id, :<%= role_u %>_id ],
|
43
|
+
unique: true, name: '<%= subj_role_tb %>_uniq_index'
|
44
|
+
<% unless @ii_opts[:without_group] %>
|
45
|
+
create_table :<%= group_role_tb %>, id: false, force: :cascade do |t|
|
46
|
+
t.belongs_to :<%= group_u %>, null: false#, index: false
|
47
|
+
t.belongs_to :<%= role_u %>, null: false#, index: false
|
48
|
+
end
|
49
|
+
|
50
|
+
# add_index :<%= group_role_tb %>, :<%= group_u %>_id, name: '<%= group_role_tb %>_index1'
|
51
|
+
# add_index :<%= group_role_tb %>, :<%= role_u %>_id, name: '<%= group_role_tb %>_index2'
|
52
|
+
add_index :<%= group_role_tb %>, [ :<%= group_u %>_id, :<%= role_u %>_id ],
|
53
|
+
unique: true, name: '<%= group_role_tb %>_uniq_index'
|
54
|
+
|
55
|
+
<% end %>
|
56
|
+
create_table :<%= role_pms_tb %>, id: false, force: :cascade do |t|
|
57
|
+
t.belongs_to :<%= role_u %>, null: false#, index: false
|
58
|
+
t.belongs_to :<%= permission_u %>, null: false#, index: false
|
59
|
+
end
|
60
|
+
|
61
|
+
# add_index :<%= role_pms_tb %>, :<%= role_u %>_id, name: '<%= role_pms_tb %>_index1'
|
62
|
+
# add_index :<%= role_pms_tb %>, :<%= permission_u %>_id, name: '<%= role_pms_tb %>_index2'
|
63
|
+
add_index :<%= role_pms_tb %>, [ :<%= role_u %>_id, :<%= permission_u %>_id ],
|
64
|
+
unique: true, name: '<%= role_pms_tb %>_uniq_index'
|
65
|
+
<% unless @ii_opts[:without_group] %>
|
66
|
+
create_table :<%= group_pms_tb %>, id: false, force: :cascade do |t|
|
67
|
+
t.belongs_to :<%= group_u %>, null: false#, index: false
|
68
|
+
t.belongs_to :<%= permission_u %>, null: false#, index: false
|
69
|
+
end
|
70
|
+
|
71
|
+
# add_index :<%= group_pms_tb %>, :<%= group_u %>_id, name: '<%= group_pms_tb %>_index1'
|
72
|
+
# add_index :<%= group_pms_tb %>, :<%= permission_u %>_id, name: '<%= group_pms_tb %>_index2'
|
73
|
+
add_index :<%= group_pms_tb %>, [ :<%= group_u %>_id, :<%= permission_u %>_id ],
|
74
|
+
unique: true, name: '<%= group_pms_tb %>_uniq_index'
|
75
|
+
|
76
|
+
<% end %>
|
77
|
+
end
|
78
|
+
end
|
@@ -1,4 +1,11 @@
|
|
1
|
-
class <%=
|
1
|
+
class <%= permission_c %> < ActiveRecord::Base
|
2
|
+
has_and_belongs_to_many :related_roles,
|
3
|
+
join_table: '<%= role_pms_tb %>', foreign_key: :<%= role_u %>_id, class_name: '<%= role_c %>', association_foreign_key: :<%= permission_u %>_id
|
4
|
+
<% unless @ii_opts[:without_group] %>
|
5
|
+
has_and_belongs_to_many :related_role_groups,
|
6
|
+
join_table: '<%= group_pms_tb %>', foreign_key: :<%= group_u %>_id, class_name: '<%= group_c %>', association_foreign_key: :<%= permission_u %>_id
|
7
|
+
<% end %>
|
8
|
+
act_as_permission
|
2
9
|
end
|
3
10
|
|
4
11
|
__END__
|
@@ -8,4 +15,4 @@ __END__
|
|
8
15
|
integer :obj_id
|
9
16
|
string :desc
|
10
17
|
|
11
|
-
index %i[pred obj_type obj_id], unique: true
|
18
|
+
index %i[ pred obj_type obj_id ], unique: true
|
@@ -1,10 +1,21 @@
|
|
1
|
-
class <%=
|
1
|
+
class <%= role_c %> < ActiveRecord::Base
|
2
|
+
has_and_belongs_to_many :related_users,
|
3
|
+
join_table: '<%= subj_role_tb %>', foreign_key: :<%= name_u %>_id, class_name: '<%= name_c %>', association_foreign_key: :<%= role_u %>_id
|
4
|
+
<% unless @ii_opts[:without_group] %>
|
5
|
+
has_and_belongs_to_many :related_stored_groups,
|
6
|
+
join_table: '<%= group_role_tb %>', foreign_key: :<%= group_u %>_id, class_name: '<%= group_c %>', association_foreign_key: :<%= role_u %>_id
|
7
|
+
<% end %>
|
8
|
+
has_and_belongs_to_many :stored_permissions,
|
9
|
+
join_table: '<%= role_pms_tb %>', foreign_key: :<%= permission_u %>_id, class_name: '<%= permission_c %>', association_foreign_key: :<%= role_u %>_id
|
10
|
+
|
11
|
+
act_as_role
|
12
|
+
|
13
|
+
# default_scope { with_stored_permissions }
|
2
14
|
end
|
3
15
|
|
4
16
|
__END__
|
5
17
|
|
6
|
-
string :name,
|
7
|
-
integer :permission_ids, array: true, default: [ ]
|
18
|
+
string :name, null: false
|
8
19
|
string :desc
|
9
20
|
|
10
|
-
index :name, unique: true
|
21
|
+
index :name, unique: true
|
@@ -1,11 +1,18 @@
|
|
1
|
-
class
|
1
|
+
class RoleGroup < ActiveRecord::Base
|
2
|
+
has_and_belongs_to_many :stored_permissions,
|
3
|
+
join_table: '<%= group_pms_tb %>', foreign_key: :<%= permission_u %>_id, class_name: '<%= permission_c %>', association_foreign_key: :<%= group_u %>_id
|
4
|
+
|
5
|
+
has_and_belongs_to_many :members,
|
6
|
+
join_table: '<%= group_role_tb %>', foreign_key: :<%= role_u %>_id, class_name: '<%= role_c %>', association_foreign_key: :<%= group_u %>_id
|
7
|
+
|
8
|
+
act_as_role_group
|
9
|
+
|
10
|
+
# default_scope { with_members.with_stored_permissions) }
|
2
11
|
end
|
3
12
|
|
4
13
|
__END__
|
5
14
|
|
6
|
-
string :name,
|
7
|
-
integer :member_ids, array: true, default: [ ]
|
8
|
-
integer :permission_ids, array: true, default: [ ]
|
15
|
+
string :name, null: false
|
9
16
|
string :desc
|
10
17
|
|
11
|
-
index :name, unique: true
|
18
|
+
index :name, unique: true
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module IAmICan
|
2
|
+
module Configs
|
3
|
+
class Config
|
4
|
+
attr_accessor :subject_class, :role_class, :role_group_class, :permission_class,
|
5
|
+
:auto_define_before, :strict_mode, :without_group, :default_save, :act
|
6
|
+
|
7
|
+
def initialize(*classes)
|
8
|
+
self.subject_class, self.role_class, self.permission_class, self.role_group_class = classes
|
9
|
+
self.auto_define_before = false
|
10
|
+
self.strict_mode = false
|
11
|
+
self.without_group = false
|
12
|
+
self.default_save = true
|
13
|
+
end
|
14
|
+
|
15
|
+
def subject_model
|
16
|
+
@subject_model ||= subject_class.constantize
|
17
|
+
end
|
18
|
+
|
19
|
+
def role_model
|
20
|
+
@role_model ||= role_class.constantize
|
21
|
+
end
|
22
|
+
|
23
|
+
def role_group_model
|
24
|
+
@role_group_model ||= role_group_class.constantize rescue nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def permission_model
|
28
|
+
@permission_model ||= permission_class.constantize
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'i_am_i_can/configs/config'
|
2
|
+
|
3
|
+
module IAmICan
|
4
|
+
module Configs
|
5
|
+
cattr_accessor :configs do
|
6
|
+
{ }
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.set_for(subject:, role:, permission:, role_group: nil, &block)
|
10
|
+
config = Config.new(subject, role, permission, role_group)
|
11
|
+
config.instance_eval(&block)
|
12
|
+
configs.merge!(
|
13
|
+
subject => config.dup,
|
14
|
+
role => config.dup,
|
15
|
+
permission => config.dup,
|
16
|
+
)
|
17
|
+
configs.merge!(role_group => config.dup) if role_group
|
18
|
+
config
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.get(class_name)
|
22
|
+
configs[class_name]
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.take
|
26
|
+
configs.values.first
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'i_am_i_can/configs/configs'
|
2
|
+
|
3
|
+
module IAmICan
|
4
|
+
module Configurable
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
class_methods do
|
8
|
+
def i_am_i_can
|
9
|
+
Configs.get(self.name)
|
10
|
+
end
|
11
|
+
|
12
|
+
def _reflect_of(key)
|
13
|
+
_name = i_am_i_can.send("#{key}_class")
|
14
|
+
reflections.each do |name, reflection|
|
15
|
+
return name if reflection.class_name == _name
|
16
|
+
end; nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
included do
|
21
|
+
def i_am_i_can
|
22
|
+
Configs.get(self.class.name)
|
23
|
+
end
|
24
|
+
|
25
|
+
delegate :_reflect_of, to: self
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module IAmICan
|
2
|
+
module DynamicGenerate
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def scopes
|
6
|
+
# Generate scopes of each specified i_am_i_can association
|
7
|
+
#
|
8
|
+
# scope :with_stored_roles, -> { includes(:stored_roles) }
|
9
|
+
#
|
10
|
+
proc do |keys|
|
11
|
+
keys.each do |k|
|
12
|
+
scope :"with_#{_reflect_of(k)}", -> { includes(_reflect_of(k)) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def class_reflections
|
18
|
+
# Extend each associated querying to a class method that returns ActiveRecord::Relation
|
19
|
+
#
|
20
|
+
# Suppose: in UserRole model,
|
21
|
+
# has_and_belongs_to_many :related_users
|
22
|
+
#
|
23
|
+
# It will do like this:
|
24
|
+
# def self.related_users
|
25
|
+
# i_am_i_can.subject_model.with_stored_roles.where(user_roles: { id: self.ids })
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# Usage:
|
29
|
+
# UserRole.all.related_users
|
30
|
+
#
|
31
|
+
proc do
|
32
|
+
%w[ subject role role_group permission ].each do |k|
|
33
|
+
next unless _reflect_of(k)
|
34
|
+
define_singleton_method _reflect_of(k) do
|
35
|
+
model = i_am_i_can.send("#{k}_model")
|
36
|
+
raise NoMethodError unless (reflect_name = model._reflect_of(i_am_i_can.act))
|
37
|
+
model.send("with_#{reflect_name}").where(
|
38
|
+
self.name.underscore.pluralize => { id: self.ids }
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def assignment_helpers
|
46
|
+
# Generate 4 methods for each Content of Assignment
|
47
|
+
#
|
48
|
+
# Example for a subject model called User, which `has_and_belongs_to_many :stored_roles`.
|
49
|
+
# You call this proc by given contents [:role], then:
|
50
|
+
#
|
51
|
+
# 1. stored_roles_add
|
52
|
+
# Add roles to a user instance
|
53
|
+
#
|
54
|
+
# 2. stored_roles_add
|
55
|
+
# Remove roles to a user instance
|
56
|
+
#
|
57
|
+
# 3. stored_role_names
|
58
|
+
# Get names of stored_roles of a user instance
|
59
|
+
#
|
60
|
+
# 4. self.stored_role_names
|
61
|
+
# Get names of stored_roles of User ActiveRecord::Relation
|
62
|
+
#
|
63
|
+
proc do |contents|
|
64
|
+
contents.each do |content|
|
65
|
+
# TODO: refactoring
|
66
|
+
define_method "#{_reflect_of(content)}_add" do |locate_vals = nil, check_size: nil, **condition|
|
67
|
+
condition = { name: locate_vals } if locate_vals
|
68
|
+
assoc = send("_#{content.to_s.pluralize}")
|
69
|
+
records = i_am_i_can.send("#{content}_model").where(condition).where.not(id: assoc.ids)
|
70
|
+
# will return false if it does nothing
|
71
|
+
return false if records.blank? || (check_size && records.count != check_size)
|
72
|
+
assoc << records
|
73
|
+
end
|
74
|
+
|
75
|
+
define_method "#{_reflect_of(content)}_rmv" do |locate_vals = nil, check_size: nil, **condition|
|
76
|
+
condition = { name: locate_vals } if locate_vals
|
77
|
+
assoc = send("_#{content.to_s.pluralize}")
|
78
|
+
records = i_am_i_can.send("#{content}_model").where(id: assoc.ids, **condition)
|
79
|
+
# will return false if it does nothing
|
80
|
+
return false if records.blank? || (check_size && records.count != check_size)
|
81
|
+
assoc.destroy(records)
|
82
|
+
end
|
83
|
+
|
84
|
+
define_method "#{_reflect_of(content).to_s.singularize}_names" do
|
85
|
+
send("_#{content.to_s.pluralize}").map(&:name).map(&:to_sym)
|
86
|
+
end
|
87
|
+
|
88
|
+
define_singleton_method "#{_reflect_of(content).to_s.singularize}_names" do
|
89
|
+
all.flat_map { |user| user.send("#{_reflect_of(content).to_s.singularize}_name") }.uniq
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|