mongo_session_store-rails 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +44 -0
  5. data/.travis.yml +29 -0
  6. data/Gemfile +3 -0
  7. data/README.md +144 -0
  8. data/Rakefile +87 -0
  9. data/gemfiles/rails-4.0-mongo.gemfile +9 -0
  10. data/gemfiles/rails-4.0-mongoid.gemfile +9 -0
  11. data/gemfiles/rails-4.1-mongo.gemfile +9 -0
  12. data/gemfiles/rails-4.1-mongoid.gemfile +9 -0
  13. data/gemfiles/rails-4.2-mongo.gemfile +9 -0
  14. data/gemfiles/rails-4.2-mongoid.gemfile +9 -0
  15. data/lib/mongo_session_store-rails.rb +1 -0
  16. data/lib/mongo_session_store.rb +27 -0
  17. data/lib/mongo_session_store/mongo_store.rb +102 -0
  18. data/lib/mongo_session_store/mongo_store_base.rb +69 -0
  19. data/lib/mongo_session_store/mongoid_store.rb +47 -0
  20. data/lib/mongo_session_store/version.rb +3 -0
  21. data/mongo_session_store.gemspec +22 -0
  22. data/perf/benchmark.rb +99 -0
  23. data/spec/.rubocop.yml +4 -0
  24. data/spec/integrations/devise_spec.rb +109 -0
  25. data/spec/lib/mongo_session_store/mongo_store/session_spec.rb +250 -0
  26. data/spec/lib/mongo_session_store/mongo_store_base_spec.rb +269 -0
  27. data/spec/lib/mongo_session_store/mongo_store_spec.rb +13 -0
  28. data/spec/lib/mongo_session_store/mongoid_store/session_spec.rb +67 -0
  29. data/spec/lib/mongo_session_store/mongoid_store_spec.rb +13 -0
  30. data/spec/lib/mongo_session_store_spec.rb +64 -0
  31. data/spec/rails_helper.rb +19 -0
  32. data/spec/spec_helper.rb +44 -0
  33. data/spec/support/apps/rails_4.0_app/Rakefile +6 -0
  34. data/spec/support/apps/rails_4.0_app/app/controllers/application_controller.rb +5 -0
  35. data/spec/support/apps/rails_4.0_app/app/controllers/home_controller.rb +4 -0
  36. data/spec/support/apps/rails_4.0_app/app/models/user.rb +5 -0
  37. data/spec/support/apps/rails_4.0_app/app/views/home/index.html.erb +10 -0
  38. data/spec/support/apps/rails_4.0_app/app/views/layouts/application.html.erb +17 -0
  39. data/spec/support/apps/rails_4.0_app/bin/bundle +3 -0
  40. data/spec/support/apps/rails_4.0_app/bin/rails +4 -0
  41. data/spec/support/apps/rails_4.0_app/bin/rake +4 -0
  42. data/spec/support/apps/rails_4.0_app/config.ru +4 -0
  43. data/spec/support/apps/rails_4.0_app/config/application.rb +11 -0
  44. data/spec/support/apps/rails_4.0_app/config/boot.rb +4 -0
  45. data/spec/support/apps/rails_4.0_app/config/database.yml +11 -0
  46. data/spec/support/apps/rails_4.0_app/config/environment.rb +5 -0
  47. data/spec/support/apps/rails_4.0_app/config/environments/development.rb +12 -0
  48. data/spec/support/apps/rails_4.0_app/config/environments/test.rb +19 -0
  49. data/spec/support/apps/rails_4.0_app/config/initializers/devise.rb +254 -0
  50. data/spec/support/apps/rails_4.0_app/config/initializers/secret_token.rb +12 -0
  51. data/spec/support/apps/rails_4.0_app/config/initializers/session_store.rb +1 -0
  52. data/spec/support/apps/rails_4.0_app/config/locales/devise.en.yml +59 -0
  53. data/spec/support/apps/rails_4.0_app/config/locales/en.yml +23 -0
  54. data/spec/support/apps/rails_4.0_app/config/mongo.yml +11 -0
  55. data/spec/support/apps/rails_4.0_app/config/mongoid.yml +13 -0
  56. data/spec/support/apps/rails_4.0_app/config/routes.rb +4 -0
  57. data/spec/support/apps/rails_4.0_app/db/migrate/20140301171212_add_devise_users.rb +11 -0
  58. data/spec/support/apps/rails_4.0_app/db/schema.rb +25 -0
  59. data/spec/support/apps/rails_4.0_app/db/seeds.rb +7 -0
  60. data/spec/support/apps/rails_4.0_app/lib/assets/.keep +0 -0
  61. data/spec/support/apps/rails_4.0_app/lib/tasks/.keep +0 -0
  62. data/spec/support/apps/rails_4.0_app/log/.keep +0 -0
  63. data/spec/support/apps/rails_4.0_app/public/404.html +58 -0
  64. data/spec/support/apps/rails_4.0_app/public/422.html +58 -0
  65. data/spec/support/apps/rails_4.0_app/public/500.html +57 -0
  66. data/spec/support/apps/rails_4.0_app/public/favicon.ico +0 -0
  67. data/spec/support/apps/rails_4.0_app/public/robots.txt +5 -0
  68. data/spec/support/apps/rails_4.1_app/Rakefile +3 -0
  69. data/spec/support/apps/rails_4.1_app/app/controllers/application_controller.rb +5 -0
  70. data/spec/support/apps/rails_4.1_app/app/controllers/home_controller.rb +4 -0
  71. data/spec/support/apps/rails_4.1_app/app/models/user.rb +5 -0
  72. data/spec/support/apps/rails_4.1_app/app/views/home/index.html.erb +10 -0
  73. data/spec/support/apps/rails_4.1_app/app/views/layouts/application.html.erb +17 -0
  74. data/spec/support/apps/rails_4.1_app/bin/bundle +3 -0
  75. data/spec/support/apps/rails_4.1_app/bin/rails +4 -0
  76. data/spec/support/apps/rails_4.1_app/bin/rake +4 -0
  77. data/spec/support/apps/rails_4.1_app/config.ru +2 -0
  78. data/spec/support/apps/rails_4.1_app/config/application.rb +11 -0
  79. data/spec/support/apps/rails_4.1_app/config/boot.rb +4 -0
  80. data/spec/support/apps/rails_4.1_app/config/database.yml +11 -0
  81. data/spec/support/apps/rails_4.1_app/config/environment.rb +5 -0
  82. data/spec/support/apps/rails_4.1_app/config/environments/development.rb +12 -0
  83. data/spec/support/apps/rails_4.1_app/config/environments/test.rb +19 -0
  84. data/spec/support/apps/rails_4.1_app/config/initializers/devise.rb +254 -0
  85. data/spec/support/apps/rails_4.1_app/config/initializers/session_store.rb +1 -0
  86. data/spec/support/apps/rails_4.1_app/config/locales/devise.en.yml +59 -0
  87. data/spec/support/apps/rails_4.1_app/config/locales/en.yml +23 -0
  88. data/spec/support/apps/rails_4.1_app/config/mongo.yml +11 -0
  89. data/spec/support/apps/rails_4.1_app/config/mongoid.yml +13 -0
  90. data/spec/support/apps/rails_4.1_app/config/routes.rb +4 -0
  91. data/spec/support/apps/rails_4.1_app/config/secrets.yml +22 -0
  92. data/spec/support/apps/rails_4.1_app/db/migrate/20140301171212_add_devise_users.rb +11 -0
  93. data/spec/support/apps/rails_4.1_app/db/schema.rb +25 -0
  94. data/spec/support/apps/rails_4.1_app/db/seeds.rb +7 -0
  95. data/spec/support/apps/rails_4.1_app/lib/assets/.keep +0 -0
  96. data/spec/support/apps/rails_4.1_app/lib/tasks/.keep +0 -0
  97. data/spec/support/apps/rails_4.1_app/log/.keep +0 -0
  98. data/spec/support/apps/rails_4.1_app/public/404.html +67 -0
  99. data/spec/support/apps/rails_4.1_app/public/422.html +67 -0
  100. data/spec/support/apps/rails_4.1_app/public/500.html +66 -0
  101. data/spec/support/apps/rails_4.1_app/public/favicon.ico +0 -0
  102. data/spec/support/apps/rails_4.1_app/public/robots.txt +5 -0
  103. data/spec/support/apps/rails_4.2_app/Rakefile +2 -0
  104. data/spec/support/apps/rails_4.2_app/app/controllers/application_controller.rb +5 -0
  105. data/spec/support/apps/rails_4.2_app/app/controllers/home_controller.rb +4 -0
  106. data/spec/support/apps/rails_4.2_app/app/models/user.rb +5 -0
  107. data/spec/support/apps/rails_4.2_app/app/views/home/index.html.erb +10 -0
  108. data/spec/support/apps/rails_4.2_app/app/views/layouts/application.html.erb +15 -0
  109. data/spec/support/apps/rails_4.2_app/bin/bundle +3 -0
  110. data/spec/support/apps/rails_4.2_app/bin/rails +8 -0
  111. data/spec/support/apps/rails_4.2_app/bin/rake +4 -0
  112. data/spec/support/apps/rails_4.2_app/config.ru +4 -0
  113. data/spec/support/apps/rails_4.2_app/config/application.rb +11 -0
  114. data/spec/support/apps/rails_4.2_app/config/boot.rb +3 -0
  115. data/spec/support/apps/rails_4.2_app/config/database.yml +11 -0
  116. data/spec/support/apps/rails_4.2_app/config/environment.rb +5 -0
  117. data/spec/support/apps/rails_4.2_app/config/environments/development.rb +12 -0
  118. data/spec/support/apps/rails_4.2_app/config/environments/test.rb +19 -0
  119. data/spec/support/apps/rails_4.2_app/config/initializers/devise.rb +254 -0
  120. data/spec/support/apps/rails_4.2_app/config/initializers/session_store.rb +1 -0
  121. data/spec/support/apps/rails_4.2_app/config/locales/en.yml +23 -0
  122. data/spec/support/apps/rails_4.2_app/config/mongo.yml +11 -0
  123. data/spec/support/apps/rails_4.2_app/config/mongoid.yml +13 -0
  124. data/spec/support/apps/rails_4.2_app/config/routes.rb +4 -0
  125. data/spec/support/apps/rails_4.2_app/config/secrets.yml +22 -0
  126. data/spec/support/apps/rails_4.2_app/db/migrate/20140301171212_add_devise_users.rb +11 -0
  127. data/spec/support/apps/rails_4.2_app/db/schema.rb +25 -0
  128. data/spec/support/apps/rails_4.2_app/db/seeds.rb +7 -0
  129. data/spec/support/helpers/test_database_helper.rb +22 -0
  130. metadata +367 -0
