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.
- 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: 
|
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
|