troles 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. data/Gemfile.lock +1 -1
  2. data/README.textile +75 -16
  3. data/VERSION +1 -1
  4. data/lib/trole/adapters/active_record/config.rb +3 -3
  5. data/lib/trole/adapters/mongoid/config.rb +5 -5
  6. data/lib/trole/config.rb +2 -2
  7. data/lib/trole_groups.rb +10 -0
  8. data/lib/trole_groups/READ THIS.textile +4 -0
  9. data/lib/trole_groups/Rolegroups design.textile +218 -0
  10. data/lib/trole_groups/adapters/active_record.rb +8 -0
  11. data/lib/trole_groups/adapters/active_record/config.rb +0 -0
  12. data/lib/trole_groups/adapters/active_record/storage.rb +0 -0
  13. data/lib/trole_groups/adapters/active_record/strategy.rb +11 -0
  14. data/lib/trole_groups/adapters/mongoid.rb +8 -0
  15. data/lib/trole_groups/adapters/mongoid/config.rb +0 -0
  16. data/lib/trole_groups/api.rb +29 -0
  17. data/lib/trole_groups/api/cache.rb +13 -0
  18. data/lib/trole_groups/api/config.rb +4 -0
  19. data/lib/trole_groups/api/core.rb +50 -0
  20. data/lib/trole_groups/api/event.rb +29 -0
  21. data/lib/trole_groups/api/read.rb +56 -0
  22. data/lib/trole_groups/api/validation.rb +44 -0
  23. data/lib/trole_groups/api/write.rb +60 -0
  24. data/lib/trole_groups/config.rb +134 -0
  25. data/lib/trole_groups/config/schema.rb +65 -0
  26. data/lib/trole_groups/config/schema/helpers.rb +11 -0
  27. data/lib/trole_groups/config/schema/role_group_helpers.rb +11 -0
  28. data/lib/trole_groups/config/valid_role_groups.rb +21 -0
  29. data/lib/trole_groups/macros.rb +42 -0
  30. data/lib/trole_groups/macros/configuration.rb +89 -0
  31. data/lib/trole_groups/macros/configuration/base_loader.rb +35 -0
  32. data/lib/trole_groups/macros/configuration/config_loader.rb +19 -0
  33. data/lib/trole_groups/macros/configuration/storage_loader.rb +20 -0
  34. data/lib/trole_groups/macros/configuration/strategy_loader.rb +38 -0
  35. data/lib/trole_groups/macros/static_roles.rb +9 -0
  36. data/lib/trole_groups/macros/strategy_options.rb +21 -0
  37. data/lib/trole_groups/operations.rb +25 -0
  38. data/lib/trole_groups/operations/read.rb +36 -0
  39. data/lib/trole_groups/operations/write.rb +42 -0
  40. data/lib/trole_groups/storage.rb +27 -0
  41. data/lib/trole_groups/storage/base_many.rb +93 -0
  42. data/lib/trole_groups/storage/embed_many.rb +58 -0
  43. data/lib/trole_groups/storage/ref_many.rb +47 -0
  44. data/lib/trole_groups/strategy.rb +30 -0
  45. data/lib/troles/adapters/active_record/config.rb +32 -9
  46. data/lib/troles/adapters/mongoid/config.rb +7 -7
  47. data/lib/troles/common/api/core.rb +1 -1
  48. data/lib/troles/common/config.rb +49 -14
  49. data/lib/troles/common/config/schema.rb +17 -23
  50. data/lib/troles/common/config/schema/helpers.rb +77 -0
  51. data/lib/troles/common/config/schema/role_helpers.rb +27 -0
  52. data/lib/troles/common/config/static_roles.rb +4 -4
  53. data/lib/troles/common/macros.rb +4 -4
  54. data/lib/troles/common/macros/configuration.rb +8 -8
  55. data/lib/troles/common/macros/strategy_options.rb +4 -4
  56. data/lib/troles/common/storage.rb +1 -0
  57. data/lib/troles/config.rb +2 -2
  58. data/lib/troles/storage/ref_many.rb +2 -3
  59. data/spec/active_record/models/ref_many.rb +3 -0
  60. data/spec/active_record/strategies/many/ref_many_spec.rb +2 -1
  61. data/spec/generic/models/role.rb +2 -1
  62. data/spec/trole_groups/api/core_api_spec.rb +14 -0
  63. data/spec/trole_groups/api/read_api_spec.rb +36 -0
  64. data/spec/trole_groups/api/write_api_spec.rb +19 -0
  65. data/spec/trole_groups/api_spec.rb +27 -0
  66. data/spec/trole_groups/generic/models.rb +3 -0
  67. data/spec/trole_groups/generic/models/role_group.rb +44 -0
  68. data/spec/trole_groups/generic/models/user.rb +9 -0
  69. data/spec/trole_groups/strategies/ref_many.rb +51 -0
  70. data/spec/trole_groups/strategy_helper.rb +9 -0
  71. data/spec/trole_groups_spec.rb +11 -0
  72. data/spec/trole_spec_helper.rb +9 -0
  73. data/spec/troles/api_spec.rb +12 -18
  74. data/troles.gemspec +52 -4
  75. metadata +85 -37
  76. data/development.sqlite3 +0 -0
  77. data/lib/troles/common/config/schema_helpers.rb +0 -95