@@ -0,0 +1,69 @@
1
+ require "action_dispatch/middleware/session/abstract_store"
2
+
3
+ module ActionDispatch
4
+ module Session
5
+ class MongoStoreBase < AbstractStore
6
+ SESSION_RECORD_KEY = "rack.session.record".freeze
7
+ ENV_SESSION_OPTIONS_KEY = Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY
8
+
9
+ def self.session_class
10
+ self::Session
11
+ end
12
+
13
+ private
14
+
15
+ def session_class
16
+ self.class.session_class
17
+ end
18
+
19
+ def generate_sid
20
+ BSON::ObjectId.new.to_s
21
+ end
22
+
23
+ def get_session(env, sid)
24
+ id, record = find_or_initialize_session(sid)
25
+ env[SESSION_RECORD_KEY] = record
26
+ [id, record.data]
27
+ end
28
+
29
+ def set_session(env, sid, session_data, _options = {})
30
+ id, record = get_session_record(env, sid)
31
+ record.data = session_data
32
+ yield if block_given?
33
+ # Rack spec dictates that set_session should return true or false
34
+ # depending on whether or not the session was saved or not.
35
+ # However, ActionPack seems to want a session id instead.
36
+ record.save ? id : false
37
+ end
38
+
39
+ def find_or_initialize_session(id)
40
+ existing_session = (id && session_class.where(:_id => id).first)
41
+ session = existing_session if existing_session
42
+ session ||= session_class.new(:_id => generate_sid)
43
+ [session._id.to_s, session]
44
+ end
45
+
46
+ def get_session_record(env, sid)
47
+ if env[ENV_SESSION_OPTIONS_KEY][:id].nil? || !env[SESSION_RECORD_KEY]
48
+ sid, env[SESSION_RECORD_KEY] = find_or_initialize_session(sid)
49
+ end
50
+
51
+ [sid, env[SESSION_RECORD_KEY]]
52
+ end
53
+
54
+ def destroy_session(env, _session_id, options)
55
+ destroy(env)
56
+ generate_sid unless options[:drop]
57
+ end
58
+
59
+ def destroy(env)
60
+ sid = current_session_id(env)
61
+ return unless sid
62
+
63
+ _, record = get_session_record(env, sid)
64
+ record.destroy
65
+ env[SESSION_RECORD_KEY] = nil
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,47 @@
1
+ require "mongo_session_store/mongo_store_base"
2
+
3
+ module ActionDispatch
4
+ module Session
5
+ class MongoidStore < MongoStoreBase
6
+ class Session
7
+ include Mongoid::Document
8
+ include Mongoid::Timestamps
9
+
10
+ attr_writer :data
11
+
12
+ store_in :collection => MongoSessionStore.collection_name
13
+
14
+ field :_data, :type => BSON::Binary, :default => -> { pack({}) }
15
+
16
+ def self.pack(data)
17
+ BSON::Binary.new(Marshal.dump(data), :generic)
18
+ end
19
+
20
+ def data
21
+ @data ||= unpack(_data)
22
+ end
23
+
24
+ private
25
+
26
+ before_save do
27
+ self._data = pack(data)
28
+ end
29
+
30
+ def pack(data)
31
+ self.class.pack(data)
32
+ end
33
+
34
+ def unpack(packed)
35
+ return unless packed
36
+ if packed.respond_to? :data
37
+ Marshal.load(packed.data)
38
+ else
39
+ Marshal.load(packed.to_s)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ MongoidStore = ActionDispatch::Session::MongoidStore
@@ -0,0 +1,3 @@
1
+ module MongoSessionStore
2
+ VERSION = "7.0.0".freeze
3
+ end
@@ -0,0 +1,22 @@
1
+ require File.expand_path("../lib/mongo_session_store/version", __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "mongo_session_store-rails"
5
+ s.version = MongoSessionStore::VERSION
6
+
7
+ s.authors = ["Tom de Bruijn", "Brian Hempel", "Nicolas M\303\251rouze", "Tony Pitale", "Chris Brickley"]
8
+ s.email = ["tom@tomdebruijn.com.com"]
9
+ s.files = `git ls-files`.split("\n")
10
+ s.test_files = `git ls-files -- {spec,perf}/*`.split("\n")
11
+ s.homepage = "http://github.com/appsignal/mongo_session_store"
12
+ s.license = "MIT"
13
+ s.require_paths = ["lib"]
14
+ s.summary = "Rails session stores for Mongoid, or any other ODM. Rails 4 compatible."
15
+
16
+ s.add_dependency "actionpack", "~> 4.0"
17
+ s.add_dependency "mongo", "~> 2.0"
18
+ s.add_development_dependency "rspec-rails", "~> 3"
19
+ s.add_development_dependency "pry", "~> 0.10"
20
+ s.add_development_dependency "rake", "~> 11"
21
+ s.add_development_dependency "rubocop", "0.45.0"
22
+ end
@@ -0,0 +1,99 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+ require "benchmark"
4
+ require "action_dispatch"
5
+ require "mongoid"
6
+ require "mongo_session_store"
7
+ require "mongo_session_store/mongoid_store"
8
+ require "mongo_session_store/mongo_store"
9
+ require File.expand_path("../../lib/mongo_session_store", __FILE__)
10
+
11
+ Mongo::Logger.logger.level = Logger::FATAL
12
+ Mongoid.logger.level = Logger::FATAL
13
+
14
+ MongoSessionStore.collection_name = "session_collection"
15
+ MongoStore::Session.database =
16
+ Mongo::Client.new(["127.0.0.1:27017"], :database => "test_database")
17
+ Mongoid.configure do |c|
18
+ c.load_configuration(
19
+ "clients" => {
20
+ "default" => {
21
+ "database" => "test_database",
22
+ "hosts" => ["127.0.0.1:27017"]
23
+ }
24
+ }
25
+ )
26
+ end
27
+
28
+ RUNS = 2000
29
+
30
+ def benchmark(test_name)
31
+ time = Benchmark.realtime do
32
+ RUNS.times do
33
+ yield
34
+ end
35
+ end
36
+ puts "#{(time / RUNS * 100_000).round / 100.0}ms per #{test_name}"
37
+ end
38
+
39
+ def benchmark_store(store)
40
+ collection = store.class.session_class.collection
41
+ collection.drop
42
+
43
+ large_session = {
44
+ :something => "not nothing",
45
+ :left => "not right",
46
+ :welcome => "not despised",
47
+ :visits => [
48
+ "http://www.google.com",
49
+ "localhost:3000",
50
+ "http://localhost:3000/increment_session",
51
+ "http://www.iso.org/iso/country_codes/iso_3166_code_lists/iso-3166-1_decoding_table.htm",
52
+ "http://www.geonames.org/search.html?q=2303+australia&country="
53
+ ],
54
+ :one_k_of_data => "a" * 1024,
55
+ :another_k => "b" * 1024,
56
+ :more_data => [5] * 500,
57
+ :too_much_data_for_a_cookie => "c" * 8000,
58
+ :a_bunch_of_floats_in_embedded_docs => [:float_a => 3.141, :float_b => -1.1] * 100
59
+ }
60
+
61
+ ids = []
62
+
63
+ env = {
64
+ "rack.session" => large_session,
65
+ "rack.session.options" => { :id => store.send(:generate_sid) }
66
+ }
67
+ benchmark "session save" do
68
+ ids << store.send(
69
+ :set_session,
70
+ env.merge("rack.session.options" => { :id => nil }), nil, env["rack.session"]
71
+ )
72
+ end
73
+
74
+ ids.shuffle!
75
+
76
+ benchmark "session load" do
77
+ id = ids.pop
78
+ local_env = { "rack.request.cookie_hash" => { "_session_id" => id } }
79
+ _, data = store.send(:get_session, local_env, id)
80
+ if data[:something] != "not nothing" || data[:a_bunch_of_floats_in_embedded_docs][0][:float_a] != 3.141
81
+ puts data
82
+ raise "saved and fetched data do not match!"
83
+ end
84
+ end
85
+
86
+ stats = database.command(:collstats => MongoSessionStore.collection_name).documents.first
87
+ puts " Total Size: #{stats["size"]}"
88
+ puts " Object count: #{stats["count"]}"
89
+ puts " Average object size: #{stats["avgObjSize"]}"
90
+ puts " Index sizes: #{stats["indexSizes"].inspect}"
91
+ end
92
+
93
+ puts "MongoidStore..."
94
+ mongoid_store = MongoidStore.new(nil)
95
+ benchmark_store(mongoid_store)
96
+
97
+ puts "MongoStore..."
98
+ mongo_store = MongoStore.new(nil)
99
+ benchmark_store(mongo_store)
@@ -0,0 +1,4 @@
1
+ inherit_from: ../.rubocop.yml
2
+
3
+ Metrics/LineLength:
4
+ Max: 100
@@ -0,0 +1,109 @@
1
+ require "rails_helper"
2
+
3
+ describe Devise::SessionsController, :type => :request do
4
+ def create_user
5
+ post "/users",
6
+ "user[email]" => "person@example.com",
7
+ "user[password]" => "secret",
8
+ "user[password_confirmation]" => "secret"
9
+ end
10
+
11
+ def sign_in
12
+ post "/users/sign_in",
13
+ "user[email]" => "person@example.com",
14
+ "user[password]" => "secret",
15
+ "user[password_confirmation]" => "secret"
16
+ end
17
+
18
+ def sign_out
19
+ delete "/users/sign_out"
20
+ end
21
+
22
+ def expect_to_be_signed_in
23
+ expect(controller.user_signed_in?).to eq(true)
24
+ get "/"
25
+ expect(response.body.squish).to include "You are signed in as person@example.com"
26
+ end
27
+
28
+ def expect_to_not_be_signed_in
29
+ expect(controller.user_signed_in?).to eq(false)
30
+ get "/"
31
+ expect(response.body.squish).to include "You are signed out"
32
+ end
33
+
34
+ it "creates a user" do
35
+ expect do
36
+ create_user
37
+ expect(response.status).to eq(302)
38
+ end.to change { User.count }.from(0).to(1)
39
+
40
+ get response.redirect_url
41
+ expect(response.body.squish).to include \
42
+ "You are signed in as person@example.com",
43
+ "You have signed up successfully"
44
+ end
45
+
46
+ describe "user session" do
47
+ describe "sign out" do
48
+ before do
49
+ create_user
50
+ expect_to_be_signed_in
51
+ end
52
+
53
+ it "signs out user" do
54
+ sign_out
55
+
56
+ expect(response.status).to eq(302)
57
+ expect_to_not_be_signed_in
58
+ expect(response.body.squish).to include "Signed out successfully"
59
+ end
60
+ end
61
+
62
+ describe "sign in" do
63
+ before do
64
+ create_user
65
+ sign_out
66
+ expect_to_not_be_signed_in
67
+ end
68
+
69
+ it "signs in user" do
70
+ sign_in
71
+
72
+ expect(response.status).to eq(302)
73
+ expect_to_be_signed_in
74
+ expect(response.body.squish).to include "Signed in successfully"
75
+ end
76
+ end
77
+ end
78
+
79
+ describe "session_store" do
80
+ subject { Rails.application.config.session_store.session_class.to_s }
81
+
82
+ it "uses the mongo #{mongo_orm} session store class" do
83
+ expect(subject).to eq \
84
+ "ActionDispatch::Session::#{mongo_orm.camelize}Store::Session"
85
+ end
86
+ end
87
+
88
+ describe "sessions collection" do
89
+ let(:collection) { test_database["sessions"] }
90
+
91
+ it "creates a record in the sessions collection" do
92
+ expect do
93
+ create_user
94
+ end.to change { collection.find.count }.from(0).to(1)
95
+ end
96
+
97
+ describe "custom collection" do
98
+ before { MongoSessionStore.collection_name = "dance_parties" }
99
+ after { MongoSessionStore.collection_name = "sessions" }
100
+ let(:collection) { test_database["dance_parties"] }
101
+
102
+ it "creates a record in the custom sessions collection" do
103
+ expect do
104
+ create_user
105
+ end.to change { collection.find.count }.from(0).to(1)
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,250 @@
1
+ require "spec_helper"
2
+
3
+ if mongo_orm == "mongo"
4
+ require "mongo_session_store"
5
+ require "mongo_session_store/mongo_store"
6
+
7
+ describe MongoStore::Session do
8
+ before { described_class.collection.drop }
9
+
10
+ describe ".database" do
11
+ subject { described_class.database }
12
+
13
+ context "with database" do
14
+ let(:database) do
15
+ MongoStore::Session.database = Mongo::Client.new(
16
+ ["127.0.0.1:27017"],
17
+ :database => TestDatabaseHelper.test_database_name
18
+ )
19
+ end
20
+ around do |example|
21
+ original_db = described_class.database
22
+ described_class.database = database
23
+ example.run
24
+ described_class.database = original_db
25
+ end
26
+
27
+ it "returns the database" do
28
+ expect(subject).to eq(database)
29
+ end
30
+ end
31
+
32
+ context "without database" do
33
+ it "raises an error" do
34
+ original_db = described_class.database
35
+ described_class.database = nil
36
+
37
+ expect { subject }.to raise_error(described_class::NoMongoClientError)
38
+
39
+ described_class.database = original_db
40
+ end
41
+ end
42
+ end
43
+
44
+ describe ".collection_name" do
45
+ # Where the name comes from:
46
+ # - "test_database" set in spec_helper
47
+ # - "sessions" is derived from model name
48
+ it "saves in the given collection_name" do
49
+ expect(described_class.collection.namespace).to eq("test_database.sessions")
50
+ end
51
+ end
52
+
53
+ describe ".where" do
54
+ subject { described_class.where(:_id => id) }
55
+
56
+ context "without matching records" do
57
+ let(:id) { nil }
58
+
59
+ it "returns an empty array" do
60
+ expect(subject).to eq([])
61
+ end
62
+ end
63
+
64
+ context "with matching records" do
65
+ let(:session) { described_class.new(:_id => BSON::ObjectId.new).tap(&:save) }
66
+ let(:id) { session._id }
67
+ before do
68
+ # Noise
69
+ described_class.new(:_id => BSON::ObjectId.new).tap(&:save)
70
+ end
71
+
72
+ it "returns session model objects" do
73
+ expect(subject.map(&:_id)).to eq([session._id])
74
+ end
75
+ end
76
+ end
77
+
78
+ describe ".load" do
79
+ let(:created_at) { Time.local(2016, 5, 27, 12, 25, 15) }
80
+ let(:updated_at) { Time.local(2016, 5, 28, 16, 15, 55) }
81
+ let(:session) { described_class.load(attributes) }
82
+
83
+ context "without data" do
84
+ let(:attributes) do
85
+ {
86
+ :_id => BSON::ObjectId.new,
87
+ :created_at => created_at,
88
+ :updated_at => updated_at
89
+ }
90
+ end
91
+
92
+ it "creates a session object with empty data" do
93
+ expect(session._id).to be_kind_of(BSON::ObjectId)
94
+ expect(session.data).to eq({})
95
+ expect(session.created_at).to eq(created_at)
96
+ expect(session.updated_at).to eq(updated_at)
97
+ end
98
+ end
99
+
100
+ context "with data key as a symbol" do
101
+ let(:attributes) do
102
+ {
103
+ :_id => BSON::ObjectId.new,
104
+ :data => BSON::Binary.new(Marshal.dump(:foo => "bar"), :generic),
105
+ :created_at => created_at,
106
+ :updated_at => updated_at
107
+ }
108
+ end
109
+
110
+ it "creates a session object with correct data" do
111
+ expect(session._id).to be_kind_of(BSON::ObjectId)
112
+ expect(session.data).to eq(:foo => "bar")
113
+ expect(session.created_at).to eq(created_at)
114
+ expect(session.updated_at).to eq(updated_at)
115
+ end
116
+ end
117
+
118
+ context "with data key as a string" do
119
+ let(:attributes) do
120
+ {
121
+ :_id => BSON::ObjectId.new,
122
+ "data" => BSON::Binary.new(Marshal.dump(:foo => "bar"), :generic),
123
+ :created_at => created_at,
124
+ :updated_at => updated_at
125
+ }
126
+ end
127
+
128
+ it "creates a session object with correct data" do
129
+ expect(session._id).to be_kind_of(BSON::ObjectId)
130
+ expect(session.data).to eq(:foo => "bar")
131
+ expect(session.created_at).to eq(created_at)
132
+ expect(session.updated_at).to eq(updated_at)
133
+ end
134
+ end
135
+ end
136
+
137
+ describe "#save" do
138
+ let(:id) { BSON::ObjectId.new }
139
+ subject { described_class.collection.find(:_id => id).first }
140
+
141
+ describe "_id attribute" do
142
+ let(:session) { described_class.new(:_id => id) }
143
+ before { session.save }
144
+
145
+ it "saves a record in MongoDB" do
146
+ expect(subject[:_id]).to eq(id)
147
+ end
148
+ end
149
+
150
+ describe "timestamps" do
151
+ let!(:time) { Time.now.utc }
152
+ let(:session) { described_class.new(:_id => id) }
153
+
154
+ describe "#created_at" do
155
+ context "without time" do
156
+ before { session.save }
157
+
158
+ it "sets the current time" do
159
+ expect(subject[:created_at]).to be_kind_of(Time)
160
+ expect(subject[:created_at].to_s).to eq(time.to_s)
161
+ end
162
+ end
163
+
164
+ context "with time" do
165
+ let!(:original_created_at) { Time.parse("Thu Nov 29 14:33:20 2001").utc }
166
+ before { session.created_at = original_created_at }
167
+
168
+ it "keeps original created_at time" do
169
+ expect do
170
+ session.save
171
+ end.to_not change { session.created_at.to_s }
172
+
173
+ expect(subject[:created_at]).to be_kind_of(Time)
174
+ expect(subject[:created_at].to_s).to eq(original_created_at.to_s)
175
+ end
176
+ end
177
+ end
178
+
179
+ describe "#updated_at" do
180
+ context "without time" do
181
+ it "sets the current time" do
182
+ expect do
183
+ session.save
184
+ end.to change { session.updated_at.to_s }.from("").to(time.to_s)
185
+
186
+ expect(subject[:updated_at]).to be_kind_of(Time)
187
+ expect(subject[:updated_at].to_s).to eq(time.to_s)
188
+ end
189
+ end
190
+
191
+ context "with time" do
192
+ let!(:original_updated_at) { Time.parse("Thu Nov 29 14:33:20 2001").utc }
193
+ before { session.updated_at = original_updated_at }
194
+
195
+ it "updates the time to the current time" do
196
+ expect do
197
+ session.save
198
+ end.to change { session.updated_at.to_s }.from(original_updated_at.to_s).to(time.to_s)
199
+
200
+ expect(subject[:updated_at]).to be_kind_of(Time)
201
+ expect(subject[:updated_at].to_s).to eq(time.to_s)
202
+ end
203
+ end
204
+ end
205
+ end
206
+
207
+ context "with data attribute" do
208
+ before do
209
+ session = described_class.new(
210
+ :_id => id,
211
+ :data => { :foo => "bar" }
212
+ )
213
+ session.save
214
+ end
215
+
216
+ it "saves the marshalled data" do
217
+ expect(subject[:data]).to be_kind_of(BSON::Binary)
218
+ unmarshalled_data = Marshal.load(StringIO.new(subject[:data].data))
219
+ expect(unmarshalled_data).to eq(:foo => "bar")
220
+ end
221
+ end
222
+
223
+ context "without data attribute" do
224
+ before do
225
+ session = described_class.new(:_id => id)
226
+ session.save
227
+ end
228
+
229
+ it "saves empty hash" do
230
+ expect(subject[:data]).to be_kind_of(BSON::Binary)
231
+ unmarshalled_data = Marshal.load(StringIO.new(subject[:data].data))
232
+ expect(unmarshalled_data).to eq({})
233
+ end
234
+ end
235
+ end
236
+
237
+ describe "#destroy" do
238
+ let(:id) { BSON::ObjectId.new }
239
+ let(:session) { described_class.new(:_id => id) }
240
+ before { session.save }
241
+
242
+ it "removes the record from the collection" do
243
+ expect do
244
+ session.destroy
245
+ end.to change { described_class.collection.count }.from(1).to(0)
246
+ expect(described_class.collection.find(:_id => session._id).count).to eq(0)
247
+ end
248
+ end
249
+ end
250
+ end