simple_switch 0.2.0 → 0.3.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.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -1
  3. data/README.md +175 -58
  4. data/Rakefile +21 -1
  5. data/app/assets/javascripts/simple_switch/application.js +1 -0
  6. data/app/assets/stylesheets/simple_switch/application.css +4 -0
  7. data/app/controllers/simple_switch/application_controller.rb +4 -0
  8. data/app/helpers/simple_switch/application_helper.rb +4 -0
  9. data/app/models/simple_switch/environment.rb +8 -0
  10. data/app/models/simple_switch/feature.rb +8 -0
  11. data/app/models/simple_switch/state.rb +15 -0
  12. data/app/views/layouts/simple_switch/application.html.erb +14 -0
  13. data/config/routes.rb +2 -0
  14. data/db/migrate/20151216180633_create_simple_switch_tables.simple_switch.rb +24 -0
  15. data/lib/generators/simple_switch/initialize_generator.rb +17 -0
  16. data/lib/generators/simple_switch/install_generator.rb +13 -3
  17. data/lib/generators/templates/feature_config_sample.yml +16 -12
  18. data/lib/generators/templates/migration.rb +24 -0
  19. data/lib/generators/templates/simple_switch.rb +9 -2
  20. data/lib/simple_switch.rb +10 -6
  21. data/lib/simple_switch/engine.rb +10 -0
  22. data/lib/simple_switch/feature_manager.rb +15 -0
  23. data/lib/simple_switch/feature_manager_db.rb +88 -0
  24. data/lib/simple_switch/feature_manager_yaml.rb +68 -0
  25. data/lib/simple_switch/manager_shared_methods.rb +52 -0
  26. data/lib/simple_switch/railtie.rb +12 -0
  27. data/lib/simple_switch/shared_controller_methods.rb +2 -2
  28. data/lib/simple_switch/version.rb +1 -1
  29. data/lib/tasks/simple_switch.rake +106 -0
  30. data/spec/config/feature_config.yml +10 -6
  31. data/spec/dummy/README.rdoc +28 -0
  32. data/spec/dummy/Rakefile +6 -0
  33. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  34. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  35. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  36. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  37. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  38. data/spec/dummy/bin/bundle +3 -0
  39. data/spec/dummy/bin/rails +4 -0
  40. data/spec/dummy/bin/rake +4 -0
  41. data/spec/dummy/bin/setup +29 -0
  42. data/spec/dummy/config.ru +4 -0
  43. data/spec/dummy/config/application.rb +26 -0
  44. data/spec/dummy/config/boot.rb +5 -0
  45. data/spec/dummy/config/database.yml +25 -0
  46. data/spec/dummy/config/environment.rb +5 -0
  47. data/spec/dummy/config/environments/development.rb +41 -0
  48. data/spec/dummy/config/environments/production.rb +79 -0
  49. data/spec/dummy/config/environments/test.rb +42 -0
  50. data/spec/dummy/config/initializers/assets.rb +11 -0
  51. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  52. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  53. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  54. data/spec/dummy/config/initializers/inflections.rb +16 -0
  55. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  56. data/spec/dummy/config/initializers/session_store.rb +3 -0
  57. data/spec/dummy/config/initializers/simple_switch.rb +17 -0
  58. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  59. data/spec/dummy/config/locales/en.yml +23 -0
  60. data/spec/dummy/config/routes.rb +4 -0
  61. data/spec/dummy/config/secrets.yml +22 -0
  62. data/spec/dummy/db/development.sqlite3 +0 -0
  63. data/spec/dummy/db/schema.rb +44 -0
  64. data/spec/dummy/db/seeds.rb +17 -0
  65. data/spec/dummy/db/test.sqlite3 +0 -0
  66. data/spec/dummy/log/development.log +54 -0
  67. data/spec/dummy/log/test.log +7280 -0
  68. data/spec/dummy/public/favicon.ico +0 -0
  69. data/spec/factories/simple_switch_environments.rb +5 -0
  70. data/spec/factories/simple_switch_features.rb +5 -0
  71. data/spec/factories/simple_switch_states.rb +5 -0
  72. data/spec/models/simple_switch/environment_spec.rb +11 -0
  73. data/spec/models/simple_switch/feature_spec.rb +11 -0
  74. data/spec/models/simple_switch/state_spec.rb +11 -0
  75. data/spec/route_helper.rb +5 -0
  76. data/spec/simple_switch_feature_managers_spec.rb +282 -0
  77. data/spec/spec_helper.rb +22 -27
  78. metadata +203 -17
  79. data/.gitignore +0 -3
  80. data/.rspec +0 -2
  81. data/.travis.yml +0 -7
  82. data/Gemfile +0 -4
  83. data/LICENSE.txt +0 -22
  84. data/codeclimate.yml +0 -3
  85. data/lib/generators/simple_switch/install_yaml_generator.rb +0 -18
  86. data/lib/simple_switch/switch.rb +0 -77
  87. data/simple_switch.gemspec +0 -26
  88. data/spec/simple_switch_spec.rb +0 -91
