i_am_i_can 3.0.0pre → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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