simple_switch 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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