@@ -6,10 +6,20 @@ module SimpleSwitch
6
6
  class InstallGenerator < Rails::Generators::Base
7
7
  source_root File.expand_path('../../templates', __FILE__)
8
8
 
9
- desc 'Copy initializer file simple_switch.rb to config/initializers.'
9
+ desc 'Copy required files for each strategy'
10
10
 
11
- def copy_initializer
12
- copy_file 'simple_switch.rb', 'config/initializers/simple_switch.rb'
11
+ def copy_files
12
+ case SimpleSwitch.feature_store
13
+ when :database
14
+ # Copy the migration file to db/migrate.
15
+ copy_file 'migration.rb',
16
+ "db/migrate/#{Time.now.utc.strftime('%Y%m%d%H%M%S')}_create_simple_switch_tables.simple_switch.rb"
17
+ when :yml
18
+ # Copy feature configuration sample file feature_config_sample.yml to customized location.
19
+ copy_file 'feature_config_sample.yml',
20
+ "#{SimpleSwitch.feature_config_file_dir}/#{SimpleSwitch.feature_config_file_name}"
21
+ else
22
+ end
13
23
  end
14
24
  end
15
25
 
@@ -1,17 +1,21 @@
1
1
  # Example:
2
2
  #
3
3
  # feature_foo:
4
- # development: true
5
- # test: true
6
- # dev: true
7
- # qa: true
8
- # prodstage: true
9
- # production: false
4
+ # description: Foo Feature
5
+ # states:
6
+ # development: true
7
+ # test: true
8
+ # dev: true
9
+ # qa: true
10
+ # prodstage: true
11
+ # production: false
10
12
  #
11
13
  # feature_bar:
12
- # development: true
13
- # test: true
14
- # dev: true
15
- # qa: true
16
- # prodstage: true
17
- # production: true
14
+ # description: Bar Feature
15
+ # states:
16
+ # development: true
17
+ # test: true
18
+ # dev: true
19
+ # qa: true
20
+ # prodstage: true
21
+ # production: true
@@ -0,0 +1,24 @@
1
+ class CreateSimpleSwitchTables < ActiveRecord::Migration
2
+ def change
3
+ create_table :simple_switch_features do |t|
4
+ t.string :name, null: false, index: true
5
+ t.string :description, limit: 500
6
+
7
+ t.timestamps null: false
8
+ end
9
+
10
+ create_table :simple_switch_environments do |t|
11
+ t.string :name, null: false, index: true
12
+
13
+ t.timestamps null: false
14
+ end
15
+
16
+ create_table :simple_switch_states do |t|
17
+ t.boolean :status, default: false
18
+ t.belongs_to :feature, index: true
19
+ t.belongs_to :environment, index: true
20
+
21
+ t.timestamps null: false
22
+ end
23
+ end
24
+ end
@@ -1,10 +1,17 @@
1
1
  SimpleSwitch.setup do |config|
2
2
 
3
+ # feature management strategy
4
+ # the supported strategies are: [:yml, :database]
5
+ # default strategy is [:database]
6
+ config.feature_store = :database
7
+
8
+ # configuration for yml strategy
3
9
  # feature switch configuration yaml file stored location, by default it is stored
4
10
  # under config directory
5
- config.feature_config_file_dir = 'config'
11
+ # config.feature_config_file_dir = 'config'
6
12
 
13
+ # configuration for yml strategy
7
14
  # feature switch configuration yaml file name, by default it is 'feature_config.yml'
8
- config.feature_config_file_name = 'feature_config.yml'
15
+ # config.feature_config_file_name = 'feature_config.yml'
9
16
 
10
17
  end
data/lib/simple_switch.rb CHANGED
@@ -1,6 +1,9 @@
1
- require 'simple_switch/version'
1
+ require 'simple_switch/engine'
2
2
 
3
3
  module SimpleSwitch
4
+ mattr_accessor :feature_store
5
+ @@feature_store = :database
6
+
4
7
  mattr_accessor :feature_config_file_dir
