flipper-mongo 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -14,5 +14,5 @@ Gem::Specification.new do |gem|
14
14
  gem.name = "flipper-mongo"
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = Flipper::Adapters::Mongo::VERSION
17
- gem.add_dependency 'flipper', '~> 0.1.1'
17
+ gem.add_dependency 'flipper', '~> 0.2'
18
18
  end
@@ -1,64 +1,55 @@
1
1
  require 'set'
2
+ require 'forwardable'
2
3
  require 'mongo'
3
4
 
4
5
  module Flipper
5
6
  module Adapters
6
7
  class Mongo
7
- DefaultId = 'flipper'
8
+ extend Forwardable
8
9
 
9
- def initialize(collection, options = {})
10
+ def initialize(collection)
10
11
  @collection = collection
11
- @options = options
12
- @mongo_criteria = {:_id => id}
13
- @mongo_options = {:upsert => true, :safe => true}
12
+ @update_options = {:safe => true, :upsert => true}
14
13
  end
15
14
 
16
15
  def read(key)
17
- read_key(key)
16
+ find_one key
18
17
  end
19
18
 
20
19
  def write(key, value)
21
- update '$set' => {key => value}
20
+ update key, {'$set' => {'v' => value}}
22
21
  end
23
22
 
24
23
  def delete(key)
25
- update '$unset' => {key => 1}
24
+ @collection.remove criteria(key)
26
25
  end
27
26
 
28
- def set_add(key, value)
29
- update '$addToSet' => {key => value}
27
+ def set_members(key)
28
+ (find_one(key) || Set.new).to_set
30
29
  end
31
30
 
32
- def set_delete(key, value)
33
- update '$pull' => {key => value}
31
+ def set_add(key, value)
32
+ update key, {'$addToSet' => {'v' => value}}
34
33
  end
35
34
 
36
- def set_members(key)
37
- read_key(key) { Set.new }.to_set
35
+ def set_delete(key, value)
36
+ update key, {'$pull' => {'v' => value}}
38
37
  end
39
38
 
40
39
  private
41
40
 
42
- def id
43
- @id ||= @options.fetch(:id) { DefaultId }
44
- end
45
-
46
- def update(updates)
47
- @collection.update(@mongo_criteria, updates, @mongo_options)
41
+ def find_one(key)
42
+ if (doc = @collection.find_one(criteria(key)))
43
+ doc['v']
44
+ end
48
45
  end
49
46
 
50
- def read_key(key, &block)
51
- load
52
-
53
- if block_given?
54
- @document.fetch(key, &block)
55
- else
56
- @document[key]
57
- end
47
+ def update(key, updates)
48
+ @collection.update criteria(key), updates, @update_options
58
49
  end
59
50
 
60
- def load
61
- @document = @collection.find_one(@mongo_criteria) || {}
51
+ def criteria(key)
52
+ {:_id => key}
62
53
  end
63
54
  end
64
55
  end
@@ -0,0 +1,77 @@
1
+ require 'set'
2
+ require 'mongo'
3
+
4
+ module Flipper
5
+ module Adapters
6
+ class MongoSingleDocument
7
+ class Document
8
+ DefaultId = 'flipper'
9
+
10
+ def initialize(collection, options = {})
11
+ @collection = collection
12
+ @options = options
13
+ @id = @options.fetch(:id) { DefaultId }
14
+ @source = @options.fetch(:source) { {} }
15
+ @criteria = {:_id => @id}
16
+ @mongo_options = {:safe => true, :upsert => true}
17
+ end
18
+
19
+ def read(key)
20
+ source[key]
21
+ end
22
+
23
+ def write(key, value)
24
+ @collection.update @criteria, {'$set' => {key => value}}, @mongo_options
25
+ @source[key] = value
26
+ end
27
+
28
+ def delete(key)
29
+ @collection.update @criteria, {'$unset' => {key => 1}}, @mongo_options
30
+ @source.delete key
31
+ end
32
+
33
+ def set_members(key)
34
+ members = source.fetch(key) { @source[key] = Set.new }
35
+
36
+ if members.is_a?(Array)
37
+ @source[key] = members.to_set
38
+ else
39
+ members
40
+ end
41
+ end
42
+
43
+ def set_add(key, value)
44
+ @collection.update @criteria, {'$addToSet' => {key => value}}, @mongo_options
45
+ set_members(key).add(value)
46
+ end
47
+
48
+ def set_delete(key, value)
49
+ @collection.update @criteria, {'$pull' => {key => value}}, @mongo_options
50
+ set_members(key).delete(value)
51
+ end
52
+
53
+ def clear
54
+ @loaded = nil
55
+ @source.clear
56
+ end
57
+
58
+ def loaded?
59
+ @loaded == true
60
+ end
61
+
62
+ private
63
+
64
+ def source
65
+ load unless loaded?
66
+ @source
67
+ end
68
+
69
+ def load
70
+ @loaded = true
71
+ @source.clear
72
+ @source.update @collection.find_one(@criteria) || {}
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -1,7 +1,7 @@
1
1
  module Flipper
