objectified_sessions 1.0.0

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