flipper-cassanity 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/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - ree
5
+ - 1.9.3
6
+ notifications:
7
+ email: false
8
+ bundler_args: --without guard
9
+ before_script: sudo service cassandra status
10
+ services:
11
+ - cassandra
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ gem 'rake'
5
+ gem 'rspec'
6
+ gem 'activesupport', :require => false
7
+
8
+ group(:guard) do
9
+ gem 'guard'
10
+ gem 'guard-rspec'
11
+ gem 'guard-bundler'
12
+ gem 'rb-fsevent'
13
+ end
data/Guardfile ADDED
@@ -0,0 +1,20 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'bundler' do
5
+ watch('Gemfile')
6
+ watch(/^.+\.gemspec/)
7
+ end
8
+
9
+ rspec_options = {
10
+ :all_after_pass => false,
11
+ :all_on_start => false,
12
+ :keep_failed => false,
13
+ }
14
+
15
+ guard 'rspec', rspec_options do
16
+ watch(%r{^spec/.+_spec\.rb$}) { "spec" }
17
+ watch(%r{^lib/(.+)\.rb$}) { "spec" }
18
+ watch(%r{shared_adapter_specs\.rb$}) { "spec" }
19
+ watch('spec/helper.rb') { "spec" }
20
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 John Nunemaker
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # Flipper::Cassanity
2
+
3
+ A [Cassanity](https://github.com/jnunemaker/cassanity) adapter for [Flipper](https://github.com/jnunemaker/flipper).
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'flipper-cassanity'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install flipper-cassanity
18
+
19
+ ## Usage
20
+
21
+ ```ruby
22
+ # Assumes keyspace created and column family exists with this schema:
23
+ # {
24
+ # primary_key: [:key, :field],
25
+ # columns: {
26
+ # key: :text,
27
+ # field: :text,
28
+ # value: :text,
29
+ # },
30
+ # }
31
+
32
+ require 'flipper/adapters/cassanity'
33
+ column_family = Cassanity::Client.new[:cassanity][:flipper]
34
+ adapter = Flipper::Adapters::Cassanity.new(column_family)
35
+ flipper = Flipper.new(adapter)
36
+ # profit...
37
+ ```
38
+
39
+ ## Internals
40
+
41
+ Each feature is stored in a single row, which means getting a feature is single query.
42
+
43
+ ```ruby
44
+ require 'flipper/adapters/cassanity'
45
+ column_family = Cassanity::Client.new[:cassanity][:flipper]
46
+ adapter = Flipper::Adapters::Cassanity.new(column_family)
47
+ flipper = Flipper.new(adapter)
48
+
49
+ # Register a few groups.
50
+ Flipper.register(:admins) { |thing| thing.admin? }
51
+ Flipper.register(:early_access) { |thing| thing.early_access? }
52
+
53
+ # Create a user class that has flipper_id instance method.
54
+ User = Struct.new(:flipper_id)
55
+
56
+ flipper[:stats].enable
57
+ flipper[:stats].enable flipper.group(:admins)
58
+ flipper[:stats].enable flipper.group(:early_access)
59
+ flipper[:stats].enable User.new('25')
60
+ flipper[:stats].enable User.new('90')
61
+ flipper[:stats].enable User.new('180')
62
+ flipper[:stats].enable flipper.random(15)
63
+ flipper[:stats].enable flipper.actors(45)
64
+
65
+ flipper[:search].enable
66
+
67
+ puts 'all docs in collection'
68
+ pp column_family.select
69
+ # all docs in collection
70
+ # [{"_id"=>"stats",
71
+ # "actors"=>["25", "90", "180"],
72
+ # "boolean"=>"true",
73
+ # "groups"=>["admins", "early_access"],
74
+ # "percentage_of_actors"=>"45",
75
+ # "percentage_of_random"=>"15"},
76
+ # {"_id"=>"flipper_features", "features"=>["stats", "search"]},
77
+ # {"_id"=>"search", "boolean"=>"true"}]
78
+ puts
79
+
80
+ puts 'flipper get of feature'
81
+ pp adapter.get(flipper[:stats])
82
+ # flipper get of feature
83
+ # {:boolean=>"true",
84
+ # :groups=>#<Set: {"admins", "early_access"}>,
85
+ # :actors=>#<Set: {"25", "90", "180"}>,
86
+ # :percentage_of_actors=>"45",
87
+ # :percentage_of_random=>"15"}
88
+ ```
89
+
90
+ ## Contributing
91
+
92
+ 1. Fork it
93
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
94
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
95
+ 4. Push to the branch (`git push origin my-new-feature`)
96
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new
6
+
7
+ task :default => :spec
data/examples/basic.rb ADDED
@@ -0,0 +1,51 @@
1
+ # Nothing to see here... move along.
2
+ # Sets up load path for examples and requires some stuff
3
+ require 'pp'
4
+ require 'pathname'
5
+ require 'logger'
6
+
7
+ root_path = Pathname(__FILE__).dirname.join('..').expand_path
8
+ lib_path = root_path.join('lib')
9
+ $:.unshift(lib_path)
10
+
11
+ require 'flipper/adapters/cassanity'
12
+ require 'cassanity/instrumentation/log_subscriber'
13
+ Cassanity::Instrumentation::LogSubscriber.logger = Logger.new(STDOUT, Logger::DEBUG)
14
+
15
+ client = Cassanity::Client.new('127.0.0.1:9160', {
16
+ instrumenter: ActiveSupport::Notifications,
17
+ })
18
+ keyspace = client.keyspace(:cassanity)
19
+ column_family = keyspace.column_family({
20
+ name: :flipper,
21
+ schema: {
22
+ primary_key: [:key, :field],
23
+ columns: {
24
+ key: :text,
25
+ field: :text,
26
+ value: :text,
27
+ },
28
+ },
29
+ })
30
+
31
+ keyspace.recreate
32
+ column_family.create
33
+
34
+ adapter = Flipper::Adapters::Cassanity.new(column_family)
35
+ flipper = Flipper.new(adapter)
36
+
37
+ flipper[:stats].enable
38
+
39
+ if flipper[:stats].enabled?
40
+ puts "\n\nEnabled!\n\n\n"
41
+ else
42
+ puts "\n\nDisabled!\n\n\n"
43
+ end
44
+
45
+ flipper[:stats].disable
46
+
47
+ if flipper[:stats].enabled?
48
+ puts "\n\nEnabled!\n\n\n"
49
+ else
50
+ puts "\n\nDisabled!\n\n\n"
51
+ end
@@ -0,0 +1,75 @@
1
+ # Nothing to see here... move along.
2
+ # Sets up load path for examples and requires some stuff
3
+ require 'pp'
4
+ require 'pathname'
5
+ require 'logger'
6
+
7
+ root_path = Pathname(__FILE__).dirname.join('..').expand_path
8
+ lib_path = root_path.join('lib')
9
+ $:.unshift(lib_path)
10
+
11
+ require 'flipper/adapters/cassanity'
12
+ require 'cassanity/instrumentation/log_subscriber'
13
+ Cassanity::Instrumentation::LogSubscriber.logger = Logger.new(STDOUT, Logger::DEBUG)
14
+
15
+ client = Cassanity::Client.new('127.0.0.1:9160', {
16
+ instrumenter: ActiveSupport::Notifications,
17
+ })
18
+ keyspace = client.keyspace(:cassanity)
19
+ column_family = keyspace.column_family({
20
+ name: :flipper,
21
+ schema: {
22
+ primary_key: [:key, :field],
23
+ columns: {
24
+ key: :text,
25
+ field: :text,
26
+ value: :text,
27
+ },
28
+ },
29
+ })
30
+
31
+ keyspace.recreate
32
+ column_family.create
33
+
34
+ adapter = Flipper::Adapters::Cassanity.new(column_family)
35
+ flipper = Flipper.new(adapter)
36
+
37
+ # Register a few groups.
38
+ Flipper.register(:admins) { |thing| thing.admin? }
39
+ Flipper.register(:early_access) { |thing| thing.early_access? }
40
+
41
+ # Create a user class that has flipper_id instance method.
42
+ User = Struct.new(:flipper_id)
43
+
44
+ flipper[:stats].enable
45
+ flipper[:stats].enable flipper.group(:admins)
46
+ flipper[:stats].enable flipper.group(:early_access)
47
+ flipper[:stats].enable User.new('25')
48
+ flipper[:stats].enable User.new('90')
49
+ flipper[:stats].enable User.new('180')
50
+ flipper[:stats].enable flipper.random(15)
51
+ flipper[:stats].enable flipper.actors(45)
52
+
53
+ flipper[:search].enable
54
+
55
+ puts 'all docs in collection'
56
+ pp column_family.select
57
+ # all docs in collection
58
+ # [{"_id"=>"stats",
59
+ # "actors"=>["25", "90", "180"],
60
+ # "boolean"=>"true",
61
+ # "groups"=>["admins", "early_access"],
62
+ # "percentage_of_actors"=>"45",
63
+ # "percentage_of_random"=>"15"},
64
+ # {"_id"=>"flipper_features", "features"=>["stats", "search"]},
65
+ # {"_id"=>"search", "boolean"=>"true"}]
66
+ puts
67
+
68
+ puts 'flipper get of feature'
69
+ pp adapter.get(flipper[:stats])
70
+ # flipper get of feature
71
+ # {:boolean=>"true",
72
+ # :groups=>#<Set: {"admins", "early_access"}>,
73
+ # :actors=>#<Set: {"25", "90", "180"}>,
74
+ # :percentage_of_actors=>"45",
75
+ # :percentage_of_random=>"15"}
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/flipper/adapters/cassanity/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "flipper-cassanity"
6
+ gem.version = Flipper::Adapters::Cassanity::VERSION
7
+ gem.authors = ["John Nunemaker"]
8
+ gem.email = ["nunemaker@gmail.com"]
9
+ gem.description = %q{Cassanity adapter for Flipper.}
10
+ gem.summary = %q{Cassanity adapter for Flipper.}
11
+ gem.homepage = "http://jnunemaker.github.com/flipper-cassanity"
12
+ gem.require_paths = ["lib"]
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 'cassanity', '~> 0.4.0'
20
+ end
@@ -0,0 +1,7 @@
1
+ module Flipper
2
+ module Adapters
3
+ class Cassanity
4
+ VERSION = "0.5.0"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,172 @@
1
+ require 'set'
2
+ require 'flipper'
3
+ require 'cassanity'
4
+
5
+ module Flipper
6
+ module Adapters
7
+ class Cassanity
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
15
+
16
+ # Private: The column family where the data is stored.
17
+ attr_reader :column_family
18
+
19
+ # Public: Initializes a Cassanity flipper adapter.
20
+ #
21
+ # column_family - The Cassanity::ColumnFamily that should store the info.
22
+ def initialize(column_family)
23
+ @column_family = column_family
24
+ @name = :cassanity
25
+ end
26
+
27
+ # Public: Gets the values for all gates for a given feature.
28
+ #
29
+ # Returns a Hash of Flipper::Gate#key => value.
30
+ def get(feature)
31
+ result = {}
32
+ doc = doc_for(feature)
33
+ fields = doc.keys
34
+
35
+ feature.gates.each do |gate|
36
+ result[gate.key] = case gate.data_type
37
+ when :boolean, :integer
38
+ doc[gate.key.to_s]
39
+ when :set
40
+ fields_to_gate_value fields, gate
41
+ else
42
+ unsupported_data_type gate.data_type
43
+ end
44
+ end
45
+
46
+ result
47
+ end
48
+
49
+ # Public: Enables a gate for a given thing.
50
+ #
51
+ # feature - The Flipper::Feature for the gate.
52
+ # gate - The Flipper::Gate to disable.
53
+ # thing - The Flipper::Type being disabled for the gate.
54
+ #
55
+ # Returns true.
56
+ def enable(feature, gate, thing)
57
+ case gate.data_type
58
+ when :boolean, :integer
59
+ update feature.key, gate.key, thing.value.to_s
60
+ when :set
61
+ update feature.key, to_field(gate, thing), thing.value.to_s
62
+ else
63
+ unsupported_data_type gate.data_type
64
+ end
65
+
66
+ true
67
+ end
68
+
69
+ # Public: Disables a gate for a given thing.
70
+ #
71
+ # feature - The Flipper::Feature for the gate.
72
+ # gate - The Flipper::Gate to disable.
73
+ # thing - The Flipper::Type being disabled for the gate.
74
+ #
75
+ # Returns true.
76
+ def disable(feature, gate, thing)
77
+ case gate.data_type
78
+ when :boolean
79
+ delete feature.key
80
+ when :integer
81
+ update feature.key, gate.key, thing.value.to_s
82
+ when :set
83
+ delete feature.key, to_field(gate, thing)
84
+ else
85
+ unsupported_data_type gate.data_type
86
+ end
87
+
88
+ true
89
+ end
90
+
91
+ # Public: Adds a feature to the set of known features.
92
+ def add(feature)
93
+ update FeaturesKey, feature.name, 1
94
+ true
95
+ end
96
+
97
+ # Public: The set of known features.
98
+ def features
99
+ rows = select(FeaturesKey)
100
+ rows.map { |row| row['field'] }.to_set
101
+ end
102
+
103
+ # Private: Converts gate and thing to hash key.
104
+ def to_field(gate, thing)
105
+ "#{gate.key}/#{thing.value}"
106
+ end
107
+
108
+ # Private: Select rows matching key and optionally field.
109
+ #
110
+ # Returns an Array of Hashes.
111
+ def select(key, field = :skip)
112
+ @column_family.select({
113
+ select: [:field, :value],
114
+ where: where(key, field),
115
+ })
116
+ end
117
+
118
+ # Private: Update key/field combo to value.
119
+ #
120
+ # Returns nothing.
121
+ def update(key, field, value)
122
+ @column_family.update({
123
+ set: {value: value},
124
+ where: {key: key, field: field},
125
+ })
126
+ end
127
+
128
+ # Private: Delete rows matching key and optionally field as well.
129
+ #
130
+ # Returns nothing.
131
+ def delete(key, field = :skip)
132
+ @column_family.delete({
133
+ where: where(key, field),
134
+ })
135
+ end
136
+
137
+ # Private: Given a key and field it returns appropriate where hash for
138
+ # querying the column family.
139
+ #
140
+ # Returns a Hash to be used as criteria for a query.
141
+ def where(key, field)
142
+ where = {key: key}
143
+ where[:field] = field unless field == :skip
144
+ where
145
+ end
146
+
147
+ # Private: Gets a hash of fields => values for the given feature.
148
+ #
149
+ # Returns a Hash of fields => values.
150
+ def doc_for(feature)
151
+ field_value_pairs = select(feature.key).map { |row| row.values }
152
+ Hash[*field_value_pairs.flatten]
153
+ end
154
+
155
+ # Private: Returns a set of values given an array of fields and a gate.
156
+ #
157
+ # Returns a Set of the values enabled for the gate.
158
+ def fields_to_gate_value(fields, gate)
159
+ regex = /^#{Regexp.escape(gate.key)}\//
160
+ keys = fields.grep(regex)
161
+ values = keys.map { |key| key.split('/', 2).last }
162
+ values.to_set
163
+ end
164
+
165
+ # Private: Raises error letting user know that data type is not
166
+ # supported by this adapter.
167
+ def unsupported_data_type(data_type)
168
+ raise "#{data_type} is not supported by this adapter"
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1 @@
1
+ require 'flipper/adapters/cassanity'
@@ -0,0 +1,30 @@
1
+ require 'helper'
2
+ require 'flipper/adapters/cassanity'
3
+ require 'flipper/spec/shared_adapter_specs'
4
+
5
+ describe Flipper::Adapters::Cassanity do
6
+ let(:client) { Cassanity::Client.new }
7
+ let(:keyspace) { client.keyspace(:cassanity) }
8
+ let(:column_family) {
9
+ keyspace.column_family({
10
+ name: :flipper,
11
+ schema: {
12
+ primary_key: [:key, :field],
13
+ columns: {
14
+ key: :text,
15
+ field: :text,
16
+ value: :text,
17
+ },
18
+ },
19
+ })
20
+ }
21
+
22
+ subject { described_class.new(column_family) }
23
+
24
+ before do
25
+ keyspace.recreate
26
+ column_family.create
27
+ end
28
+
29
+ it_should_behave_like 'a flipper adapter'
30
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,18 @@
1
+ $:.unshift(File.expand_path('../../lib', __FILE__))
2
+
3
+ require 'bundler'
4
+ Bundler.setup :default
5
+
6
+ require 'flipper-cassanity'
7
+
8
+ RSpec.configure do |config|
9
+ config.filter_run :focused => true
10
+ config.alias_example_to :fit, :focused => true
11
+ config.alias_example_to :xit, :pending => true
12
+ config.run_all_when_everything_filtered = true
13
+ config.fail_fast = true
14
+
15
+ config.backtrace_clean_patterns = [
16
+ /rspec-(core|expectations)/,
17
+ ]
18
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flipper-cassanity
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - John Nunemaker
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-16 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: flipper
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.5.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 0.5.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: cassanity
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 0.4.0
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: 0.4.0
46
+ description: Cassanity adapter for Flipper.
47
+ email:
48
+ - nunemaker@gmail.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - .rspec
55
+ - .travis.yml
56
+ - Gemfile
57
+ - Guardfile
58
+ - LICENSE
59
+ - README.md
60
+ - Rakefile
61
+ - examples/basic.rb
62
+ - examples/internals.rb
63
+ - flipper-cassanity.gemspec
64
+ - lib/flipper-cassanity.rb
65
+ - lib/flipper/adapters/cassanity.rb
66
+ - lib/flipper/adapters/cassanity/version.rb
67
+ - spec/cassanity_spec.rb
68
+ - spec/helper.rb
69
+ homepage: http://jnunemaker.github.com/flipper-cassanity
70
+ licenses: []
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ segments:
82
+ - 0
83
+ hash: 216004828098320081
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ segments:
91
+ - 0
92
+ hash: 216004828098320081
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 1.8.23
96
+ signing_key:
97
+ specification_version: 3
98
+ summary: Cassanity adapter for Flipper.
99
+ test_files:
100
+ - spec/cassanity_spec.rb
101
+ - spec/helper.rb