activerecord-session_store 1.0.0.pre → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord-session_store might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e266cfc34f1078245ab5c7b89003a2fba65041d3
4
- data.tar.gz: c9f398c65a0511ca359d45c09b824d1af1965142
3
+ metadata.gz: c0920d7a8356d636009b0ab31c27a96cbaad5f1b
4
+ data.tar.gz: f395a9a6b74ed0c90120e971f91ea1bc5087b62f
5
5
  SHA512:
6
- metadata.gz: bc865efe6b4efdb8e448b227787bfcc153069796ec22de12e803293f3798124764aee1b3feb50766248399b15795ab12f5775f273677ef4f841521bb2f825e25
7
- data.tar.gz: 930fa087e6dd906fb2d5df43d3131cbe75808e1008e9280a0e67307f257d800ce0d7885987c6382d423361fa2bfb5aa7840b5062f6860b755bcf29a92ca8f518
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, and data column.
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).destroy
102
- request.env[SESSION_RECORD_KEY] = nil
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
- def marshal(data)
8
- ::Base64.encode64(Marshal.dump(data)) if data
10
+ mattr_accessor :serializer
11
+
12
+ def serialize(data)
13
+ serializer_class.dump(data) if data
9
14
  end
10
15
 
11
- def unmarshal(data)
12
- Marshal.load(::Base64.decode64(data)) if data
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 :marshal_data!
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-unmarshal session state.
69
+ # Lazy-deserialize session state.
70
70
  def data
71
- @data ||= self.class.unmarshal(read_attribute(@@data_column_name)) || {}
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 marshal_data!
82
+ def serialize_data!
83
83
  return false unless loaded?
84
- write_attribute(@@data_column_name, self.class.marshal(data))
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. Marshaling and unmarshaling
10
+ # are configurable class attributes. Serializing and deserializeing
11
11
  # are implemented as class methods that you may override. By default,
12
- # marshaling data is
12
+ # serializing data is
13
13
  #
14
14
  # ::Base64.encode64(Marshal.dump(data))
15
15
  #
16
- # and unmarshaling data is
16
+ # and deserializing data is
17
17
  #
18
18
  # Marshal.load(::Base64.decode64(data))
19
19
  #
20
- # This marshaling behavior is intended to store the widest range of
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 unmarshal its data if found.
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, :marshaled_data => record['data'])
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 marshaled data, self.find_by_session_id's way of
77
- # telling us to postpone unmarshaling until the data is requested.
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
- @marshaled_data = attributes[:marshaled_data]
83
- @new_record = @marshaled_data.nil?
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-unmarshal session state.
91
+ # Lazy-deserialize session state.
92
92
  def data
93
93
  unless @data
94
- if @marshaled_data
95
- @data, @marshaled_data = self.class.unmarshal(@marshaled_data) || {}, nil
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
- marshaled_data = self.class.marshal(data)
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(marshaled_data)} )
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(marshaled_data)}
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
@@ -0,0 +1,5 @@
1
+ module ActiveRecord
2
+ module SessionStore
3
+ VERSION = '1.0.0'
4
+ end
5
+ end
@@ -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.pre
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-04-07 00:00:00.000000000 Z
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: 1.3.1
181
+ version: '0'
161
182
  requirements: []
162
183
  rubyforge_project:
163
184
  rubygems_version: 2.5.1