5
8
  @@feature_config_file_name = 'config'
6
9
 
@@ -12,15 +15,16 @@ module SimpleSwitch
12
15
  end
13
16
 
14
17
  def self.feature_manager
15
- SimpleSwitch::Switch.instance
16
- end
17
-
18
- class Engine < ::Rails::Engine
18
+ SimpleSwitch::FeatureManager.get_manager
19
19
  end
20
20
  end
21
21
 
22
- require 'simple_switch/switch'
22
+ require 'simple_switch/manager_shared_methods'
23
+ require 'simple_switch/feature_manager_db'
24
+ require 'simple_switch/feature_manager_yaml'
25
+ require 'simple_switch/feature_manager'
23
26
  require 'simple_switch/shared_methods'
24
27
  require 'simple_switch/shared_controller_methods'
25
28
  require 'simple_switch/action_controller/base'
26
29
  require 'simple_switch/active_record/base'
30
+ require 'simple_switch/railtie'
@@ -0,0 +1,10 @@
1
+ module SimpleSwitch
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace SimpleSwitch
4
+
5
+ config.generators do |g|
6
+ g.test_framework :rspec, fixture: false
7
+ g.fixture_replacement :factory_girl, dir: 'spec/factories'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ module SimpleSwitch
2
+ class FeatureManager
3
+ class << self
4
+ def get_manager
5
+ case SimpleSwitch.feature_store
6
+ when :database
7
+ FeatureManagerDb.instance
8
+ when :yml
9
+ FeatureManagerYaml.instance
10
+ else
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,88 @@
1
+ module SimpleSwitch
2
+ class FeatureManagerDb
3
+ include SimpleSwitch::ManagerSharedMethods
4
+ attr_reader :feature_config
5
+
6
+ def initialize
7
+ @feature_config = load_config
8
+ end
9
+
10
+ def self.instance
11
+ return @@instance ||= send(:new)
12
+ end
13
+
14
+ private_class_method :new
15
+
16
+ def has_feature?(feature)
17
+ SimpleSwitch::Feature.find_by_name(feature).present?
18
+ end
19
+
20
+ def add_feature(options)
21
+ feature = SimpleSwitch::Feature.find_or_create_by(name: options[:name])
22
+ feature.update(description: options[:description])
23
+
24
+ reload_config!
25
+ end
26
+
27
+ def has_environment?(environment)
28
+ SimpleSwitch::Environment.find_by_name(environment).present?
29
+ end
30
+
31
+ def add_environment(options)
32
+ SimpleSwitch::Environment.find_or_create_by(name: options[:name])
33
+
34
+ reload_config!
35
+ end
36
+
37
+ def add_state(options)
38
+ feature = SimpleSwitch::Feature.find_by_name(options[:feature])
39
+ environment = SimpleSwitch::Environment.find_by_name(options[:environment])
40
+
41
+ feature.states.create(status: options[:status], environment: environment)
42
+ reload_config!
43
+ end
44
+
45
+ def update(feature, env, value)
46
+ states(feature)[env][0] = value if valid_feature_name_for_env?(feature, env)
47
+
48
+ SimpleSwitch::State.update(states(feature)[env][1], status: value)
49
+ end
50
+
51
+ def delete(feature)
52
+ feature_config.delete(feature) if valid_feature_name?(feature)
53
+
54
+ SimpleSwitch::Feature.find_by_name(feature).destroy
55
+ end
56
+
57
+ private
58
+
59
+ def load_config
60
+ features = SimpleSwitch::Feature.includes([:states, :environments]).all
61
+
62
+ HashWithIndifferentAccess.new(
63
+ features.inject({}) do |hash, f|
64
+ hash.merge(
65
+ f.name => {
66
+ description: f.description,
67
+ states: f.states.inject({}) { |h, s| h.merge(s.environment.name => [s.status, s.id]) }
68
+ })
69
+ end
70
+ )
71
+ end
72
+
73
+ def valid_feature_name_with_message?(feature)
74
+ return true if valid_feature_name_without_message?(feature)
75
+
76
+ raise "Cannot find feature '#{feature}', check out your database."
77
+ end
78
+
79
+ def valid_feature_name_for_env_with_message?(feature, env=Rails.env)
80
+ return true if valid_feature_name_for_env_without_message?(feature, env)
81
+
82
+ raise "Cannot find environment '#{env}' for feature '#{feature}', check out your database."
83
+ end
84
+
85
+ alias_method_chain :valid_feature_name?, :message
86
+ alias_method_chain :valid_feature_name_for_env?, :message
87
+ end
88
+ end
@@ -0,0 +1,68 @@
1
+ module SimpleSwitch
2
+ class FeatureManagerYaml
3
+ include SimpleSwitch::ManagerSharedMethods
4
+ attr_reader :feature_config, :file_dir, :file_name
5
+
6
+ def initialize
7
+ @file_dir = SimpleSwitch.feature_config_file_dir
8
+ @file_name = SimpleSwitch.feature_config_file_name
9
+ @feature_config = load_config
10
+ end
11
+
12
+ def self.instance
13
+ return @@instance ||= send(:new)
14
+ end
15
+
16
+ private_class_method :new
17
+
18
+ def update(feature, env, value)
19
+ states(feature)[env] = value if valid_feature_name_for_env?(feature, env)
20
+
21
+ save_to_yaml
22
+ end
23
+
24
+ def delete(feature)
25
+ feature_config.delete(feature) if valid_feature_name?(feature)
26
+
27
+ save_to_yaml
28
+ end
29
+
30
+ private
31
+
32
+ def file_path
33
+ Rails.root.join(file_dir, file_name)
34
+ end
35
+
36
+ def load_config
37
+ HashWithIndifferentAccess.new(YAML::load(File.open(file_path)))
38
+ end
39
+
40
+ def save_to_yaml
41
+ begin
42
+ File.open(file_path, 'w') do |f|
43
+ f.puts @feature_config.to_hash.to_yaml
44
+ end
45
+
46
+ true
47
+ rescue
48
+ false
49
+ end
50
+ end
51
+
52
+ def valid_feature_name_with_message?(feature)
53
+ return true if valid_feature_name_without_message?(feature)
54
+
55
+ raise "Cannot find feature '#{feature}', check out your #{file_name} file."
56
+ end
57
+
58
+ def valid_feature_name_for_env_with_message?(feature, env=Rails.env)
59
+ return true if valid_feature_name_for_env_without_message?(feature, env)
60
+
61
+ raise "Cannot find environment '#{env}' for feature '#{feature}', "\
62
+ "check out your #{file_name} file."
63
+ end
64
+
65
+ alias_method_chain :valid_feature_name?, :message
66
+ alias_method_chain :valid_feature_name_for_env?, :message
67
+ end
68
+ end
@@ -0,0 +1,52 @@
1
+ module SimpleSwitch
2
+ module ManagerSharedMethods
3
+
4
+ def on?(feature, env=Rails.env)
5
+ reload_config! if env == 'development'
6
+
7
+ begin
8
+ if valid_feature_name_for_env?(feature, env)
9
+ config = states(feature)[env]
10
+
11
+ config.is_a?(Array) ? config[0] : config
12
+ end
13
+ rescue
14
+ true
15
+ end
16
+ end
17
+
18
+ def off?(feature, env=Rails.env)
19
+ !on?(feature, env)
20
+ end
21
+
22
+ def turn_on(feature, env)
23
+ update(feature, env, true)
24
+ end
25
+
26
+ def turn_off(feature, env)
27
+ update(feature, env, false)
28
+ end
29
+
30
+ private
31
+ def reload_config!
32
+ @feature_config = load_config
33
+ end
34
+
35
+ def states(feature)
36
+ feature_config[feature][:states]
37
+ end
38
+
39
+ def valid_feature_name?(feature)
40
+ reload_config! unless feature_config.has_key?(feature)
41
+
42
+ feature_config.has_key?(feature)
43
+ end
44
+
45
+ def valid_feature_name_for_env?(feature, env)
46
+ valid_feature_name?(feature)
47
+
48
+ states(feature).has_key?(env)
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,12 @@
1
+ require 'simple_switch'
2
+ require 'rails'
3
+
4
+ module SimpleSwitch
5
+ class Railtie < Rails::Railtie
6
+ railtie_name :simple_switch
7
+
8
+ rake_tasks do
9
+ load 'tasks/simple_switch.rake'
10
+ end
11
+ end
12
+ end
@@ -6,11 +6,11 @@ module SimpleSwitch
6
6
  end
