pinball_wizard 0.0.1.pre → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,52 @@
1
+ require 'pinball_wizard/helpers/hash'
2
+
3
+ module PinballWizard
4
+ class Feature
5
+ attr_reader :name, :active, :options
6
+ attr_reader :disabled, :message
7
+
8
+ def initialize(name, options = {})
9
+ @name = name.to_s
10
+ @options = options
11
+ @active = ensure_callable(options.fetch(:active, false))
12
+ @options = Helpers::Hash.without(options, :name, :active)
13
+ @disabled = false
14
+ end
15
+
16
+ alias_method :to_s, :name
17
+
18
+ def active?
19
+ active.call
20
+ end
21
+
22
+ def disabled?
23
+ determine_state # Called here for Registry#disabled?
24
+ disabled
25
+ end
26
+
27
+ def disable(message = 'No reason given.')
28
+ @disabled = true
29
+ @message = message
30
+ end
31
+
32
+ def determine_state
33
+ # noop: use defaults
34
+ end
35
+
36
+ def state
37
+ if disabled?
38
+ "disabled: #{message}"
39
+ elsif active?
40
+ 'active'
41
+ else
42
+ 'inactive'
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def ensure_callable(object)
49
+ object.respond_to?(:call) ? object : proc { object }
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,26 @@
1
+ # Further Reading: https://practicingruby.com/articles/ruby-and-the-singleton-pattern-dont-get-along
2
+ module PinballWizard
3
+ module Helpers
4
+ module Hash
5
+ module_function
6
+
7
+ # Convert [:a, :b, { c: 'd' }] => { a: true, b: true, c: 'd' }
8
+ def normalize_options(options)
9
+ options.each_with_object({}) do |opt, memo|
10
+ if opt.is_a?(::Hash)
11
+ memo.merge!(opt)
12
+ else
13
+ memo[opt] = true
14
+ end
15
+ end
16
+ end
17
+
18
+ def without(hash, *keys)
19
+ keys.each do |key|
20
+ hash.delete(key)
21
+ end
22
+ hash
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,7 @@
1
+ module PinballWizard
2
+ class NullFeature < Feature
3
+ def determine_state
4
+ disable 'Feature not found'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ require 'pinball_wizard/view_helpers/rails'
2
+
3
+ module PinballWizard
4
+ class Railtie < Rails::Railtie
5
+ initializer "pinball_wizard.action_view.add_pinball_helpers" do
6
+ ActionView::Base.send :include, PinballWizard::ViewHelpers::Rails
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,44 @@
1
+ require 'pinball_wizard/helpers/hash'
2
+ require 'pinball_wizard/null_feature'
3
+
4
+ # Further Reading: https://practicingruby.com/articles/ruby-and-the-singleton-pattern-dont-get-along
5
+ module PinballWizard
6
+ module Registry
7
+ extend self
8
+
9
+ def add(feature)
10
+ collection[feature.name] = feature
11
+ end
12
+
13
+ def get(name)
14
+ collection.fetch(name.to_s) { null_feature }
15
+ end
16
+
17
+ def disabled?(name)
18
+ get(name).disabled?
19
+ end
20
+
21
+ def collection
22
+ @collection ||= {}
23
+ end
24
+
25
+ def clear
26
+ @collection = {}
27
+ end
28
+
29
+ def to_h
30
+ pairs = collection.map do |name, feature|
31
+ [feature.to_s, feature.state]
32
+ end
33
+ ::Hash[pairs]
34
+ end
35
+
36
+ alias_method :to_hash, :to_h
37
+
38
+ private
39
+
40
+ def null_feature
41
+ @null_feature ||= NullFeature.new 'null'
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ module PinballWizard
2
+ VERSION = '0.3.0'
3
+ end
@@ -0,0 +1,13 @@
1
+ module PinballWizard
2
+ module ViewHelpers
3
+ module Rails
4
+ def feature(name, options = {})
5
+ unless PinballWizard::Registry.disabled?(name)
6
+ partial_name = options.fetch(:partial) { name }
7
+ options[:partial] = "features/#{partial_name}"
8
+ render options
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module PinballWizard
2
+ module ViewHelpers
3
+ module Sinatra
4
+ module Slim
5
+ def feature(name, opts_or_locals = {})
6
+ unless PinballWizard::Registry.disabled?(name)
7
+ partial_name = opts_or_locals.fetch(:partial) { name }
8
+ locals = opts_or_locals.tap { |h| h.delete(:partial) }
9
+ partial "features/#{partial_name}", locals
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
data/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "pinball_wizard",
3
+ "version": "0.2.0",
4
+ "description": "pinball_wizard",
5
+ "homepage": "https://github.com/primedia/pinball_wizard",
6
+ "license": "MIT",
7
+ "author": {
8
+ "name": "RentPath"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/primedia/pinball_wizard.git"
13
+ },
14
+ "github": "https://github.com/primedia/pinball_wizard.git",
15
+ "devDependencies": {
16
+ "del": "^1.1.0",
17
+ "gulp": "^3.8.10",
18
+ "gulp-coffee": "^2.2.0",
19
+ "gulp-sourcemaps": "~1.2.0",
20
+ "gulp-util": "^3.0.1",
21
+ "karma": "~0.12.31",
22
+ "karma-jasmine": "~0.3.4",
23
+ "karma-cli": "0.0.4",
24
+ "karma-chrome-launcher": "~0.1.7",
25
+ "requirejs": "~2.1.15",
26
+ "karma-requirejs": "~0.2.2",
27
+ "gulp": "~3.8.10"
28
+ },
29
+ "scripts": {
30
+ "compile": "gulp build",
31
+ "build": "gulp dist",
32
+ "livereload": "gulp",
33
+ "watch": "gulp",
34
+ "watch:test": "karma start",
35
+ "test": "karma start --single-run",
36
+ "test:clean": "npm run clean && npm run compile && npm test",
37
+ "clean": "rm -rf .tmp",
38
+ "preclean:all": "npm run clean",
39
+ "clean:all": "rm -rf node_modules",
40
+ "prereset": "npm run clean:all",
41
+ "reset": "npm install",
42
+ "postreset": "npm run compile",
43
+ "preversion": "npm run reset && npm test && npm run build"
44
+ }
45
+ }
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/pinball_wizard/version', __FILE__)
3
+ require 'date'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.authors = ["Caleb Wright", "Mark Herman", "RentPath Team"]
7
+ gem.email = ["cwright@rentpath.com", "markherman@rentpath.com"]
8
+ gem.homepage = 'https://github.com/primedia/pinball_wizard'
9
+ gem.description = 'Build flippable features.'
10
+ gem.summary = "Lib to build flippable features."
11
+ gem.date = Date.today.to_s
12
+ gem.licenses = ['MIT']
13
+ gem.executables = []
14
+ gem.files = `git ls-files | grep -v myapp`.split("\n")
15
+ gem.test_files = `git ls-files -- test/*`.split("\n")
16
+ gem.name = "pinball_wizard"
17
+ gem.require_paths = ["lib"]
18
+ gem.version = PinballWizard::VERSION
19
+ gem.required_ruby_version = '>= 1.9'
20
+ end
@@ -0,0 +1,73 @@
1
+ require 'pinball_wizard'
2
+
3
+ describe PinballWizard::DSL do
4
+
5
+ before(:each) do
6
+ PinballWizard::Registry.clear
7
+ end
8
+
9
+ describe '.build' do
10
+ context 'without builder class flags' do
11
+ before(:each) do
12
+ PinballWizard::DSL.build do
13
+ feature :example_a
14
+ feature :example_b, :active
15
+ feature :example_c, active: proc { false }
16
+ end
17
+ end
18
+
19
+ it 'adds to the registry' do
20
+ expect(PinballWizard::Registry.to_h).to eq({
21
+ 'example_a' => 'inactive',
22
+ 'example_b' => 'active',
23
+ 'example_c' => 'inactive'
24
+ })
25
+ end
26
+
27
+ it 'creates a Feature class instance' do
28
+ expect(PinballWizard::Registry.get('example_a')).to be_a(PinballWizard::Feature)
29
+ end
30
+ end
31
+
32
+ context 'with a builder class flag' do
33
+
34
+ class MyCustomFeature < PinballWizard::Feature; end
35
+
36
+ let(:config) do
37
+ PinballWizard::Configuration.new my_custom_feature: MyCustomFeature
38
+ end
39
+
40
+ before(:each) do
41
+ PinballWizard::DSL.build(config) do
42
+ feature :example_a, :my_custom_feature
43
+ feature :example_b, :foo, my_custom_feature: { b: true }
44
+ feature :example_c
45
+ end
46
+ end
47
+
48
+ it 'adds to the registry' do
49
+ expect(PinballWizard::Registry.to_h).to eq({
50
+ 'example_a' => 'inactive',
51
+ 'example_b' => 'inactive',
52
+ 'example_c' => 'inactive'
53
+ })
54
+ end
55
+
56
+ it 'creates a custom class instance when given a symbol' do
57
+ expect(PinballWizard::Registry.get('example_a')).to be_a(MyCustomFeature)
58
+ end
59
+
60
+ it 'creates a custom class instance when given a hash' do
61
+ expect(PinballWizard::Registry.get('example_b')).to be_a(MyCustomFeature)
62
+ end
63
+
64
+ it 'createa a Feature class instance for the non-specified' do
65
+ expect(PinballWizard::Registry.get('example_c')).to be_a(PinballWizard::Feature)
66
+ end
67
+
68
+ it 'passes on symbols and hash into Feature options' do
69
+ expect(PinballWizard::Registry.get('example_b').options).to eq({ foo: true, my_custom_feature: { b: true } })
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,89 @@
1
+ require 'pinball_wizard'
2
+
3
+ describe PinballWizard::Feature do
4
+ describe '#state' do
5
+ context 'with defaults' do
6
+ subject { PinballWizard::Feature.new 'example' }
7
+
8
+ it 'should not be active' do
9
+ expect(subject.state).to eq('inactive')
10
+ end
11
+ end
12
+
13
+ context 'when setting active to a boolean' do
14
+ subject do
15
+ PinballWizard::Feature.new 'example', active: true
16
+ end
17
+
18
+ it 'should be activate' do
19
+ expect(subject.state).to eq('active')
20
+ end
21
+ end
22
+
23
+ context 'when setting active to a proc' do
24
+ subject do
25
+ PinballWizard::Feature.new 'example', active: proc { true }
26
+ end
27
+
28
+ it 'should be activate' do
29
+ expect(subject.state).to eq('active')
30
+ end
31
+ end
32
+
33
+ context 'when disabled' do
34
+ subject do
35
+ PinballWizard::Feature.new 'example'
36
+ end
37
+
38
+ it 'should be disabled with a message' do
39
+ subject.disable 'reason'
40
+ expect(subject.state).to eq('disabled: reason')
41
+ end
42
+ end
43
+ end
44
+
45
+ describe '#active?' do
46
+ context 'without setting a value' do
47
+ subject { PinballWizard::Feature.new 'example' }
48
+
49
+ it 'should not be active' do
50
+ expect(subject).not_to be_active
51
+ end
52
+ end
53
+
54
+ context 'with a boolean value' do
55
+ subject do
56
+ PinballWizard::Feature.new 'example', active: true
57
+ end
58
+
59
+ it 'should not be activated' do
60
+ expect(subject).to be_active
61
+ end
62
+ end
63
+
64
+ context 'with a proc' do
65
+ subject do
66
+ PinballWizard::Feature.new 'example', active: proc { true }
67
+ end
68
+
69
+ it 'should not be activated' do
70
+ expect(subject).to be_active
71
+ end
72
+ end
73
+ end
74
+
75
+ describe '#name' do
76
+ subject do
77
+ PinballWizard::Feature.new('my_super_duper').name
78
+ end
79
+
80
+ it { should eq('my_super_duper') }
81
+ end
82
+
83
+ describe '.new' do
84
+ it 'takes a hash of options' do
85
+ feature = PinballWizard::Feature.new 'example', foo: 'bar'
86
+ expect(feature.options).to eq({ foo: 'bar' })
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,56 @@
1
+ require 'pinball_wizard'
2
+
3
+ describe PinballWizard::Registry do
4
+
5
+ before(:each) do
6
+ PinballWizard::Registry.clear
7
+ end
8
+
9
+ let(:default_feature) do
10
+ PinballWizard::Feature.new 'default'
11
+ end
12
+
13
+ class DisabledFeature < PinballWizard::Feature
14
+ def determine_state
15
+ disable 'Reason'
16
+ end
17
+ end
18
+
19
+ let(:disabled_feature) do
20
+ DisabledFeature.new 'disabled'
21
+ end
22
+
23
+ describe '.add' do
24
+ it 'should add to the registry with defaults' do
25
+ PinballWizard::Registry.add(default_feature)
26
+ expect(PinballWizard::Registry.collection).to eq({
27
+ 'default' => default_feature
28
+ })
29
+ end
30
+ end
31
+
32
+ describe '.disabled?' do
33
+ it 'should be true for a disabled feature' do
34
+ PinballWizard::Registry.add(disabled_feature)
35
+ expect(PinballWizard::Registry.disabled?('disabled')).to eq(true)
36
+ end
37
+
38
+ it 'should be true for a non-existant feature' do
39
+ expect(PinballWizard::Registry.disabled?('foo')).to eq(true)
40
+ end
41
+
42
+ it 'should be false for a active feature' do
43
+ PinballWizard::Registry.add(default_feature)
44
+ expect(PinballWizard::Registry.disabled?('default')).to eq(false)
45
+ end
46
+ end
47
+
48
+ describe '.to_h' do
49
+ it 'should build a hash' do
50
+ PinballWizard::Registry.add(default_feature)
51
+ expect(PinballWizard::Registry.to_h).to eq({
52
+ 'default' => 'inactive'
53
+ })
54
+ end
55
+ end
56
+ end