2
2
  module Adapters
3
3
  class Mongo
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
6
6
  end
7
7
  end
@@ -0,0 +1,25 @@
1
+ require 'set'
2
+ require 'forwardable'
3
+ require 'mongo'
4
+ require 'flipper/adapters/mongo/document'
5
+
6
+ module Flipper
7
+ module Adapters
8
+ class MongoSingleDocument
9
+ extend Forwardable
10
+
11
+ def initialize(collection, options = {})
12
+ @collection = collection
13
+ @options = options
14
+ end
15
+
16
+ def_delegators :document, :read, :write, :delete, :set_members, :set_add, :set_delete
17
+
18
+ private
19
+
20
+ def document
21
+ Document.new(@collection, :id => @options[:id])
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,275 @@
1
+ require 'helper'
2
+ require 'flipper/adapters/mongo/document'
3
+
4
+ describe Flipper::Adapters::MongoSingleDocument::Document do
5
+ subject { described_class.new(collection, :id => id, :source => source) }
6
+ let(:collection) { Mongo::Connection.new.db('testing')['testing'] }
7
+ let(:id) { described_class::DefaultId }
8
+ let(:source) { {} }
9
+ let(:criteria) { {:_id => id} }
10
+ let(:options) { {:safe => true, :upsert => true} }
11
+
12
+ def document
13
+ collection.find_one(criteria)
14
+ end
15
+
16
+ before do
17
+ collection.remove(criteria)
18
+ end
19
+
20
+ it "defaults id to flipper" do
21
+ described_class.new(collection).instance_variable_get("@id").should eq('flipper')
22
+ end
23
+
24
+ describe "loading document" do
25
+ before do
26
+ collection.update(criteria, {'$set' => {'foo' => 'bar', 'people' => [1, 2, 3]}}, options)
27
+ end
28
+
29
+ it "only happens once" do
30
+ collection.should_receive(:find_one).with(criteria).once.and_return({})
31
+ subject.read('foo')
32
+ subject.set_members('people')
33
+ end
34
+
35
+ it "happens again if document is cleared" do
36
+ collection.should_receive(:find_one).with(criteria)
37
+ subject.read('foo')
38
+ subject.set_members('people')
39
+
40
+ subject.clear
41
+
42
+ collection.should_receive(:find_one).with(criteria)
43
+ subject.read('foo')
44
+ subject.set_members('people')
45
+ end
46
+ end
47
+
48
+ describe "#read" do
49
+ context "existing key" do
50
+ before do
51
+ source['baz'] = 'wick'
52
+ collection.update(criteria, {'$set' => {'foo' => 'bar'}}, options)
53
+ @result = subject.read('foo')
54
+ end
55
+
56
+ it "returns value" do
57
+ @result.should eq('bar')
58
+ end
59
+
60
+ it "clears and loads source hash" do
61
+ source.should eq({
62
+ '_id' => id,
63
+ 'foo' => 'bar',
64
+ })
65
+ end
66
+ end
67
+
68
+ context "missing key" do
69
+ before do
70
+ collection.update(criteria, {'$set' => {'foo' => 'bar'}}, options)
71
+ @result = subject.read('apple')
72
+ end
73
+
74
+ it "returns nil" do
75
+ @result.should be_nil
76
+ end
77
+ end
78
+
79
+ context "missing document" do
80
+ before do
81
+ @result = subject.read('foo')
82
+ end
83
+
84
+ it "returns nil" do
85
+ @result.should be_nil
86
+ end
87
+ end
88
+ end
89
+
90
+ describe "#write" do
91
+ context "existing key" do
92
+ before do
93
+ collection.update(criteria, {'$set' => {'foo' => 'bar'}}, options)
94
+ subject.write('foo', 'new value')
95
+ end
96
+
97
+ it "sets key" do
98
+ document.fetch('foo').should eq('new value')
99
+ source.fetch('foo').should eq('new value')
100
+ end
101
+ end
102
+
103
+ context "missing key" do
104
+ before do
105
+ collection.update(criteria, {'$set' => {'foo' => 'bar'}}, options)
106
+ subject.write('apple', 'orange')
107
+ end
108
+
109
+ it "sets key" do
110
+ document.fetch('apple').should eq('orange')
111
+ source.fetch('apple').should eq('orange')
112
+ end
113
+ end
114
+
115
+ context "missing document" do
116
+ before do
117
+ subject.write('foo', 'bar')
118
+ end
119
+
120
+ it "creates document" do
121
+ document.should_not be_nil
122
+ end
123
+
124
+ it "sets key" do
125
+ document.fetch('foo').should eq('bar')
126
+ source.fetch('foo').should eq('bar')
127
+ end
128
+ end
129
+ end
130
+
131
+ describe "#delete" do
132
+ before do
133
+ collection.update(criteria, {'$set' => {'foo' => 'bar', 'apple' => 'orange'}}, options)
134
+ @result = subject.delete('foo')
135
+ end
136
+
137
+ it "removes the key" do
138
+ document.key?('foo').should be_false
139
+ source.key?('foo').should be_false
140
+ end
141
+
142
+ it "does not remove other keys" do
143
+ document.fetch('apple').should eq('orange')
144
+ end
145
+ end
146
+
147
+ describe "#set_members" do
148
+ context "existing key" do
149
+ before do
150
+ collection.update(criteria, {'$set' => {'people' => [1, 2, 3], 'foo' => 'bar'}}, options)
151
+ @result = subject.set_members('people')
152
+ end
153
+
154
+ it "returns set" do
155
+ @result.should eq(Set[1, 2, 3])
156
+ end
157
+
158
+ it "loads source hash" do
159
+ source.should eq({
160
+ '_id' => id,
161
+ 'people' => Set[1, 2, 3],
162
+ 'foo' => 'bar',
163
+ })
164
+ end
165
+ end
166
+
167
+ context "missing key" do
168
+ before do
169
+ collection.update(criteria, {'$set' => {'people' => [1, 2, 3]}}, options)
170
+ @result = subject.set_members('users')
171
+ end
172
+
173
+ it "returns empty set" do
174
+ @result.should eq(Set.new)
175
+ end
176
+ end
177
+
178
+ context "missing document" do
179
+ it "returns empty set" do
180
+ subject.set_members('people').should eq(Set.new)
181
+ end
182
+ end
183
+ end
184
+
185
+ describe "#set_add" do
186
+ context "existing key" do
187
+ before do
188
+ collection.update(criteria, {'$set' => {'people' => [1, 2, 3]}}, options)
189
+ subject.set_add('people', 4)
190
+ end
191
+
192
+ it "adds value to set" do
193
+ document.fetch('people').should eq([1, 2, 3, 4])
194
+ source.fetch('people').should eq(Set[1, 2, 3, 4])
195
+ end
196
+ end
197
+
198
+ context "missing key" do
199
+ before do
200
+ collection.update(criteria, {'$set' => {'people' => [1, 2, 3]}}, options)
201
+ subject.set_add('users', 1)
202
+ end
203
+
204
+ it "adds value to set" do
205
+ document.fetch('users').should eq([1])
206
+ source.fetch('users').should eq(Set[1])
207
+ end
208
+ end
209
+
210
+ context "missing document" do
211
+ before do
212
+ subject.set_add('people', 1)
213
+ end
214
+
215
+ it "creates document" do
216
+ document.should_not be_nil
217
+ end
218
+
219
+ it "adds value to set" do
220
+ document.fetch('people').should eq([1])
221
+ source.fetch('people').should eq(Set[1])
222
+ end
223
+ end
224
+ end
225
+
226
+ describe "#set_delete" do
227
+ context "existing key" do
228
+ before do
229
+ collection.update(criteria, {'$set' => {'people' => [1, 2, 3]}}, options)
230
+ subject.set_delete 'people', 3
231
+ end
232
+
233
+ it "removes value to key" do
234
+ document.fetch('people').should eq([1, 2])
235
+ source.fetch('people').should eq(Set[1, 2])
236
+ end
237
+ end
238
+
239
+ context "missing key" do
240
+ before do
241
+ collection.update(criteria, {'$set' => {'people' => [1, 2, 3]}}, options)
242
+ end
243
+
244
+ it "does not error" do
245
+ expect { subject.set_delete 'foo', 1 }.to_not raise_error
246
+ end
247
+ end
248
+
249
+ context "missing document" do
250
+ it "does not error" do
251
+ expect { subject.set_delete 'foo', 1 }.to_not raise_error
252
+ end
253
+ end
254
+ end
255
+
256
+ describe "#clear" do
257
+ before do
258
+ collection.update(criteria, {'$set' => {'foo' => 'bar'}}, options)
259
+ subject.read('foo') # load the source hash
260
+ subject.clear
261
+ end
262
+
263
+ it "clears the source hash" do
264
+ source.should be_empty
265
+ end
266
+
267
+ it "does not remove the document" do
268
+ document.should_not be_empty
269
+ end
270
+
271
+ it "marks the document as not loaded" do
272
+ subject.loaded?.should be_false
273
+ end
274
+ end
275
+ end
@@ -0,0 +1,39 @@
1
+ require 'helper'
2
+ require 'flipper/adapters/mongo_single_document'
3
+ require 'flipper/spec/shared_adapter_specs'
4
+
5
+ describe Flipper::Adapters::MongoSingleDocument do
6
+ let(:collection) { Mongo::Connection.new.db('testing')['testing'] }
7
+ let(:criteria) { {:_id => id} }
8
+ let(:id) { 'flipper' }
9
+
10
+ subject { described_class.new(collection, :id => id) }
11
+
12
+ before do
13
+ collection.remove(criteria)
14
+ end
15
+
16
+ def read_key(key)
17
+ if (doc = collection.find_one(criteria))
18
+ value = doc[key]
19
+
20
+ if value.is_a?(::Array)
21
+ value = value.to_set
22
+ end
23
+
24
+ value
25
+ end
26
+ end
27
+
28
+ def write_key(key, value)
29
+ if value.is_a?(::Set)
30
+ value = value.to_a
31
+ end
32
+
33
+ options = {:upsert => true}
34
+ updates = {'$set' => {key => value}}
35
+ collection.update criteria, updates, options
36
+ end
37
+
38
+ it_should_behave_like 'a flipper adapter'
39
+ end
@@ -4,18 +4,17 @@ require 'flipper/spec/shared_adapter_specs'
4
4
 