data/Gemfile.lock CHANGED
@@ -129,7 +129,7 @@ GEM
129
129
  rack (~> 1.0)
130
130
  tilt (!= 1.3.0, ~> 1.1)
131
131
  sqlite3 (1.3.3)
132
- sugar-high (0.4.4.1)
132
+ sugar-high (0.4.4.2)
133
133
  thor (0.14.6)
134
134
  tilt (1.3.2)
135
135
  treetop (1.4.9)
data/README.textile CHANGED
@@ -6,23 +6,61 @@ The project currently consists of:
6
6
 
7
7
  * Trole - for single role strategies
8
8
  * Troles - for many roles strategies
9
+ * TroleGroups - for groups of roles
9
10
 
10
11
  The Troles project uses _role caching_ to optimize performance!
11
12
  The roles list cache of a role subject (fx a user) is only updated (retrieved from data store) when the roles of the role subject changes!
12
13
 
13
14
  Note: Troles is a full redesign of _roles generic_ and company, using lessons learned. Troles uses a much cleaner design. It is aimed at being easy to extend and easy to create adapters for etc.
14
15
 
15
- h2. Status June 2, 2011
16
+ h2. Status June 7, 2011
16
17
 
17
- Trole and Troles are now mostly implemented. Looks like a very clean and intuitive design, and now (almost) fully documented!
18
- The specs are mostly in place, but there is a need to test more usage of the APIs to ensure corner cases are covered
19
- with ArgumentException raised and such. Also needs some debugging/finetuning in various places.
18
+ I'm now in the process of implementing TroleGroups! Finally a fully integrated role groups solution for Ruby and Rails ;)
19
+ Due to the flexible design ot troles and trole, I can reuse the same infrastructure/design/architecture to implement trole_groups!
20
+ *Yiii...haaa!*
21
+
22
+ "A RoleGroup is simply another role_subject and can thus be configured with a given role strategy!"
20
23
 
21
- h3. Active Record specs!!!
24
+ Simple and Sweet :)
22
25
 
23
- The Active Record specs infrastructure is finally set up and ready. Try: @$ rspec spec/active_record/strategies/many/string_many.rb@
26
+ h2. The Ruby Class Decorator (RCD) pattern is born!!!
24
27
 
