fickle 0.0.1

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