mongo_session_store 3.2.1 → 3.3.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.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +136 -0
  3. data/.github/workflows/danger-comment.yml +17 -0
  4. data/.github/workflows/danger.yml +53 -0
  5. data/.gitignore +2 -0
  6. data/.rubocop.yml +15 -6
  7. data/.rubocop_todo.yml +195 -0
  8. data/CHANGELOG.md +20 -13
  9. data/Dangerfile +5 -1
  10. data/Gemfile +2 -0
  11. data/Gemfile.danger +5 -0
  12. data/README.md +71 -9
  13. data/RELEASING.md +37 -0
  14. data/docker-compose.yml +5 -0
  15. data/gemfiles/rails-5.1-mongoid.gemfile +1 -1
  16. data/gemfiles/rails-6.0-mongo.gemfile +4 -1
  17. data/gemfiles/rails-6.0-mongoid.gemfile +4 -1
  18. data/gemfiles/rails-8.1-mongo.gemfile +15 -0
  19. data/gemfiles/rails-8.1-mongoid.gemfile +15 -0
  20. data/gemfiles/rails-current-mongo.gemfile +16 -0
  21. data/gemfiles/rails-current-mongoid.gemfile +3 -0
  22. data/lib/mongo_session_store/mongo_store.rb +1 -1
  23. data/lib/mongo_session_store/mongo_store_base.rb +7 -0
  24. data/lib/mongo_session_store/mongoid_store.rb +1 -0
  25. data/lib/mongo_session_store/version.rb +1 -1
  26. data/mongo_session_store.gemspec +4 -7
  27. data/script/test +20 -0
  28. data/script/test_inner +19 -0
  29. data/script/testenv +6 -0
  30. data/spec/.rubocop.yml +1 -1
  31. data/spec/integrations/devise_spec.rb +2 -2
  32. data/spec/lib/mongo_session_store/legacy_serialization_spec.rb +17 -0
  33. data/spec/lib/mongo_session_store/serialization_spec.rb +36 -0
  34. data/spec/lib/mongo_session_store/stale_sessions_spec.rb +77 -0
  35. data/spec/lib/mongo_session_store/version_spec.rb +8 -0
  36. data/spec/rails_helper.rb +4 -0
  37. data/spec/spec_helper.rb +14 -2
  38. data/spec/support/apps/rails_6.0_app/config/database.yml +9 -3
  39. data/spec/support/apps/rails_6.0_app/config/environments/test.rb +1 -1
  40. data/spec/support/helpers/test_database_helper.rb +1 -3
  41. data/spec/support/rack_app.rb +3 -1
  42. metadata +42 -235
  43. data/.travis.yml +0 -109
  44. data/gemfiles/rails-4.0-mongo.gemfile +0 -9
  45. data/gemfiles/rails-4.0-mongoid.gemfile +0 -9
  46. data/gemfiles/rails-4.1-mongo.gemfile +0 -9
  47. data/gemfiles/rails-4.1-mongoid.gemfile +0 -9
  48. data/gemfiles/rails-4.2-mongo.gemfile +0 -9
  49. data/gemfiles/rails-4.2-mongoid.gemfile +0 -9
  50. data/spec/support/apps/rails_4.0_app/Rakefile +0 -6
  51. data/spec/support/apps/rails_4.0_app/app/controllers/application_controller.rb +0 -5
  52. data/spec/support/apps/rails_4.0_app/app/controllers/home_controller.rb +0 -2
  53. data/spec/support/apps/rails_4.0_app/app/models/user.rb +0 -5
  54. data/spec/support/apps/rails_4.0_app/app/views/home/index.html.erb +0 -9
  55. data/spec/support/apps/rails_4.0_app/app/views/layouts/application.html.erb +0 -17
  56. data/spec/support/apps/rails_4.0_app/bin/bundle +0 -3
  57. data/spec/support/apps/rails_4.0_app/bin/rails +0 -4
  58. data/spec/support/apps/rails_4.0_app/bin/rake +0 -4
  59. data/spec/support/apps/rails_4.0_app/config/application.rb +0 -11
  60. data/spec/support/apps/rails_4.0_app/config/boot.rb +0 -4
  61. data/spec/support/apps/rails_4.0_app/config/database.yml +0 -11
  62. data/spec/support/apps/rails_4.0_app/config/environment.rb +0 -5
  63. data/spec/support/apps/rails_4.0_app/config/environments/development.rb +0 -12
  64. data/spec/support/apps/rails_4.0_app/config/environments/test.rb +0 -19
  65. data/spec/support/apps/rails_4.0_app/config/initializers/devise.rb +0 -254
  66. data/spec/support/apps/rails_4.0_app/config/initializers/secret_token.rb +0 -12
  67. data/spec/support/apps/rails_4.0_app/config/initializers/session_store.rb +0 -1
  68. data/spec/support/apps/rails_4.0_app/config/locales/devise.en.yml +0 -59
  69. data/spec/support/apps/rails_4.0_app/config/locales/en.yml +0 -23
  70. data/spec/support/apps/rails_4.0_app/config/mongo.yml +0 -11
  71. data/spec/support/apps/rails_4.0_app/config/mongoid.yml +0 -13
  72. data/spec/support/apps/rails_4.0_app/config/routes.rb +0 -5
  73. data/spec/support/apps/rails_4.0_app/config.ru +0 -4
  74. data/spec/support/apps/rails_4.0_app/db/migrate/20140301171212_add_devise_users.rb +0 -11
  75. data/spec/support/apps/rails_4.0_app/db/schema.rb +0 -25
  76. data/spec/support/apps/rails_4.0_app/lib/assets/.keep +0 -0
  77. data/spec/support/apps/rails_4.0_app/lib/tasks/.keep +0 -0
  78. data/spec/support/apps/rails_4.0_app/log/.keep +0 -0
  79. data/spec/support/apps/rails_4.1_app/Rakefile +0 -3
  80. data/spec/support/apps/rails_4.1_app/app/controllers/application_controller.rb +0 -5
  81. data/spec/support/apps/rails_4.1_app/app/controllers/home_controller.rb +0 -2
  82. data/spec/support/apps/rails_4.1_app/app/models/user.rb +0 -5
  83. data/spec/support/apps/rails_4.1_app/app/views/home/index.html.erb +0 -9
  84. data/spec/support/apps/rails_4.1_app/app/views/layouts/application.html.erb +0 -17
  85. data/spec/support/apps/rails_4.1_app/bin/bundle +0 -3
  86. data/spec/support/apps/rails_4.1_app/bin/rails +0 -4
  87. data/spec/support/apps/rails_4.1_app/bin/rake +0 -4
  88. data/spec/support/apps/rails_4.1_app/config/application.rb +0 -11
  89. data/spec/support/apps/rails_4.1_app/config/boot.rb +0 -4
  90. data/spec/support/apps/rails_4.1_app/config/database.yml +0 -11
  91. data/spec/support/apps/rails_4.1_app/config/environment.rb +0 -5
  92. data/spec/support/apps/rails_4.1_app/config/environments/development.rb +0 -12
  93. data/spec/support/apps/rails_4.1_app/config/environments/test.rb +0 -19
  94. data/spec/support/apps/rails_4.1_app/config/initializers/devise.rb +0 -254
  95. data/spec/support/apps/rails_4.1_app/config/initializers/session_store.rb +0 -1
  96. data/spec/support/apps/rails_4.1_app/config/locales/devise.en.yml +0 -59
  97. data/spec/support/apps/rails_4.1_app/config/locales/en.yml +0 -23
  98. data/spec/support/apps/rails_4.1_app/config/mongo.yml +0 -11
  99. data/spec/support/apps/rails_4.1_app/config/mongoid.yml +0 -13
  100. data/spec/support/apps/rails_4.1_app/config/routes.rb +0 -5
  101. data/spec/support/apps/rails_4.1_app/config/secrets.yml +0 -22
  102. data/spec/support/apps/rails_4.1_app/config.ru +0 -2
  103. data/spec/support/apps/rails_4.1_app/db/migrate/20140301171212_add_devise_users.rb +0 -11
  104. data/spec/support/apps/rails_4.1_app/db/schema.rb +0 -25
  105. data/spec/support/apps/rails_4.1_app/lib/assets/.keep +0 -0
  106. data/spec/support/apps/rails_4.1_app/lib/tasks/.keep +0 -0
  107. data/spec/support/apps/rails_4.1_app/log/.keep +0 -0
  108. data/spec/support/apps/rails_4.2_app/Rakefile +0 -2
  109. data/spec/support/apps/rails_4.2_app/app/controllers/application_controller.rb +0 -5
  110. data/spec/support/apps/rails_4.2_app/app/controllers/home_controller.rb +0 -2
  111. data/spec/support/apps/rails_4.2_app/app/models/user.rb +0 -5
  112. data/spec/support/apps/rails_4.2_app/app/views/home/index.html.erb +0 -9
  113. data/spec/support/apps/rails_4.2_app/app/views/layouts/application.html.erb +0 -15
  114. data/spec/support/apps/rails_4.2_app/bin/bundle +0 -3
  115. data/spec/support/apps/rails_4.2_app/bin/rails +0 -8
  116. data/spec/support/apps/rails_4.2_app/bin/rake +0 -4
  117. data/spec/support/apps/rails_4.2_app/config/application.rb +0 -11
  118. data/spec/support/apps/rails_4.2_app/config/boot.rb +0 -3
  119. data/spec/support/apps/rails_4.2_app/config/database.yml +0 -11
  120. data/spec/support/apps/rails_4.2_app/config/environment.rb +0 -5
  121. data/spec/support/apps/rails_4.2_app/config/environments/development.rb +0 -12
  122. data/spec/support/apps/rails_4.2_app/config/environments/test.rb +0 -19
  123. data/spec/support/apps/rails_4.2_app/config/initializers/devise.rb +0 -254
  124. data/spec/support/apps/rails_4.2_app/config/initializers/session_store.rb +0 -1
  125. data/spec/support/apps/rails_4.2_app/config/locales/en.yml +0 -23
  126. data/spec/support/apps/rails_4.2_app/config/mongo.yml +0 -11
  127. data/spec/support/apps/rails_4.2_app/config/mongoid.yml +0 -13
  128. data/spec/support/apps/rails_4.2_app/config/routes.rb +0 -5
  129. data/spec/support/apps/rails_4.2_app/config/secrets.yml +0 -22
  130. data/spec/support/apps/rails_4.2_app/config.ru +0 -4
  131. data/spec/support/apps/rails_4.2_app/db/migrate/20140301171212_add_devise_users.rb +0 -11
  132. data/spec/support/apps/rails_4.2_app/db/schema.rb +0 -25
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # MongoSessionStore
2
2
 
