flipper-mongo 0.3.0 → 0.5.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.
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