5
5
  describe Flipper::Adapters::Mongo do
6
6
  let(:collection) { Mongo::Connection.new.db('testing')['testing'] }
7
- let(:criteria) { {:_id => id} }
8
- let(:id) { described_class::DefaultId }
7
+ let(:id) { 'flipper' }
9
8
 
10
- subject { Flipper::Adapters::Mongo.new(collection) }
9
+ subject { described_class.new(collection) }
11
10
 
12
11
  before do
13
- collection.remove(criteria)
12
+ collection.remove
14
13
  end
15
14
 
16
15
  def read_key(key)
17
- if (doc = collection.find_one(criteria))
18
- value = doc[key]
16
+ if (doc = collection.find_one(:_id => key))
17
+ value = doc['v']
19
18
 
20
19
  if value.is_a?(::Array)
21
20
  value = value.to_set
@@ -30,8 +29,9 @@ describe Flipper::Adapters::Mongo do
30
29
  value = value.to_a
31
30
  end
32
31
 
33
- options = {:upsert => true}
34
- updates = {'$set' => {key => value}}
32
+ criteria = {:_id => key}
33
+ updates = {'$set' => {'v' => value}}
34
+ options = {:upsert => true}
35
35
  collection.update criteria, updates, options
36
36
  end
37
37
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flipper-mongo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,19 +9,19 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-03 00:00:00.000000000 Z
12
+ date: 2012-08-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: flipper
16
- requirement: &70164043485300 !ruby/object:Gem::Requirement
16
+ requirement: &70285634449460 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: 0.1.1
21
+ version: '0.2'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70164043485300
24
+ version_requirements: *70285634449460
25
25
  description: Mongo adapter for Flipper
