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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +3 -1
  3. data/README.md +46 -23
  4. data/i_am_i_can.gemspec +3 -2
  5. data/lib/generators/i_am_i_can/setup_generator.rb +53 -16
  6. data/lib/generators/i_am_i_can/templates/initializers/i_am_i_can.erb +8 -0
  7. data/lib/generators/i_am_i_can/templates/migrations/i_am_i_can.erb +78 -0
  8. data/lib/generators/i_am_i_can/templates/models/permission.erb +9 -2
  9. data/lib/generators/i_am_i_can/templates/models/role.erb +15 -4
  10. data/lib/generators/i_am_i_can/templates/models/role_group.erb +12 -5
  11. data/lib/i_am_i_can/configs/config.rb +32 -0
  12. data/lib/i_am_i_can/configs/configs.rb +29 -0
  13. data/lib/i_am_i_can/configurable.rb +28 -0
  14. data/lib/i_am_i_can/dynamic_generate.rb +95 -0
  15. data/lib/i_am_i_can/permission/assignment.rb +3 -18
  16. data/lib/i_am_i_can/permission/definition.rb +1 -21
  17. data/lib/i_am_i_can/permission/helpers.rb +35 -8
  18. data/lib/i_am_i_can/permission.rb +50 -50
  19. data/lib/i_am_i_can/reflection.rb +25 -0
  20. data/lib/i_am_i_can/resource.rb +29 -0
  21. data/lib/i_am_i_can/role/assignment.rb +2 -16
  22. data/lib/i_am_i_can/role/definition.rb +4 -46
  23. data/lib/i_am_i_can/role/helpers.rb +43 -5
  24. data/lib/i_am_i_can/role.rb +17 -0
  25. data/lib/i_am_i_can/subject/permission_querying.rb +4 -4
  26. data/lib/i_am_i_can/subject.rb +24 -0
  27. data/lib/i_am_i_can/version.rb +1 -1
  28. data/lib/i_am_i_can.rb +52 -35
  29. metadata +32 -12
  30. data/lib/generators/i_am_i_can/templates/migrations/add_to_subject.erb +0 -5
  31. data/lib/generators/i_am_i_can/templates/migrations/permission.erb +0 -15
  32. data/lib/generators/i_am_i_can/templates/migrations/role.erb +0 -13
  33. data/lib/generators/i_am_i_can/templates/migrations/role_group.erb +0 -14
  34. 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: 8a7c648cdb9a60d42ca538e28735070ef9644325
4
- data.tar.gz: 4a5ee5a0a56967049baeeddafc944fe6fc548479
3
+ metadata.gz: 2cda6740e5fdc39a1bf281df8ec61425d001171a
4
+ data.tar.gz: 263b17f70ce6ff0fdfdfff8338a85b798e43b500
5
5
  SHA512:
6
- metadata.gz: f684f54b656c925ff293706fec374eee0592b7ad91df3502e4fa031255f17f96cac95c22c8ee9e0426786453539ec6d26400b6fb8c9afe11d10dfa1b306d4343
7
- data.tar.gz: a855e8ebc30a47548b42be4f43ef07d4ec90816418369962e9562b3e773e878c7a2c748501911c41f77d891cd4f85562ec5491a7dec7c44e699510954c8c9bba
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.0pre)
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
+ [![Gem Version](https://badge.fury.io/rb/i_am_i_can.svg)](https://badge.fury.io/rb/i_am_i_can)
3
4
  [![Build Status](https://travis-ci.org/zhandao/i_am_i_can.svg?branch=master)](https://travis-ci.org/zhandao/i_am_i_can)
4
5
  [![Maintainability](https://api.codeclimate.com/v1/badges/27b664da01b6cc7180e3/maintainability)](https://codeclimate.com/github/zhandao/i_am_i_can/maintainability)
5
6
  [![Test Coverage](https://api.codeclimate.com/v1/badges/27b664da01b6cc7180e3/test_coverage)](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
- - definition: TODO
56
- - uniqueness: by `name`
57
- 1. Role Group
58
- - definition: TODO
59
- - uniqueness: by `name`
60
- 1. Permission
61
- - definition: TODO
62
- - uniqueness: by `predicate + object` (name)
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
- 4. enable it in your subject model, like:
132
+ 3. Add the code returned by the generator to your subject model, like:
123
133
 
124
134
  ```ruby
125
135
  class User
126
- act_as_i_am_i_can
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
- ### Options
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
- unless yes?('Do you want to use role group?')
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
- unless yes?('Do yo want it to save role and permission to database by default?')
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?('Do you want it to raise error when you are doing wrong definition or assignment?')
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
- dest_prefix = 'db/migrate/i_am_i_can_'
33
- migration_template 'migrations/add_to_subject.erb', "#{dest_prefix}add_role_ids_to_#{name.underscore}.rb"
34
- migration_template 'migrations/role.erb', "#{dest_prefix}create_#{name.underscore}_roles.rb"
35
- migration_template 'migrations/role_group.erb', "#{dest_prefix}create_#{name.underscore}_role_groups.rb" unless @ii_opts[:without_group]
36
- migration_template 'migrations/permission.erb', "#{dest_prefix}create_#{name.underscore}_permissions.rb"
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/#{name.underscore}_role.rb"
41
- template 'models/role_group.erb', "app/models/#{name.underscore}_role_group.rb" unless @ii_opts[:without_group]
42
- template 'models/permission.erb', "app/models/#{name.underscore}_permission.rb"
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 tip
46
- options = ' ' + @ii_opts.to_s[2..-2].gsub('=>', ': ').gsub(', :', ', ') if @ii_opts.keys.present?
47
- puts 'Please add this line to your subject model:'.green
48
- puts " act_as_i_am_i_can#{options}".green
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 <%= name.camelize %>Permission < ActiveRecord::Base
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, name: 'permission_unique_index'
18
+ index %i[ pred obj_type obj_id ], unique: true
@@ -1,10 +1,21 @@
1
- class <%= name.camelize %>Role < ActiveRecord::Base
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, null: false
7
- integer :permission_ids, array: true, default: [ ]
18
+ string :name, null: false
8
19
  string :desc
9
20
 
10
- index :name, unique: true, name: 'role_unique_index'
21
+ index :name, unique: true
@@ -1,11 +1,18 @@
1
- class <%= name.camelize %>RoleGroup < ActiveRecord::Base
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, null: false
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, name: 'role_group_unique_index'
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