activerecord-session_store 1.0.0.pre → 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.
Potentially problematic release.
This version of activerecord-session_store might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +9 -2
- data/lib/action_dispatch/session/active_record_store.rb +17 -3
- data/lib/active_record/session_store.rb +62 -4
- data/lib/active_record/session_store/session.rb +5 -5
- data/lib/active_record/session_store/sql_bypass.rb +16 -16
- data/lib/active_record/session_store/version.rb +5 -0
- data/lib/tasks/database.rake +8 -0
- metadata +25 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c0920d7a8356d636009b0ab31c27a96cbaad5f1b
|
4
|
+
data.tar.gz: f395a9a6b74ed0c90120e971f91ea1bc5087b62f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e5c6788589a3c53391308c31718e1c8bf181b2bf7180ad70a1720cd9129815e5f77043ad4b51efec0b3ebbc404c4132fab404bb8a76880570c89d1948e76958d
|
7
|
+
data.tar.gz: b7f0155f5446dbadf8a5c9dca98a42f24615015035991402810908b28d7fce21257eb7264c046c7596902b8a5f55f2501820541d269358c8f62d7da97067affc
|
data/README.md
CHANGED
@@ -38,13 +38,14 @@ Session data is marshaled to the `data` column in Base64 format.
|
|
38
38
|
If the data you write is larger than the column's size limit,
|
39
39
|
ActionController::SessionOverflowError will be raised.
|
40
40
|
|
41
|
-
You may configure the table name, primary key,
|
42
|
-
For example, at the end of `config/application.rb`:
|
41
|
+
You may configure the table name, primary key, data column, and
|
42
|
+
serializer type. For example, at the end of `config/application.rb`:
|
43
43
|
|
44
44
|
```ruby
|
45
45
|
ActiveRecord::SessionStore::Session.table_name = 'legacy_session_table'
|
46
46
|
ActiveRecord::SessionStore::Session.primary_key = 'session_id'
|
47
47
|
ActiveRecord::SessionStore::Session.data_column_name = 'legacy_session_data'
|
48
|
+
ActiveRecord::SessionStore::Session.serializer = :json
|
48
49
|
```
|
49
50
|
|
50
51
|
Note that setting the primary key to the `session_id` frees you from
|
@@ -52,6 +53,12 @@ having a separate `id` column if you don't want it. However, you must
|
|
52
53
|
set `session.model.id = session.session_id` by hand! A before filter
|
53
54
|
on ApplicationController is a good place.
|
54
55
|
|
56
|
+
The serializer may be one of `marshal`, `json`, or `hybrid`. `marshal` is
|
57
|
+
the default and uses the built-in Marshal methods coupled with Base64
|
58
|
+
encoding. `json` does what it says on the tin, using the `parse()` and
|
59
|
+
`generate()` methods of the JSON module. `hybrid` will read either type
|
60
|
+
but write as JSON.
|
61
|
+
|
55
62
|
Since the default class is a simple Active Record, you get timestamps
|
56
63
|
for free if you add `created_at` and `updated_at` datetime columns to
|
57
64
|
the `sessions` table, making periodic session expiration a snap.
|
@@ -98,10 +98,24 @@ module ActionDispatch
|
|
98
98
|
def delete_session(request, session_id, options)
|
99
99
|
logger.silence_logger do
|
100
100
|
if sid = current_session_id(request)
|
101
|
-
get_session_model(request, sid)
|
102
|
-
|
101
|
+
if model = get_session_model(request, sid)
|
102
|
+
data = model.data
|
103
|
+
model.destroy
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
request.env[SESSION_RECORD_KEY] = nil
|
108
|
+
|
109
|
+
unless options[:drop]
|
110
|
+
new_sid = generate_sid
|
111
|
+
|
112
|
+
if options[:renew]
|
113
|
+
new_model = @@session_class.new(:session_id => new_sid, :data => data)
|
114
|
+
new_model.save
|
115
|
+
request.env[SESSION_RECORD_KEY] = new_model
|
116
|
+
end
|
117
|
+
new_sid
|
103
118
|
end
|
104
|
-
generate_sid unless options[:drop]
|
105
119
|
end
|
106
120
|
end
|
107
121
|
|
@@ -1,15 +1,20 @@
|
|
1
|
+
require 'active_record/session_store/version'
|
1
2
|
require 'action_dispatch/session/active_record_store'
|
2
3
|
require "active_record/session_store/extension/logger_silencer"
|
4
|
+
require 'active_support/core_ext/hash/keys'
|
5
|
+
require 'multi_json'
|
3
6
|
|
4
7
|
module ActiveRecord
|
5
8
|
module SessionStore
|
6
9
|
module ClassMethods # :nodoc:
|
7
|
-
|
8
|
-
|
10
|
+
mattr_accessor :serializer
|
11
|
+
|
12
|
+
def serialize(data)
|
13
|
+
serializer_class.dump(data) if data
|
9
14
|
end
|
10
15
|
|
11
|
-
def
|
12
|
-
|
16
|
+
def deserialize(data)
|
17
|
+
serializer_class.load(data) if data
|
13
18
|
end
|
14
19
|
|
15
20
|
def drop_table!
|
@@ -33,6 +38,59 @@ module ActiveRecord
|
|
33
38
|
end
|
34
39
|
connection.add_index table_name, session_id_column, :unique => true
|
35
40
|
end
|
41
|
+
|
42
|
+
def serializer_class
|
43
|
+
case self.serializer
|
44
|
+
when :marshal, nil then
|
45
|
+
MarshalSerializer
|
46
|
+
when :json then
|
47
|
+
JsonSerializer
|
48
|
+
when :hybrid then
|
49
|
+
HybridSerializer
|
50
|
+
else
|
51
|
+
self.serializer
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Use Marshal with Base64 encoding
|
56
|
+
class MarshalSerializer
|
57
|
+
def self.load(value)
|
58
|
+
Marshal.load(::Base64.decode64(value))
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.dump(value)
|
62
|
+
::Base64.encode64(Marshal.dump(value))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Uses built-in JSON library to encode/decode session
|
67
|
+
class JsonSerializer
|
68
|
+
def self.load(value)
|
69
|
+
hash = MultiJson.load(value)
|
70
|
+
hash.is_a?(Hash) ? hash.with_indifferent_access[:value] : hash
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.dump(value)
|
74
|
+
MultiJson.dump(value: value)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Transparently migrates existing session values from Marshal to JSON
|
79
|
+
class HybridSerializer < JsonSerializer
|
80
|
+
MARSHAL_SIGNATURE = 'BAh'.freeze
|
81
|
+
|
82
|
+
def self.load(value)
|
83
|
+
if needs_migration?(value)
|
84
|
+
Marshal.load(::Base64.decode64(value))
|
85
|
+
else
|
86
|
+
super
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.needs_migration?(value)
|
91
|
+
value.start_with?(MARSHAL_SIGNATURE)
|
92
|
+
end
|
93
|
+
end
|
36
94
|
end
|
37
95
|
end
|
38
96
|
end
|
@@ -14,7 +14,7 @@ module ActiveRecord
|
|
14
14
|
cattr_accessor :data_column_name
|
15
15
|
self.data_column_name = 'data'
|
16
16
|
|
17
|
-
before_save :
|
17
|
+
before_save :serialize_data!
|
18
18
|
before_save :raise_on_session_data_overflow!
|
19
19
|
|
20
20
|
# This method is defiend in `protected_attributes` gem. We can't check for
|
@@ -66,9 +66,9 @@ module ActiveRecord
|
|
66
66
|
super
|
67
67
|
end
|
68
68
|
|
69
|
-
# Lazy-
|
69
|
+
# Lazy-deserialize session state.
|
70
70
|
def data
|
71
|
-
@data ||= self.class.
|
71
|
+
@data ||= self.class.deserialize(read_attribute(@@data_column_name)) || {}
|
72
72
|
end
|
73
73
|
|
74
74
|
attr_writer :data
|
@@ -79,9 +79,9 @@ module ActiveRecord
|
|
79
79
|
end
|
80
80
|
|
81
81
|
private
|
82
|
-
def
|
82
|
+
def serialize_data!
|
83
83
|
return false unless loaded?
|
84
|
-
write_attribute(@@data_column_name, self.class.
|
84
|
+
write_attribute(@@data_column_name, self.class.serialize(data))
|
85
85
|
end
|
86
86
|
|
87
87
|
# Ensures that the data about to be stored in the database is not
|
@@ -7,17 +7,17 @@ module ActiveRecord
|
|
7
7
|
# an example session model class meant as a basis for your own classes.
|
8
8
|
#
|
9
9
|
# The database connection, table name, and session id and data columns
|
10
|
-
# are configurable class attributes.
|
10
|
+
# are configurable class attributes. Serializing and deserializeing
|
11
11
|
# are implemented as class methods that you may override. By default,
|
12
|
-
#
|
12
|
+
# serializing data is
|
13
13
|
#
|
14
14
|
# ::Base64.encode64(Marshal.dump(data))
|
15
15
|
#
|
16
|
-
# and
|
16
|
+
# and deserializing data is
|
17
17
|
#
|
18
18
|
# Marshal.load(::Base64.decode64(data))
|
19
19
|
#
|
20
|
-
# This
|
20
|
+
# This serializing behavior is intended to store the widest range of
|
21
21
|
# binary session data in a +text+ column. For higher performance,
|
22
22
|
# store in a +blob+ column instead and forgo the Base64 encoding.
|
23
23
|
class SqlBypass
|
@@ -58,10 +58,10 @@ module ActiveRecord
|
|
58
58
|
@connection_pool ||= ActiveRecord::Base.connection_pool
|
59
59
|
end
|
60
60
|
|
61
|
-
# Look up a session by id and
|
61
|
+
# Look up a session by id and deserialize its data if found.
|
62
62
|
def find_by_session_id(session_id)
|
63
63
|
if record = connection.select_one("SELECT #{connection.quote_column_name(data_column)} AS data FROM #{@@table_name} WHERE #{connection.quote_column_name(@@session_id_column)}=#{connection.quote(session_id.to_s)}")
|
64
|
-
new(:session_id => session_id, :
|
64
|
+
new(:session_id => session_id, :serialized_data => record['data'])
|
65
65
|
end
|
66
66
|
end
|
67
67
|
end
|
@@ -73,14 +73,14 @@ module ActiveRecord
|
|
73
73
|
|
74
74
|
attr_writer :data
|
75
75
|
|
76
|
-
# Look for normal and
|
77
|
-
# telling us to postpone
|
76
|
+
# Look for normal and serialized data, self.find_by_session_id's way of
|
77
|
+
# telling us to postpone deserializing until the data is requested.
|
78
78
|
# We need to handle a normal data attribute in case of a new record.
|
79
79
|
def initialize(attributes)
|
80
80
|
@session_id = attributes[:session_id]
|
81
81
|
@data = attributes[:data]
|
82
|
-
@
|
83
|
-
@new_record = @
|
82
|
+
@serialized_data = attributes[:serialized_data]
|
83
|
+
@new_record = @serialized_data.nil?
|
84
84
|
end
|
85
85
|
|
86
86
|
# Returns true if the record is persisted, i.e. it's not a new record
|
@@ -88,11 +88,11 @@ module ActiveRecord
|
|
88
88
|
!@new_record
|
89
89
|
end
|
90
90
|
|
91
|
-
# Lazy-
|
91
|
+
# Lazy-deserialize session state.
|
92
92
|
def data
|
93
93
|
unless @data
|
94
|
-
if @
|
95
|
-
@data, @
|
94
|
+
if @serialized_data
|
95
|
+
@data, @serialized_data = self.class.deserialize(@serialized_data) || {}, nil
|
96
96
|
else
|
97
97
|
@data = {}
|
98
98
|
end
|
@@ -106,7 +106,7 @@ module ActiveRecord
|
|
106
106
|
|
107
107
|
def save
|
108
108
|
return false unless loaded?
|
109
|
-
|
109
|
+
serialized_data = self.class.serialize(data)
|
110
110
|
connect = connection
|
111
111
|
|
112
112
|
if @new_record
|
@@ -117,12 +117,12 @@ module ActiveRecord
|
|
117
117
|
#{connect.quote_column_name(data_column)} )
|
118
118
|
VALUES (
|
119
119
|
#{connect.quote(session_id)},
|
120
|
-
#{connect.quote(
|
120
|
+
#{connect.quote(serialized_data)} )
|
121
121
|
end_sql
|
122
122
|
else
|
123
123
|
connect.update <<-end_sql, 'Update session'
|
124
124
|
UPDATE #{table_name}
|
125
|
-
SET #{connect.quote_column_name(data_column)}=#{connect.quote(
|
125
|
+
SET #{connect.quote_column_name(data_column)}=#{connect.quote(serialized_data)}
|
126
126
|
WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id)}
|
127
127
|
end_sql
|
128
128
|
end
|
data/lib/tasks/database.rake
CHANGED
@@ -11,4 +11,12 @@ namespace 'db:sessions' do
|
|
11
11
|
task :clear => [:environment, 'db:load_config'] do
|
12
12
|
ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::SessionStore::Session.table_name}"
|
13
13
|
end
|
14
|
+
|
15
|
+
desc "Trim old sessions from the table (default: > 30 days)"
|
16
|
+
task :trim => [:environment, 'db:load_config'] do
|
17
|
+
cutoff_period = (ENV['SESSION_DAYS_TRIM_THRESHOLD'] || 30).to_i.days.ago
|
18
|
+
ActiveRecord::SessionStore::Session.
|
19
|
+
where("updated_at < ?", cutoff_period).
|
20
|
+
delete_all
|
21
|
+
end
|
14
22
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-session_store
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Heinemeier Hansson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-05-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -90,6 +90,26 @@ dependencies:
|
|
90
90
|
- - "<"
|
91
91
|
- !ruby/object:Gem::Version
|
92
92
|
version: '3'
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: multi_json
|
95
|
+
requirement: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - "~>"
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '1.11'
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 1.11.2
|
103
|
+
type: :runtime
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '1.11'
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: 1.11.2
|
93
113
|
- !ruby/object:Gem::Dependency
|
94
114
|
name: sqlite3
|
95
115
|
requirement: !ruby/object:Gem::Requirement
|
@@ -134,6 +154,7 @@ files:
|
|
134
154
|
- lib/active_record/session_store/railtie.rb
|
135
155
|
- lib/active_record/session_store/session.rb
|
136
156
|
- lib/active_record/session_store/sql_bypass.rb
|
157
|
+
- lib/active_record/session_store/version.rb
|
137
158
|
- lib/activerecord/session_store.rb
|
138
159
|
- lib/generators/active_record/session_migration_generator.rb
|
139
160
|
- lib/generators/active_record/templates/migration.rb
|
@@ -155,9 +176,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
155
176
|
version: 1.9.3
|
156
177
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
157
178
|
requirements:
|
158
|
-
- - "
|
179
|
+
- - ">="
|
159
180
|
- !ruby/object:Gem::Version
|
160
|
-
version:
|
181
|
+
version: '0'
|
161
182
|
requirements: []
|
162
183
|
rubyforge_project:
|
163
184
|
rubygems_version: 2.5.1
|