26
26
  email:
27
27
  - nunemaker@gmail.com
@@ -38,10 +38,12 @@ files:
38
38
  - flipper-mongo.gemspec
39
39
  - lib/flipper-mongo.rb
40
40
  - lib/flipper/adapters/mongo.rb
41
+ - lib/flipper/adapters/mongo/document.rb
41
42
  - lib/flipper/adapters/mongo/version.rb
42
- - lib/flipper/adapters/mongo_with_ttl.rb
43
+ - lib/flipper/adapters/mongo_single_document.rb
44
+ - spec/flipper/adapters/mongo/document_spec.rb
45
+ - spec/flipper/adapters/mongo_single_document_spec.rb
43
46
  - spec/flipper/adapters/mongo_spec.rb
44
- - spec/flipper/adapters/mongo_with_ttl_spec.rb
45
47
  - spec/helper.rb
46
48
  - spec/support/accessor_helpers.rb
47
49
  homepage: http://jnunemaker.github.com/flipper-mongo
@@ -58,7 +60,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
58
60
  version: '0'
59
61
  segments:
60
62
  - 0
61
- hash: -3074058746914182371
63
+ hash: -10539439190267456
62
64
  required_rubygems_version: !ruby/object:Gem::Requirement
63
65
  none: false
64
66
  requirements:
