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