3
- [![Build Status](https://travis-ci.org/mongoid/mongo_session_store.svg?branch=master)](https://travis-ci.org/mongoid/mongo_session_store)
3
+ [![CI](https://github.com/mongoid/mongo_session_store/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/mongoid/mongo_session_store/actions/workflows/ci.yml?query=branch%3Amain)
4
4
  [![Gem Version](https://badge.fury.io/rb/mongo_session_store.svg)](http://badge.fury.io/rb/mongo_session_store)
5
- [![Code Climate](https://codeclimate.com/github/mongoid/mongo_session_store/badges/gpa.svg)](https://codeclimate.com/github/mongoid/mongo_session_store)
5
+ [![Coverage Status](https://coveralls.io/repos/github/mongoid/mongo_session_store/badge.svg?branch=main)](https://coveralls.io/github/mongoid/mongo_session_store?branch=main)
6
6
 
7
7
  ## Description
8
8
 
@@ -11,7 +11,7 @@ MongoSessionStore is a [Rails][rails]-compatible session store for
11
11
  Driver][mongo]. It also allows for custom Mongo session store that works with
12
12
  any (or no!) Mongo ODM.
13
13
 
14
- MongoSessionStore version 3 is compatible with Rails 4.0 through 5.2. For Rails
14
+ MongoSessionStore version 3 is compatible with Rails 5.0+. For Rails
15
15
  3 support please check out issue [#17][issue-rails3] for options and let us
16
16
  know if you need support.
17
17
 
@@ -68,13 +68,57 @@ MongoidStore::Session.where(:updated_at.gt => 2.days.ago)
68
68
  MongoStore::Session.where("updated_at" => { "$gt" => 2.days.ago })
69
69
  ```
70
70
 
71
+ ### Serializable session values
72
+
73
+ Both adapters serialize session data with Ruby's `Marshal`. Store serializable values such as strings, numbers, arrays, and hashes, not requests, input streams, or uploaded files. Serialization errors are raised rather than silently discarding data.
74
+
75
+ Do not store `ActionController::Parameters` objects directly in the session. On Rails 7 and newer, their logging context can retain the request and its input stream, causing `TypeError: can't dump IO` (or a similar error for `StringIO`). A nested `params[:search]` object can retain that context too. Explicitly permit the values your application needs and convert them to a hash before saving:
76
+
77
+ ```ruby
78
+ session[:search] = params.require(:search).permit(:query, :sort).to_h
79
+ ```
80
+
81
+ This stores the permitted values without the request context. It does not make uploaded files or other non-serializable values serializable; store a durable identifier for those instead.
82
+
83
+ ### Deleting stale sessions
84
+
85
+ Sessions are not automatically expired or deleted. To opt in to cleanup, call the store's `delete_stale_sessions` method with an explicit `Time` cutoff:
86
+
87
+ ```ruby
88
+ # Mongoid
89
+ MongoidStore.delete_stale_sessions(before: 1.week.ago)
90
+
91
+ # MongoDB Ruby Driver
92
+ MongoStore.delete_stale_sessions(before: 1.week.ago)
93
+ ```
94
+
95
+ The method returns the number deleted and uses the configured session collection. It performs a bulk database deletion without loading session data or running model callbacks. Only records whose `updated_at` is strictly earlier than the cutoff are deleted; records at the cutoff or without a timestamp are retained. Invalid cutoffs raise `ArgumentError`, and database errors propagate to the caller.
96
+
97
+ Run cleanup explicitly from a scheduled job or a Rails runner command, choosing a retention policy appropriate for your application:
98
+
99
+ ```sh
100
+ bin/rails runner 'MongoidStore.delete_stale_sessions(before: 1.week.ago)'
101
+ ```
102
+
103
+ Deleting a session signs that session out on its next request. Cleanup is not cookie expiration or a guarantee of inactivity-based logout: `updated_at` tracks session saves, and a request that does not save the session may not refresh it. No default maximum age, background job, or TTL index is installed. For large collections, consider creating an ordinary index on `updated_at` as part of your database setup.
104
+
71
105
  ## Development
72
106
 
107
+ ### Test environment
108
+
109
+ Start MongoDB with this script:
110
+
111
+ ```
112
+ script/testenv
113
+ ```
114
+
73
115
  ### Testing
74
116
 
75
117
  To run the tests for a specific store. You must first set a `BUNDLE_GEMFILE` in
76
118
  the environment.
77
119
 
120
+ You can also use Docker, see the section below this one.
121
+
78
122
  ```sh
79
123
  bundle exec rake
80
124
  ```
@@ -82,12 +126,6 @@ bundle exec rake
82
126
  Examples:
83
127
 
84
128
  ```sh
85
- BUNDLE_GEMFILE=gemfiles/rails-4.0-mongo.gemfile bundle exec rake
86
- BUNDLE_GEMFILE=gemfiles/rails-4.0-mongoid.gemfile bundle exec rake
87
- BUNDLE_GEMFILE=gemfiles/rails-4.1-mongo.gemfile bundle exec rake
88
- BUNDLE_GEMFILE=gemfiles/rails-4.1-mongoid.gemfile bundle exec rake
89
- BUNDLE_GEMFILE=gemfiles/rails-4.2-mongo.gemfile bundle exec rake
90
- BUNDLE_GEMFILE=gemfiles/rails-4.2-mongoid.gemfile bundle exec rake
91
129
  BUNDLE_GEMFILE=gemfiles/rails-5.0-mongo.gemfile bundle exec rake
92
130
  BUNDLE_GEMFILE=gemfiles/rails-5.0-mongoid.gemfile bundle exec rake
93
131
  BUNDLE_GEMFILE=gemfiles/rails-5.1-mongo.gemfile bundle exec rake
@@ -98,6 +136,30 @@ BUNDLE_GEMFILE=gemfiles/rails-6.0-mongo.gemfile bundle exec rake
98
136
  BUNDLE_GEMFILE=gemfiles/rails-6.0-mongoid.gemfile bundle exec rake
99
137
  ```
100
138
 
139
+ For Rails 6.1 and newer, use `gemfiles/rails-current-mongo.gemfile` or `gemfiles/rails-current-mongoid.gemfile` with `RAILS_VERSION` and, for Mongoid, `MONGOID_VERSION` constraints:
140
+
141
+ ```sh
142
+ RAILS_VERSION='~> 7.2.0' MONGOID_VERSION='~> 9.0' BUNDLE_GEMFILE=gemfiles/rails-current-mongoid.gemfile bundle exec rake test
143
+ ```
144
+
145
+ For Rails 8.0, also set `SQLITE_VERSION='~> 2.1'`; the dedicated Rails 8.1 Gemfiles already select SQLite 2.
146
+
147
+ Set `MONGODB_HOST` and `MONGODB_PORT` to use a non-default test server. The default is `127.0.0.1:27017`.
148
+
149
+ #### Docker testing
150
+
151
+ To run tests in a Docker container with a specific Ruby version and gemfile:
152
+
153
+ ```sh
154
+ BUNDLE_GEMFILE=gemfiles/rails-6.0-mongo.gemfile script/test
155
+ ```
156
+
157
+ The `BUNDLE_GEMFILE` environment variable is required. Ruby version defaults to 3.4 but can be customized:
158
+
159
+ ```sh
160
+ RUBY_VERSION=2.7 BUNDLE_GEMFILE=gemfiles/rails-6.0-mongo.gemfile script/test
161
+ ```
162
+
101
163
  ### Performance benchmark
102
164
 
103
165
  The repository includes a performance benchmark. It runs against all available
data/RELEASING.md ADDED
@@ -0,0 +1,37 @@
1
+ # Releasing
2
+
3
+ Release from a clean local checkout of the upstream repository, not a fork. You need push access, RubyGems ownership with MFA, and an authenticated GitHub CLI.
4
+
5
+ ## Prepare
6
+
7
+ 1. Run `git switch main`, `git pull --ff-only`, and confirm that `origin` points to `mongoid/mongo_session_store`.
8
+ 2. Choose the next semantic version, update `lib/mongo_session_store/version.rb`, and replace the unreleased changelog heading with the version and release date. Remove the contribution placeholder.
9
+ 3. With MongoDB running, install dependencies and run `bundle exec rake test` using each supported adapter's `BUNDLE_GEMFILE`, for example `gemfiles/rails-6.0-mongo.gemfile` and `gemfiles/rails-6.0-mongoid.gemfile`. Run `bundle exec rubocop` with the default Gemfile.
10
+ 4. Commit the preparation, push `main`, and wait for the complete GitHub Actions matrix to pass for that commit.
11
+
12
+ ## Publish
13
+
14
+ Do not use the legacy `rake release` task: it assumes `master` and performs interactive edits. Build and publish the prepared version explicitly:
15
+
16
+ ```sh
17
+ version=3.2.2 # Replace with the version being released.
18
+ gem build mongo_session_store.gemspec
19
+ git tag "v$version"
20
+ git push origin "v$version"
21
+ gem push "mongo_session_store-$version.gem"
22
+ ```
23
+
24
+ Enter a fresh RubyGems MFA code when prompted. Create a GitHub release from the same tag, using the matching changelog section as the release notes:
25
+
26
+ ```sh
27
+ gh release create "v$version" --repo mongoid/mongo_session_store \
28
+ --verify-tag --title "$version" --notes-file /path/to/release-notes.md --latest
29
+ ```
30
+
31
+ Verify the exact version at `https://rubygems.org/gems/mongo_session_store/versions/VERSION` and inspect `gh release view "v$version" --repo mongoid/mongo_session_store` to confirm a published, non-draft release.
32
+
33
+ If publication is interrupted, check the remote tag, RubyGems version, and GitHub release first. Resume only the missing step; never move a published tag or rebuild an already published version. If the gem push needs a new code, retry that command with `--otp CODE`.
34
+
35
+ ## Next development version
36
+
37
+ Increment the patch version in `lib/mongo_session_store/version.rb`, add a new `## (unreleased)` section with a contribution placeholder, and commit and push the changes to `main`. Remove the local built gem after successful publication.
@@ -0,0 +1,5 @@
1
+ services:
2
+ mongodb:
3
+ image: mongo:3.6
4
+ ports:
5
+ - "27017:27017"
@@ -1,6 +1,6 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- gem "mongoid"
3
+ gem "mongoid", "~> 7.0"
4
4
 
5
5
  gem "rails", "~> 5.1.0"
6
6
  gem "devise", ">= 4.2"
@@ -4,6 +4,9 @@ gem "mongo"
4
4
 
5
5
  gem "rails", "~> 6.0.0"
6
6
  gem "devise", "~> 4.7.0"
7
- gem "sqlite3", "~> 1.4.0"
7
+ gem "sqlite3", "~> 1.4"
8
+ gem "bigdecimal"
9
+ gem "mutex_m"
10
+ gem "drb"
8
11
 
9
12
  gemspec :path => "../"
@@ -4,6 +4,9 @@ gem "mongoid"
4
4
 
5
5
  gem "rails", "~> 6.0.0"
6
6
  gem "devise", "~> 4.7.0"
7
- gem "sqlite3", "~> 1.4.0"
7
+ gem "sqlite3", "~> 1.4"
8
+ gem "bigdecimal"
9
+ gem "mutex_m"
10
+ gem "drb"
8
11
 
9
12
  gemspec :path => "../"
@@ -0,0 +1,15 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "mongo", "~> 2.0"
4
+ gem "rails", "~> 8.1.0"
5
+ gem "devise", "~> 5.0"
6
+ gem "sqlite3", "~> 2.0"
7
+ gem "bigdecimal"
8
+ gem "mutex_m"
9
+ gem "drb"
10
+ gem "base64"
11
+ gem "benchmark"
12
+ gem "ostruct"
13
+ gem "simplecov", "~> 0.22", :require => false
14
+
15
+ gemspec :path => "../"
@@ -0,0 +1,15 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "mongoid", "~> 9.0"
4
+ gem "rails", "~> 8.1.0"
5
+ gem "devise", "~> 5.0"
6
+ gem "sqlite3", "~> 2.0"
7
+ gem "bigdecimal"
8
+ gem "mutex_m"
9
+ gem "drb"
10
+ gem "base64"
11
+ gem "benchmark"
12
+ gem "ostruct"
13
+ gem "simplecov", "~> 0.22", :require => false
14
+
15
+ gemspec :path => "../"
@@ -0,0 +1,16 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "rails", ENV.fetch("RAILS_VERSION", "~> 7.2.0")
4
+ gem "mongo", "~> 2.0"
5
+ gem "devise", "~> 4.9"
6
+ gem "sqlite3", ENV.fetch("SQLITE_VERSION", "~> 1.4")
7
+ gem "logger"
8
+ gem "bigdecimal"
9
+ gem "mutex_m"
10
+ gem "drb"
11
+ gem "base64"
12
+ gem "benchmark"
13
+ gem "ostruct"
14
+ gem "simplecov", "~> 0.22", :require => false if ENV["COVERAGE"] == "true"
15
+
16
+ gemspec :path => "../"
@@ -0,0 +1,3 @@
1
+ eval_gemfile "rails-current-mongo.gemfile"
2
+
3
+ gem "mongoid", ENV.fetch("MONGOID_VERSION", "~> 9.0")
@@ -92,7 +92,7 @@ module ActionDispatch
92
92
  end
93
93
 
94
94
  def unpack(packed)
95
- self.class.unpack(packed)
95
+ self.class.send(:unpack, packed)
96
96
  end
97
97
  end
98
98
  end
@@ -1,3 +1,4 @@
1
+ require "action_dispatch"
1
2
  require "action_dispatch/middleware/session/abstract_store"
2
3
 
3
4
  module ActionDispatch
@@ -15,6 +16,12 @@ module ActionDispatch
15
16
  self::Session
16
17
  end
17
18
 
19
+ def self.delete_stale_sessions(before:)
20
+ raise ArgumentError, "before must be a Time" unless before.is_a?(Time)
21
+
22
+ session_class.collection.find("updated_at" => { "$lt" => before.getutc }).delete_many.deleted_count
23
+ end
24
+
18
25
  private
19
26
 
20
27
  def session_class
@@ -38,6 +38,7 @@ module ActionDispatch
38
38
 
39
39
  def unpack(packed)
40
40
  return unless packed
41
+
41
42
  if packed.respond_to? :data
42
43
  Marshal.load(packed.data)
43
44
  else
@@ -1,3 +1,3 @@
1
1
  module MongoSessionStore
2
- VERSION = "3.2.1".freeze
2
+ VERSION = "3.3.0".freeze
3
3
  end
@@ -1,4 +1,4 @@
1
- require File.expand_path("../lib/mongo_session_store/version", __FILE__)
1
+ require File.expand_path("lib/mongo_session_store/version", __dir__)
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "mongo_session_store"
@@ -17,10 +17,7 @@ Gem::Specification.new do |s|
17
17
  s.add_dependency "actionpack", ">= 4.0"
18
18
  s.add_dependency "mongo", "~> 2.0"
19
19
 
20
- s.add_development_dependency "rspec-rails", "~> 3.6"
21
- s.add_development_dependency "capybara", "~> 2.15.0"
22
- s.add_development_dependency "pry", "~> 0.10"
23
- s.add_development_dependency "rake", "~> 11"
24
- s.add_development_dependency "rubocop", "0.45.0"
25
- s.add_development_dependency "mongoid-danger", "~> 0.1.0"
20
+ s.add_development_dependency "capybara", ">= 2.15"
21
+ s.add_development_dependency "rake"
22
+ s.add_development_dependency "rspec-rails", ">= 4.0"
26
23
  end
data/script/test ADDED
@@ -0,0 +1,20 @@
1
+ #!/bin/bash
2
+
3
+ set -eu
4
+
5
+ if [ -z "${BUNDLE_GEMFILE:-}" ]; then
6
+ echo "Error: BUNDLE_GEMFILE environment variable is required"
7
+ echo "Example: BUNDLE_GEMFILE=gemfiles/rails-6.0-mongo.gemfile script/test"
8
+ exit 1
9
+ fi
10
+
11
+ RUBY_VERSION=${RUBY_VERSION:-3.4}
12
+
13
+ docker run --rm -it \
14
+ --network mongo_session_store_default \
15
+ -e MONGODB_HOST=mongodb \
16
+ -e BUNDLE_GEMFILE="${BUNDLE_GEMFILE}" \
17
+ -v "$(pwd):/app" \
18
+ -w /app \
19
+ "ruby:${RUBY_VERSION}" \
20
+ /app/script/test_inner
data/script/test_inner ADDED
@@ -0,0 +1,19 @@
1
+ #!/bin/bash
2
+
3
+ set -eu
4
+
5
+ export BUNDLE_GEMFILE
6
+ export BUNDLE_PATH="/app/tmp/bundler/$RUBY_VERSION/$BUNDLE_GEMFILE"
7
+
8
+ ruby_version=$(ruby -e 'puts RUBY_VERSION')
9
+ major=$(echo "$ruby_version" | cut -d. -f1)
10
+ minor=$(echo "$ruby_version" | cut -d. -f2)
11
+
12
+ if [ "$major" -lt 2 ] || ([ "$major" -eq 2 ] && [ "$minor" -le 7 ]); then
13
+ gem install bundler -v 1.17.3
14
+ else
15
+ gem install bundler
16
+ fi
17
+
18
+ bundle install
19
+ bundle exec rspec
data/script/testenv ADDED
@@ -0,0 +1,6 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ echo "Starting test environment..."
6
+ docker compose up
data/spec/.rubocop.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  inherit_from: ../.rubocop.yml
2
2
 
3
- Metrics/LineLength:
3
+ Layout/LineLength:
4
4
  Max: 100
@@ -15,7 +15,7 @@ describe Devise::SessionsController, :type => :request do
15
15
  end
16
16
 
17
17
  def create_user
18
- post "/users", prepare_request_params(
18
+ post "/users", **prepare_request_params(
19
19
  "user[email]" => "person@example.com",
20
20
  "user[password]" => "secret",
21
21
  "user[password_confirmation]" => "secret"
@@ -23,7 +23,7 @@ describe Devise::SessionsController, :type => :request do
23
23
  end
24
24
 
25
25
  def sign_in
26
- post "/users/sign_in", prepare_request_params(
26
+ post "/users/sign_in", **prepare_request_params(
27
27
  "user[email]" => "person@example.com",
28
28
  "user[password]" => "secret",
29
29
  "user[password_confirmation]" => "secret"
@@ -0,0 +1,17 @@
1
+ require "spec_helper"
2
+
3
+ describe "Legacy session serialization" do
4
+ let(:session_class) { mongo_orm == "mongoid" ? MongoidStore::Session : MongoStore::Session }
5
+
6
+ it "reads raw marshalled strings as well as BSON binary data" do
7
+ payload = { "nested" => { "count" => 2 }, "tags" => %w[old session] }
8
+ session = session_class.new
9
+
10
+ expect(session.send(:unpack, Marshal.dump(payload))).to eq(payload)
11
+ expect(session.send(:unpack, BSON::Binary.new(Marshal.dump(payload), :generic))).to eq(payload)
12
+ end
13
+
14
+ it "returns nil when there is no serialized payload" do
15
+ expect(session_class.new.send(:unpack, nil)).to be_nil
16
+ end
17
+ end
@@ -0,0 +1,36 @@
1
+ require "rails_helper"
2
+
3
+ describe "Session data serialization" do
4
+ let(:session_class) { mongo_orm == "mongoid" ? MongoidStore::Session : MongoStore::Session }
5
+ let(:id) { generate_sid }
6
+ let(:search) { { "query" => "painting", "admin" => "1" } }
7
+ let(:parameters) do
8
+ values = { "search" => search }
9
+ if Gem.loaded_specs["rails"].version >= Gem::Version.new("7.0")
10
+ request = ActionDispatch::TestRequest.create("rack.input" => StringIO.new)
11
+ ActionController::Parameters.new(values, :request => request)
12
+ else
13
+ ActionController::Parameters.new(values)
14
+ end
15
+ end
16
+
17
+ it "round-trips explicitly permitted parameter values without request context" do
18
+ record = session_class.new(:_id => id)
19
+ record.data = { "search" => parameters.require(:search).permit(:query).to_h }
20
+ record.save
21
+
22
+ expect(session_class.where(:_id => id).first.data).to eq(
23
+ "search" => { "query" => "painting" }
24
+ )
25
+ end
26
+
27
+ if Gem.loaded_specs["rails"].version >= Gem::Version.new("7.0")
28
+ it "reports serialization errors for raw parameters retaining an input stream" do
29
+ record = session_class.new(:_id => id)
30
+ record.data = { "search" => parameters[:search] }
31
+
32
+ expect { record.save }.to raise_error(TypeError, /StringIO/)
33
+ expect(session_class.collection.find(:_id => id).count).to eq(0)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,77 @@
1
+ require "spec_helper"
2
+
3
+ describe ".delete_stale_sessions" do
4
+ let(:store) { mongo_orm == "mongoid" ? MongoidStore : MongoStore }
5
+ let(:collection) { store.session_class.collection }
6
+ let(:cutoff) { Time.utc(2026, 1, 1) }
7
+
8
+ it "deletes only sessions last updated strictly before the cutoff" do
9
+ collection.insert_many([
10
+ { :_id => "stale", :updated_at => cutoff - 1 },
11
+ { :_id => "boundary", :updated_at => cutoff },
12
+ { :_id => "active", :created_at => cutoff - 100, :updated_at => cutoff + 1 },
13
+ { :_id => "undated" },
14
+ { :_id => "null-date", :updated_at => nil }
15
+ ])
16
+
17
+ expect(store.delete_stale_sessions(:before => cutoff)).to eq(1)
18
+ expect(collection.find.map { |doc| doc["_id"] }.sort).to eq(
19
+ %w[active boundary null-date undated]
20
+ )
21
+ expect(store.delete_stale_sessions(:before => cutoff)).to eq(0)
22
+ end
23
+
24
+ it "returns zero for an empty collection" do
25
+ expect(store.delete_stale_sessions(:before => cutoff)).to eq(0)
26
+ end
27
+
28
+ it "accepts a cutoff with a UTC offset" do
29
+ collection.insert_one(:_id => "stale", :updated_at => cutoff - 1)
30
+ local_cutoff = cutoff.getlocal("-05:00")
31
+
32
+ expect(store.delete_stale_sessions(:before => local_cutoff)).to eq(1)
33
+ expect(local_cutoff.utc_offset).to eq(-5 * 60 * 60)
34
+ end
35
+
36
+ it "accepts a Rails time-zone cutoff" do
37
+ collection.insert_one(:_id => "stale", :updated_at => cutoff - 1)
38
+ zoned_cutoff = cutoff.in_time_zone("Eastern Time (US & Canada)")
39
+ expect(store.delete_stale_sessions(:before => zoned_cutoff)).to eq(1)
40
+ end
41
+
42
+ it "rejects invalid cutoffs without deleting sessions" do
43
+ collection.insert_one(:_id => "stale", :updated_at => cutoff - 1)
44
+
45
+ [nil, "2026-01-01", 1].each do |value|
46
+ expect { store.delete_stale_sessions(:before => value) }.to raise_error(
47
+ ArgumentError, "before must be a Time"
48
+ )
49
+ end
50
+ expect(collection.find.count).to eq(1)
51
+ end
52
+
53
+ it "uses the configured collection without touching the default collection" do
54
+ collection.insert_one(:_id => "default", :updated_at => cutoff - 1)
55
+ original_name = MongoSessionStore.collection_name
56
+ begin
57
+ MongoSessionStore.collection_name = "custom_sessions"
58
+ custom_collection = store.session_class.collection
59
+ custom_collection.insert_one(:_id => "custom", :updated_at => cutoff - 1)
60
+
61
+ expect(store.delete_stale_sessions(:before => cutoff)).to eq(1)
62
+ expect(custom_collection.find.count).to eq(0)
63
+ ensure
64
+ MongoSessionStore.collection_name = original_name
65
+ end
66
+ expect(collection.find.count).to eq(1)
67
+ end
68
+
69
+ it "does not suppress database errors" do
70
+ allow(store.session_class).to receive(:collection)
71
+ .and_raise(MongoSessionStore::Error, "unavailable")
72
+
73
+ expect { store.delete_stale_sessions(:before => cutoff) }.to raise_error(
74
+ MongoSessionStore::Error, "unavailable"
75
+ )
76
+ end
77
+ end
@@ -0,0 +1,8 @@
1
+ require "spec_helper"
2
+
3
+ describe MongoSessionStore do
4
+ it "defines a frozen semantic version" do
5
+ expect(MongoSessionStore::VERSION).to match(/\A\d+\.\d+\.\d+\z/)
6
+ expect(MongoSessionStore::VERSION).to be_frozen
7
+ end
8
+ end
data/spec/rails_helper.rb CHANGED
@@ -1,5 +1,9 @@
1
1
  ENV["RAILS_ENV"] = "test"
2
+ require "logger"
3
+ require "active_support"
2
4
  rails_version = Gem.loaded_specs["rails"].version.to_s[/^\d\.\d/]
5
+ # Later Rails versions use the same minimal integration app as Rails 6.
6
+ rails_version = "6.0" if Gem.loaded_specs["rails"].version >= Gem::Version.new("6.0")
3
7
  if Gem.loaded_specs["rails"].version >= Gem::Version.new("5.2")
4
8
  require "active_support/message_encryptor"
5
9
  end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,13 @@
1
+ if ENV["COVERAGE"] == "true"
2
+ require "simplecov"
3
+ SimpleCov.command_name "RSpec #{Gem.loaded_specs.key?("mongoid") ? "Mongoid" : "Mongo"}"
4
+ SimpleCov.start do
5
+ track_files "lib/**/*.rb"
6
+ add_filter "/spec/"
7
+ end
8
+ end
9
+
10
+ require "logger"
1
11
  require "mongo"
2
12
  require "mongoid" if Gem.loaded_specs["mongoid"]
3
13
  require "mongo_session_store"
@@ -27,6 +37,8 @@ RSpec.configure do |config|
27
37
  config.before :suite do
28
38
  Mongo::Logger.logger.level = ::Logger::INFO
29
39
 
40
+ mongodb_host = ENV["MONGODB_HOST"] || "127.0.0.1"
41
+ mongodb_port = ENV["MONGODB_PORT"] || "27017"
30
42
  if mongo_orm == "mongoid"
31
43
  Mongoid.logger.level = Logger::INFO
32
44
  Mongoid.configure do |c|
@@ -34,14 +46,14 @@ RSpec.configure do |config|
34
46
  "clients" => {
35
47
  "default" => {
36
48
  "database" => TestDatabaseHelper.test_database_name,
37
- "hosts" => ["127.0.0.1:27017"]
49
+ "hosts" => ["#{mongodb_host}:#{mongodb_port}"]
38
50
  }
39
51
  }
40
52
  )
41
53
  end
42
54
  else
43
55
  MongoStore::Session.database = Mongo::Client.new(
44
- ["127.0.0.1:27017"],
56
+ ["#{mongodb_host}:#{mongodb_port}"],
45
57
  :database => TestDatabaseHelper.test_database_name
46
58
  )
47
59
  end
@@ -10,16 +10,22 @@ default: &default
10
10
  timeout: 5000
11
11
 
12
12
  development:
13
- <<: *default
13
+ adapter: sqlite3
14
+ pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
15
+ timeout: 5000
14
16
  database: db/development.sqlite3
15
17
 
16
18
  # Warning: The database defined as "test" will be erased and
17
19
  # re-generated from your development database when you run "rake".
18
20
  # Do not set this db to the same as development or production.
19
21
  test:
20
- <<: *default
22
+ adapter: sqlite3
23
+ pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
24
+ timeout: 5000
21
25
  database: db/test.sqlite3
22
26
 
23
27
  production:
24
- <<: *default
28
+ adapter: sqlite3
29
+ pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
30
+ timeout: 5000
25
31
  database: db/production.sqlite3
@@ -31,7 +31,7 @@ Rails.application.configure do
31
31
  config.action_controller.allow_forgery_protection = false
32
32
 
33
33
  # Print deprecation notices to the stderr.
34
- config.active_support.deprecation = :stderr
34
+ config.active_support.deprecation = :stderr if Rails.gem_version < Gem::Version.new("7.1")
35
35
 
36
36
  # Raises error for missing translations.
37
37
  # config.action_view.raise_on_missing_translations = true
@@ -6,9 +6,7 @@ module TestDatabaseHelper
6
6
  when "mongoid"
7
7
  MongoidStore::Session.mongo_client
8
8
  when "mongo"
9
- Mongo::Client
10
- .new(["127.0.0.1:27017"], :database => test_database_name)
11
- .database
9
+ MongoStore::Session.database.database
12
10
  end
13
11
  end
14
12
 
@@ -9,6 +9,8 @@ class MyApp
9
9
  end
10
10
 
11
11
  MyAppWrapped = Rack::Builder.new do |builder|
12
- builder.use Rack::Session::Cookie, :secret => "Very secret secret"
12
+ if defined?(Rack::Session::Cookie)
13
+ builder.use Rack::Session::Cookie, :secret => "Very secret secret"
14
+ end
13
15
  builder.run MyApp.new
14
16
  end