objectified_sessions 1.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 (30) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.travis.yml +21 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +391 -0
  7. data/Rakefile +6 -0
  8. data/lib/objectified_session_generator.rb +139 -0
  9. data/lib/objectified_sessions/base.rb +299 -0
  10. data/lib/objectified_sessions/errors.rb +55 -0
  11. data/lib/objectified_sessions/field_definition.rb +105 -0
  12. data/lib/objectified_sessions/version.rb +3 -0
  13. data/lib/objectified_sessions.rb +157 -0
  14. data/objectified_sessions.gemspec +43 -0
  15. data/spec/objectified_sessions/helpers/controller_helper.rb +55 -0
  16. data/spec/objectified_sessions/helpers/exception_helpers.rb +20 -0
  17. data/spec/objectified_sessions/system/basic_system_spec.rb +135 -0
  18. data/spec/objectified_sessions/system/error_handling_system_spec.rb +217 -0
  19. data/spec/objectified_sessions/system/prefix_system_spec.rb +62 -0
  20. data/spec/objectified_sessions/system/retired_inactive_system_spec.rb +188 -0
  21. data/spec/objectified_sessions/system/setup_system_spec.rb +65 -0
  22. data/spec/objectified_sessions/system/strings_symbols_system_spec.rb +73 -0
  23. data/spec/objectified_sessions/system/unknown_data_system_spec.rb +65 -0
  24. data/spec/objectified_sessions/system/visibility_system_spec.rb +61 -0
  25. data/spec/objectified_sessions/unit/objectified_session_generator_spec.rb +121 -0
  26. data/spec/objectified_sessions/unit/objectified_sessions/base_spec.rb +484 -0
  27. data/spec/objectified_sessions/unit/objectified_sessions/errors_spec.rb +75 -0
  28. data/spec/objectified_sessions/unit/objectified_sessions/field_definition_spec.rb +138 -0
  29. data/spec/objectified_sessions/unit/objectified_sessions_spec.rb +149 -0
  30. metadata +120 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA512:
