objectified_sessions 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +21 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +391 -0
- data/Rakefile +6 -0
- data/lib/objectified_session_generator.rb +139 -0
- data/lib/objectified_sessions/base.rb +299 -0
- data/lib/objectified_sessions/errors.rb +55 -0
- data/lib/objectified_sessions/field_definition.rb +105 -0
- data/lib/objectified_sessions/version.rb +3 -0
- data/lib/objectified_sessions.rb +157 -0
- data/objectified_sessions.gemspec +43 -0
- data/spec/objectified_sessions/helpers/controller_helper.rb +55 -0
- data/spec/objectified_sessions/helpers/exception_helpers.rb +20 -0
- data/spec/objectified_sessions/system/basic_system_spec.rb +135 -0
- data/spec/objectified_sessions/system/error_handling_system_spec.rb +217 -0
- data/spec/objectified_sessions/system/prefix_system_spec.rb +62 -0
- data/spec/objectified_sessions/system/retired_inactive_system_spec.rb +188 -0
- data/spec/objectified_sessions/system/setup_system_spec.rb +65 -0
- data/spec/objectified_sessions/system/strings_symbols_system_spec.rb +73 -0
- data/spec/objectified_sessions/system/unknown_data_system_spec.rb +65 -0
- data/spec/objectified_sessions/system/visibility_system_spec.rb +61 -0
- data/spec/objectified_sessions/unit/objectified_session_generator_spec.rb +121 -0
- data/spec/objectified_sessions/unit/objectified_sessions/base_spec.rb +484 -0
- data/spec/objectified_sessions/unit/objectified_sessions/errors_spec.rb +75 -0
- data/spec/objectified_sessions/unit/objectified_sessions/field_definition_spec.rb +138 -0
- data/spec/objectified_sessions/unit/objectified_sessions_spec.rb +149 -0
- 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
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
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 — 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 — 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 — 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 — 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** — **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 — 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 — 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 — 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_ — 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,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
|