troles 0.5.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 (243) hide show
  1. data/.rspec +1 -0
  2. data/Gemfile +26 -0
  3. data/Gemfile.lock +161 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.textile +686 -0
  6. data/Rakefile +48 -0
  7. data/VERSION +1 -0
  8. data/config/database.yml +4 -0
  9. data/development.sqlite3 +0 -0
  10. data/lib/trole/adapters/active_record/config.rb +23 -0
  11. data/lib/trole/adapters/active_record/storage.rb +0 -0
  12. data/lib/trole/adapters/active_record/strategy.rb +0 -0
  13. data/lib/trole/adapters/active_record.rb +8 -0
  14. data/lib/trole/adapters/mongoid/config.rb +34 -0
  15. data/lib/trole/adapters/mongoid/storage.rb +0 -0
  16. data/lib/trole/adapters/mongoid/strategy.rb +0 -0
  17. data/lib/trole/adapters/mongoid.rb +0 -0
  18. data/lib/trole/api/cache.rb +9 -0
  19. data/lib/trole/api/config.rb +12 -0
  20. data/lib/trole/api/core.rb +20 -0
  21. data/lib/trole/api/event.rb +9 -0
  22. data/lib/trole/api/read.rb +14 -0
  23. data/lib/trole/api/validation.rb +9 -0
  24. data/lib/trole/api/write.rb +25 -0
  25. data/lib/trole/api.rb +41 -0
  26. data/lib/trole/config.rb +15 -0
  27. data/lib/trole/macros.rb +5 -0
  28. data/lib/trole/operations/read.rb +12 -0
  29. data/lib/trole/operations/write.rb +11 -0
  30. data/lib/trole/operations.rb +34 -0
  31. data/lib/trole/storage/base_one.rb +46 -0
  32. data/lib/trole/storage/bit_one.rb +43 -0
  33. data/lib/trole/storage/embed_one.rb +36 -0
  34. data/lib/trole/storage/ref_one.rb +39 -0
  35. data/lib/trole/storage/string_one.rb +50 -0
  36. data/lib/trole/storage.rb +14 -0
  37. data/lib/trole/strategy.rb +35 -0
  38. data/lib/trole.rb +10 -0
  39. data/lib/troles/adapters/active_record/Design Notes.textile +4 -0
  40. data/lib/troles/adapters/active_record/config.rb +51 -0
  41. data/lib/troles/adapters/active_record/storage/embed_many.rb +8 -0
  42. data/lib/troles/adapters/active_record/storage.rb +5 -0
  43. data/lib/troles/adapters/active_record/strategy.rb +11 -0
  44. data/lib/troles/adapters/active_record.rb +8 -0
  45. data/lib/troles/adapters/mongoid/Design Notes.textile +3 -0
  46. data/lib/troles/adapters/mongoid/config.rb +45 -0
  47. data/lib/troles/adapters/mongoid.rb +8 -0
  48. data/lib/troles/api/cache.rb +4 -0
  49. data/lib/troles/api/config.rb +9 -0
  50. data/lib/troles/api/core.rb +9 -0
  51. data/lib/troles/api/event.rb +4 -0
  52. data/lib/troles/api/read.rb +4 -0
  53. data/lib/troles/api/validation.rb +4 -0
  54. data/lib/troles/api/write.rb +4 -0
  55. data/lib/troles/api.rb +40 -0
  56. data/lib/troles/common/api/cache.rb +12 -0
  57. data/lib/troles/common/api/config.rb +4 -0
  58. data/lib/troles/common/api/core.rb +52 -0
  59. data/lib/troles/common/api/event.rb +39 -0
  60. data/lib/troles/common/api/read.rb +44 -0
  61. data/lib/troles/common/api/validation.rb +44 -0
  62. data/lib/troles/common/api/write.rb +76 -0
  63. data/lib/troles/common/api.rb +28 -0
  64. data/lib/troles/common/config/schema.rb +72 -0
  65. data/lib/troles/common/config/schema_helpers.rb +95 -0
  66. data/lib/troles/common/config/static_roles.rb +14 -0
  67. data/lib/troles/common/config/valid_roles.rb +21 -0
  68. data/lib/troles/common/config.rb +96 -0
  69. data/lib/troles/common/dependencies.rb +9 -0
  70. data/lib/troles/common/event_manager.rb +40 -0
  71. data/lib/troles/common/macros/configuration/base_loader.rb +40 -0
  72. data/lib/troles/common/macros/configuration/config_loader.rb +19 -0
  73. data/lib/troles/common/macros/configuration/storage_loader.rb +20 -0
  74. data/lib/troles/common/macros/configuration/strategy_loader.rb +38 -0
  75. data/lib/troles/common/macros/configuration.rb +89 -0
  76. data/lib/troles/common/macros/static_roles.rb +9 -0
  77. data/lib/troles/common/macros/strategy_options.rb +21 -0
  78. data/lib/troles/common/macros.rb +38 -0
  79. data/lib/troles/common/marshaller/bitmask.rb +43 -0
  80. data/lib/troles/common/marshaller/generic.rb +24 -0
  81. data/lib/troles/common/marshaller.rb +14 -0
  82. data/lib/troles/common/operations/read.rb +28 -0
  83. data/lib/troles/common/operations/write.rb +42 -0
  84. data/lib/troles/common/operations.rb +33 -0
  85. data/lib/troles/common/storage.rb +73 -0
  86. data/lib/troles/common.rb +17 -0
  87. data/lib/troles/config.rb +15 -0
  88. data/lib/troles/macros.rb +7 -0
  89. data/lib/troles/meta.rb +5 -0
  90. data/lib/troles/operations/read.rb +6 -0
  91. data/lib/troles/operations/write.rb +6 -0
  92. data/lib/troles/operations.rb +12 -0
  93. data/lib/troles/storage/base_many.rb +25 -0
  94. data/lib/troles/storage/bit_many.rb +56 -0
  95. data/lib/troles/storage/embed_many.rb +58 -0
  96. data/lib/troles/storage/ref_many.rb +44 -0
  97. data/lib/troles/storage/string_many.rb +41 -0
  98. data/lib/troles/storage.rb +13 -0
  99. data/lib/troles/strategy.rb +34 -0
  100. data/lib/troles.rb +11 -0
  101. data/playbox/old_rake +25 -0
  102. data/spec/Guide to running specs.textile +16 -0
  103. data/spec/active_record/migrations/many/bit_many.rb +16 -0
  104. data/spec/active_record/migrations/many/ref_many.rb +31 -0
  105. data/spec/active_record/migrations/many/string_many.rb +16 -0
  106. data/spec/active_record/migrations/one/bit_one.rb +14 -0
  107. data/spec/active_record/migrations/one/ref_one.rb +20 -0
  108. data/spec/active_record/migrations/one/string_one.rb +14 -0
  109. data/spec/active_record/models/ref_many.rb +10 -0
  110. data/spec/active_record/models/ref_one.rb +10 -0
  111. data/spec/active_record/models/role.rb +2 -0
  112. data/spec/active_record/models/user.rb +5 -0
  113. data/spec/active_record/models.rb +2 -0
  114. data/spec/active_record/strategies/many/bit_many_spec.rb +41 -0
  115. data/spec/active_record/strategies/many/ref_many_spec.rb +45 -0
  116. data/spec/active_record/strategies/many/string_many_spec.rb +39 -0
  117. data/spec/active_record/strategies/one/bit_one_spec.rb +35 -0
  118. data/spec/active_record/strategies/one/ref_one_spec.rb +41 -0
  119. data/spec/active_record/strategies/one/string_one_spec.rb +35 -0
  120. data/spec/active_record/strategy_helper.rb +4 -0
  121. data/spec/active_record_helper.rb +50 -0
  122. data/spec/db/database.yml +4 -0
  123. data/spec/dummy/Gemfile.lock +108 -0
  124. data/spec/dummy/Rakefile +7 -0
  125. data/spec/dummy/app/assets/images/rails.png +0 -0
  126. data/spec/dummy/app/assets/javascripts/application.js +9 -0
  127. data/spec/dummy/app/assets/stylesheets/application.css +7 -0
  128. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  129. data/spec/dummy/app/controllers/main_controller.rb +16 -0
  130. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  131. data/spec/dummy/app/mailers/.gitkeep +0 -0
  132. data/spec/dummy/app/models/.gitkeep +0 -0
  133. data/spec/dummy/app/models/ref_many_user.rb +7 -0
  134. data/spec/dummy/app/models/ref_one_user.rb +3 -0
  135. data/spec/dummy/app/models/role.rb +4 -0
  136. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  137. data/spec/dummy/app/views/main/index.html.erb +3 -0
  138. data/spec/dummy/config/application.rb +54 -0
  139. data/spec/dummy/config/boot.rb +10 -0
  140. data/spec/dummy/config/database.yml +25 -0
  141. data/spec/dummy/config/environment.rb +5 -0
  142. data/spec/dummy/config/environments/development.rb +24 -0
  143. data/spec/dummy/config/environments/production.rb +52 -0
  144. data/spec/dummy/config/environments/test.rb +39 -0
  145. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  146. data/spec/dummy/config/initializers/inflections.rb +10 -0
  147. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  148. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  149. data/spec/dummy/config/initializers/session_store.rb +8 -0
  150. data/spec/dummy/config/initializers/troles.rb +3 -0
  151. data/spec/dummy/config/initializers/wrap_parameters.rb +12 -0
  152. data/spec/dummy/config/locales/en.yml +5 -0
  153. data/spec/dummy/config/routes.rb +60 -0
  154. data/spec/dummy/config.ru +4 -0
  155. data/spec/dummy/db/migrate/01_create_roles.rb +14 -0
  156. data/spec/dummy/db/migrate/02_create_ref_many_users.rb +20 -0
  157. data/spec/dummy/db/schema.rb +32 -0
  158. data/spec/dummy/db/seeds.rb +4 -0
  159. data/spec/dummy/log/.gitkeep +0 -0
  160. data/spec/dummy/public/404.html +26 -0
  161. data/spec/dummy/public/422.html +26 -0
  162. data/spec/dummy/public/500.html +26 -0
  163. data/spec/dummy/public/favicon.ico +0 -0
  164. data/spec/dummy/script/rails +6 -0
  165. data/spec/dummy_spec_helper.rb +33 -0
  166. data/spec/factories.rb +8 -0
  167. data/spec/generic/models/accounts/admin_account.rb +7 -0
  168. data/spec/generic/models/accounts/blogger_account.rb +7 -0
  169. data/spec/generic/models/accounts/user_account.rb +7 -0
  170. data/spec/generic/models/accounts.rb +1 -0
  171. data/spec/generic/models/base_user.rb +27 -0
  172. data/spec/generic/models/role.rb +37 -0
  173. data/spec/generic/models/user.rb +7 -0
  174. data/spec/generic/models.rb +3 -0
  175. data/spec/integration/navigation_spec.rb +9 -0
  176. data/spec/integration/troles/Running dummy tests.textile +35 -0
  177. data/spec/integration/troles/navigation_spec.rb +49 -0
  178. data/spec/mongoid/models/ref_many.rb +15 -0
  179. data/spec/mongoid/models/ref_one.rb +15 -0
  180. data/spec/mongoid/models/role.rb +5 -0
  181. data/spec/mongoid/models/user.rb +9 -0
  182. data/spec/mongoid/models.rb +2 -0
  183. data/spec/mongoid/strategies/many/bit_many_spec.rb +35 -0
  184. data/spec/mongoid/strategies/many/ref_many_spec.rb +35 -0
  185. data/spec/mongoid/strategies/many/string_many_spec.rb +30 -0
  186. data/spec/mongoid/strategies/one/bit_one_spec.rb +26 -0
  187. data/spec/mongoid/strategies/one/ref_one_spec.rb +31 -0
  188. data/spec/mongoid/strategies/one/string_one_spec.rb +26 -0
  189. data/spec/mongoid/strategy_helper.rb +4 -0
  190. data/spec/mongoid_helper.rb +19 -0
  191. data/spec/playbox/rspec_examples.rb +381 -0
  192. data/spec/support/shared_examples.rb +1 -0
  193. data/spec/trole/Trole Design.textile +4 -0
  194. data/spec/trole/api/cache_api_spec.rb +2 -0
  195. data/spec/trole/api/core_api_spec.rb +4 -0
  196. data/spec/trole/api/event_api.rb +2 -0
  197. data/spec/trole/api/operations_api_spec.rb +2 -0
  198. data/spec/trole/api/read_api_spec.rb +5 -0
  199. data/spec/trole/api/validation_api_spec.rb +2 -0
  200. data/spec/trole/api/write_api_spec.rb +2 -0
  201. data/spec/trole/api_spec.rb +60 -0
  202. data/spec/trole/multi_roles_spec.rb +163 -0
  203. data/spec/trole/operations/read_spec.rb +18 -0
  204. data/spec/trole/operations/write_spec.rb +0 -0
  205. data/spec/trole/playbox/shared_examples.rb +107 -0
  206. data/spec/trole/strategies/bit_one_spec.rb +22 -0
  207. data/spec/trole/strategies/embed_one_spec.rb +32 -0
  208. data/spec/trole/strategies/ref_one_spec.rb +29 -0
  209. data/spec/trole/strategies/string_one_spec.rb +26 -0
  210. data/spec/trole/strategy_helper.rb +3 -0
  211. data/spec/trole/two_roles_spec.rb +76 -0
  212. data/spec/trole_spec.rb +12 -0
  213. data/spec/trole_spec_helper.rb +20 -0
  214. data/spec/troles/api/cache_api_spec.rb +2 -0
  215. data/spec/troles/api/core_api_spec.rb +4 -0
  216. data/spec/troles/api/event_api.rb +2 -0
  217. data/spec/troles/api/read_api_spec.rb +2 -0
  218. data/spec/troles/api/validation_api_spec.rb +2 -0
  219. data/spec/troles/api/write_api_spec.rb +2 -0
  220. data/spec/troles/api_spec.rb +41 -0
  221. data/spec/troles/common/api/cache_api_spec.rb +31 -0
  222. data/spec/troles/common/api/config_api.rb +0 -0
  223. data/spec/troles/common/api/core_api_spec.rb +14 -0
  224. data/spec/troles/common/api/event_api_spec.rb +9 -0
  225. data/spec/troles/common/api/operations_api_spec.rb +55 -0
  226. data/spec/troles/common/api/read_api_spec.rb +23 -0
  227. data/spec/troles/common/api/validation_api_spec.rb +46 -0
  228. data/spec/troles/common/api/write_api_spec.rb +81 -0
  229. data/spec/troles/common/api_spec.rb +101 -0
  230. data/spec/troles/common/config_spec.rb +11 -0
  231. data/spec/troles/common/multi_roles_spec.rb +142 -0
  232. data/spec/troles/marshaller/bitmask_spec.rb +14 -0
  233. data/spec/troles/operations/read_ops_spec.rb +0 -0
  234. data/spec/troles/operations/write_ops_spec.rb +0 -0
  235. data/spec/troles/playbox/shared_examples.rb +68 -0
  236. data/spec/troles/strategies/bit_many_spec.rb +30 -0
  237. data/spec/troles/strategies/embed_many_spec.rb +35 -0
  238. data/spec/troles/strategies/ref_many_spec.rb +36 -0
  239. data/spec/troles/strategies/string_many_spec.rb +32 -0
  240. data/spec/troles/strategy_helper.rb +3 -0
  241. data/spec/troles_spec.rb +10 -0
  242. data/troles.gemspec +325 -0
  243. metadata +469 -0