@@ -67,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
67
69
  version: '0'
68
70
  segments:
69
71
  - 0
70
- hash: -3074058746914182371
72
+ hash: -10539439190267456
71
73
  requirements: []
72
74
  rubyforge_project:
73
75
  rubygems_version: 1.8.10
@@ -75,7 +77,8 @@ signing_key:
75
77
  specification_version: 3
76
78
  summary: Mongo adapter for Flipper
77
79
  test_files:
80
+ - spec/flipper/adapters/mongo/document_spec.rb
81
+ - spec/flipper/adapters/mongo_single_document_spec.rb
78
82
  - spec/flipper/adapters/mongo_spec.rb
79
- - spec/flipper/adapters/mongo_with_ttl_spec.rb
80
83
  - spec/helper.rb
81
84
  - spec/support/accessor_helpers.rb
@@ -1,36 +0,0 @@
1
- require 'set'
2
- require 'mongo'
3
- require 'flipper/adapters/mongo'
4
-
5
- module Flipper
6
- module Adapters
7
- class MongoWithTTL < Mongo
8
- private
9
-
10
- # Override Mongo adapter's load
11
- def load
12
- if expired?
13
- @document = fresh_load
14
- end
15
- end
16
-
17
- def fresh_load
18
- @last_load_at = Time.now.to_i
19
- @collection.find_one(@mongo_criteria) || {}
20
- end
21
-
22
- def ttl
23
- @options.fetch(:ttl) { 0 }
24
- end
25
-
26
- def expired?
27
- return true if never_loaded?
28
- Time.now.to_i >= (@last_load_at + ttl)
29
- end
30
-
31
- def never_loaded?
32
- @last_load_at.nil?
33
- end
34
- end
35
- end
36
- end
@@ -1,44 +0,0 @@
1
- require 'helper'
2
- require 'flipper/adapters/mongo_with_ttl'
3
- require 'flipper/spec/shared_adapter_specs'
4
-
5
- describe Flipper::Adapters::MongoWithTTL do
6
- let(:collection) { Mongo::Connection.new.db('testing')['testing'] }
7
- let(:criteria) { {:_id => id} }
8
- let(:id) { described_class::DefaultId }
9
-
10
- subject { Flipper::Adapters::MongoWithTTL.new(collection) }
11
-
12
- before do
13
- collection.remove(criteria)
14
- end
15
-
16
- it_should_behave_like 'a flipper adapter'
17
-
18
- it "can cache document in process for a number of seconds" do
19
- options = {:ttl => 10}
20
- adapter = Flipper::Adapters::MongoWithTTL.new(collection, options)
21
- adapter.write('foo', 'bar')
22
- now = Time.now
23
- Timecop.freeze(now)
24
-
25
- collection.should_receive(:find_one).with(:_id => id)
26
- adapter.read('foo')
27
-
28
- adapter.read('foo')
29
- adapter.read('bar')
30
-
31
- Timecop.travel(3)
32
- adapter.read('foo')
33
-
34
- Timecop.travel(6)
35
- adapter.read('foo')
36
-
37
- collection.should_receive(:find_one).with(:_id => id)
38
- Timecop.travel(1)
39
- adapter.read('foo')
40
-
41
- Timecop.travel(4)
42
- adapter.read('foo')
43
- end
44
- end