7
7
 
8
8
  def turn_on(feature, env)
9
- SimpleSwitch.feature_manager.update(feature, env, true)
9
+ SimpleSwitch.feature_manager.turn_on(feature, env)
10
10
  end
11
11
 
12
12
  def turn_off(feature, env)
13
- SimpleSwitch.feature_manager.update(feature, env, false)
13
+ SimpleSwitch.feature_manager.turn_off(feature, env)
14
14
  end
15
15
 
16
16
  end
@@ -1,3 +1,3 @@
1
1
  module SimpleSwitch
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -0,0 +1,106 @@
1
+ namespace :simple_switch do
2
+
3
+ # eg: bundle exec rake simple_switch:add_feature name='Foo' description='Foo feature'
4
+ desc 'Add a feature'
5
+ task :add_feature => :environment do
6
+ check_strategy
7
+
8
+ name = ENV['name']
9
+ description = ENV['description']
10
+ feature_manager = SimpleSwitch.feature_manager
11
+
12
+ abort 'Feature name is required.' if name.blank?
13
+ abort "Feature #{name} already exists." if feature_manager.has_feature?(name)
14
+
15
+ feature_manager.add_feature(name: name, description: description)
16
+ abort "Feature #{name} added."
17
+ end
18
+
19
+ # eg: bundle exec rake simple_switch:add_environment name='test'
20
+ desc 'Add an environment'
21
+ task :add_environment => :environment do
22
+ check_strategy
23
+
24
+ name = ENV['name']
25
+ feature_manager = SimpleSwitch.feature_manager
26
+
27
+ abort 'Environment name is required.' if name.blank?
28
+ abort "Environment #{name} already exists." if feature_manager.has_environment?(name)
29
+
30
+ feature_manager.add_environment(name: name)
31
+ abort "Environment #{name} added."
32
+ end
33
+
34
+ # eg: bundle exec rake simple_switch:add_feature_config feature='foo' environment='test' status=true
35
+ desc 'Add feature configuration'
36
+ task :add_feature_config => :environment do
37
+ check_strategy
38
+
39
+ feature = ENV['feature']
40
+ environment = ENV['environment']
41
+ status = ENV['status']
42
+
43
+ feature_manager = SimpleSwitch.feature_manager
44
+
45
+ abort 'Feature name is required.' if feature.blank?
46
+ abort "Feature #{feature} does not exist, add this feature first." unless feature_manager.has_feature?(feature)
47
+ abort 'Environment name is required.' if environment.blank?
48
+ abort "Environment #{environment} does not exist, add this environment first." unless feature_manager.has_environment?(environment)
49
+
50
+ feature_manager.add_state(feature: feature, environment: environment, status: status)
51
+
52
+ abort 'Feature configuration added.'
53
+ end
54
+
55
+ # eg: bundle exec rake simple_switch:turn_on feature='foo' environment='test'
56
+ desc 'Turn on feature for an environment'
57
+ task :turn_on => :environment do
58
+ toggle_feature_task(:on)
59
+ end
60
+
61
+ # eg: bundle exec rake simple_switch:turn_off feature='foo' environment='test'
62
+ desc 'Turn off feature for an environment'
63
+ task :turn_off => :environment do
64
+ toggle_feature_task(:off)
65
+ end
66
+
67
+ private
68
+ def check_strategy
69
+ # TODO: Temporarily disable rake tasks for yml strategy
70
+ abort "'yml' strategy does not support this task, please modify feature"\
71
+ ' configuration yaml file instead.' if SimpleSwitch.feature_store != :database
72
+ end
73
+
74
+ def toggle_feature_task(status)
75
+ check_strategy
76
+
77
+ environment, feature, feature_manager = validate_args
78
+ toggle_feature(feature, environment, feature_manager, status)
79
+ end
80
+
81
+ def validate_args
82
+ feature = ENV['feature']
83
+ environment = ENV['environment']
84
+
85
+ feature_manager = SimpleSwitch.feature_manager
86
+
87
+ abort 'Feature name is required.' if feature.blank?
88
+ abort "Feature #{feature} does not exist." unless feature_manager.has_feature?(feature)
89
+ abort 'Environment name is required.' if environment.blank?
90
+ abort "Environment #{environment} does not exist, add this environment first." unless feature_manager.has_environment?(environment)
91
+ return environment, feature, feature_manager
92
+ end
93
+
94
+ def toggle_feature(feature, environment, feature_manager, status)
95
+ success_msg = "Feature '#{feature}' has been turned #{status} for environment '#{environment}'."
96
+ error_msg = "Feature '#{feature}' is not configured on environment '#{environment}'."
97
+ toggle_method = status == :on ? :turn_on : :turn_off
98
+
99
+ begin
100
+ feature_manager.send(toggle_method, feature, environment)
101
+ abort success_msg
102
+ rescue
103
+ abort error_msg
104
+ end
105
+ end
106
+ end