mongo_session_store-rails 7.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.
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