25
- h3. Yard docu
28
+ I realized a while ago that I had come upon a very cool kind of "class decorator" design pattern that can be generalized to any gem that "decorates" a class (or perhaps multiple classes) with certain behavior. Gems such as _act_as_xxx_ come to mind fx.
29
+
30
+ I will soon try to generalize this pattern into a separate project and then have trole, troles and trole_groups all use this to leverage their functionality. Also, I will make use of an idea that I just had while taking a cold shower (try it sometime if you have a mind block!).
31
+ The idea is to have an internal default API, and an exposed API that by default is a reflection of the internal API. However the user is free to block parts of the API from being loaded or define his own external API that utilizes the inner API.
32
+
33
+ Since this is a general purpose decorator pattern, there should also be a kind of Index where you can see which decorators have been applied to an object and what API each such decorator exposes! Cool stuff!!!!
34
+
35
+ Some example usage:
36
+ <pre>
37
+ return if !User.has_decorator?(:troles_group)
38
+ # do some trole group stuff!!!
39
+
40
+ # later ...
41
+
42
+ # list all decorators currently applied to the User class
43
+ puts User.decorators
44
+
45
+ # print the methods of the public Write API for the :troles_group decorator :)
46
+ puts User.decorator(:troles_group).public_api(:write).methods.sort
47
+
48
+ # print the methods of the internal Write API for the :troles_group decorator :)
49
+ puts User.decorator(:troles_group).internal_api(:write).methods.sort
50
+ </pre>
51
+
52
+ I will use the new Decorator API very soon... sth like this:
53
+
54
+ <pre>
55
+ User.decorator(:trole_groups).configure(:strategy) :ref_one do |strategy|
56
+ strategy.orm = :mongoid
57
+ strategy.auto_load = true
58
+ end.configure!
59
+ </pre>
60
+
61
+ Awesome!
62
+
63
+ h3. Yard documentation
26
64
 
27
65
  I'm using "Yard":http://rubydoc.info/docs/yard/file/docs/GettingStarted.md for documentation.
28
66
 
@@ -197,17 +235,17 @@ A custom Config class for _:single_ role strategies using _Mongoid_ could look s
197
235
  module Trole::Mongoid
198
236
  class Config < Troles::Common::Config
199
237
 
200
- def initialize clazz, options = {}
238
+ def initialize subject_class, options = {}
201
239
  super
202
240
  end
203
241
 
204
242
  def configure_relation
205
243
  case strategy
206
244
  when :ref_one
207
- has_one_for clazz, :role
208
- belongs_to_for role_model, :user
245
+ has_one_for subject_class, object_model
246
+ belongs_to_for object_model, subject_class
209
247
  when :embed_one
210
- embeds_one_for clazz, :role
248
+ embeds_one_for subject_class, object_model
211
249
  end
212
250
  end
213
251
 
@@ -218,7 +256,7 @@ module Trole::Mongoid
218
256
  when :string_one
219
257
  String
220
258
  end
221
- clazz.send(:field, role_field, type) if type
259
+ subject_class.send(:field, role_field, type) if type
222
260
  end
223
261
  </pre>
224
262
 
@@ -231,7 +269,7 @@ Example: Config class for :many roles strategies with _Mongoid_
231
269
  module Troles::Mongoid
232
270
  class Config < Troles::Common::Config
233
271
 
234
- def initialize clazz, options = {}
272
+ def initialize subject_class, options = {}
235
273
  super
236
274
  end
237
275
 
@@ -248,7 +286,7 @@ module Troles::Mongoid
248
286
  when :string_many
249
287
  String
250
288
  end
251
- clazz.send(:field, role_field, type: => type) if type
289
+ subject_class.send(:field, role_field, type: => type) if type
252
290
  end
253
291
  </pre>
254
292
 
@@ -497,9 +535,9 @@ User.troles_strategy :bit_one, :orm => :active_record do |c|
497
535
  c.auto_load = false
498
536
  c.valid_roles = [:troll_commander, :troll_warrior]
499
537
 
500
- c.static_roles = true # note: false by default
538
+ c.auto_config[:relations] = false # to take over control of setting up model relationships
501
539
 
502
- end.configure! :role_model => 'Troll', :join_model => 'UserTroll'
540
+ end.configure! :role_model => 'Troll', :role_join_model => 'UserTroll'
503
541
  </pre>
504
542
 
505
543
  Enjoy :)
@@ -511,6 +549,9 @@ The following is a list of the global Troles common configuration options:
511
549
  * default_orm
512
550
  * auto_load (true|false)
