flipper-mongo 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -5,8 +5,6 @@ gem 'rake'
5
5
  gem 'rspec'
6
6
  gem 'timecop'
7
7
  gem 'bson_ext'
8
- gem 'mongo'
9
- gem 'rack-test'
10
8
 
11
9
  group(:guard) do
12
10
  gem 'guard'
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Flipper Mongo
2
2
 
3
- A mongo adapter for [Flipper](https://github.com/jnunemaker/flipper), the feature flipping gems.
3
+ A [MongoDB](https://github.com/mongodb/mongo-ruby-driver) adapter for [Flipper](https://github.com/jnunemaker/flipper).
4
4
 
5
5
  ## Installation
6
6
 
@@ -20,12 +20,62 @@ Or install it yourself with:
20
20
 
21
21
  ```ruby
22
22
  require 'flipper/adapters/mongo'
23
- collection = Mongo::Connection.new.db('testing')['flipper']
24
- adapter = Flipper::Adapters::Mongo.new(collection, 'features')
23
+ collection = Mongo::MongoClient.new.db('testing')['flipper']
24
+ adapter = Flipper::Adapters::Mongo.new(collection)
25
25
  flipper = Flipper.new(adapter)
26
26
  # profit...
27
27
  ```
28
28
 
29
+ ## Internals
30
+
31
+ Each feature is stored in a document, which means getting a feature is single query.
32
+
33
+ ```ruby
34
+ require 'flipper/adapters/mongo'
35
+ collection = Mongo::MongoClient.new.db('testing')['flipper']
36
+ adapter = Flipper::Adapters::Mongo.new(collection)
37
+ flipper = Flipper.new(adapter)
38
+
39
+ # Register a few groups.
40
+ Flipper.register(:admins) { |thing| thing.admin? }
41
+ Flipper.register(:early_access) { |thing| thing.early_access? }
42
+
43
+ # Create a user class that has flipper_id instance method.
44
+ User = Struct.new(:flipper_id)
45
+
46
+ flipper[:stats].enable
47
+ flipper[:stats].enable flipper.group(:admins)
48
+ flipper[:stats].enable flipper.group(:early_access)
49
+ flipper[:stats].enable User.new('25')
50
+ flipper[:stats].enable User.new('90')
51
+ flipper[:stats].enable User.new('180')
52
+ flipper[:stats].enable flipper.random(15)
53
+ flipper[:stats].enable flipper.actors(45)
54
+
55
+ flipper[:search].enable
56
+
57
+ puts 'all docs in collection'
58
+ pp collection.find.to_a
59
+ # all docs in collection
60
+ # [{"_id"=>"stats",
61
+ # "actors"=>["25", "90", "180"],
62
+ # "boolean"=>"true",
63
+ # "groups"=>["admins", "early_access"],
64
+ # "percentage_of_actors"=>"45",
65
+ # "percentage_of_random"=>"15"},
66
+ # {"_id"=>"flipper_features", "features"=>["stats", "search"]},
67
+ # {"_id"=>"search", "boolean"=>"true"}]
68
+
69
+ puts 'flipper get of feature'
70
+ pp adapter.get(flipper[:stats])
71
+ # flipper get of feature
72
+ # {:boolean=>"true",
73
+ # :groups=>#<Set: {"admins", "early_access"}>,
74
+ # :actors=>#<Set: {"25", "90", "180"}>,
75
+ # :percentage_of_actors=>"45",
76
+ # :percentage_of_random=>"15"}
77
+ ```
78
+
29
79
  ## Contributing
30
80
 
31
81
  1. Fork it
data/examples/basic.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'pathname'
2
+ require 'logger'
3
+
4
+ root_path = Pathname(__FILE__).dirname.join('..').expand_path
5
+ lib_path = root_path.join('lib')
6
+ $:.unshift(lib_path)
7
+
8
+ require 'flipper/adapters/mongo'
9
+ collection = Mongo::MongoClient.new.db('testing')['flipper']
10
+ adapter = Flipper::Adapters::Mongo.new(collection)
11
+ flipper = Flipper.new(adapter)
12
+
13
+ flipper[:stats].enable
14
+
15
+ if flipper[:stats].enabled?
16
+ puts "Enabled!"
17
+ else
18
+ puts "Disabled!"
19
+ end
20
+
21
+ flipper[:stats].disable
22
+
23
+ if flipper[:stats].enabled?
24
+ puts "Enabled!"
25
+ else
26
+ puts "Disabled!"
27
+ end
@@ -0,0 +1,52 @@
1
+ require 'pp'
2
+ require 'pathname'
3
+ require 'logger'
4
+
5
+ root_path = Pathname(__FILE__).dirname.join('..').expand_path
6
+ lib_path = root_path.join('lib')
7
+ $:.unshift(lib_path)
8
+
9
+ require 'flipper/adapters/mongo'
10
+ collection = Mongo::MongoClient.new.db('testing')['flipper']
11
+ adapter = Flipper::Adapters::Mongo.new(collection)
12
+ flipper = Flipper.new(adapter)
13
+
14
+ # Register a few groups.
15
+ Flipper.register(:admins) { |thing| thing.admin? }
16
+ Flipper.register(:early_access) { |thing| thing.early_access? }
17
+
18
+ # Create a user class that has flipper_id instance method.
19
+ User = Struct.new(:flipper_id)
20
+
21
+ flipper[:stats].enable
22
+ flipper[:stats].enable flipper.group(:admins)
23
+ flipper[:stats].enable flipper.group(:early_access)
24
+ flipper[:stats].enable User.new('25')
25
+ flipper[:stats].enable User.new('90')
26
+ flipper[:stats].enable User.new('180')
27
+ flipper[:stats].enable flipper.random(15)
28
+ flipper[:stats].enable flipper.actors(45)
29
+
30
+ flipper[:search].enable
31
+
32
+ puts 'all docs in collection'
33
+ pp collection.find.to_a
34
+ # all docs in collection
35
+ # [{"_id"=>"stats",
36
+ # "actors"=>["25", "90", "180"],
37
+ # "boolean"=>"true",
38
+ # "groups"=>["admins", "early_access"],
39
+ # "percentage_of_actors"=>"45",
40
+ # "percentage_of_random"=>"15"},
41
+ # {"_id"=>"flipper_features", "features"=>["stats", "search"]},
42
+ # {"_id"=>"search", "boolean"=>"true"}]
43
+ puts
44
+
45
+ puts 'flipper get of feature'
46
+ pp adapter.get(flipper[:stats])
47
+ # flipper get of feature
48
+ # {:boolean=>"true",
49
+ # :groups=>#<Set: {"admins", "early_access"}>,
50
+ # :actors=>#<Set: {"25", "90", "180"}>,
51
+ # :percentage_of_actors=>"45",
52
+ # :percentage_of_random=>"15"}
@@ -2,17 +2,19 @@
2
2
  require File.expand_path('../lib/flipper/adapters/mongo/version', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |gem|
5
+ gem.name = "flipper-mongo"
6
+ gem.version = Flipper::Adapters::Mongo::VERSION
5
7
  gem.authors = ["John Nunemaker"]
6
8
  gem.email = ["nunemaker@gmail.com"]
7
9
  gem.description = %q{Mongo adapter for Flipper}
8
10
  gem.summary = %q{Mongo adapter for Flipper}
9
11
  gem.homepage = "http://jnunemaker.github.com/flipper-mongo"
10
-
11
- gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
- gem.files = `git ls-files`.split("\n")
13
- gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
- gem.name = "flipper-mongo"
15
12
  gem.require_paths = ["lib"]
16
- gem.version = Flipper::Adapters::Mongo::VERSION
17
- gem.add_dependency 'flipper', '~> 0.4'
13
+
14
+ gem.files = `git ls-files`.split($/)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+
18
+ gem.add_dependency 'flipper', '~> 0.5.0'
19
+ gem.add_dependency 'mongo', '~> 1.8'
18
20
  end
data/lib/flipper-mongo.rb CHANGED
@@ -1,2 +1 @@
1
- require 'flipper'
2
1
  require 'flipper/adapters/mongo'
@@ -1,59 +1,123 @@
1
1
  require 'set'
2
- require 'forwardable'
2
+ require 'flipper'
3
3
  require 'mongo'
4
4
 
5
5
  module Flipper
6
6
  module Adapters
7
7
  class Mongo
8
- extend Forwardable
8
+ include Flipper::Adapter
9
+
10
+ # Private: The key that stores the set of known features.
11
+ FeaturesKey = :flipper_features
12
+
13
+ # Public: The name of the adapter.
14
+ attr_reader :name
9
15
 
10
16
  def initialize(collection)
11
17
  @collection = collection
12
- @update_options = {:safe => true, :upsert => true}
18
+ @name = :mongo
13
19
  end
14
20
 
15
- def read(key)
16
- find_one key
17
- end
21
+ # Public: Gets the values for all gates for a given feature.
22
+ #
23
+ # Returns a Hash of Flipper::Gate#key => value.
24
+ def get(feature)
25
+ result = {}
26
+ doc = find(feature.key)
18
27
 
19
- def write(key, value)
20
- update key, {'$set' => {'v' => value.to_s}}
21
- end
28
+ feature.gates.each do |gate|
29
+ result[gate.key] = case gate.data_type
30
+ when :boolean, :integer
31
+ doc[gate.key.to_s]
32
+ when :set
33
+ doc.fetch(gate.key.to_s) { Set.new }.to_set
34
+ else
35
+ unsupported_data_type gate.data_type
36
+ end
37
+ end
22
38
 
23
- def delete(key)
24
- remove key
39
+ result
25
40
  end
26
41
 
27
- def set_members(key)
28
- (find_one(key) || Set.new).to_set
42
+ # Public: Enables a gate for a given thing.
43
+ #
44
+ # feature - The Flipper::Feature for the gate.
45
+ # gate - The Flipper::Gate to disable.
46
+ # thing - The Flipper::Type being disabled for the gate.
47
+ #
48
+ # Returns true.
49
+ def enable(feature, gate, thing)
50
+ case gate.data_type
51
+ when :boolean, :integer
52
+ update feature.key, '$set' => {
53
+ gate.key.to_s => thing.value.to_s,
54
+ }
55
+ when :set
56
+ update feature.key, '$addToSet' => {
57
+ gate.key.to_s => thing.value.to_s,
58
+ }
59
+ else
60
+ unsupported_data_type gate.data_type
61
+ end
62
+
63
+ true
29
64
  end
30
65
 
31
- def set_add(key, value)
32
- update key, {'$addToSet' => {'v' => value.to_s}}
66
+ # Public: Disables a gate for a given thing.
67
+ #
68
+ # feature - The Flipper::Feature for the gate.
69
+ # gate - The Flipper::Gate to disable.
70
+ # thing - The Flipper::Type being disabled for the gate.
71
+ #
72
+ # Returns true.
73
+ def disable(feature, gate, thing)
74
+ case gate.data_type
75
+ when :boolean
76
+ remove feature.key
77
+ when :integer
78
+ update feature.key, '$set' => {gate.key.to_s => thing.value.to_s}
79
+ when :set
80
+ update feature.key, '$pull' => {gate.key.to_s => thing.value.to_s}
81
+ else
82
+ unsupported_data_type gate.data_type
83
+ end
84
+
85
+ true
33
86
  end
34
87
 
35
- def set_delete(key, value)
36
- update key, {'$pull' => {'v' => value.to_s}}
88
+ # Public: Adds a feature to the set of known features.
89
+ def add(feature)
90
+ update FeaturesKey, '$addToSet' => {'features' => feature.name.to_s}
91
+ true
37
92
  end
38
93
 
39
- private
94
+ # Public: The set of known features.
95
+ def features
96
+ find(FeaturesKey).fetch('features') { Set.new }.to_set
97
+ end
40
98
 
41
- def find_one(key)
42
- doc = @collection.find_one(criteria(key))
99
+ # Private
100
+ def unsupported_data_type(data_type)
101
+ raise "#{data_type} is not supported by this adapter"
102
+ end
43
103
 
44
- unless doc.nil?
45
- doc['v']
46
- end
104
+ # Private
105
+ def find(key)
106
+ @collection.find_one(criteria(key)) || {}
47
107
  end
48
108
 
109
+ # Private
49
110
  def update(key, updates)
50
- @collection.update criteria(key), updates, @update_options
111
+ options = {:upsert => true}
112
+ @collection.update criteria(key), updates, options
51
113
  end
52
114
 
115
+ # Private
53
116
  def remove(key)
54
117
  @collection.remove criteria(key)
55
118
  end
56
119
 
120
+ # Private
57
121
  def criteria(key)
58
122
  {:_id => key.to_s}
59
123
  end
@@ -1,7 +1,7 @@
1
1
  module Flipper
2
2
  module Adapters
3
3
  class Mongo
4
- VERSION = "0.3.0"
4
+ VERSION = "0.5.0"
5
5
  end
6
6
  end
7
7
  end
data/spec/helper.rb CHANGED
@@ -1,29 +1,17 @@
1
1
  $:.unshift(File.expand_path('../../lib', __FILE__))
2
2
 
3
- require 'pathname'
4
- require 'logger'
5
-
6
- root_path = Pathname(__FILE__).dirname.join('..').expand_path
7
- lib_path = root_path.join('lib')
8
- log_path = root_path.join('log')
9
- log_path.mkpath
10
-
11
- require 'rubygems'
12
3
  require 'bundler'
13
-
14
- Bundler.require(:default, :test)
15
-
4
+ Bundler.setup :default
16
5
  require 'flipper-mongo'
17
6
 
18
- Logger.new(log_path.join('test.log'))
19
-
20
- require 'support/accessor_helpers'
21
-
22
7
  RSpec.configure do |config|
23
8
  config.filter_run :focused => true
24
9
  config.alias_example_to :fit, :focused => true
25
10
  config.alias_example_to :xit, :pending => true
26
11
  config.run_all_when_everything_filtered = true
12
+ config.fail_fast = true
27
13
 
28
- config.include AccessorHelpers
14
+ config.backtrace_clean_patterns = [
15
+ /rspec-(core|expectations)/,
16
+ ]
29
17
  end
@@ -0,0 +1,15 @@
1
+ require 'helper'
2
+ require 'flipper/adapters/mongo'
3
+ require 'flipper/spec/shared_adapter_specs'
4
+
5
+ describe Flipper::Adapters::Mongo do
6
+ let(:collection) { Mongo::MongoClient.new.db('testing')['testing'] }
7
+
8
+ subject { described_class.new(collection) }
9
+
10
+ before do
11
+ collection.remove
12
+ end
13
+
14
+ it_should_behave_like 'a flipper adapter'
15
+ end
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.3.0
4
+ version: 0.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-06 00:00:00.000000000 Z
12
+ date: 2013-02-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: flipper
@@ -18,7 +18,7 @@ dependencies:
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: '0.4'
21
+ version: 0.5.0
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,7 +26,23 @@ dependencies:
26
26
  requirements:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
- version: '0.4'
29
+ version: 0.5.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: mongo
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '1.8'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '1.8'
30
46
  description: Mongo adapter for Flipper
31
47
  email:
32
48
  - nunemaker@gmail.com
@@ -41,19 +57,14 @@ files:
41
57
  - LICENSE
42
58
  - README.md
43
59
  - Rakefile
60
+ - examples/basic.rb
61
+ - examples/internals.rb
44
62
  - flipper-mongo.gemspec
45
63
  - lib/flipper-mongo.rb
46
64
  - lib/flipper/adapters/mongo.rb
47
- - lib/flipper/adapters/mongo/document.rb
48
65
  - lib/flipper/adapters/mongo/version.rb
49
- - lib/flipper/adapters/mongo_single_document.rb
50
- - lib/flipper/middleware/mongo_single_document_query_cache.rb
51
- - spec/flipper/adapters/mongo/document_spec.rb
52
- - spec/flipper/adapters/mongo_single_document_spec.rb
53
- - spec/flipper/adapters/mongo_spec.rb
54
- - spec/flipper/middleware/mongo_single_document_query_cache_spec.rb
55
66
  - spec/helper.rb
56
- - spec/support/accessor_helpers.rb
67
+ - spec/mongo_spec.rb
57
68
  homepage: http://jnunemaker.github.com/flipper-mongo
58
69
  licenses: []
59
70
  post_install_message:
@@ -68,7 +79,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
68
79
  version: '0'
69
80
  segments:
70
81
  - 0
71
- hash: 1196015138740871413
82
+ hash: 1536259830562571499
72
83
  required_rubygems_version: !ruby/object:Gem::Requirement
73
84
  none: false
74
85
  requirements:
@@ -77,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
77
88
  version: '0'
78
89
  segments:
79
90
  - 0
80
- hash: 1196015138740871413
91
+ hash: 1536259830562571499
81
92
  requirements: []
82
93
  rubyforge_project:
83
94
  rubygems_version: 1.8.23
@@ -85,9 +96,5 @@ signing_key:
85
96
  specification_version: 3
86
97
  summary: Mongo adapter for Flipper
87
98
  test_files:
88
- - spec/flipper/adapters/mongo/document_spec.rb
89
- - spec/flipper/adapters/mongo_single_document_spec.rb
90
- - spec/flipper/adapters/mongo_spec.rb
91
- - spec/flipper/middleware/mongo_single_document_query_cache_spec.rb
92
99
  - spec/helper.rb
93
- - spec/support/accessor_helpers.rb
100
+ - spec/mongo_spec.rb
@@ -1,80 +0,0 @@
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[: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.to_s]
21
- end
22
-
23
- def write(key, value)
24
- value = value.to_s
25
- @collection.update @criteria, {'$set' => {key.to_s => value}}, @mongo_options
26
- @source[key.to_s] = value
27
- end
28
-
29
- def delete(key)
30
- @collection.update @criteria, {'$unset' => {key.to_s => 1}}, @mongo_options
31
- @source.delete key.to_s
32
- end
33
-
34
- def set_members(key)
35
- members = source.fetch(key.to_s) { @source[key.to_s] = Set.new }
36
-
37
- if members.is_a?(Array)
38
- @source[key.to_s] = members.to_set
39
- else
40
- members
41
- end
42
- end
43
-
44
- def set_add(key, value)
45
- value = value.to_s
46
- @collection.update @criteria, {'$addToSet' => {key.to_s => value}}, @mongo_options
47
- set_members(key.to_s).add(value)
48
- end
49
-
50
- def set_delete(key, value)
51
- value = value.to_s
52
- @collection.update @criteria, {'$pull' => {key.to_s => value}}, @mongo_options
53
- set_members(key.to_s).delete(value)
54
- end
55
-
56
- def clear
57
- @loaded = nil
58
- @source.clear
59
- end
60
-
61
- def loaded?
62
- @loaded == true
63
- end
64
-
65
- private
66
-
67
- def source
68
- load unless loaded?
69
- @source
70
- end
71
-
72
- def load
73
- @loaded = true
74
- @source.clear
75
- @source.update @collection.find_one(@criteria) || {}
76
- end
77
- end
78
- end
79
- end
80
- end
@@ -1,55 +0,0 @@
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
- @document_cache = false
15
- end
16
-
17
- def_delegators :document, :read, :write, :delete, :set_members, :set_add, :set_delete
18
-
19
- def using_document_cache?
20
- @document_cache == true
21
- end
22
-
23
- def document_cache=(value)
24
- reset_document_cache
25
- @document_cache = value
26
- end
27
-
28
- def use_document_cache(&block)
29
- original = @document_cache
30
- @document_cache = true
31
- yield
32
- ensure
33
- @document_cache = original
34
- end
35
-
36
- def reset_document_cache
37
- @document = nil
38
- end
39
-
40
- private
41
-
42
- def document
43
- if @document_cache == true
44
- @document ||= fresh_document
45
- else
46
- fresh_document
47
- end
48
- end
49
-
50
- def fresh_document
51
- Document.new(@collection, :id => @options[:id])
52
- end
53
- end
54
- end
55
- end
@@ -1,36 +0,0 @@
1
- module Flipper
2
- module Middleware
3
- class MongoSingleDocumentQueryCache
4
- class Body
5
- def initialize(target, adapter, original)
6
- @target = target
7
- @adapter = adapter
8
- @original = original
9
- end
10
-
11
- def each(&block)
12
- @target.each(&block)
13
- end
14
-
15
- def close
16
- @target.close if @target.respond_to?(:close)
17
- ensure
18
- @adapter.document_cache = @original
19
- end
20
- end
21
-
22
- def initialize(app, adapter)
23
- @app = app
24
- @adapter = adapter
25
- end
26
-
27
- def call(env)
28
- original = @adapter.using_document_cache?
29
- @adapter.document_cache = true
30
-
31
- status, headers, body = @app.call(env)
32
- [status, headers, Body.new(body, @adapter, original)]
33
- end
34
- end
35
- end
36
- end
@@ -1,279 +0,0 @@
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
- it "defaults id to flipper even if nil passed in for id" do
25
- described_class.new(collection, :id => nil).instance_variable_get("@id").should eq('flipper')
26
- end
27
-
28
- describe "loading document" do
29
- before do
30
- collection.update(criteria, {'$set' => {'foo' => 'bar', 'people' => ['1', '2', '3']}}, options)
31
- end
32
-
33
- it "only happens once" do
34
- collection.should_receive(:find_one).with(criteria).once.and_return({})
35
- subject.read('foo')
36
- subject.set_members('people')
37
- end
38
-
39
- it "happens again if document is cleared" do
40
- collection.should_receive(:find_one).with(criteria)
41
- subject.read('foo')
42
- subject.set_members('people')
43
-
44
- subject.clear
45
-
46
- collection.should_receive(:find_one).with(criteria)
47
- subject.read('foo')
48
- subject.set_members('people')
49
- end
50
- end
51
-
52
- describe "#read" do
53
- context "existing key" do
54
- before do
55
- source['baz'] = 'wick'
56
- collection.update(criteria, {'$set' => {'foo' => 'bar'}}, options)
57
- @result = subject.read('foo')
58
- end
59
-
60
- it "returns value" do
61
- @result.should eq('bar')
62
- end
63
-
64
- it "clears and loads source hash" do
65
- source.should eq({
66
- '_id' => id,
67
- 'foo' => 'bar',
68
- })
69
- end
70
- end
71
-
72
- context "missing key" do
73
- before do
74
- collection.update(criteria, {'$set' => {'foo' => 'bar'}}, options)
75
- @result = subject.read('apple')
76
- end
77
-
78
- it "returns nil" do
79
- @result.should be_nil
80
- end
81
- end
82
-
83
- context "missing document" do
84
- before do
85
- @result = subject.read('foo')
86
- end
87
-
88
- it "returns nil" do
89
- @result.should be_nil
90
- end
91
- end
92
- end
93
-
94
- describe "#write" do
95
- context "existing key" do
96
- before do
97
- collection.update(criteria, {'$set' => {'foo' => 'bar'}}, options)
98
- subject.write('foo', 'new value')
99
- end
100
-
101
- it "sets key" do
102
- document.fetch('foo').should eq('new value')
103
- source.fetch('foo').should eq('new value')
104
- end
105
- end
106
-
107
- context "missing key" do
108
- before do
109
- collection.update(criteria, {'$set' => {'foo' => 'bar'}}, options)
110
- subject.write('apple', 'orange')
111
- end
112
-
113
- it "sets key" do
114
- document.fetch('apple').should eq('orange')
115
- source.fetch('apple').should eq('orange')
116
- end
117
- end
118
-
119
- context "missing document" do
120
- before do
121
- subject.write('foo', 'bar')
122
- end
123
-
124
- it "creates document" do
125
- document.should_not be_nil
126
- end
127
-
128
- it "sets key" do
129
- document.fetch('foo').should eq('bar')
130
- source.fetch('foo').should eq('bar')
131
- end
132
- end
133
- end
134
-
135
- describe "#delete" do
136
- before do
137
- collection.update(criteria, {'$set' => {'foo' => 'bar', 'apple' => 'orange'}}, options)
138
- @result = subject.delete('foo')
139
- end
140
-
141
- it "removes the key" do
142
- document.key?('foo').should be_false
143
- source.key?('foo').should be_false
144
- end
145
-
146
- it "does not remove other keys" do
147
- document.fetch('apple').should eq('orange')
148
- end
149
- end
150
-
151
- describe "#set_members" do
152
- context "existing key" do
153
- before do
154
- collection.update(criteria, {'$set' => {'people' => ['1', '2', '3'], 'foo' => 'bar'}}, options)
155
- @result = subject.set_members('people')
156
- end
157
-
158
- it "returns set" do
159
- @result.should eq(Set['1', '2', '3'])
160
- end
161
-
162
- it "loads source hash" do
163
- source.should eq({
164
- '_id' => id,
165
- 'people' => Set['1', '2', '3'],
166
- 'foo' => 'bar',
167
- })
168
- end
169
- end
170
-
171
- context "missing key" do
172
- before do
173
- collection.update(criteria, {'$set' => {'people' => ['1', '2', '3']}}, options)
174
- @result = subject.set_members('users')
175
- end
176
-
177
- it "returns empty set" do
178
- @result.should eq(Set.new)
179
- end
180
- end
181
-
182
- context "missing document" do
183
- it "returns empty set" do
184
- subject.set_members('people').should eq(Set.new)
185
- end
186
- end
187
- end
188
-
189
- describe "#set_add" do
190
- context "existing key" do
191
- before do
192
- collection.update(criteria, {'$set' => {'people' => ['1', '2', '3']}}, options)
193
- subject.set_add('people', 4)
194
- end
195
-
196
- it "adds value to set" do
197
- document.fetch('people').should eq(['1', '2', '3', '4'])
198
- source.fetch('people').should eq(Set['1', '2', '3', '4'])
199
- end
200
- end
201
-
202
- context "missing key" do
203
- before do
204
- collection.update(criteria, {'$set' => {'people' => ['1', '2', '3']}}, options)
205
- subject.set_add('users', '1')
206
- end
207
-
208
- it "adds value to set" do
209
- document.fetch('users').should eq(['1'])
210
- source.fetch('users').should eq(Set['1'])
211
- end
212
- end
213
-
214
- context "missing document" do
215
- before do
216
- subject.set_add('people', '1')
217
- end
218
-
219
- it "creates document" do
220
- document.should_not be_nil
221
- end
222
-
223
- it "adds value to set" do
224
- document.fetch('people').should eq(['1'])
225
- source.fetch('people').should eq(Set['1'])
226
- end
227
- end
228
- end
229
-
230
- describe "#set_delete" do
231
- context "existing key" do
232
- before do
233
- collection.update(criteria, {'$set' => {'people' => ['1', '2', '3']}}, options)
234
- subject.set_delete 'people', '3'
235
- end
236
-
237
- it "removes value to key" do
238
- document.fetch('people').should eq(['1', '2'])
239
- source.fetch('people').should eq(Set['1', '2'])
240
- end
241
- end
242
-
243
- context "missing key" do
244
- before do
245
- collection.update(criteria, {'$set' => {'people' => ['1', '2', '3']}}, options)
246
- end
247
-
248
- it "does not error" do
249
- expect { subject.set_delete 'foo', '1' }.to_not raise_error
250
- end
251
- end
252
-
253
- context "missing document" do
254
- it "does not error" do
255
- expect { subject.set_delete 'foo', 1 }.to_not raise_error
256
- end
257
- end
258
- end
259
-
260
- describe "#clear" do
261
- before do
262
- collection.update(criteria, {'$set' => {'foo' => 'bar'}}, options)
263
- subject.read('foo') # load the source hash
264
- subject.clear
265
- end
266
-
267
- it "clears the source hash" do
268
- source.should be_empty
269
- end
270
-
271
- it "does not remove the document" do
272
- document.should_not be_empty
273
- end
274
-
275
- it "marks the document as not loaded" do
276
- subject.loaded?.should be_false
277
- end
278
- end
279
- end
@@ -1,95 +0,0 @@
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.to_s]
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
- value = if value.is_a?(::Set)
30
- value.to_a.map(&:to_s)
31
- else
32
- value.to_s
33
- end
34
-
35
- options = {:upsert => true}
36
- updates = {'$set' => {key.to_s => value}}
37
- collection.update criteria, updates, options
38
- end
39
-
40
- context "with cache" do
41
- before do
42
- subject.document_cache = true
43
- end
44
-
45
- it_should_behave_like 'a flipper adapter'
46
-
47
- it "should only query mongo once until reloaded" do
48
- collection.should_receive(:find_one).with(criteria).once.and_return({})
49
- subject.read('foo')
50
- subject.read('foo')
51
- subject.read('foo')
52
- subject.set_members('users')
53
-
54
- subject.reset_document_cache
55
-
56
- collection.should_receive(:find_one).with(criteria).once.and_return({})
57
- subject.read('foo')
58
- subject.read('foo')
59
- subject.set_members('users')
60
- end
61
- end
62
-
63
- context "without cache" do
64
- before do
65
- subject.document_cache = false
66
- end
67
-
68
- it_should_behave_like 'a flipper adapter'
69
- end
70
-
71
- describe "#use_document_cache" do
72
- it "turns cache on for block and restores to original after block" do
73
- subject.using_document_cache?.should be_false
74
- subject.use_document_cache do
75
- subject.using_document_cache?.should be_true
76
- end
77
- subject.using_document_cache?.should be_false
78
- end
79
- end
80
-
81
- describe "#document_cache=" do
82
- it "sets document cache" do
83
- subject.document_cache = true
84
- subject.using_document_cache?.should be_true
85
-
86
- subject.document_cache = false
87
- subject.using_document_cache?.should be_false
88
- end
89
-
90
- it "resets cached document" do
91
- subject.should_receive(:reset_document_cache)
92
- subject.document_cache = true
93
- end
94
- end
95
- end
@@ -1,41 +0,0 @@
1
- require 'helper'
2
- require 'flipper/adapters/mongo'
3
- require 'flipper/spec/shared_adapter_specs'
4
-
5
- describe Flipper::Adapters::Mongo do
6
- let(:collection) { Mongo::Connection.new.db('testing')['testing'] }
7
- let(:id) { 'flipper' }
8
-
9
- subject { described_class.new(collection) }
10
-
11
- before do
12
- collection.remove
13
- end
14
-
15
- def read_key(key)
16
- if (doc = collection.find_one(:_id => key.to_s))
17
- value = doc['v']
18
-
19
- if value.is_a?(::Array)
20
- value = value.to_set
21
- end
22
-
23
- value
24
- end
25
- end
26
-
27
- def write_key(key, value)
28
- value = if value.is_a?(::Set)
29
- value.to_a.map(&:to_s)
30
- else
31
- value.to_s
32
- end
33
-
34
- criteria = {:_id => key.to_s}
35
- updates = {'$set' => {'v' => value}}
36
- options = {:upsert => true}
37
- collection.update criteria, updates, options
38
- end
39
-
40
- it_should_behave_like 'a flipper adapter'
41
- end
@@ -1,121 +0,0 @@
1
- require 'helper'
2
- require 'rack/test'
3
- require 'flipper/middleware/mongo_single_document_query_cache'
4
-
5
- describe Flipper::Middleware::MongoSingleDocumentQueryCache do
6
- include Rack::Test::Methods
7
-
8
- let(:collection) { Mongo::Connection.new.db('testing')['testing'] }
9
- let(:adapter) { Flipper::Adapters::MongoSingleDocument.new(collection) }
10
- let(:flipper) { Flipper.new(adapter) }
11
-
12
- class Enum < Struct.new(:iter)
13
- def each(&b)
14
- iter.call(&b)
15
- end
16
- end
17
-
18
- let(:app) {
19
- # ensure scoped for builder block, annoying...
20
- instance = adapter
21
- middleware = described_class
22
-
23
- Rack::Builder.new do
24
- use middleware, instance
25
-
26
- map "/" do
27
- run lambda {|env| [200, {}, []] }
28
- end
29
-
30
- map "/fail" do
31
- run lambda {|env| raise "FAIL!" }
32
- end
33
- end.to_app
34
- }
35
-
36
- it "delegates" do
37
- called = false
38
- app = lambda { |env|
39
- called = true
40
- [200, {}, nil]
41
- }
42
- middleware = described_class.new app, adapter
43
- middleware.call({})
44
- called.should be_true
45
- end
46
-
47
- it "enables document cache during delegation" do
48
- app = lambda { |env|
49
- adapter.using_document_cache?.should be_true
50
- [200, {}, nil]
51
- }
52
- middleware = described_class.new app, adapter
53
- middleware.call({})
54
- end
55
-
56
- it "enables document cache for body each" do
57
- app = lambda { |env|
58
- [200, {}, Enum.new(lambda { |&b|
59
- adapter.using_document_cache?.should be_true
60
- b.call "hello"
61
- })]
62
- }
63
- middleware = described_class.new app, adapter
64
- body = middleware.call({}).last
65
- body.each { |x| x.should eql('hello') }
66
- end
67
-
68
- it "disables document cache after body close" do
69
- app = lambda { |env| [200, {}, []] }
70
- middleware = described_class.new app, adapter
71
- body = middleware.call({}).last
72
-
73
- adapter.using_document_cache?.should be_true
74
- body.close
75
- adapter.using_document_cache?.should be_false
76
- end
77
-
78
- it "clears document cache after body close" do
79
- app = lambda { |env| [200, {}, []] }
80
- middleware = described_class.new app, adapter
81
- body = middleware.call({}).last
82
- adapter.write('hello', 'world')
83
-
84
- adapter.instance_variable_get("@document").should_not be_nil
85
- body.close
86
- adapter.instance_variable_get("@document").should be_nil
87
- end
88
-
89
- it "really does cache" do
90
- flipper[:stats].enable
91
-
92
- collection.should_receive(:find_one).once.and_return({})
93
-
94
- app = lambda { |env|
95
- flipper[:stats].enabled?
96
- flipper[:stats].enabled?
97
- flipper[:stats].enabled?
98
- flipper[:stats].enabled?
99
- flipper[:stats].enabled?
100
- flipper[:stats].enabled?
101
-
102
- [200, {}, []]
103
- }
104
- middleware = described_class.new app, adapter
105
- middleware.call({})
106
- end
107
-
108
- context "with a successful request" do
109
- it "clears the document cache" do
110
- adapter.should_receive(:reset_document_cache).twice
111
- get '/'
112
- end
113
- end
114
-
115
- context "when the request raises an error" do
116
- it "clears the document cache" do
117
- adapter.should_receive(:reset_document_cache).once
118
- get '/fail' rescue nil
119
- end
120
- end
121
- end
@@ -1,23 +0,0 @@
1
- module AccessorHelpers
2
- def read_key(key)
3
- if (doc = collection.find_one(criteria))
4
- value = doc[key]
5
-
6
- if value.is_a?(::Array)
7
- value = value.to_set
8
- end
9
-
10
- value
11
- end
12
- end
13
-
14
- def write_key(key, value)
15
- if value.is_a?(::Set)
16
- value = value.to_a
17
- end
18
-
19
- options = {:upsert => true}
20
- updates = {'$set' => {key => value}}
21
- collection.update criteria, updates, options
22
- end
23
- end