fickle 0.0.1

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 (38) hide show
  1. data/.gitignore +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +4 -0
  4. data/README.md +132 -0
  5. data/Rakefile +5 -0
  6. data/app/controllers/fickle/application_controller.rb +22 -0
  7. data/app/controllers/fickle/features_controller.rb +37 -0
  8. data/app/controllers/fickle/model_features_controller.rb +34 -0
  9. data/app/views/fickle/features/index.html.erb +9 -0
  10. data/app/views/fickle/model_features/index.html.erb +5 -0
  11. data/app/views/layouts/fickle.html.erb +21 -0
  12. data/fickle.gemspec +25 -0
  13. data/lib/fickle.rb +39 -0
  14. data/lib/fickle/abstract_backend.rb +59 -0
  15. data/lib/fickle/abstract_feature_set.rb +52 -0
  16. data/lib/fickle/backends.rb +5 -0
  17. data/lib/fickle/backends/memory.rb +42 -0
  18. data/lib/fickle/backends/memory/feature_set.rb +39 -0
  19. data/lib/fickle/class_methods.rb +14 -0
  20. data/lib/fickle/config.rb +29 -0
  21. data/lib/fickle/controllers.rb +5 -0
  22. data/lib/fickle/model_extensions.rb +28 -0
  23. data/lib/fickle/railtie.rb +8 -0
  24. data/lib/fickle/routes.rb +12 -0
  25. data/lib/fickle/version.rb +3 -0
  26. data/lib/fickle/view_helpers.rb +7 -0
  27. data/lib/generators/fickle/install/USAGE +8 -0
  28. data/lib/generators/fickle/install/install_generator.rb +22 -0
  29. data/lib/generators/fickle/install/templates/fickle.css +112 -0
  30. data/lib/generators/fickle/install/templates/fickle.js +0 -0
  31. data/lib/generators/fickle/install/templates/fickle.rb.erb +4 -0
  32. data/spec/class_methods_spec.rb +22 -0
  33. data/spec/feature_set_spec.rb +30 -0
  34. data/spec/fickle_spec.rb +21 -0
  35. data/spec/model_extensions_spec.rb +24 -0
  36. data/spec/spec_helper.rb +1 -0
  37. data/spec/view_helpers_spec.rb +34 -0
  38. metadata +142 -0
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ test_project
5
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in fickle.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,132 @@
1
+ fickle
2
+ ======
3
+ *Don't make up your mind.*
4
+
5
+ Fickle is a feature engine for Rails 3. It is currently under heavy development and
6
+ is not stable in any sense of the word. Especially not the emotional sense.
7
+
8
+ Unlike other solutions, fickle lets you manage your features without __opening a file__,
9
+ __committing to a repository__, __performing a deploy__, or __breaking out ssh__. Just
10
+ point a browser at your application and bend it to your will!
11
+
12
+ Not only that, but fickle allows you to override your global features by mixing feature-set
13
+ functionality into __any model in your application__. If you want to restrict some experimental
14
+ features to a group of beta testers, it's easy to implement. If you want to enable every
15
+ feature when you're logged in as, say, "guinea_pig," you got it.
16
+
17
+ Quickstart
18
+ ----------
19
+
20
+ 1. Add fickle to your Gemfile
21
+
22
+ gem 'fickle'
23
+
24
+ 2. Run the generator
25
+
26
+ > ./script/rails generate fickle:install
27
+
28
+ 3. Add feature switches
29
+
30
+ <%- if feature_enabled? :my_totally_sweet_feature %>
31
+ <!--
32
+ My totally sweet code
33
+ -->
34
+ <%- else %>
35
+ <!--
36
+ My slightly less sweet code
37
+ -->
38
+ <%- end %>
39
+
40
+
41
+ 4. Administer!
42
+
43
+ ![Fickle Administrative Panel](http://i.imgur.com/2yL86.png "http://localhost:3000/features")
44
+
45
+ Web Interface
46
+ -------------
47
+
48
+ Fickle includes a web interface for managing your features. You can globally turn features on and off,
49
+ and you can manage them on a model-by-model basis. You can even hardstop a feature if it starts causing
50
+ you trouble.
51
+
52
+ The `fickle:install` generator adds this line to your `config/routes.rb` file:
53
+
54
+ fickle_admin 'features'
55
+
56
+ You can change this line for more advanced control, or you can remove it altogether if you don't want
57
+ to use the web interface.
58
+
59
+ The following options are currently supported:
60
+
61
+ path - the first argument, where the fickle admin panel should live.
62
+ :restrict_on - symbol which refers to a method in ApplicationController for
63
+ access restriction (ex. :current_user). If provided, fickle
64
+ will return a 404 unless the result of this method call returns
65
+ true for "can_administer_fickle?"
66
+ :controller - The controller to use. If you would like to override fickle's
67
+ behavior, you can subclass "Fickle::FeaturesController" with
68
+ your own controller and provide the name here.
69
+
70
+
71
+ Advanced Features
72
+ -----------------
73
+
74
+ Let's say you want to limit features on a per-user basis.
75
+
76
+ ### app/models/user.rb
77
+
78
+ class User < ActiveRecord::Base
79
+
80
+ has_feature_set
81
+
82
+ end
83
+
84
+ ### app/controllers/experimental_controller.rb
85
+
86
+ class ExperimentalController
87
+
88
+ before_filter :authenticate
89
+
90
+ def secret_feature
91
+ status 404 and return unless current_user.has_feature? :my_secret_new_feature
92
+ # Do secret stuff
93
+ end
94
+
95
+ end
96
+
97
+ You can also access this method in the views. Cause, you know, it's on the model.
98
+
99
+ Tips/tricks
100
+ -----------
101
+
102
+
103
+ Want a user who gets all the goodies? It's ruby!
104
+
105
+ ### app/models/user.rb
106
+
107
+ def has_feature?(feature)
108
+ admin? || super
109
+ end
110
+
111
+ Oh, snap that was easy.
112
+
113
+
114
+ Copyright (c) 2011 chrisrhoden && lazerclub industries
115
+
116
+ Permission is hereby granted, free of charge, to any person obtaining a copy
117
+ of this software and associated documentation files (the "Software"), to deal
118
+ in the Software without restriction, including without limitation the rights
119
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
120
+ copies of the Software, and to permit persons to whom the Software is
121
+ furnished to do so, subject to the following conditions:
122
+
123
+ The above copyright notice and this permission notice shall be included in
124
+ all copies or substantial portions of the Software.
125
+
126
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
127
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
128
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
129
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
130
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
131
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
132
+ THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1,22 @@
1
+ module Fickle
2
+ class ApplicationController < ActionController::Base
3
+ layout 'fickle'
4
+ before_filter :get_models, :get_features, :get_backend, :get_overrides
5
+
6
+ def get_models
7
+ @models = Fickle::Config.backend.models
8
+ end
9
+
10
+ def get_features
11
+ @features = Fickle::Config.backend.feature_set('global')
12
+ end
13
+
14
+ def get_backend
15
+ @backend = Fickle::Config.backend
16
+ end
17
+
18
+ def get_overrides
19
+ @overrides = Fickle::Config.backend.feature_set('master')
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,37 @@
1
+ module Fickle
2
+ class FeaturesController < ApplicationController
3
+
4
+
5
+ def index
6
+
7
+ end
8
+
9
+ def update
10
+ if params[:enabled] == 'true'
11
+ @backend.global_enable(params[:id])
12
+ elsif params[:enabled] == 'false'
13
+ @backend.global_disable(params[:id])
14
+ elsif params[:hard_stop] == 'true'
15
+ @backend.override_disable(params[:id])
16
+ elsif params[:hard_stop] == 'false'
17
+ @backend.remove_override(params[:id])
18
+ end
19
+
20
+ respond_to do |format|
21
+ format.js { render :js => 'true'}
22
+ end
23
+ end
24
+
25
+ def create
26
+
27
+ end
28
+
29
+ def edit
30
+
31
+ end
32
+
33
+ def show
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,34 @@
1
+ module Fickle
2
+ class ModelFeaturesController < ApplicationController
3
+ before_filter :find_model
4
+
5
+ def index
6
+ @features = @backend.model_feature_sets[@model]
7
+ end
8
+
9
+ def show; end
10
+
11
+ def update
12
+ @feature_set = @backend.feature_set(params[:id], @model)
13
+
14
+ if params[:enabled] == 'true'
15
+ @feature_set.enable_feature(params[:feature])
16
+ elsif params[:enabled] == 'false'
17
+ @feature_set.enable_feature(params[:feature])
18
+ end
19
+
20
+ respond_to do |format|
21
+ format.js { render :js => 'true'}
22
+ end
23
+ end
24
+
25
+
26
+ private
27
+ def find_model
28
+ @model = (Fickle::Config.backend.models & [params[:model]]).first
29
+ unless @model
30
+ render :text => 'Not Found', :status => 404
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,9 @@
1
+ <ul id="features">
2
+ <%- @features.each do |name, enabled| %>
3
+ <li>
4
+ <span class="feature_name"><%= name.to_s.humanize %></span>
5
+ <input type='checkbox' class='hardstop' name='<%= name %>'<%= ' checked' if @overrides.enabled?(name) == false %>/>
6
+ <input type='checkbox' class='feature' name='<%= name %>'<%= ' checked' if enabled %>/>
7
+ </li>
8
+ <%- end %>
9
+ </ul>
@@ -0,0 +1,5 @@
1
+ <ul id="features">
2
+ <%- @features.each do |feature| %>
3
+ <li><span class="feature_name"><%= feature[0].to_s.humanize %></span><input type='checkbox' name='features[<%= feature[0] %>]'<%= ' checked' if feature[1] %>/></li>
4
+ <%- end %>
5
+ </ul>
@@ -0,0 +1,21 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Fickle!</title>
5
+ <%= stylesheet_link_tag 'fickle/fickle' %>
6
+ </head>
7
+ <body>
8
+ <h1>Fickle</h1>
9
+ <ul id="tabs">
10
+ <li><%= link_to 'Features', fickle_features_path %>
11
+ <%# @models.each do |model|
12
+ <li><%= link_to model.to_s.pluralize, fickle_model_features_path(model) </li>
13
+ end %>
14
+ </ul>
15
+ <div id="main">
16
+ <%= yield %>
17
+ </div>
18
+ <%= javascript_include_tag 'http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js' %>
19
+ <%= javascript_include_tag 'fickle' %>
20
+ </body>
21
+ </html>
data/fickle.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "fickle/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "fickle"
7
+ s.version = Fickle::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["chrisrhoden"]
10
+ s.email = ["chris@lazerclub.co"]
11
+ s.homepage = ""
12
+ s.summary = %q{The feature library for rails that can't make up its mind}
13
+ s.description = %q{Fickle lets you turn features on and off at will in your rails app using a web interface.
14
+ It also lets you manage features on a model-by-model basis (maybe users?)}.gsub(' ','')
15
+
16
+ s.rubyforge_project = "fickle"
17
+
18
+ s.add_development_dependency 'rspec', '~> 2.4.0'
19
+ s.add_development_dependency 'rails', '~> 3.0.0'
20
+
21
+ s.files = `git ls-files`.split("\n")
22
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
23
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
24
+ s.require_paths = ["lib"]
25
+ end
data/lib/fickle.rb ADDED
@@ -0,0 +1,39 @@
1
+ module Fickle
2
+
3
+ MODELS = []
4
+
5
+ def self.setup!
6
+ Class.send :include, Fickle::ClassMethods unless Class.respond_to? :has_featureset
7
+ begin
8
+ ApplicationHelper
9
+ rescue Exception; nil
10
+ else
11
+ ApplicationHelper.send :include, Fickle::ViewHelpers
12
+ end
13
+ begin
14
+ ActionDispatch::Routing::Mapper
15
+ rescue Exception; nil
16
+ else
17
+ ActionDispatch::Routing::Mapper.send :include, Fickle::Routes
18
+ end
19
+ end
20
+
21
+ def self.config
22
+ yield Fickle::Config if block_given?
23
+ end
24
+
25
+ autoload :FeatureSet, 'fickle/feature_set'
26
+ autoload :ClassMethods, 'fickle/class_methods'
27
+ autoload :Railtie, 'fickle/railtie'
28
+ autoload :VERSION, 'fickle/version'
29
+ autoload :ViewHelpers, 'fickle/view_helpers'
30
+ autoload :ModelExtensions, 'fickle/model_extensions'
31
+ autoload :Routes, 'fickle/routes'
32
+ autoload :Config, 'fickle/config'
33
+ autoload :Backends, 'fickle/backends'
34
+ autoload :AbstractFeatureSet, 'fickle/abstract_feature_set'
35
+ autoload :AbstractBackend, 'fickle/abstract_backend'
36
+
37
+ require 'fickle/railtie' if defined? Rails
38
+
39
+ end
@@ -0,0 +1,59 @@
1
+ module Fickle
2
+ class AbstractBackend
3
+
4
+ def initialize(options)
5
+ @_klass = options[:feature_set_class]
6
+ add_feature_set('global', new_feature_set('global', :set_default => true))
7
+ add_feature_set('master', new_feature_set('master'))
8
+ end
9
+
10
+ def new_feature_set(feature_set_key, options={})
11
+ options.merge!(:key => feature_set_key)
12
+ if !['master', 'global'].include?(feature_set_key)
13
+ options[:override] = feature_set('master')
14
+ options[:fallthrough] = feature_set('global')
15
+ end
16
+ @_klass.new(options)
17
+ end
18
+
19
+ def global_enable(feature)
20
+ feature_set('global').enable_feature(feature)
21
+ end
22
+
23
+ def global_disable(feature)
24
+ feature_set('global').disable_feature(feature)
25
+ end
26
+
27
+ def remove_global(feature)
28
+ feature_set('global').remove_feature(feature)
29
+ end
30
+
31
+ def override_enable(feature)
32
+ feature_set('master').enable_feature(feature)
33
+ end
34
+
35
+ def override_disable(feature)
36
+ feature_set('master').disable_feature(feature)
37
+ end
38
+
39
+ def remove_override(feature)
40
+ feature_set('master').remove_feature(feature)
41
+ end
42
+
43
+ def feature_set(key, model=nil)
44
+ if model
45
+ feature_set = get_feature_set("#{model}-#{key}")
46
+ add_model_feature_set("#{model}-#{key}", model)
47
+ else
48
+ feature_set = get_feature_set(key)
49
+ end
50
+ return feature_set
51
+ end
52
+
53
+ def inspect
54
+ "#<Fickle::Backend overrides:#{feature_set('master').inspect} fallthroughs:#{feature_set('global').inspect}>"
55
+ end
56
+
57
+ alias_method :to_s, :inspect
58
+ end
59
+ end
@@ -0,0 +1,52 @@
1
+ module Fickle
2
+ class AbstractFeatureSet
3
+
4
+ include Enumerable
5
+
6
+ def initialize(options = {})
7
+ @_override = options[:override]
8
+ @_fallthrough = options[:fallthrough]
9
+ @_set_default = options[:set_default]
10
+ @_name = options[:key]
11
+ end
12
+
13
+ def enabled?(feature)
14
+ return overridden(feature) if overridden?(feature)
15
+ return feature_enabled?(feature) if feature_defined?(feature)
16
+ return fallthrough(feature)
17
+ end
18
+
19
+ def inspect
20
+ "#<Fickle::FeatureSet #@_name#{map{|key, value| " #{key}:#{value}"}.join}>"
21
+ end
22
+
23
+ alias_method :to_s, :inspect
24
+
25
+ private
26
+
27
+ def overridden?(feature)
28
+ return false unless @_override
29
+ if !@_override.feature_enabled?(feature).nil?
30
+ return true
31
+ end
32
+ return false
33
+ end
34
+
35
+ def overridden(feature)
36
+ @_override.feature_enabled?(feature)
37
+ end
38
+
39
+ def feature_defined?(feature)
40
+ !feature_enabled?(feature).nil?
41
+ end
42
+
43
+ def fallthrough(feature)
44
+ return @_fallthrough.enabled?(feature) if @_fallthrough
45
+ if @_set_default
46
+ Fickle::Config.default_state ? enable_feature(feature) : disable_feature(feature)
47
+ return feature_enabled?(feature)
48
+ end
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,5 @@
1
+ module Fickle
2
+ module Backends
3
+ autoload :Memory, 'fickle/backends/memory'
4
+ end
5
+ end
@@ -0,0 +1,42 @@
1
+ module Fickle
2
+ module Backends
3
+ class Memory < Fickle::AbstractBackend
4
+
5
+ autoload :FeatureSet, 'fickle/backends/memory/feature_set'
6
+
7
+ def initialize(options={})
8
+ @models = []
9
+ @model_feature_sets = {}
10
+ @sets = {}
11
+ super(options.merge(:feature_set_class => Fickle::Backends::Memory::FeatureSet))
12
+ end
13
+
14
+ def get_feature_set(feature_set_key)
15
+ @sets[feature_set_key] ||= new_feature_set(feature_set_key)
16
+ end
17
+
18
+ def add_model_feature_set(feature_set_key, model)
19
+ @model_feature_sets[model] ||= []
20
+ @model_feature_sets[model] = (@model_feature_sets[model] | [feature_set_key])
21
+ end
22
+
23
+ def add_model(model)
24
+ @models = (@models | [model])
25
+ end
26
+
27
+ def remove_model(model)
28
+ @models = (@models - [model])
29
+ end
30
+
31
+ attr_reader :models, :model_feature_sets
32
+
33
+ def add_feature_set(feature_set_key, feature_set)
34
+ @sets[feature_set_key] = feature_set
35
+ end
36
+
37
+ def feature_sets
38
+ @sets
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,39 @@
1
+ module Fickle
2
+ module Backends
3
+ class Memory
4
+ class FeatureSet < Fickle::AbstractFeatureSet
5
+
6
+ def initialize(options={})
7
+ super
8
+ @features = {}
9
+ end
10
+
11
+ def feature_enabled?(feature)
12
+ features[feature.to_s]
13
+ end
14
+
15
+ def enable_feature(feature)
16
+ features[feature.to_s] = true
17
+ end
18
+
19
+ def disable_feature(feature)
20
+ features[feature.to_s] = false
21
+ end
22
+
23
+ def remove_feature(feature)
24
+ features.delete(feature.to_s)
25
+ end
26
+
27
+ def each
28
+ features.each do |feature, enabled|
29
+ yield feature, enabled
30
+ end
31
+ end
32
+
33
+ private
34
+ attr_reader :features
35
+
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,14 @@
1
+ module Fickle
2
+ module ClassMethods
3
+
4
+ def has_feature_set
5
+ Fickle::Config.backend.add_model(name)
6
+ include Fickle::ModelExtensions
7
+ end
8
+
9
+ alias_method :is_subjugated, :has_feature_set
10
+
11
+ end
12
+ end
13
+
14
+
@@ -0,0 +1,29 @@
1
+ module Fickle
2
+ module Config
3
+
4
+ # Please ignore the mess. Memoizing on a ModuleAccessor without ActiveSupport
5
+ def self.default_state
6
+ @default_state ||= false
7
+ def self.default_state
8
+ @default_state
9
+ end
10
+ @default_state
11
+ end
12
+
13
+ def self.backend=(backend)
14
+ @backend = backend
15
+ end
16
+
17
+ def self.backend
18
+ @backend ||= Fickle::Backends::Memory.new
19
+ def self.backend
20
+ @backend
21
+ end
22
+ @backend
23
+ end
24
+
25
+ def self.default_state=(default_state)
26
+ @default_state = default_state
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ module Fickle
2
+ module Controllers
3
+ autoload :FeaturesController, 'fickle/controllers/features_controller'
4
+ end
5
+ end
@@ -0,0 +1,28 @@
1
+ module Fickle
2
+ module ModelExtensions
3
+ def has_feature?(feature)
4
+ feature_set.enabled?(feature)
5
+ end
6
+
7
+ def add_feature(feature)
8
+ feature_set.enable_feature(feature)
9
+ end
10
+
11
+ def remove_feature(feature)
12
+ feature_set.disable_feature(feature)
13
+ end
14
+
15
+ def feature_set
16
+ @feature_set = Fickle::Config.backend.feature_set(fickle_id, self.class.name)
17
+ end
18
+
19
+ def fickle_id
20
+ return @fickle_id if @fickle_id
21
+ @fickle_id = case self
22
+ when (defined? ActiveRecord and ActiveRecord::Base) then id
23
+ when (defined? Mongoid and Mongoid::Document) then id
24
+ else hash
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,8 @@
1
+ module Fickle
2
+ class Railtie < Rails::Engine
3
+
4
+ config.to_prepare do
5
+ Fickle.setup!
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ module Fickle
2
+ module Routes
3
+ def fickle_admin(path, options = {})
4
+ options = {:controller => 'Fickle::Features', :model_controller => 'Fickle::ModelFeatures'}.merge(options)
5
+
6
+ namespace 'fickle', :module => nil, :path => path do
7
+ resources :model_features, :path => 'models/:model', :controller => options.delete(:model_controller)
8
+ resources :features, options.merge(:path => '')
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module Fickle
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,7 @@
1
+ module Fickle
2
+ module ViewHelpers
3
+ def feature_enabled?(feature)
4
+ Fickle::Config.backend.feature_set('views').enabled?(feature)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ rails generate fickle:install
6
+
7
+ This will create:
8
+ what/will/it/create
@@ -0,0 +1,22 @@
1
+ module Fickle
2
+ class InstallGenerator < Rails::Generators::Base
3
+ source_root File.expand_path('../templates', __FILE__)
4
+
5
+ class_option :route, :type => :boolean, :default => true, :desc => "Add route for the Fickle web interface"
6
+ class_option :default_enabled, :type => :boolean, :default => false, :desc => "Default all features to be enabled"
7
+
8
+ def generate_route
9
+ route("fickle_admin 'features'") if options.route
10
+ end
11
+
12
+ def generate_config_file
13
+ template "fickle.rb.erb", "config/initializers/fickle.rb"
14
+ end
15
+
16
+ def generate_public_files
17
+ copy_file 'fickle.css', 'public/stylesheets/fickle/fickle.css'
18
+ copy_file 'fickle.js', 'public/javascripts/fickle.js'
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,112 @@
1
+ body, html {
2
+ font-family: "Helvetica Neue", "Arial", "Sans-Serif";
3
+ font-weight: 100;
4
+ letter-spacing: 0.1em;
5
+ }
6
+
7
+ h1 {
8
+ width: 500px;
9
+ margin: 0 auto;
10
+ position: relative;
11
+ top: 1.0em;
12
+ font-weight: 100;
13
+ letter-spacing:0.2em;
14
+ position: relative;
15
+ z-index: 0;
16
+ }
17
+
18
+ #main {
19
+ width: 535px;
20
+ margin: 0 auto;
21
+ background: #347af4;
22
+ padding: 1px 0;
23
+ border-radius: 15px;
24
+ }
25
+
26
+ #features {
27
+ width: 500px;
28
+ background: #333;
29
+ color: #fff;
30
+ list-style-type: none;
31
+ padding:35px 10px;
32
+ font-size: 2.0em;
33
+ border-radius: 10px;
34
+ margin: 7px auto;
35
+ text-shadow: 0 -1px 0 #000;
36
+ }
37
+
38
+ #tabs {
39
+ width: 515px;
40
+ margin: 0 auto;
41
+ list-style-type: none;
42
+ padding: 0;
43
+ text-align:right;
44
+ font-weight: 100;
45
+ position: relative;
46
+ z-index: 5;
47
+ }
48
+
49
+ #tabs li {
50
+ display: inline-block;
51
+ padding: 5px 10px;
52
+ margin: 0 5px;
53
+ border-top-left-radius: 10px;
54
+ border-top-right-radius: 10px;
55
+ background: #347af4;
56
+ color: #fff;
57
+ text-shadow: 0 -1px 0 #333;
58
+ }
59
+
60
+ #tabs li a {
61
+ font-weight: 400;
62
+ color: #FFF;
63
+ text-decoration: none;
64
+ }
65
+
66
+ .toggle {
67
+ width: 150px;
68
+ color: #FFF;
69
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.7);
70
+ position: relative;
71
+ height: 36px;
72
+ margin: 0 30px 0 0;
73
+ border-radius: 5px;
74
+ float: right;
75
+ font-weight: 400;
76
+ }
77
+
78
+ .toggle .on, .toggle .off {
79
+ display:inline-block;
80
+ width: 75px;
81
+ margin: 0;
82
+ text-align: center;
83
+ height: 36px;
84
+ -webkit-box-shadow: 0 1px 0 rgba(255,255,255,0.4), inset 0 2px 3px rgba(0,0,0,0.6);
85
+
86
+ }
87
+ .toggle .on {
88
+ border-top-right-radius: 5px;
89
+ border-bottom-right-radius: 5px;
90
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #347af4), color-stop(0.5, #3680f8), color-stop(0.501, #5091f3), color-stop(1, #75acfc));
91
+ }
92
+
93
+ .toggle .off {
94
+ border-top-left-radius: 5px;
95
+ border-bottom-left-radius: 5px;
96
+ color: #333;
97
+ text-shadow: 0 1px 0 #fff;
98
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #999), color-stop(0.5, #AAA), color-stop(0.501, #bbb), color-stop(1, #ddd));
99
+ }
100
+
101
+ .toggle .slider {
102
+ cursor:pointer;
103
+ width: 78px;
104
+ height: 33px;
105
+ top:1px;
106
+ left:1px;
107
+ border-top: 1px solid #bbb;
108
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #666), color-stop(0.5, #777), color-stop(1, #555));
109
+ -webkit-box-shadow: 0 0 1px 1px #222;
110
+ position: absolute;
111
+ border-radius: 5px;
112
+ }
File without changes
@@ -0,0 +1,4 @@
1
+ Fickle.config do |config|
2
+ config.default_state = <%= options.default_enabled? %>
3
+ config.backend = Fickle::Backends::Memory.new
4
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fickle::ClassMethods do
4
+
5
+ describe '#has_featureset' do
6
+
7
+ before :all do
8
+ Fickle.setup!
9
+ class MagicClass
10
+ has_featureset
11
+ end
12
+ end
13
+
14
+ it 'should add the class to the models pile' do
15
+ Fickle::MODELS.should include MagicClass
16
+ end
17
+
18
+ it 'should mix in the feature-specific methods' do
19
+ MagicClass.new.should respond_to :has_feature?
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fickle::FeatureSet do
4
+ it 'should allow the enabling of features' do
5
+ subject.enable_feature(:kill)
6
+ subject.enabled?(:kill).should be true
7
+ end
8
+
9
+ it 'should allow the disabling of features' do
10
+ subject.disable_feature(:kill)
11
+ subject.enabled?(:kill).should be false
12
+ end
13
+
14
+ it 'should default to false' do
15
+ subject.enabled?(:jabberwocky).should be false
16
+ end
17
+
18
+ context 'persistence' do
19
+ before :each do
20
+ @feature_set_one = Fickle::FeatureSet.for('kings')
21
+ @feature_set_two = Fickle::FeatureSet.for('kings')
22
+ end
23
+
24
+ it 'should work' do
25
+ @feature_set_one.enable_feature(:drawing)
26
+ @feature_set_two.enabled?(:drawing).should be true
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fickle do
4
+ it { should respond_to :setup! }
5
+
6
+ context 'in a rails app' do
7
+ before :all do
8
+ ApplicationHelper = Class.new
9
+ Fickle.setup!
10
+ end
11
+
12
+ it 'should include the view helpers in ApplicationHelper' do
13
+ ApplicationHelper.new.should respond_to :feature_enabled?
14
+ end
15
+
16
+ it 'should extend class to include the has_featureset method' do
17
+ class TestClass; end
18
+ TestClass.should respond_to :has_featureset
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+ Fickle.setup!
3
+
4
+ class Genius
5
+ has_featureset
6
+ end
7
+
8
+ describe Genius do
9
+ it { should respond_to :has_feature? }
10
+
11
+ it 'should allow features to be enabled' do
12
+ subject.add_feature :thrill
13
+ subject.has_feature?(:thrill).should be true
14
+ end
15
+
16
+ it 'should allow features to be disabled' do
17
+ subject.remove_feature :chill
18
+ subject.has_feature?(:chill).should be false
19
+ end
20
+
21
+ it 'should default to false for new features' do
22
+ subject.has_feature?(:evil).should be false
23
+ end
24
+ end
@@ -0,0 +1 @@
1
+ require 'test_project/config/application'
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe Fickle::ViewHelpers do
4
+
5
+ class TestClass
6
+ include Fickle::ViewHelpers
7
+ end
8
+
9
+ subject do
10
+ TestClass.new
11
+ end
12
+
13
+ it { should respond_to :feature_enabled? }
14
+
15
+ describe '#feature_enabled?' do
16
+
17
+ before :each do
18
+ @feature_set = Fickle::FeatureSet.for('global')
19
+ end
20
+
21
+ it 'should create features that have not been tested before and default to false' do
22
+ subject.feature_enabled?(:feature).should be false
23
+ end
24
+ it 'should return false when a feature is not enabled' do
25
+ @feature_set.disable_feature(:kill_robots)
26
+ subject.feature_enabled?(:kill_robots).should be false
27
+ end
28
+ it 'should return true when a feature is enabled' do
29
+ @feature_set.enable_feature(:kill_robots)
30
+ subject.feature_enabled?(:kill_robots).should be true
31
+ end
32
+ end
33
+
34
+ end
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fickle
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - chrisrhoden
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-03-20 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 31
30
+ segments:
31
+ - 2
32
+ - 4
33
+ - 0
34
+ version: 2.4.0
35
+ type: :development
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: rails
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 7
46
+ segments:
47
+ - 3
48
+ - 0
49
+ - 0
50
+ version: 3.0.0
51
+ type: :development
52
+ version_requirements: *id002
53
+ description: |-
54
+ Fickle lets you turn features on and off at will in your rails app using a web interface.
55
+ It also lets you manage features on a model-by-model basis (maybe users?)
56
+ email:
57
+ - chris@lazerclub.co
58
+ executables: []
59
+
60
+ extensions: []
61
+
62
+ extra_rdoc_files: []
63
+
64
+ files:
65
+ - .gitignore
66
+ - .rspec
67
+ - Gemfile
68
+ - README.md
69
+ - Rakefile
70
+ - app/controllers/fickle/application_controller.rb
71
+ - app/controllers/fickle/features_controller.rb
72
+ - app/controllers/fickle/model_features_controller.rb
73
+ - app/views/fickle/features/index.html.erb
74
+ - app/views/fickle/model_features/index.html.erb
75
+ - app/views/layouts/fickle.html.erb
76
+ - fickle.gemspec
77
+ - lib/fickle.rb
78
+ - lib/fickle/abstract_backend.rb
79
+ - lib/fickle/abstract_feature_set.rb
80
+ - lib/fickle/backends.rb
81
+ - lib/fickle/backends/memory.rb
82
+ - lib/fickle/backends/memory/feature_set.rb
83
+ - lib/fickle/class_methods.rb
84
+ - lib/fickle/config.rb
85
+ - lib/fickle/controllers.rb
86
+ - lib/fickle/model_extensions.rb
87
+ - lib/fickle/railtie.rb
88
+ - lib/fickle/routes.rb
89
+ - lib/fickle/version.rb
90
+ - lib/fickle/view_helpers.rb
91
+ - lib/generators/fickle/install/USAGE
92
+ - lib/generators/fickle/install/install_generator.rb
93
+ - lib/generators/fickle/install/templates/fickle.css
94
+ - lib/generators/fickle/install/templates/fickle.js
95
+ - lib/generators/fickle/install/templates/fickle.rb.erb
96
+ - spec/class_methods_spec.rb
97
+ - spec/feature_set_spec.rb
98
+ - spec/fickle_spec.rb
99
+ - spec/model_extensions_spec.rb
100
+ - spec/spec_helper.rb
101
+ - spec/view_helpers_spec.rb
102
+ has_rdoc: true
103
+ homepage: ""
104
+ licenses: []
105
+
106
+ post_install_message:
107
+ rdoc_options: []
108
+
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ hash: 3
117
+ segments:
118
+ - 0
119
+ version: "0"
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ hash: 3
126
+ segments:
127
+ - 0
128
+ version: "0"
129
+ requirements: []
130
+
131
+ rubyforge_project: fickle
132
+ rubygems_version: 1.4.2
133
+ signing_key:
134
+ specification_version: 3
135
+ summary: The feature library for rails that can't make up its mind
136
+ test_files:
137
+ - spec/class_methods_spec.rb
138
+ - spec/feature_set_spec.rb
139
+ - spec/fickle_spec.rb
140
+ - spec/model_extensions_spec.rb
141
+ - spec/spec_helper.rb
142
+ - spec/view_helpers_spec.rb