513
551
  * log_on (true|false)
552
+ * auto_config[:models] (true|false)
553
+ * auto_config[:fields] (true|false)
554
+ * auto_config[:relations] (true|false)
514
555
 
515
556
  Examples:
516
557
 
@@ -526,6 +567,24 @@ Ensures the adapter is autoloaded from the troles internal _/adapter_ folder. Le
526
567
 
527
568
  Turns on some logging to make it easier to debug what goes on behind the curtain (note: to be improved...).
528
569
 
570
+ @Troles::Common::Config.auto_config[:models] = false@
571
+
572
+ Disables troles auto configuration of models, allowing you full control.
573
+
574
+ @Troles::Common::Config.auto_config[:relations] = false@
575
+
576
+ Disables troles auto configuration of model relationships (has_many, belongs_to and such), allowing you full control.
577
+
578
+ @Troles::Common::Config.auto_config[:fields] = false@
579
+
580
+ Disables troles auto configuration of model fields (for data stores such as Data Mapper, Mongoid etc that have the concept of data fields), allowing you full control.
581
+
582
+ Note: For all the global on/off options you can opt to use same option on an individual strategy basis as part of an individual strategy configuration.
583
+
584
+ In the strategy Config class they can be used like this
585
+
586
+ @if auto_config?(:fields)@
587
+
529
588
  h2. Other notes on Application-User control
530
589
 
531
590
  _troles_ will be part of a larger project under development that will go under the name "dancing tango with trolls". This will be a rework of _cream_ and _cancan-permits_ that will target use in apllications with multiple user accounts and multiple sub applications. In this new system, _dancing_ will be the replacement of _cream_ and _tango_ the replacement of _cancan-permits_. I hope to give a talk on RubyConf 2011 about this system.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.0
1
+ 0.5.1
@@ -1,15 +1,15 @@
1
1
  module Trole::ActiveRecord
2
2
  class Config < Troles::Common::Config
3
3
 
4
- def initialize clazz, options = {}
4
+ def initialize subject_class, options = {}
5
5
  super
6
6
  end
7
7
 
8
8
  def configure_relation
9
9
  case strategy
10
10
  when :ref_one
11
- belongs_to_for clazz, role_model, :key => role_field
12
- has_many_for role_model, clazz
11
+ belongs_to_for subject_class, object_model, :key => main_field
12
+ has_many_for object_model, subject_class
13
13
  when :embed_one
14
14
  raise "EmbedOne is currently not supported by the Active Record adapter. It will be soon..."
15
15
  #clazz.send(:embeds_many, role_model_key, :class_name => role_model_class_name)
@@ -1,17 +1,17 @@
1
1
  module Trole::Mongoid
2
2
  class Config < Troles::Common::Config
3
3
 
4
- def initialize clazz, options = {}
4
+ def initialize subject_class, options = {}
5
5
  super
6
6
  end
7
7
 
8
8
  def configure_relation
9
9
  case strategy
10
10
  when :ref_one
11
- has_one_for clazz, :role
12
- belongs_to_for role_model, :user
11
+ has_one_for subject_class, object_model
12
+ belongs_to_for object_model, subject_class
13
13
  when :embed_one
14
- embeds_one clazz, :role
14
+ embeds_one subject_class, object_model
15
15
  end
16
16
  end
17
17
 
@@ -22,7 +22,7 @@ module Trole::Mongoid
22
22
  when :string_one
23
23
  String
24
24
  end
25
- clazz.send(:field, role_field, type) if type
25
+ subject_class.send(:field, role_field, type) if type
26
26
  end
27
27
 
28
28
  protected
data/lib/trole/config.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  module Trole
2
2
  class Config < Troles::Common::Config
3
- def initialize clazz, options = {}
3
+ def initialize subject_class, options = {}
4
4
  super
5
5
  end
6
6
 
7
- def configure_role_field
7
+ def configure_models
8
8
  super
9
9
  end
10
10
 