3
+ data.tar.gz: bff5500fbeed0f446febd09e23819d2b143d3592d3c957bcaedc836786d5bfdbb8c393c0659278f9b2c96fb2fd16cf1b81bddacbda97b9233c61dad7b5d0559b
4
+ metadata.gz: f24564e7778790033ba6307ec1c339e66e9ec8d87aff6c6b0bcb833b9a47793c402a29bc0f9ccc1bc4e57782b16bea70276f45c4ccbf6e74d90cddfa241fc724
5
+ SHA1:
6
+ data.tar.gz: a7f49fbd4fd51d8854823f9fd4c7f01041ce022b
7
+ metadata.gz: 557be601d1396648ece538afa384e65cc2f2ebe7
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,21 @@
1
+ before_install:
2
+ - gem install rubygems-update -v2.1.11
3
+ - gem update --system 2.1.11
4
+ - gem --version
5
+ rvm:
6
+ - "2.0.0"
7
+ - "1.9.3"
8
+ - "1.8.7"
9
+ - "jruby-1.7.4"
10
+ env:
11
+ - OBJECTIFIED_SESSIONS_RAILS_TEST_VERSION=3.0.20
12
+ - OBJECTIFIED_SESSIONS_RAILS_TEST_VERSION=3.1.12
13
+ - OBJECTIFIED_SESSIONS_RAILS_TEST_VERSION=3.2.16
14
+ - OBJECTIFIED_SESSIONS_RAILS_TEST_VERSION=4.0.2
15
+ # - OBJECTIFIED_SESSIONS_RAILS_TEST_VERSION=master
16
+ matrix:
17
+ exclude:
18
+ - rvm: 1.8.7
19
+ env: OBJECTIFIED_SESSIONS_RAILS_TEST_VERSION=4.0.2
20
+ - rvm: 1.8.7
21
+ env: OBJECTIFIED_SESSIONS_RAILS_TEST_VERSION=master
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in objectified_sessions.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Andrew Geweke
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,391 @@
1
+ # ObjectifiedSessions
2
+
3
+ Encapsulate and carefully manage access to your Rails session by modeling it as an object that you add fields and
4
+ methods to, rather than a free-for-all Hash.
5
+
6
+ By default, Rails models your session as a Hash. While this makes it really easy to use, it also makes it really easy
7
+ to make a mess: as your app grows, you have to search the entire codebase for usage of `session` (a pretty common
8
+ word that's certain to be used in many irrelevant ways, as well) to figure out how it's being used. It's easy for it
9
+ to grow almost without bound, and hard to keep a team of developers in sync about how it's being used. Further, the
10
+ otherwise-extremely-nice
11
+ [CookieStore](http://api.rubyonrails.org/classes/ActionDispatch/Session/CookieStore.html) exacerbates these problems
12
+ — because you no longer have the power to change the sessions that are now stored in users' browsers, as cookies.
13
+
14
+ Using ObjectifiedSessions:
15
+
16
+ * You can define exactly what session fields can be used, and control access to them through accessor methods that you
17
+ can override to do anything you want. You can validate stored data, apply defaults when returning data, and so on.
18
+ You can ensure that data is carefully filtered to store it in the most-compact possible format, and unpack it before
19
+ returning it. (For example, you can store values as simple integers in the session to save space, but read and write
20
+ them using clear, easy symbols from client code.)
21
+ * You can eliminate the tension between using long, descriptive, maintainable names for session data —
22
+ and hence wasting very valuable session storage space — and using compact, unmaintainable names to save
23
+ space is gone. _Storage aliases_ let you access data using a long, descriptive name, while ObjectifiedSessions
24
+ automatically stores it in the session using a short, compact alias.
25
+ * You can automatically clean up old, no-longer-used session data: if requested, ObjectifiedSessions will automatically
26
+ delete data from the session that is no longer being used. (This is switched off by default, for obvious reasons.)
27
+ * _Inactive_ fields let you preserve data that you want to make sure you aren't currently using, but which you don't
28
+ want deleted forever.
29
+ * _Retired_ fields let you keep track, forever, of session fields that you used to use — and, with the
30
+ CookieStore, may forever exist in inbound sessions — so you don't ever accidentally re-use the same name for
31
+ different session data, causing potentially catastrophic effects.
32
+ * Explicit field definition lets you immediately see exactly what data you're using.
33
+ * There's absolutely no additional restriction on what you can store, vis-à-vis Rails' normal session support.
34
+
35
+ And, best of all, you can migrate to ObjectifiedSessions completely incrementally; it interoperates perfectly with
36
+ traditional session-handling code. You can migrate call site by call site, at your own pace; there's no need to
37
+ migrate all at once, or even migrate all code for a given session key all at once.
38
+
39
+ Current build status: ![Current Build Status](https://api.travis-ci.org/ageweke/objectified_sessions.png?branch=master)
40
+
41
+ ## Installation
42
+
43
+ Add this line to your application's Gemfile:
44
+
45
+ gem 'objectified_sessions'
46
+
47
+ And then execute:
48
+
49
+ $ bundle
50
+
51
+ Or install it yourself as:
52
+
53
+ $ gem install objectified_sessions
54
+
55
+ ## Usage
56
+
57
+ #### Quick Start
58
+
59
+ Simply installing the Gem won't break anything. However, before the `#objsession` call from inside a controller will
60
+ work, you need to create the class that implements your session. The simplest way to do this is by running
61
+ `rails generate objectified_session`; this will write a file to `lib/objsession.rb` that defines an empty
62
+ objectified session.
63
+
64
+ To start storing data, you need to define one or more fields on your session:
65
+
66
+ class Objsession < ::ObjectifiedSessions::Base
67
+ field :last_login
68
+ field :user_id
69
+ end
70
+
71
+ ...and now you can use it from controllers (or anywhere else, if you pass around the `#objsession` object) via:
72
+
73
+ objsession.last_login = Time.now
74
+ User.find(objsession.user_id)
75
+
76
+ ...and so on.
77
+
78
+ The fields you define map exactly to traditional session fields &mdash; given the above, `objsession.user_id` and
79
+ `session[:user_id]` will _always_ return exactly the same value, and assigning one will assign the other. In other
80
+ words, ObjectifiedSessions is not doing anything magical or scary to your session; rather, it's simply giving you a
81
+ very clean, maintainable interface on top of the `session` you already know and love. You can assign any value to a
82
+ field that is supported by Rails' traditional `session`, from an integer to an array of disparate Objects, or anything
83
+ else you want.
84
+
85
+ Already, you have a single point where all known session fields are defined (assuming you're not using any old-style
86
+ calls to `#session`). Read on for more benefits.
87
+
88
+ #### Adding Methods
89
+
90
+ You can, of course, define methods on this class that do anything you want &mdash; write fields, read fields, or simply
91
+ answer questions:
92
+
93
+ class Objsession < ::ObjectifiedSessions::Base
94
+ field :last_login
95
+ field :user_id
96
+
97
+ def logged_in!(user)
98
+ self.last_login = Time.now unless self.last_login >= 5.minutes.ago
99
+ self.user_id = user.id
100
+ end
101
+
102
+ def logged_in_today?
103
+ self.last_login >= Time.now.at_midnight
104
+ end
105
+ end
106
+
107
+ ...and then, in your controllers, you can say:
108
+
109
+ def login!
110
+ my_user = User.where(:username => params[:username])
111
+ if my_user.password_matches?(params[:password])
112
+ objsession.logged_in!(my_user)
113
+ end
114
+ end
115
+
116
+ def some_other_action
117
+ @logged_in_today = objsession.logged_in_today?
118
+ end
119
+
120
+ #### Private Methods
121
+
122
+ If you'd like to ensure your fields aren't modified outside the class, you can make them private:
123
+
124
+ class Objsession < ::ObjectifiedSessions::Base
125
+ field :last_login, :visibility => :private
126
+ field :user_id, :visibility => :private
127
+
128
+ def logged_in!(user)
129
+ self.last_login = Time.now unless self.last_login >= 5.minutes.ago
130
+ self.user_id = user.id
131
+ end
132
+
133
+ def logged_in_today?
134
+ self.last_login >= Time.now.at_midnight
135
+ end
136
+ end
137
+
138
+ Now, if someone says `objsession.last_login = Time.now` in a controller, or `objsession.user_id`,
139
+ they'll get a `NoMethodError`. Like all Ruby code, you can, of course, use `#send` to work around this if you need to.
140
+
141
+ If you want all methods to be private, you can set the default visibility, and then set fields' accessors to be public
142
+ if you want them to be:
143
+
144
+ class Objsession < ::ObjectifiedSessions::Base
145
+ default_visibility :private
146
+
147
+ field :last_login
148
+ field :user_id
149
+ field :nickname, :visibility => :public
150
+ end
151
+
152
+ #### Overriding methods, Hash-style access, and `super`
153
+
154
+ You can override accessor methods; `super` will work properly, and you can also access properties using Hash-style
155
+ access (which is always private, unless you use `public :[], :[]=` to make it public):
156
+
157
+ class Objsession < ::ObjectifiedSessions::Base
158
+ field :user_type
159
+
160
+ def user_type=(new_type)
161
+ unless [ :guest, :normal, :admin ].include?(new_type)
162
+ raise ArgumentError, "Invalid user type: #{new_type}"
163
+ end
164
+
165
+ super(new_type)
166
+ end
167
+
168
+ def user_type
169
+ super || :normal
170
+ end
171
+
172
+ def is_admin?
173
+ self[:user_type] == :admin
174
+ end
175
+ end
176
+
177
+ #### Storage Aliasing
178
+
179
+ Unlike database columns, the names of session keys are embedded in _every single instance_ of stored session data.
180
+ You're often stuck in the tension between wanting to use long names to make your code readable, and short names to
181
+ save precious session-storage space.
182
+
183
+ Enter storage aliases:
184
+
185
+ class Objsession < ::ObjectifiedSessions::Base
186
+ field :custom_background_color, :storage => :cbc
187
+ end
188
+
189
+ Now, your controller looks like:
190
+
191
+ if objsession.custom_background_color
192
+ ...
193
+ end
194
+
195
+ ...while you're now using three, rather than 23, bytes of storage space for the key for that field.
196
+
197
+ **IMPORTANT**: Changing the storage alias for a field, or setting one, will cause _all existing data for that field
198
+ to disappear_. (Hopefully this is obvious; this is because ObjectifiedSessions will now be looking under a different
199
+ key for that data.) It is, however, safe to do the reverse, by renaming a field and setting its storage alias to
200
+ be its old name.
201
+
202
+ #### Retiring Fields
203
+
204
+ Let's say you (probably wisely) stop supporting custom background colors, and remove that field. So far, so good.
205
+
206
+ Time passes, and now you introduce a session field saying whether or not the user has behaved consistently on your
207
+ site in some way &mdash; a "consistent behavior check". You add an appropriate session field:
208
+
209
+ class Objsession < ::ObjectifiedSessions::Base
210
+ field :consistent_behavior_check, :storage => :cbc
211
+ end
212
+
213
+ Uh-oh. Now you're going to start interpreting whatever data was there for your old `custom_background_color` field
214
+ as `consistent_behavior_check` data, and bad, bad things may happen. (Using a CookieStore often makes this problem
215
+ worse, since sessions can last an arbitrarily long time unless you set a cookie timeout &mdash; which has other
216
+ disadvantages.)
217
+
218
+ To avoid this, when you remove a field, _don't_ remove it entirely from the session class; instead, use the `retired`
219
+ keyword instead of `field`:
220
+
221
+ class Objsession < ::ObjectifiedSessions::Base
222
+ retired :custom_background_color, :storage => :cbc
223
+ end
224
+
225
+ Now, when you add the new `consistent_behavior_check` field...
226
+
227
+ class Objsession < ::ObjectifiedSessions::Base
228
+ field :consistent_behavior_check, :storage => :cbc
229
+
230
+ retired :custom_background_color, :storage => :cbc
231
+ end
232
+
233
+ ...you'll get an error:
234
+
235
+ ObjectifiedSessions::Errors::DuplicateFieldStorageNameError (Class Objsession already has a field, :custom_background_color, with storage name "cbc"; you can't define field :consistent_behavior_check with that same storage name.)
236
+
237
+ #### Cleaning Up Unused Fields
238
+
239
+ Particularly if you're using the CookieStore to store session data, values for fields you no longer use may still be
240
+ sitting in the session, taking up valuable space. You can tell ObjectifiedSessions to automatically remove any data
241
+ that isn't defined as a `field`:
242
+
243
+ class Objsession < ::ObjectifiedSessions::Base
244
+ unknown_fields :delete
245
+
246
+ field :user_id
247
+ field :last_login
248
+ ...
249
+ end
250
+
251
+ Now, if, for example, a session is found that has a field `cbc` set, ObjectifiedSessions will automatically delete that
252
+ key from the session.
253
+
254
+ **Important Notes** &mdash; **BEFORE** you use this feature, read these:
255
+
256
+ 1. **You can LOSE DATA if you combine this with traditional session access**. If you have code that reads or
257
+ writes `session[:foo]`, and you have no `field :foo` declared in your `objsession`, then ObjectifiedSessions
258
+ will go around deleting field `:foo` from your session, breaking your code in horrible, horrible ways.
259
+ Be **absolutely certain** that one of the following is true: (a) you're only using `objsession` to access session
260
+ data, (b) you've defined a `field` in your `objsession` for any data that your traditional session code touches,
261
+ or (c) use a `prefix`, as discussed below.
262
+ 2. **Be aware of Gems or plugins that may use the session!** These may be storing data in the session that you're not
263
+ aware of, and that you won't discover by searching your codebase. To be safe, examine actual, real-world session
264
+ data and the keys that it's using. (Iterating over all sessions in `memcached`, for example, or tracking keys
265
+ used in all cookie-based sessions over the course of a whole day, can be very valuable, too.)
266
+ 3. **Retired fields ARE deleted**. This is because by retiring a field, you're saying, "I will never use this data
267
+ again, but keep an eye on it for me to make sure I don't accidentally re-use that key". If you want to use
268
+ `unknown_fields :delete` and don't want this behavior, use the `inactive` keyword instead of `retired`; it
269
+ behaves identically (you can't access the data, and you can't define a field that conflicts), but it won't delete
270
+ the data for that key.
271
+ 4. **Be extremely careful when removing or retiring fields**. This goes without saying, but, once you've deleted that
272
+ data, it's gone forever. If you have any doubt, use `inactive` until you're certain.
273
+ 5. Deletion doesn't happen unless you actually instantiate the ObjectifiedSession, which only happens when you call
274
+ `objsession` from inside a controller. This is intentional &mdash; we don't want ObjectifiedSessions to add any
275
+ overhead whatsoever until you need it. If you want to ensure that this happens on every request, simply add a
276
+ `before_filter` that calls `objsession`. (You don't need to read or write any fields, so simply calling
277
+ `objsession` is sufficient.)
278
+
279
+ #### Partitioning Off the Session (Using a Prefix)
280
+
281
+ In certain cases, you may want ObjectifiedSessions to manage (and keep tidy) new session code, but want to make sure
282
+ it cannot conflict at all with existing session data. In this case, you can set a _prefix_; this is a key under which
283
+ all session data managed by ObjectifiedSessions will be stored.
284
+
285
+ For example &mdash; without the prefix:
286
+
287
+ class Objsession < ::ObjectifiedSessions::Base
288
+ field :user_id
289
+ field :last_login
290
+ end
291
+
292
+ objsession.user_id = 123
293
+ objsession.last_login = Time.now
294
+
295
+ session[:user_id] # => 123
296
+ session[:last_login] # => Thu Dec 26 19:35:55 -0600 2013
297
+
298
+ But with the prefix:
299
+
300
+ class Objsession < ::ObjectifiedSessions::Base
301
+ prefix :p
302
+
303
+ field :user_id
304
+ field :last_login
305
+ end
306
+
307
+ objsession.user_id = 123
308
+ objsession.last_login = Time.now
309
+
310
+ session[:user_id] # => nil
311
+ session[:last_login] # => nil
312
+ session[:p] # => { 'user_id' => 123, 'last_login' => Thu Dec 26 19:35:55 -0600 2013 }
313
+ session[:p]['user_id'] # => 123
314
+ session[:p]['last_login'] # Thu Dec 26 19:35:55 -0600 2013
315
+
316
+ Think carefully before you use this feature. In many cases, it is simply not necessary; ObjectifiedSessions
317
+ interoperates just fine with traditional session-handling code. The only case where it's really required is if you have
318
+ a very large base of code using the traditional `session` object, and you want to introduce ObjectifiedSessions bit
319
+ by bit, _and_ use `unknown_fields :delete`. This should be a very rare case, however.
320
+
321
+ **Changing the prefix will make all your existing data disappear!** Hopefully this is obvious, but setting the
322
+ prefix makes ObjectifiedSessions look in a different place when reading or writing data; this means that changing it
323
+ will cause all existing data to effectively disappear. Think carefully, choose whether to use a prefix or not, and then
324
+ leave it alone.
325
+
326
+ #### Strings vs. Symbols
327
+
328
+ ObjectifiedSessions acts as a
329
+ [HashWithIndifferentAccess](http://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html)
330
+ internally, so you can use either a String or a Symbol to access a given field when using Hash syntax, and you'll get
331
+ the exact same result. It always talks to the Session using Strings, but this should be irrelevant in almost all
332
+ cases.
333
+
334
+ (The only case where this actually matters is if you use a prefix; data stored under the prefix will be a Hash with
335
+ Strings as keys, not Symbols.)
336
+
337
+ #### Changing the Objectified-Session Class, and Session Loading
338
+
339
+ If, for some reason, you want the class you use for your objectified session to be called something other than
340
+ `Objsession`, you can change it like so in `config/application.rb`:
341
+
342
+ ObjectifiedSessions.session_class = :MyObjectifiedSession
343
+ # or ObjectifiedSessions.session_class = 'MyObjectifiedSession'
344
+ # or ObjectifiedSessions.session_class = MyObjectifiedSession
345
+ # ...i.e., you can set a Class object itself
346
+
347
+ If you use either the String or Symbol form, then ObjectifiedSessions will attempt to `require` the corresponding
348
+ file before resolving the class (but won't fail if that doesn't work &mdash; only if it still can't resolve the
349
+ class afterwards). This means that the class you use does need to either already be loaded, or the file it's in needs
350
+ to be named correctly and on one of Rails' `load_paths`.
351
+
352
+ #### Debugging and Other Tools
353
+
354
+ You can call #fields on the objectified-session object to get back an Array of Symbols, listing the fields that _can_
355
+ be set on the session. You can call #fields on the objectified-session object to get back an Array of Symbols, listing
356
+ the fields that _have_ something set (besides _nil_ &mdash; note, in this case, that `false` is distinct from `nil`)
357
+ at present.
358
+
359
+ Calling `#to_s` or `#inspect` (which produce the same result) on the objectified session will produce a nice string
360
+ containing, in alphabetical order, all data that's set on the session. Long data is abbreviated at forty characters;
361
+ passing an argument of `false` to either of these methods will remove such abbreivation.
362
+
363
+ #### Migrating To ObjectifiedSessions
364
+
365
+ If you have an existing application and want to migrate to ObjectifiedSessions bit by bit, here's how I'd do it:
366
+
367
+ 1. Install the gem.
368
+ 2. Run the generator (`rails generate objectified_session`).
369
+ 3. Find some traditional session-handling code.
370
+ 4. Make sure there's a `field` declared in the ObjectifiedSession for whatever key the traditional session-handling
371
+ code is using.
372
+ 5. Define methods on the ObjectifiedSession, if appropriate, to add appropriate functionality (value checking,
373
+ question-answering, and so on) around this field.
374
+ 6. Change the traditional session-handling code to use `objsession` and the new methods.
375
+ 7. Test, commit, and deploy.
376
+ 8. Repeat steps 3-7.
377
+
378
+ The key point is that you don't have to migrate to ObjectifiedSessions all at once, or even all code that uses a single
379
+ session field all at once.
380
+
381
+ Once you're done, and you're _completely_ certain you've eliminated all use of traditional session code (and checked
382
+ for Gems, plugins, or other code that may be using the session without your knowledge), you can set
383
+ `unknown_fields :delete`, if you'd like.
384
+
385
+ ## Contributing
386
+
387
+ 1. Fork it
388
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
389
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
390
+ 4. Push to the branch (`git push origin my-new-feature`)
391
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,139 @@
1
+ require 'rails'
2
+ require 'rails/generators'
3
+ require 'fileutils'
4
+
5
+ # The ObjectifiedSessionGenerator is what gets invoked when you run <tt>rails generate objectified_session</tt>. It
6
+ # looks at whatever you have set as your ObjectifiedSessions class (which, of course, is overwhelmingly going to be the
7
+ # default at this point, since users likely won't have changed/customized it before they run this), and then plunks a
8
+ # file under lib/, in the right place, with an empty class, with nice comments in it.
9
+ class ObjectifiedSessionGenerator < Rails::Generators::Base
10
+ def create_session_file
11
+ class_name = ::ObjectifiedSessions.session_class
12
+
13
+ # Check to see if this class exists in Ruby -- if so, we don't want to do anything; we don't want to rely on users
14
+ # putting it under lib/, in the exact same place we would.
15
+ if class_exists?(class_name)
16
+ say "You appear to already have a class #{class_name.inspect}; doing nothing."
17
+ else
18
+ class_name = class_name.name if class_name.kind_of?(Class)
19
+ class_path = class_name.underscore
20
+ target_file = File.expand_path(File.join(Rails.root, 'lib', class_path + ".rb"))
21
+
22
+ if (! File.exist?(target_file))
23
+ # The success case -- write the class.
24
+ write_objsession_class(target_file, class_name)
25
+ say "Class #{class_name} created at #{target_file.inspect}."
26
+ else
27
+ # Somehow, we can't resolve the class, yet there's a file on disk in exactly the same place we want to put
28
+ # one. Let the user know, and bail out.
29
+ say %{You've configured ObjectifiedSessions to use class #{class_name} as your session class;
30
+ I can't currently load that class, but there's already a file at the following path:
31
+
32
+ #{target_file.inspect}
33
+
34
+ Please check that file to see what class it contains; if it's incorrect, remove it, and try again.}
35
+ end
36
+ end
37
+ end
38
+
39
+ private
40
+ # Can we resolve a class (in Ruby) with the given name?
41
+ def class_exists?(class_name)
42
+ begin
43
+ class_name.constantize
44
+ true
45
+ rescue NameError => ne
46
+ false
47
+ end
48
+ end
49
+
50
+ # Write a set of lines, specified as a multi-line string, to the given location, indented by the specified
51
+ # number of spaces.
52
+ def write_indented_lines(where, lines_string, indent_amount)
53
+ lines_as_array = lines_string.split(/\r|\n|\r\n/)
54
+ lines_as_array.each do |line|
55
+ where << " " * indent_amount
56
+ where.puts line
57
+ end
58
+ end
59
+
60
+ # Write a template class to the given +target_file+, with the given +class_name+.
61
+ #
62
+ # If given a class that's nested under a module, this method goes out of its way to define nested modules (and
63
+ # even indent properly!) surrounding the class. This is because lib/ is not under Rails' autoload path (any more),
64
+ # and so it won't automatically generate modules mapped to file paths for us.
65
+ def write_objsession_class(target_file, class_name)
66
+ FileUtils.mkdir_p(File.dirname(target_file))
67
+
68
+ class_components = [ ]
69
+ while class_name =~ /^(.*?)::(.*)$/i
70
+ class_components << $1
71
+ class_name = $2
72
+ end
73
+ class_components << class_name
74
+
75
+ File.open(target_file, "w") do |f|
76
+ f.puts <<-EOF
77
+ # This is your ObjectifiedSession class. An instance of this class will automatically be available by calling
78
+ # #objsession from your controller, just like calling #session gets you (and will still get you) the normal session.
79
+ #
80
+ # See https://github.com/ageweke/objectified_sessions for more information.
81
+ EOF
82
+
83
+ class_components[0..-2].each_with_index do |module_name, index|
84
+ write_indented_lines(f, "module #{module_name}", index * 2)
85
+ end
86
+
87
+ write_indented_lines(f, "class #{class_name} < ::ObjectifiedSessions::Base", (class_components.length - 1) * 2)
88
+
89
+ write_indented_lines(f, <<-EOF, class_components.length * 2)
90
+ # FIELD DEFINITION
91
+ # ==============================================================================================================
92
+
93
+ # This defines a field named :foo; you can access it via self[:foo], self[:foo]=, and #foo and #foo=.
94
+ # You can override these methods and call #super in them, and they'll work properly.
95
+ # field :foo
96
+
97
+ # This does the same, but the #foo reader and #foo= writer will be private.
98
+ # field :foo, :visibility => :private
99
+
100
+ # This creates a field named :foo, that's still accessed via self[:foo], self.foo, and so on, but which is actually
101
+ # stored in the session object under just 'f'. You can use this to keep long names in your code but short names in
102
+ # your precious session storage.
103
+ # field :foo, :storage => :f
104
+
105
+ # This creates an inactive field named :foo. Inactive fields can't be read or written in any way, but any data in
106
+ # them will not be deleted, even if you set unknown_fields :delete.
107
+ # inactive :foo
108
+
109
+ # This creates a retired field named :foo. Retired fields don't really exist and any data in them will be deleted if
110
+ # you set unknown_fields :delete, but you'll get an error if you try to also define a normal field with the same
111
+ # name or storage setting. You can use retired fields to ensure you don't accidentally re-use old session fields.
112
+ # retired :foo
113
+
114
+ # CONFIGURATION
115
+ # ==============================================================================================================
116
+
117
+ # Sets the sub-key under which all data in your objectified session lives. This is useful if you already have a large
118
+ # system with lots of session usage, and want to start using ObjectifiedSessions to manage new session use, but
119
+ # partition it off completely from old, traditional session use.
120
+ # prefix nil
121
+
122
+ # Sets the default visibility of fields. The default is :public; if you set it to :private, you can still override it
123
+ # on a field-by-field basis by saying :visibility => :public on those fields.
124
+ # default_visibility :public
125
+
126
+ # By default, ObjectifiedSessions will never delete session data unless you ask it to. However, if you set
127
+ # unknown_fields :delete, then any unknown fields -- those you haven't mentioned in this class at all -- and any
128
+ # retired fields will be automatically deleted from the session as soon as you touch it (_i.e._, the moment you
129
+ # call #objsession in your controller). No matter what, however, nothing outside the prefix will ever be touched, if
130
+ # a prefix is set.
131
+ # unknown_fields :preserve
132
+ EOF
133
+
134
+ class_components.each_with_index do |module_name, index|
135
+ write_indented_lines(f, "end", (class_components.length - (index + 1)) * 2)
136
+ end
137
+ end
138
+ end
139
+ end