data/README.textile ADDED
@@ -0,0 +1,686 @@
1
+ h1. Troles
2
+
3
+ Troles aims to be a complete (end-all) roles solution for Ruby and is also targeted to be used with Rails 3.
4
+ As of early June it is still under development but getting close to first release.
5
+ The project currently consists of:
6
+
7
+ * Trole - for single role strategies
8
+ * Troles - for many roles strategies
9
+
10
+ The Troles project uses _role caching_ to optimize performance!
11
+ 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
+ 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
+ h2. Status June 2, 2011
16
+
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.
20
+
21
+ h3. Active Record specs!!!
22
+
23
+ The Active Record specs infrastructure is finally set up and ready. Try: @$ rspec spec/active_record/strategies/many/string_many.rb@
24
+
25
+ h3. Yard docu
26
+
27
+ I'm using "Yard":http://rubydoc.info/docs/yard/file/docs/GettingStarted.md for documentation.
28
+
29
+ <pre>$ yard server
30
+
31
+ From browser, go to: http://0.0.0.0:8808/ # then you can enjoy! the nice documentation :)
32
+ </pre>
33
+
34
+ There is now support for Caching and invalidation of the _role_list_ when the roles change.
35
+ The specs now validate that caching works as it should.
36
+
37
+ Please help out to finalize this project! :)
38
+
39
+ h2. Bug hunting by running specs
40
+
41
+ Run specs for at most one strategy at the time for now...
42
+
43
+ @$ rspec spec/troles/strategies/bit_many_spec.rb@
44
+
45
+ Please see the document _spec/Guide to running specs.textile_ where I advise on how best to do "bug hunting" to help get this project off the ground!
46
+
47
+ h2. Role strategies
48
+
49
+ The following lists the role strategies to be supported
50
+
51
+ * Single - one role from a set of valid roles
52
+ * Many - many roles from a set of valid roles
53
+
54
+ h3. Single role strategies
55
+
56
+ Schemas:
57
+ * Boolean field on the User class (admin role)
58
+ * String field on the User class
59
+ * One reference to a Role
60
+ * Embeds one Role (document store only)
61
+
62
+ Field stored in the data store: @trole@
63
+
64
+ The field is named _trole_, in order not to conflict with the method _#role_ used in the _Role DSL_.
65
+
66
+ h3. Multiple roles strategies
67
+
68
+ Schemas
69
+ * Integer (bitmap) field on the User class
70
+ * String of comma delimited roles on User class
71
+ * References to multiple Roles
72
+ * Embeds multiple Roles
73
+
74
+ Field stored in the datastore: @troles@
75
+
76
+ The field is named troles, in order not to conflict with the method _#roles_ used in the _Role DSL_.
77
+
78
+ These strategies can be grouped and named as follows:
79
+
80
+ Single role:
81
+ * bit_one
82
+ * string_one
83
+ * ref_one
84
+ * embed_one (document store)
85
+
86
+ Multiple roles
87
+ * bit_many
88
+ * string_many
89
+ * ref_many
90
+ * embed_many (document store)
91
+
92
+ These strategies can be implemented for any data store using any schema format.
93
+
94
+ h3. Using roles strategies with Users and User accounts
95
+
96
+ Roles are assigned to role subject classes such as _User_ or _UserAccount_ (YES, Devise can have multiple user accounts!). The class that has a role strategy assigned is referred to as the _role subject class_.
97
+ Different role subject classes can have different role strategies!
98
+
99
+ When using Devise this could translate fx into a UserAccount with a "many roles" strategy and an AdminAccount with a "single role" strategy (or vice versa).
100
+
101
+ Example:
102
+
103
+ <pre>
104
+ require 'troles'
105
+ require 'troles/macros'
106
+
107
+ class UserAccount
108
+ troles_strategy(:string_many).configure!
109
+ end
110
+
111
+ class AdminAccount
112
+ troles_strategy(:bit_one, :static => true).configure!
113
+ end
114
+ </pre>
115
+
116
+ The special troles macros must be enabled my requiring the _trole/macros_ file.
117
+
118
+ h3. Macro options
119
+
120
+ The @:static => true@ options is used to indicate, that the role can not be changed after being initially set.
121
+ But we are getting ahead of ourselves... (more on this later).
122
+ Troles can easily be extended to support other macro options if needed.
123
+
124
+ h2. Roles API
125
+
126
+ The Roles API can be divided into:
127
+
128
+ * Core
129
+ * Event/Cache
130
+ * Read
131
+ * Write
132
+ * Store
133
+ * Validation
134
+ * Operations object
135
+
136
+ There is an equivalen Trole API for single role strategies.
137
+
138
+ h3. Event/Cache API
139
+
140
+ The User class should have an event trigger after *save* to react when the roles have changed.
141
+ If the roles were changed, an even should be sent to an _Event Manager_ to handle this. The event manager
142
+ can have subscribers to events.
143
+
144
+ Also any write event to the datastore should be predicated on _#static_roles?_ not being true for the user (thus ensuring guest roles are never updated).
145
+
146
+ Save triggers call to Event#update_roles
147
+ <pre>
148
+ User
149
+ after_save: update_roles # event handler
150
+ </pre>
151
+
152
+ <pre>
153
+ module Troles::Api::Event
154
+ def update_roles
155
+ def publish_change event
156
+ </pre>
157
+
158
+ h3. Roles Read API
159
+
160
+ This API operates directly on a user, fx _user#has_role?_
161
+
162
+ <pre>
163
+ user.has_role? :admin
164
+ user.is_role? :editor
165
+ user.has_any_roles? :editor, :admin
166
+ </pre>
167
+
168
+ h3. Roles Write API
169
+
170
+ This API operates directly on a user, fx _user#has_role?_
171
+
172
+ <pre>
173
+ user.add_role :admin
174
+ user.remove_role :editor
175
+ </pre>
176
+
177
+ h3. Roles Operations object
178
+
179
+ The Roles Operations object is available on user#roles
180
+
181
+ <pre>
182
+ user.roles + :admin
183
+ user.roles - :editor
184
+ user.roles << [:editor, :admin]
185
+ user.roles.clear!
186
+ </pre>
187
+
188
+ h3. Creating a custom Data Store Adapter (DSA)
189
+
190
+ An adapter almost always requires a custom Config class implementation. Look at the _troles/common/config.rb_ to see what functionality is available that you can use.
191
+
192
+ Note that :single role strategies always have the namespace 'Trole' whereas for :many it is 'Troles'. This convention is used throughout troles and breaking this convention will thus break the troles functionality.
193
+
194
+ A custom Config class for _:single_ role strategies using _Mongoid_ could look sth. like this:
195
+
196
+ <pre>
197
+ module Trole::Mongoid
198
+ class Config < Troles::Common::Config
199
+
200
+ def initialize clazz, options = {}
201
+ super
202
+ end
203
+
204
+ def configure_relation
205
+ case strategy
206
+ when :ref_one
207
+ has_one_for clazz, :role
208
+ belongs_to_for role_model, :user
209
+ when :embed_one
210
+ embeds_one_for clazz, :role
211
+ end
212
+ end
213
+
214
+ def configure_field
215
+ type = case strategy
216
+ when :bit_one
217
+ Boolean
218
+ when :string_one
219
+ String
220
+ end
221
+ clazz.send(:field, role_field, type) if type
222
+ end
223
+ </pre>
224
+
225
+ As you can see, you have several nice convenience methods available to help set up these somewhat complex model relationships!
226
+ See the _schema_helpers.rb_ file for more details!
227
+
228
+ Example: Config class for :many roles strategies with _Mongoid_
229
+
230
+ <pre>
231
+ module Troles::Mongoid
232
+ class Config < Troles::Common::Config
233
+
234
+ def initialize clazz, options = {}
235
+ super
236
+ end
237
+
238
+ def configure_relation
239
+ case strategy
240
+ ...
241
+ end
242
+ end
243
+
244
+ def configure_field
245
+ type = case strategy
246
+ when :bit_many
247
+ Integer
248
+ when :string_many
249
+ String
250
+ end
251
+ clazz.send(:field, role_field, type: => type) if type
252
+ end
253
+ </pre>
254
+
255
+ h3. Creating a custom Strategy Storage Adapter (SSA)
256
+
257
+ Often you need to define Storage class implementations for some of the strategies, especially those that reference or embed other models.
258
+
259
+ You must implement the following methods:
260
+
261
+ - #display_roles (returns symbol list of roles)
262
+ - #clear! (clear the roles in the datastore)
263
+ - #set_roles (set the roles in the datastore)
264
+ - #set_role (as #set_roles)
265
+
266
+ Note: #set_role is only required for :single role strategies. In this case #set_roles is not required, as the superclass implementation simply calls #set_role with the first role.
267
+
268
+ Example custom SSA (encrypted role string):
269
+
270
+ <pre>
271
+ module Trole::Storage
272
+ module EncryptedStringOne < BaseOne
273
+ def initialize role_subject
274
+ super
275
+ end
276
+
277
+ def display_roles
278
+ decrypted_value.split(',')
279
+ end
280
+
281
+ # saves the role of the role subject in the data store
282
+ # @param [Symbol] the role name
283
+ def set_role role
284
+ set_encrypted_ds_field role.to_s
285
+ end
286
+
287
+ # sets the role to its default state
288
+ def clear!
289
+ set_encrypted_ds_field ""
290
+ end
291
+
292
+ protected
293
+
294
+ def decrypted_value
295
+ ds_field_value.decrypt!
296
+ end
297
+
298
+ def set_encrypted_value value
299
+ set_ds_field value.encrypt!
300
+ end
301
+ end
302
+ end
303
+ </pre>
304
+
305
+ h3. Custom Storage for an ORM (or data store)
306
+
307
+ In some cases it is useful to rewrite part of the base Storage functionality. One such method is #find_roles.
308
+
309
+ <pre>
310
+ module Troles::Storage
311
+ class BaseMany < Troles::Common::Storage
312
+ def find_roles *roles
313
+ role_model.where(:name => roles.flatten).all
314
+ end
315
+ ...
316
+ end
317
+ </pre>
318
+
319
+ Active Record and Mongoid both implement the above API, so no need to customize this method in the Storage adapter.
320
+ For most other ORMs/data stores you will likely have to write your own logic to achieve this.
321
+
322
+ <pre>
323
+ module Troles::MongoidStorage
324
+ class RefMany < Troles::Storage::BaseMany
325
+
326
+ def find_roles *roles
327
+ # my own custom datastore logic!!!
328
+ end
329
+ ...
330
+ </pre>
331
+
332
+ h3. Custom data marshaller
333
+
334
+ You can also use a custom "Marshaller":http://en.wikipedia.org/wiki/Marshalling_%28computer_science%29 to store the roles in a non-standard format. This is used for the _BitMany_ strategy, which has a special _Marshaller::BitMask_ class which handles conversion between a list of symbols to an _Integer_ bitmap representing it (relative to a valid roles list!). You can create your own _Marshaller_, fx to encrypt the roles info or whatever you like!
335
+
336
+ <pre>
337
+ module Troles::Marshaller
338
+ class Encryption < Generic
339
+ def initialize role_subject
340
+ super
341
+ end
342
+
343
+ # convert marshalled value into roles symbols list
344
+ def read
345
+ ...
346
+ end
347
+
348
+ # convert roles symbols list into value to be marshalled
349
+ def write *roles
350
+ ...
351
+ end
352
+ end
353
+ end
354
+ </pre>
355
+
356
+ h3. Using a custom Marshaller in a Storage implementation
357
+
358
+ The following example is taken from the BitOne Storage implementation that is part of troles:
359
+
360
+ <pre>
361
+ require 'troles/common/marshaller'
362
+
363
+ module Trole::Storage
364
+ class BitOne < BaseOne
365
+ # display the role as a list of one symbol
366
+ # see Troles::Marshaller::Bitmask
367
+ # @return [Array<Symbol>] roles list
368
+ def display_roles
369
+ raise "BitOne requires exactly two valid roles, was: #{valid_roles}" if !(valid_roles.size == 2)
370
+ [bitmask.read].flatten
371
+ end
372
+
373
+ # saves the role for the role subject in the data store
374
+ # @param [Symbol] role name
375
+ def set_role role
376
+ num = bitmask.write role
377
+ set_ds_field(num == 1 ? false : true) # boolean field in Data store
378
+ end
379
+
380
+ # Clears the role state of the role subject
381
+ def clear!
382
+ set_ds_field false
383
+ end
384
+
385
+ protected
386
+
387
+ def bitmask
388
+ @bitmask ||= Troles::Common::Marshaller::Bitmask.new role_subject
389
+ end
390
+ ...
391
+ </pre>
392
+
393
+ Note that the same _BitMask_ marshaller is also reused in the _BitMany_ storage!
394
+
395
+ The _#bitmask_ method returns an instance of the _Bitmask_ marshaller, instantiated with the _role_subject_ (the instance that has the #role_list method, typically the user or user account). To use the _Encryption_ marshaller in an Encryption storage we could create a _#marshaller_ method:
396
+
397
+ <pre>
398
+ def marshaller
399
+ @marshaller ||= Troles::Marshaller::Encryption.new role_subject
400
+ end
401
+ </pre>
402
+
403
+ Then use @marshaller.write(*roles)@ in the _set_xxxx_ methods and @marshaller.read@ in _#display_roles_ method of the storage, as needed.
404
+
405
+ h3. Custom troles strategy
406
+
407
+ To add a new strategy, you can optionally create a special Strategy module that acts as the module included by the troles macro.
408
+ Using standard naming convention it will be found and used by the _troles_strategy_ macro method :)
409
+ If you don't create a specialized Strategy module, troles will just use the _BaseOne_ or _BaseMany_ depending on the singularity (one or many)
410
+ of the strategy.
411
+
412
+ Using naming conventions, the strategy will always try to use a matching storage class (substitute Strategy for Storage).
413
+ If you don't define a custom Strategy module or Storage class, troles will just use a generic implementation.
414
+
415
+ Try to keep it simple and start by defining only a Config class and then see if you need to implement a custom Storage class.
416
+ You rarely need to implement a custom Strategy module or custom API implementations.
417
+
418
+ Here is an example of a custom Strategy implementation for _EncryptedStringMany_ that simply wraps the _BaseMany_ strategy implementation.
419
+ This example demonstrates how you can easily override functionality with custom implementations by including modules "on top".
420
+
421
+ <pre>
422
+ module Troles::Strategy
423
+ module EncryptedStringMany
424
+
425
+ # What to add to the role subject class when this role strategy is included
426
+ # @param [Class] the role subject class to
427
+ def self.included(base)
428
+ base.send :include, BaseMany
429
+ base.send :include, InstanceMethods
430
+ base.extend ClassMethods
431
+ end
432
+
433
+ module ClassMethods
434
+ # .. my custom class methods
435
+ end
436
+
437
+ module InstanceMethods
438
+ # .. my custom instance methods
439
+ end
440
+ end
441
+ end
442
+ </pre>
443
+
444
+ h3. Creating a Base strategy for an Adapter
445
+
446
+ In some cases you need a custom Base strategy that contains common functionality shared among strategies with the same singularity (one or many).
447
+
448
+ Example: _BaseMany_, used as the base for all Many roles strategies
449
+
450
+ <pre>
451
+ module Troles::Mongoid
452
+ module Strategy
453
+ module BaseMany
454
+ # @param [Class] the role subject class for which to include the Role strategy (fx User Account)
455
+ #
456
+ def self.included(base)
457
+ base.send :include, Troles::Strategy::BaseMany
458
+
459
+ # base.send :include, InstanceMethods
460
+ # base.extend ClassMethods
461
+ end
462
+ end
463
+ end
464
+ end
465
+ </pre>
466
+
467
+ To use your adapter, simply pass an extra option to the _troles_strategy_ macro:
468
+
469
+ <pre>
470
+ User.troles_strategy(:bit_one, :orm => :mongoid).configure!
471
+ </pre>
472
+
473
+ This even allows you to use different ORM role strategies/storages for different user accounts simultaneously!!!
474
+
475
+ Using the :auto_load option will 'auto load' (i.e require) the orm adapter from the built-in catalog of adapters that come with troles.
476
+ You can include a specific custom (or 3rd party) adapter manually. In the future it will be possible to configure troles with adapters
477
+ and specify how/where to load them from as part of this configuration!
478
+
479
+ <pre>
480
+ User.troles_strategy(:bit_one, :orm => :mongoid, :auto_load => true).configure!
481
+ </pre>
482
+
483
+ You can also specify some of the options relevant to model configuration on the call to #configure if you like ;)
484
+
485
+ <pre>
486
+ User.troles_strategy(:bit_one, :orm => :active_record, :auto_load => true).configure! :role_model => 'Troll'
487
+ </pre>
488
+
489
+ The _troles_strategy_ macro will yield the Config object if you pass it a block.
490
+ This allows you to configure your stategy with troles inside the block and then call _configure!_ on end of the block.
491
+ Using all this in combination, you could configure it all doing sth. like this:
492
+
493
+ <pre>
494
+ require 'my/own/active_record/adapter'
495
+
496
+ User.troles_strategy :bit_one, :orm => :active_record do |c|
497
+ c.auto_load = false
498
+ c.valid_roles = [:troll_commander, :troll_warrior]
499
+
500
+ c.static_roles = true # note: false by default
501
+
502
+ end.configure! :role_model => 'Troll', :join_model => 'UserTroll'
503
+ </pre>
504
+
505
+ Enjoy :)
506
+
507
+ h2. Global configuration
508
+
509
+ The following is a list of the global Troles common configuration options:
510
+
511
+ * default_orm
512
+ * auto_load (true|false)
513
+ * log_on (true|false)
514
+
515
+ Examples:
516
+
517
+ @Troles::Common::Config.default_orm = :mongoid@
518
+
519
+ Troles uses this as the ORM setting in case you don't specify the orm when you configure a troles strategy.
520
+
521
+ @Troles::Common::Config.auto_load = true@
522
+
523
+ Ensures the adapter is autoloaded from the troles internal _/adapter_ folder. Leave this to false (defalt) if you have rolled your own or use a 3rd party adapter.
524
+
525
+ @Troles::Common::Config.log_on = true@
526
+
527
+ Turns on some logging to make it easier to debug what goes on behind the curtain (note: to be improved...).
528
+
529
+ h2. Other notes on Application-User control
530
+
531
+ _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.
532
+
533
+ h3. Guest users
534
+
535
+ From the Devise wiki: https://github.com/plataformatec/devise/wiki/How-To:-Create-a-guest-user
536
+
537
+ "In some applications, it's useful to have a guest User object to pass around even before the (human) user has registered or logged in. Normally, you want this guest user to persist as long as the browser session persists.
538
+
539
+ Our approach is to create a guest user object in the database and store its id in session[:guest_user_id]. When (and if) the user registers or logs in, we delete the guest user and clear the session variable. A helper function, current_or_guest_user, returns guest_user if the user is not logged in and current_user if the user is logged in."
540
+
541
+ <pre>
542
+ module ApplicationHelper
543
+ ...
544
+ # if user is logged in, return current_user, else return guest_user
545
+ def current_or_guest_user
546
+ if current_user
547
+ if session[:guest_user_id]
548
+ logging_in
549
+ guest_user.destroy
550
+ session[:guest_user_id] = nil
551
+ end
552
+ current_user
553
+ else
554
+ guest_user
555
+ end
556
+ end
557
+
558
+ # find guest_user object associated with the current session,
559
+ # creating one as needed
560
+ def guest_user
561
+ guest_user_id = session[:guest_user_id] ||= User.create(:name => "guest").id
562
+ User.find(guest_user_id)
563
+ end
564
+
565
+ # called (once) when the user logs in, insert any code your application needs
566
+ # to hand off from guest_user to current_user.
567
+ def logging_in
568
+ end
569
+ ...
570
+ end
571
+ </pre>
572
+
573
+ In the new system, I propose the following:
574
+
575
+ <pre>
576
+
577
+ # this will make the current user the guest user account for the given scope!
578
+ def sign_in_guest scope, options = {}
579
+ warden.set_user(guest_user.account, options.merge!(:scope => scope))
580
+ end
581
+
582
+ # sign_in :user, @user # sign_in(scope, resource)
583
+ # sign_in @user # sign_in(resource)
584
+ # sign_in @user, :event => :authentication # sign_in(resource, options)
585
+ # sign_in @user, :bypass => true # sign_in(resource, options)
586
+ #
587
+ def sign_in(resource_or_scope, *args)
588
+ ...
589
+ post_sign_in resource, scope
590
+ end
591
+
592
+ def post_sign_in resource, scope
593
+ user = warden.user(scope)
594
+ return if !user
595
+
596
+ user.transfer_guest(guest_user) if guest_user? && user.respond_to?(:transfer)
597
+ session[:guest_user] = nil
598
+ end
599
+
600
+ def guest_user?
601
+ !session[:guest_user].nil?
602
+ end
603
+
604
+ def guest_user
605
+ session[:guest_user] ||= GuestUser.new
606
+ end
607
+ </pre>
608
+
609
+ <pre>
610
+ class GuestUserAccount
611
+ troles_strategy(:static_one, :role => :guest) do |c|
612
+ # c.valid_roles = [:guest] not needed!
613
+ end.configure!
614
+
615
+ attr_accessor :guest
616
+
617
+ def user
618
+ guest
619
+ end
620
+
621
+ def initialize
622
+ roles.set_default!
623
+ end
624
+ end
625
+ </pre>
626
+
627
+ Here the special _:static_many_ strategy is used, which means that whatever the _role_list_ is first set to can never change for that user.
628
+
629
+ <pre>
630
+ module BaseAccount
631
+ # transfer guest settings to logged_in user/account
632
+ def transfer_guest(guest_user)
633
+ end
634
+ end
635
+
636
+ class UserAccount
637
+ include Mongoid::Document
638
+
639
+ troles_strategy(:bit_many, :orm => :mongoid) do |c|
640
+ c.valid_roles = [:user, :admin, :guest]
641
+ end.configure!
642
+ end
643
+
644
+ class BloggerAccount
645
+ include Mongoid::Document
646
+
647
+ troles_strategy(:string_many, :orm => :mongoid) do |c|
648
+ c.valid_roles = [:blogger, :editor, :admin]
649
+ end.configure!
650
+ end
651
+ </pre>
652
+
653
+ And here the User setup:
654
+
655
+ <pre>
656
+ module BaseUser
657
+ end
658
+
659
+ class User
660
+ include Mongoid::Document
661
+ include BaseUser
662
+
663
+ has_one :user_account
664
+ has_one :admin_account
665
+ has_one :blogger_account
666
+ ...
667
+ end
668
+ </pre>
669
+
670
+ <pre>
671
+ class GuestUser
672
+ include BaseUser
673
+
674
+ def account
675
+ @guest_user_account ||= GuestUserAccount.new
676
+ end
677
+ end
678
+ </pre>
679
+
680
+ The Guest class should be set up to only have relationship to those special guest user accounts. The Guest class should implement the same API as User but can stub or hardcode much functionality in order to act as a guest and not a "real" user.
681
+
682
+ The guest user should (usually) not have any user info persisted, so it makes sense to use a generic account that is in-memory.
683
+
684
+ h2. License
685
+
686
+ This project rocks and uses MIT-LICENSE.