@@ -0,0 +1,10 @@
1
+ require 'troles/common'
2
+ require 'troles/macros'
3
+
4
+ module TroleGroups
5
+ autoload :Config, 'trole_groups/config'
6
+ autoload :Api, 'trole_groups/api'
7
+ autoload :Operations, 'trole_groups/operations'
8
+ autoload :Strategy, 'trole_groups/strategy'
9
+ autoload :Storage, 'trole_groups/storage'
10
+ end
@@ -0,0 +1,4 @@
1
+ h1. Important Trole Groups considerations
2
+
3
+ When role groups are enabled, it makes no sense to only enable a single role group, hence no need for :one strategies.
4
+ I will thus only implement the :many strategies similar to troles.
@@ -0,0 +1,218 @@
1
+ h1. Role groups
2
+
3
+ A RoleGroup is simply another role_subject and can thus be configured with a given role strategy! Sweet :)
4
+
5
+ In some scenarios it makes sense to allow a user to be assigned one or more role groups.
6
+
7
+ <pre>
8
+ Rolegroup.get(:bloggers).set_roles :blog_admin, :admin
9
+ Rolegroup.get(:super_admin).set_roles :blog_admin, :admin
10
+ Rolegroup.create(:super_admin, :roles => [:blog_admin, :admin])
11
+
12
+ Rolegroup.create(:super_admin).set_roles :blog_admin, :admin
13
+ </pre>
14
+
15
+ <pre>
16
+ user.rolegroups << [:bloggers, :admins]
17
+ user.in_rolegroup? :bloggers
18
+ </pre>
19
+
20
+ many rolegroups from a set of valid rolegroups
21
+
22
+
23
+ Multiple roles strategy
24
+
25
+ Schema
26
+ Integer (bitmap) field on the User class
27
+ String of comma delimited role groups on User class
28
+ References to multiple RoleGroups
29
+ Embeds multiple RoleGroups (document store)
30
+
31
+ Field stored in the datastore
32
+
33
+ trolegroups
34
+
35
+ The field is named trolegroups, in order not to conflict with the method #rolegroups used in the role group DSL.
36
+
37
+ These strategies can be named:
38
+
39
+ bit_many
40
+ string_many
41
+ ref_many
42
+ embed_many
43
+
44
+ These strategies can be implemented for any data store using any schema format.
45
+
46
+ <pre>
47
+ User
48
+ include Troles::GroupAdapter::RefMany
49
+ </pre>
50
+
51
+ When a user is assigned a given role group, he is automatically treated as having the roles of that role group. The role group cache of the user thus changes when he is assigned or removed from a role group.
52
+
53
+ The role group however can also change, and this will effect all users assigned to that role group. The RoleGroups::EventManager must be called in all these cases.
54
+
55
+ Roles API
56
+ The Roles API can be divided into
57
+
58
+ * Read operations
59
+ * Write operations and related functionality
60
+
61
+ RoleGroup Read API
62
+ These methods are available on the User instance
63
+
64
+ <pre>
65
+ # any? on rolegroups_list
66
+ def in_rolegroup? rolegroup
67
+
68
+ # rolegroup_list has one element which is rolegroup
69
+ def is_rolegroup? rolegroup
70
+
71
+ # subtraction of role_groups from rolegroups_list is empty
72
+ def has_all_rolegroups? rolegroups
73
+
74
+ # union of rolegroups and rolegroups_list is not empty
75
+ def in_any_rolegroup? rolegroups
76
+
77
+ # return roles of that rolegroup
78
+ def roles_of rolegroup
79
+
80
+ # return Set of symbols,where each symbol is a rolegroup name
81
+ # This set should be cached and only invalidated when the user has a change of roles
82
+ def rolegroups_list
83
+ </pre>
84
+
85
+ RoleGroup Write API
86
+
87
+ The User class should have an event trigger after save to see if the roles were changed.
88
+ If the roles were changed, an even should be sent to an event manager to handle this, invalidating role caches etc.
89
+
90
+ <pre>
91
+ User
92
+ after_save: update_role_groups # add event handler
93
+ </pre>
94
+
95
+ These methods are available on the User instance
96
+
97
+ <pre>
98
+ # a change to the roles of the user should be published to an event handler
99
+ # this can be used to update both the Role cache of the user and fx the RolePermit cache.
100
+ # Both (and potentially others, fx for Role Groups) can subscribe to this event!
101
+ def update_role_groups
102
+ publish_change(:role_groups) if field_changed?(rolegroups_field)
103
+ end
104
+
105
+ # check if a field on the model changed
106
+ # See http://api.rubyonrails.org/classes/ActiveModel/Dirty.html
107
+ def field_changed? name
108
+ send :“#{name}_changed?”
109
+ end
110
+
111
+ # can be customized
112
+ # here uses singleton EventManager
113
+ def publish_change event
114
+ Roles::EventManager.publish_change event, :from => self
115
+ end
116
+
117
+ # return the role field used, fx :rolegroup_value etc.
118
+ # should NOT be mutable
119
+ def rolegroups_field
120
+ :rolegroup_value
121
+ end
122
+
123
+ def add_rolegroup
124
+ role_groups << role
125
+ end
126
+
127
+ def remove_rolegroup
128
+ role_groups << role
129
+ end
130
+
131
+ # should return a RoleGroups::Operations object
132
+ def role_groups
133
+ TRoles::RoleGroups::Operations.new(self)
134
+
135
+ class TRoles::RoleGroups:: Operations
136
+ include ReadOperations
137
+ include WriteOperations
138
+
139
+ def initialize user
140
+ end
141
+ end
142
+ </pre>
143
+
144
+ <pre>
145
+ module TRoles: RoleGroups::ReadOperations
146
+ # check if any of the rolegroups have the given role
147
+
148
+ def contains? role_group
149
+ list.include? role_group
150
+ end
151
+ alias_method :includes?, :contains?
152
+
153
+ # symbol list of role groups
154
+ def list
155
+
156
+ # Set of roles from all role groups
157
+ def roles_list
158
+
159
+ def get *role_groups
160
+
161
+ end
162
+ </pre>
163
+
164
+ <pre>
165
+ module TRoles: RoleGroups::WriteOperations
166
+ def + # add role group
167
+ alias_method <<
168
+
169
+ def - # remove role groups
170
+ end
171
+
172
+ user.roles_groups.get(:bloggers) => returns :bloggers if in roles_list, or raises error
173
+ user.roles_groups.get(:bloggers, :admins) => returns [:bloggers, :admins], or error
174
+
175
+ if user.role_groups.roles_list == [:admin, :blogger]
176
+ if user.role_groups.have_role? :blogger
177
+
178
+ user.role_groups.add :bloggers
179
+ user.role_groups << :bloggers
180
+ user.role_groups + [:bloggers, :editors]
181
+ user.role_groups - :admins
182
+ </pre>
183
+
184
+ Relational schema
185
+
186
+ <pre>
187
+ RoleGroup
188
+ has_many :roles
189
+ </pre>
190
+
191
+ <pre>
192
+ Role
193
+ belongs_to :role_group
194
+ belongs_to :user
195
+ </pre>
196
+
197
+ If the RoleGroup is used in a Relational schema model, the RoleGroup should belong to a user and a Role should belong to a Group.
198
+
199
+ Roles field on RoleGroup
200
+
201
+ <pre>
202
+ RoleGroup
203
+ def valid_roles
204
+
205
+ belongs_to :user
206
+ field roles (String, Integer bitmap)
207
+ </pre>
208
+
209
+ In a non-relational schema model, the RoleGroup would still belong to a User but the roles could be a field of either String or Integer (bitmap) instead of a relation to a Role model.
210
+ If an integer bitmap is used, the bits would map onto the valid_roles list that returns a list of role symbols.
211
+
212
+ The valid_roles method should get the list of valid roles from a singleton such as TRoles::Configuration. The actual implementation could either use a static list, pull it from a yaml file or perhaps execute all_roles on Role.
213
+
214
+ <pre>
215
+ class Role
216
+ scope :role_list, lambda { all.map {|r| name.to_sym} }
217
+ end
218
+ </pre>