sapling 0.2.2 → 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.
data/README.md CHANGED
@@ -44,10 +44,16 @@ Server-side Usage
44
44
 
45
45
  To check if a feature is enabled for a user in rails controllers or views: use
46
46
 
47
- feature_active?(:space_chat [, :user => the current user])
47
+ sapling.active?(:space_chat [, :user => the current user])
48
+
48
49
 
49
50
  *Note*: sapling will automatically populate the user argument by calling `current_user`
50
51
 
52
+ To get the css classes the javascript adds to the HTML element for a feature:
53
+
54
+ sapling.css_class(:space_chat)
55
+ sapling.css_toggle_class(:space_chat)
56
+
51
57
  To enable a feature for a specific user, in the rails console:
52
58
 
53
59
  Sapling::ActiveRecord.new.activate_user(:space_chat, space_admin)
@@ -67,6 +73,23 @@ To disable a feature activated for anyone but individually-activated users, in t
67
73
  *Note*: Individually-activated users are always activated, regardless of the percentage setting. A deactivated user
68
74
  may still have access to a feature if they fall within an active percentage.
69
75
 
76
+ Custom Feature-Active Tests
77
+ ---------------------------
78
+
79
+ You can optionally inject your own code for testing if a feature is active. In rails, just added methods of the following form to your ApplicationController:
80
+
81
+ # Example: override feature-active? for feature :new_homepage
82
+ # options:
83
+ # :feature => object of type Sapling::Feature
84
+ # (current settings for the feature from the database)
85
+ # :user => user to test for feature-active
86
+ # return true if the feature is active.
87
+ def new_homepage_active?(options={})
88
+ # your test here
89
+ end
90
+
91
+ *Note* If you are using Sapling manually, when you create a Sapling instance, you can pass in an any object which responds to "current_user" and implements zero or more of your feature-active overrides.
92
+
70
93
  Client-side Usage
71
94
  -----------------
72
95
 
@@ -100,11 +123,11 @@ CSS:
100
123
 
101
124
  ERB:
102
125
 
103
- <div class="<%= feature_class(:chat) %> enabled">
126
+ <div class="<%= sapling.css_class(:chat) %> enabled">
104
127
  REJOICE - SPIFF CHAT ENABLED FOR YOU
105
128
  </div>
106
129
 
107
- <div class="<%= feature_class(:chat) %> disabled">
130
+ <div class="<%= sapling.css_class(:chat) %> disabled">
108
131
  DESPAIR - SPIFF CHAT NOT ENABLED FOR YOU
109
132
  </div>
110
133
 
@@ -116,5 +139,4 @@ TODO
116
139
 
117
140
  * Rails 3 compatibility
118
141
  * Remove mootools dependency
119
- * CSS generator
120
142
  * Database migration generator
@@ -0,0 +1,10 @@
1
+ API changes in 0.3.0
2
+ ====================
3
+
4
+ When using Sapling in rails, there are some api changes:
5
+
6
+ old: feature_active?(feature_name)
7
+ new: sapling.active?(feature_name,options={})
8
+
9
+ old: feature_class(feature_name)
10
+ new: sapling.css_class(feature_name)
@@ -1,11 +1,11 @@
1
1
  require "sapling/version"
2
2
  require "sapling/util"
3
3
  require "sapling/base"
4
+ require "sapling/feature"
4
5
  require "sapling/memory"
5
- require "sapling/model"
6
+ require "sapling/active_record_model"
6
7
  require "sapling/active_record"
7
8
  require "sapling/generators/javascript_generator"
8
- require "sapling/generators/css_generator"
9
9
 
10
10
  module Sapling
11
11
  # Your code goes here...
@@ -2,71 +2,58 @@ module Sapling
2
2
  class ActiveRecord < Base
3
3
 
4
4
  def table_name
5
- Model.table_name
5
+ ActiveRecordModel.table_name
6
6
  end
7
7
 
8
- private
9
- def query_conditions(feature,normalized_options={})
10
- ret=[
11
- "#{'feature = ? AND ' if feature} ((user_id IS NOT NULL AND user_id = ?) OR (percentage IS NOT NULL AND ? < percentage)) "
12
- ]
13
- ret<<feature.to_s if feature
14
- ret+=[
15
- normalized_options[:user_id],
16
- Util::modded_context_id(normalized_options)
17
- ]
18
- ret
19
- end
20
- public
21
-
22
- module ClientAPI
23
- # see Sapling::API::Client
24
- def active?(feature, options={})
25
- options = Util.normalized_options options
26
- v = Model.count(:conditions => query_conditions(feature,options))
27
- v > 0
28
- end
29
-
30
- def features
31
- features = Model.find :all, :select => "feature", :group => "feature", :order => "feature"
32
- features.map {|record| record.feature.to_sym}
8
+ # returns hash of all features
9
+ # keys are feature names (symbols)
10
+ # values are:
11
+ # :percentage => nil or 0-100
12
+ # :user_ids => [] or array of user_ids (integers)
13
+ def features
14
+ @features ||= ActiveRecordModel.find(:all).inject({}) do |ret,feature|
15
+ name = feature.feature.to_sym
16
+ ret[name]||=Feature.new(name)
17
+ ret[name].percentage ||= feature.percentage
18
+ ret[name].users[feature.user_id]=true if feature.user_id
19
+ ret
33
20
  end
21
+ end
34
22
 
35
- # returns a list of features enabled for a user
36
- # see Sapling::API::Client
37
- def active_features(options={})
38
- options = Util.normalized_options options
39
- features = Model.find :all, :conditions => query_conditions(nil,options)
40
- features.map {|record| record.feature.to_sym}.uniq
41
- end
23
+ def reload
24
+ @features=nil
25
+ true
42
26
  end
43
- include ClientAPI
44
27
 
45
28
  module AdminAPI
46
29
 
47
30
  def activate_user(feature, user)
48
- Model.transaction do
31
+ ActiveRecordModel.transaction do
49
32
  deactivate_user(feature, user)
50
- Model.create(:feature => feature.to_s, :user_id => user.id)
33
+ ActiveRecordModel.create(:feature => feature.to_s, :user_id => user.id)
51
34
  end
35
+ reload
52
36
  end
53
37
 
54
38
  def deactivate_user(feature, user)
55
- Model.delete_all ["feature = ? AND percentage IS NULL and user_id = ?",feature.to_s,user.id]
39
+ ActiveRecordModel.delete_all ["feature = ? AND percentage IS NULL and user_id = ?",feature.to_s,user.id]
40
+ reload
56
41
  end
57
42
 
58
43
  def activate_percentage(feature, percentage)
59
44
  raise "invalid percentage #{percentage.inspect}" unless percentage.kind_of?(Integer) && percentage>=0 && percentage<=100
60
- Model.transaction do
45
+ ActiveRecordModel.transaction do
61
46
  deactivate_percentage(feature)
62
- Model.create(:feature => feature.to_s, :percentage => percentage)
47
+ ActiveRecordModel.create(:feature => feature.to_s, :percentage => percentage)
63
48
  end
49
+ reload
64
50
  end
65
51
 
66
52
  def deactivate_percentage(feature)
67
- Model.delete_all ["feature = ? AND percentage IS NOT NULL AND user_id IS NULL",
53
+ ActiveRecordModel.delete_all ["feature = ? AND percentage IS NOT NULL AND user_id IS NULL",
68
54
  feature.to_s
69
55
  ]
56
+ reload
70
57
  end
71
58
  end
72
59
 
@@ -0,0 +1,9 @@
1
+ require "active_record"
2
+ class Sapling::ActiveRecordModel < ::ActiveRecord::Base
3
+ set_table_name "sapling_settings"
4
+
5
+ #structure:
6
+ # feature varchar(255),
7
+ # percentage integer, -- 0 to 100 or NULL
8
+ # user_id integer -- user's ID or NULL
9
+ end
@@ -1,4 +1,65 @@
1
1
  module Sapling
2
2
  class Base
3
+ attr_accessor :controller
4
+
5
+ def initialize(controller=nil)
6
+ @controller = controller
7
+ end
8
+
9
+ def features; @features||={}; end
10
+
11
+ private
12
+ def active_internal(feature_name,options)
13
+ feature_method = "#{feature_name}_active?".to_sym
14
+ if controller && controller.respond_to?(feature_method)
15
+ # use override
16
+ controller.send(feature_method,options.merge(:feature => features[feature_name]))
17
+ else
18
+ (f=features[feature_name]) && f.active?(options)
19
+ end
20
+ end
21
+
22
+ # override feature test example for feature :new_homepage
23
+ # options:
24
+ # feature is of type Feature
25
+ # :user
26
+ # returns true if the feature is enabled
27
+ # def new_homepage_active?(options={})
28
+ # # your test here
29
+ # end
30
+ public
31
+
32
+ # see Sapling::API::Client
33
+ def active?(feature, options={})
34
+ options = Util.normalized_options options, controller
35
+ active_internal(feature,options)
36
+ end
37
+
38
+ # returns a list of features enabled for a user
39
+ # see Sapling::API::Client
40
+ def active_features(options={})
41
+ options = Util.normalized_options options, controller
42
+ ret={}
43
+ features.each {|feature_name,feature| ret[feature_name]=feature if active_internal(feature_name,options)}
44
+ ret
45
+ end
46
+
47
+ def js_generator
48
+ @js_generator ||= JavascriptGenerator.new
49
+ end
50
+
51
+ def css_class_prefix
52
+ "sapling_feature"
53
+ end
54
+
55
+ # Use these classes on the container elements of your features
56
+ def css_class(feature)
57
+ "#{css_class_prefix}_#{feature.to_s}"
58
+ end
59
+
60
+ # Put these on the html element to turn on/off features
61
+ def css_toggle_class(feature, on)
62
+ "#{css_class(feature)}_#{on ? 'on' : 'off'}"
63
+ end
3
64
  end
4
65
  end
@@ -0,0 +1,58 @@
1
+ module Sapling
2
+ class Feature
3
+ attr_accessor :name,:users,:percentage
4
+
5
+ protected
6
+ def normalize_users
7
+ return unless users.kind_of?(Array)
8
+ users = @users
9
+ @users = {}
10
+ users.each {|user_id| @users[user_id]=true}
11
+ end
12
+ public
13
+
14
+ # options
15
+ # :users => array or hash
16
+ # users can be an array of user-ids or hash of user_ids mapped to true
17
+ # :percentage =>
18
+ def initialize(name=nil,options={})
19
+ @name = name
20
+ @users = options[:users] || {}
21
+ normalize_users
22
+ self.percentage = options[:percentage]
23
+ end
24
+
25
+ # the Feature reports its name with "to_s"
26
+ def to_s; name.to_s; end
27
+
28
+ # see Sapling::API::Client
29
+ def active?(options={})
30
+ options = Util.normalized_options(options)
31
+ individually_active?(options[:user]) || percentage_active?(options)
32
+ end
33
+
34
+ def percentage_active?(options={})
35
+ percentage && (Util.modded_context_id(options)) < percentage
36
+ end
37
+
38
+ def individually_active?(user)
39
+ user && users[user.id]
40
+ end
41
+
42
+ def activate_user(user)
43
+ users[user.id]=true
44
+ end
45
+
46
+ def deactivate_user(user)
47
+ users.delete(user.id)
48
+ end
49
+
50
+ def activate_percentage(percentage)
51
+ @percentage=percentage
52
+ end
53
+
54
+ def deactivate_percentage
55
+ @percentage=0
56
+ end
57
+ end
58
+ end
@@ -7,39 +7,26 @@ class Sapling::JavascriptGenerator
7
7
  @sapling=sapling
8
8
  end
9
9
 
10
- def prefix
11
- "sapling_feature"
12
- end
13
-
14
- # Use these classes on the container elements of your features
15
- def css_container_class(feature)
16
- "#{prefix}_#{feature.to_s}"
17
- end
18
-
19
- # Put these on the html element to turn on/off features
20
- def css_toggle_class(feature, on)
21
- "#{css_container_class(feature)}_#{on ? 'on' : 'off'}"
22
- end
23
-
24
-
25
10
  # see Sapling::API::Client for options
26
-
27
11
  def mootools_for_feature(feature, on)
28
- "html.removeClass('" + css_toggle_class(feature,!on) + "');html.addClass('" + css_toggle_class(feature,on) + "');"
12
+ "html.removeClass('" + sapling.css_toggle_class(feature,!on) + "');html.addClass('" + sapling.css_toggle_class(feature,on) + "');"
29
13
  end
30
-
14
+
15
+ # options
16
+ # options is passed directly into sapling.active_features
17
+ # One useful option is to override the user:
18
+ # :user => user
31
19
  def generate(options={})
32
- features = Set.new @sapling.features
33
- active_features = Set.new @sapling.active_features options
20
+ features = sapling.features.keys
21
+ active_features = sapling.active_features(options).keys
34
22
  inactive_features = features - active_features
35
23
 
36
-
37
24
  <<-END
38
25
  (function() {
39
26
  if (window.MooTools) {
40
27
  html = $$('html')[0];
41
28
  #{inactive_features.map{|f| mootools_for_feature(f,false) }.join}
42
- #{active_features.map{|f| mootools_for_feature(f,true) }.join}
29
+ #{active_features.map{|f| mootools_for_feature(f,true) }.join}
43
30
  }
44
31
  })();
45
32
  END
@@ -1,69 +1,9 @@
1
1
  module Sapling
2
2
  class Memory < Base
3
3
 
4
- class Feature
5
- attr_accessor :users,:percentage
6
-
7
- def initialize
8
- @users={}
9
- self.percentage = 0
10
- end
11
-
12
- # see Sapling::API::Client
13
- def active?(options={})
14
- options = Util.normalized_options(options)
15
- individually_active?(options[:user]) || percentage_active?(options)
16
- end
17
-
18
- def percentage_active?(options={})
19
- (Util.modded_context_id(options)) < percentage
20
- end
21
-
22
- def individually_active?(user)
23
- user && users[user.id]
24
- end
25
-
26
- def activate_user(user)
27
- users[user.id]=true
28
- end
29
-
30
- def deactivate_user(user)
31
- users.delete(user.id)
32
- end
33
-
34
- def activate_percentage(percentage)
35
- @percentage=percentage
36
- end
37
-
38
- def deactivate_percentage
39
- @percentage=0
40
- end
41
- end
42
-
43
- def initialize
44
- @features={}
45
- end
46
-
47
- module ClientAPI
48
- # see Sapling::API::Client
49
- def active?(feature, options={})
50
- options = Util.normalized_options(options)
51
- (f = @features[feature]) && f.active?(options)
52
- end
53
-
54
- def features
55
- @features.keys.sort_by {|k| k.to_s}
56
- end
57
-
58
- # see Sapling::API::Client
59
- def active_features(options={})
60
- features.select {|feature| active?(feature,options)}
61
- end
62
- end
63
-
64
4
  module AdminAPI
65
5
  def activate_feature(feature)
66
- @features[feature]||=Feature.new
6
+ features[feature]||=Feature.new
67
7
  end
68
8
 
69
9
  def activate_user(feature, user)
@@ -71,7 +11,7 @@ module Sapling
71
11
  end
72
12
 
73
13
  def deactivate_user(feature, user)
74
- (f=@features[feature]) && f.deactivate_user(user)
14
+ (f=features[feature]) && f.deactivate_user(user)
75
15
  end
76
16
 
77
17
  def activate_percentage(feature, percentage)
@@ -82,8 +22,7 @@ module Sapling
82
22
  activate_feature(feature).deactivate_percentage
83
23
  end
84
24
  end
85
-
86
- include ClientAPI
87
25
  include AdminAPI
26
+
88
27
  end
89
28
  end
@@ -1,23 +1,12 @@
1
1
  require 'action_controller'
2
2
 
3
3
  module Sapling::ActionControllerExt
4
- def feature_active?(feature, options={})
5
- options[:user] ||= current_user
6
- sapling.active?(feature, options)
7
- end
8
-
9
-
10
4
  def sapling
11
- @@sapling ||= Sapling::ActiveRecord.new
12
- end
13
-
14
- def sapling_js_generator
15
- @@sapling_js_generator ||= Sapling::JavascriptGenerator.new(sapling)
5
+ @sapling ||= Sapling::ActiveRecord.new(self)
16
6
  end
17
-
18
7
  end
19
8
 
20
9
  class ActionController::Base
21
10
  include Sapling::ActionControllerExt
22
- helper_method :feature_active?, :sapling, :sapling_js_generator
23
- end
11
+ helper_method :sapling
12
+ end
@@ -1,7 +1,11 @@
1
1
  class SaplingController < ApplicationController
2
2
  unloadable
3
+
3
4
  def script
4
- js = sapling_js_generator.generate(:user => current_user)
5
+ js = sapling.js_generator.generate(
6
+ :features => sapling.features.keys,
7
+ :active_features => sapling.active_features
8
+ )
5
9
  render :text => js, :content_type => 'text/javascript'
6
10
  end
7
11
  end
@@ -1,10 +1,4 @@
1
1
  module Sapling
2
2
  module ViewHelpers
3
-
4
- # include ActionView::Helpers::CaptureHelper
5
- def feature_class(feature)
6
- sapling_js_generator.css_container_class(feature)
7
- end
8
-
9
3
  end
10
- end
4
+ end
@@ -10,7 +10,8 @@ module Sapling
10
10
  ((cid=context_id(options)) && (cid%100)) || CONTEXT_ID_ONLY_ENABLED_IF_100_PERCENT_ENABLED
11
11
  end
12
12
 
13
- def normalized_options(options)
13
+ def normalized_options(options,controller=nil)
14
+ options[:user] ||= controller.current_user if controller
14
15
  options[:user_id] ||= options[:user].id if options[:user]
15
16
  options[:context_id] ||= options[:user_id]
16
17
  options
@@ -1,3 +1,3 @@
1
1
  module Sapling
2
- VERSION = "0.2.2"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -1,8 +1,8 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
- describe "Sapling::Memory::Feature" do
3
+ describe "Sapling::Feature" do
4
4
  it "should support activating users" do
5
- f=Sapling::Memory::Feature.new
5
+ f=Sapling::Feature.new
6
6
  u=UserMock.new
7
7
 
8
8
  f.active?(:user=>u).should be_false
@@ -17,8 +17,15 @@ describe "Sapling::JavascriptGenerator" do
17
17
  @sapling.activate_user(:chat, stub(:id => 1))
18
18
  end
19
19
 
20
- it "outputs js" do
21
- Sapling::JavascriptGenerator.new(@sapling).generate(:user => stub(:id => 1)).should include 'html.addClass(\'sapling_feature_chat_on\');'
20
+ it "outputs js with user set via controller's current_user method" do
21
+ @sapling.controller = stub(:current_user => stub(:id => 1))
22
+ output = Sapling::JavascriptGenerator.new(@sapling).generate
23
+ output.should include 'html.addClass(\'sapling_feature_chat_on\');'
24
+ end
25
+
26
+ it "outputs js with user set by passing in the user as an option" do
27
+ output = Sapling::JavascriptGenerator.new(@sapling).generate(:user => stub(:id => 1))
28
+ output.should include 'html.addClass(\'sapling_feature_chat_on\');'
22
29
  end
23
30
  end
24
31
 
@@ -29,7 +36,7 @@ describe "Sapling::JavascriptGenerator" do
29
36
  @sapling.activate_user(:pwn, stub(:id => 102))
30
37
  @sapling.activate_user(:juggle, stub(:id => 115))
31
38
  end
32
-
39
+
33
40
  it "test user bicycle & pwn user" do
34
41
  output = Sapling::JavascriptGenerator.new(@sapling).generate(:user => stub(:id => 102))
35
42
  expected_features = {
@@ -43,7 +50,7 @@ describe "Sapling::JavascriptGenerator" do
43
50
  output.should include "html.removeClass('sapling_feature_#{key}_#{!enabled ? 'on' : 'off'}');"
44
51
  end
45
52
  end
46
-
53
+
47
54
  it "test chat & juggle user" do
48
55
  output = Sapling::JavascriptGenerator.new(@sapling).generate(:user => stub(:id => 115))
49
56
  expected_features = {
@@ -7,14 +7,19 @@ class ApplicationController < ActionController::Base
7
7
 
8
8
  # Scrub sensitive parameters from your log
9
9
  # filter_parameter_logging :password
10
-
10
+
11
11
  def current_user
12
12
  u = User.new
13
- u.id = session[:current_user_id]
13
+ u.id = session[:current_user_id]
14
14
  u
15
15
  end
16
-
16
+
17
17
  def current_user_id
18
18
  session[:current_user_id]
19
19
  end
20
+
21
+ def my_custom_test_feature_active?(options)
22
+ # if the user is in the sapling list, the feature is DISABLED for them
23
+ !options[:feature].users[current_user.id]
24
+ end
20
25
  end
@@ -2,89 +2,22 @@ class SpacemanSpiffsController < ApplicationController
2
2
  # GET /spaceman_spiffs
3
3
  # GET /spaceman_spiffs.xml
4
4
  def index
5
- if feature_active?(:listing)
5
+ if sapling.active?(:listing)
6
6
  @spaceman_spiffs = SpacemanSpiff.all
7
7
 
8
8
  respond_to do |format|
9
9
  format.html # index.html.erb
10
10
  format.xml { render :xml => @spaceman_spiffs }
11
11
  end
12
- else
12
+ else
13
13
  render :nothing => true, :status => :forbidden
14
14
  end
15
15
  end
16
-
17
- def multiple_features
18
- end
19
-
20
- # GET /spaceman_spiffs/1
21
- # GET /spaceman_spiffs/1.xml
22
- def show
23
- @spaceman_spiff = SpacemanSpiff.find(params[:id])
24
-
25
- respond_to do |format|
26
- format.html # show.html.erb
27
- format.xml { render :xml => @spaceman_spiff }
28
- end
29
- end
30
-
31
- # GET /spaceman_spiffs/new
32
- # GET /spaceman_spiffs/new.xml
33
- def new
34
- @spaceman_spiff = SpacemanSpiff.new
35
-
36
- respond_to do |format|
37
- format.html # new.html.erb
38
- format.xml { render :xml => @spaceman_spiff }
39
- end
40
- end
41
-
42
- # GET /spaceman_spiffs/1/edit
43
- def edit
44
- @spaceman_spiff = SpacemanSpiff.find(params[:id])
45
- end
46
16
 
47
- # POST /spaceman_spiffs
48
- # POST /spaceman_spiffs.xml
49
- def create
50
- @spaceman_spiff = SpacemanSpiff.new(params[:spaceman_spiff])
51
-
52
- respond_to do |format|
53
- if @spaceman_spiff.save
54
- format.html { redirect_to(@spaceman_spiff, :notice => 'SpacemanSpiff was successfully created.') }
55
- format.xml { render :xml => @spaceman_spiff, :status => :created, :location => @spaceman_spiff }
56
- else
57
- format.html { render :action => "new" }
58
- format.xml { render :xml => @spaceman_spiff.errors, :status => :unprocessable_entity }
59
- end
60
- end
61
- end
62
-
63
- # PUT /spaceman_spiffs/1
64
- # PUT /spaceman_spiffs/1.xml
65
- def update
66
- @spaceman_spiff = SpacemanSpiff.find(params[:id])
67
-
68
- respond_to do |format|
69
- if @spaceman_spiff.update_attributes(params[:spaceman_spiff])
70
- format.html { redirect_to(@spaceman_spiff, :notice => 'SpacemanSpiff was successfully updated.') }
71
- format.xml { head :ok }
72
- else
73
- format.html { render :action => "edit" }
74
- format.xml { render :xml => @spaceman_spiff.errors, :status => :unprocessable_entity }
75
- end
76
- end
17
+ def multiple_features
77
18
  end
78
19
 
79
- # DELETE /spaceman_spiffs/1
80
- # DELETE /spaceman_spiffs/1.xml
81
- def destroy
82
- @spaceman_spiff = SpacemanSpiff.find(params[:id])
83
- @spaceman_spiff.destroy
84
-
85
- respond_to do |format|
86
- format.html { redirect_to(spaceman_spiffs_url) }
87
- format.xml { head :ok }
88
- end
20
+ def custom_test_feature
21
+ render :text => (sapling.active?(:my_custom_test_feature) ? "1" : "0")
89
22
  end
90
23
  end
@@ -3,9 +3,11 @@ class UserSessionsController < ApplicationController
3
3
  def show
4
4
  render :text => current_user_id.to_s
5
5
  end
6
+
6
7
  def create
7
8
  session[:current_user_id] = params[:user_id]
8
9
  end
10
+
9
11
  def set_manually
10
12
  session[:current_user_id] = params[:user_id]
11
13
  render :text => current_user_id.to_s
@@ -1,7 +1,7 @@
1
1
  ActionController::Routing::Routes.draw do |map|
2
- map.resources :spaceman_spiffs, :collection => {:multiple_features => :get }
2
+ map.resources :spaceman_spiffs, :collection => {:multiple_features => :get, :custom_test_feature => :get }
3
3
  map.resource :user_sessions, :collection => {:set_manually => :get }
4
-
4
+
5
5
  map.sapling_script 'sapling/script.js', :controller => 'sapling', :action => 'script'
6
6
 
7
7
  # The priority is based upon order of creation: first created -> highest priority.
@@ -22,7 +22,7 @@ ActionController::Routing::Routes.draw do |map|
22
22
 
23
23
  # Sample resource route with sub-resources:
24
24
  # map.resources :products, :has_many => [ :comments, :sales ], :has_one => :seller
25
-
25
+
26
26
  # Sample resource route with more complex sub-resources
27
27
  # map.resources :products do |products|
28
28
  # products.resources :comments
@@ -4,6 +4,10 @@ calvin_allowed:
4
4
  feature: listing
5
5
  user_id: 1
6
6
 
7
+ calvin_not_allowed:
8
+ feature: my_custom_test_feature
9
+ user_id: 1
10
+
7
11
  hobbes_allowed:
8
12
  feature: chat
9
13
  user_id: 2
@@ -10,6 +10,22 @@ class FeatureTest < ActionController::IntegrationTest
10
10
  assert_equal '1', response.body
11
11
  end
12
12
 
13
+ test "users cant use 'my_custom_test_feature'" do
14
+ # user1 is listed, user2 is not
15
+ assert Sapling::ActiveRecord.new.features[:my_custom_test_feature].users[1]
16
+ assert_nil Sapling::ActiveRecord.new.features[:my_custom_test_feature].users[2]
17
+
18
+ # but user1 is denied
19
+ post user_sessions_path, :user_id => 1
20
+ get custom_test_feature_spaceman_spiffs_path
21
+ assert_equal '0', response.body
22
+
23
+ # and user2 is permitted
24
+ post user_sessions_path, :user_id => 2
25
+ get custom_test_feature_spaceman_spiffs_path
26
+ assert_equal '1', response.body
27
+ end
28
+
13
29
  test "listing is enabled for user one" do
14
30
  post user_sessions_path, :user_id => 1
15
31
  get spaceman_spiffs_path
@@ -40,11 +40,11 @@ shared_examples_for Sapling do
40
40
  (1..1000).select { |id| @sapling.active?(:chat, :user=>UserMock.new(id)) }.length.should == 200
41
41
  end
42
42
 
43
-
43
+
44
44
  it "should not be active even without a context or user" do
45
45
  @sapling.should_not be_active(:chat)
46
46
  end
47
-
47
+
48
48
  end
49
49
 
50
50
  describe "activating a 100 percent" do
@@ -55,7 +55,7 @@ shared_examples_for Sapling do
55
55
  it "activates the feature for that percentage of users" do
56
56
  (1..1000).select { |id| @sapling.active?(:chat, :user=>UserMock.new(id)) }.length.should == 1000
57
57
  end
58
-
58
+
59
59
 
60
60
  it "should be active even without a context or user" do
61
61
  @sapling.should be_active(:chat)
@@ -123,7 +123,7 @@ shared_examples_for Sapling do
123
123
  end
124
124
 
125
125
  it "returns only two features" do
126
- @sapling.features.should == [:chat, :pwn]
126
+ @sapling.features.keys.should == [:chat, :pwn]
127
127
  end
128
128
  end
129
129
 
@@ -136,8 +136,30 @@ shared_examples_for Sapling do
136
136
  end
137
137
 
138
138
  it "should return only the active features for the specific user" do
139
- @sapling.active_features(:user => stub(:id => 102)).should == [:bicycle, :pwn]
140
- @sapling.active_features(:user => stub(:id => 115)).should == [:chat, :juggle]
139
+ @sapling.active_features(:user => stub(:id => 102)).keys.should == [:bicycle, :pwn]
140
+ @sapling.active_features(:user => stub(:id => 115)).keys.should == [:chat, :juggle]
141
+ end
142
+ end
143
+
144
+ describe "test custom override" do
145
+ before do
146
+ @sapling.activate_user(:chat, stub(:id => 115,:name=>"fred"))
147
+ @sapling.activate_user(:chat, stub(:id => 116,:name=>"harry"))
148
+ @sapling.activate_user(:chat, stub(:id => 117,:name=>"franny"))
149
+ end
150
+
151
+ class Override
152
+ def chat_active?(options)
153
+ !!options[:user].name[/^f/]
154
+ end
155
+ end
156
+
157
+ it "should return only the active features for the specific user" do
158
+ @sapling.controller = Override.new
159
+
160
+ @sapling.active?(:chat,:user => stub(:id=>115,:name => "fred")).should == true
161
+ @sapling.active?(:chat,:user => stub(:id=>120,:name => "h")).should == false
162
+ @sapling.active?(:chat,:user => stub(:id=>130,:name => "frank")).should == true
141
163
  end
142
164
  end
143
165
 
metadata CHANGED
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 2
9
- - 2
10
- version: 0.2.2
8
+ - 3
9
+ - 0
10
+ version: 0.3.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Shane Brinkman-Davis
@@ -16,8 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2011-12-20 00:00:00 -08:00
20
- default_executable:
19
+ date: 2012-02-03 00:00:00 Z
21
20
  dependencies:
22
21
  - !ruby/object:Gem::Dependency
23
22
  name: activerecord
@@ -111,15 +110,16 @@ files:
111
110
  - Gemfile
112
111
  - README.md
113
112
  - Rakefile
113
+ - changelog.md
114
114
  - db/create.sql
115
115
  - lib/sapling.rb
116
116
  - lib/sapling/active_record.rb
117
+ - lib/sapling/active_record_model.rb
117
118
  - lib/sapling/api.rb
118
119
  - lib/sapling/base.rb
119
- - lib/sapling/generators/css_generator.rb
120
+ - lib/sapling/feature.rb
120
121
  - lib/sapling/generators/javascript_generator.rb
121
122
  - lib/sapling/memory.rb
122
- - lib/sapling/model.rb
123
123
  - lib/sapling/rails.rb
124
124
  - lib/sapling/rails/action_controller.rb
125
125
  - lib/sapling/rails/controllers/sapling_controller.rb
@@ -130,9 +130,8 @@ files:
130
130
  - rails/init.rb
131
131
  - sapling.gemspec
132
132
  - spec/active_record_spec.rb
133
- - spec/css_generator_spec.rb
133
+ - spec/feature_spec.rb
134
134
  - spec/javascript_generator_spec.rb
135
- - spec/memory_feature_spec.rb
136
135
  - spec/memory_spec.rb
137
136
  - spec/rails_app/README
138
137
  - spec/rails_app/Rakefile
@@ -202,7 +201,6 @@ files:
202
201
  - spec/sapling_examples.rb
203
202
  - spec/spec.opts
204
203
  - spec/spec_helper.rb
205
- has_rdoc: true
206
204
  homepage: https://github.com/imikimi/sapling
207
205
  licenses: []
208
206
 
@@ -232,15 +230,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
232
230
  requirements: []
233
231
 
234
232
  rubyforge_project: sapling
235
- rubygems_version: 1.5.3
233
+ rubygems_version: 1.8.10
236
234
  signing_key:
237
235
  specification_version: 3
238
236
  summary: Incrementally roll out your features. Uses ActiveRecord to store configuration and supports client-side roll-out of cached pages.
239
237
  test_files:
240
238
  - spec/active_record_spec.rb
241
- - spec/css_generator_spec.rb
239
+ - spec/feature_spec.rb
242
240
  - spec/javascript_generator_spec.rb
243
- - spec/memory_feature_spec.rb
244
241
  - spec/memory_spec.rb
245
242
  - spec/rails_app/README
246
243
  - spec/rails_app/Rakefile
@@ -1,31 +0,0 @@
1
- require "set"
2
-
3
- module Sapling
4
- class CssGenerator
5
- attr_accessor :sapling
6
-
7
- def initialize(sapling)
8
- @sapling=sapling
9
- end
10
-
11
- def prefix
12
- "sapling_css"
13
- end
14
-
15
- def css_class(feature, on)
16
- "#{prefix}_#{feature.to_s}_#{on ? 'on' : 'off'}"
17
- end
18
-
19
- # see Sapling::API::Client for options
20
- def to_s(options={})
21
- features = Set.new @sapling.features
22
- active_features = Set.new @sapling.active_features options
23
- inactive_features = features - active_features
24
-
25
- [
26
- active_features.collect {|f|".#{css_class(f, false)} { display:none !important; }"},
27
- inactive_features.collect {|f|".#{css_class(f, true)} { display:none !important; }"},
28
- ].flatten.join("\n")
29
- end
30
- end
31
- end
@@ -1,4 +0,0 @@
1
- require "active_record"
2
- class Sapling::Model < ::ActiveRecord::Base
3
- set_table_name "sapling_settings"
4
- end
@@ -1,47 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
-
3
- describe "Sapling::CssGenerator" do
4
-
5
- before do
6
- ActiveRecord::Base.establish_connection(
7
- :adapter => 'sqlite3',
8
- :database => ':memory:'
9
- )
10
- sql = File.read(File.expand_path(File.dirname(__FILE__) + '/../db/create.sql'))
11
- ActiveRecord::Base.connection.execute sql
12
- @sapling = Sapling::ActiveRecord.new
13
- end
14
-
15
- describe "creating basic css" do
16
- before do
17
- @sapling.activate_user(:chat, stub(:id => 1))
18
- end
19
-
20
- it "outputs css" do
21
- Sapling::CssGenerator.new(@sapling).to_s(:user => stub(:id => 1)).should == ".sapling_css_chat_off { display:none !important; }"
22
- end
23
- end
24
-
25
- describe "creating more complex css" do
26
- before do
27
- @sapling.activate_percentage(:bicycle, 10)
28
- @sapling.activate_user(:chat, stub(:id => 115))
29
- @sapling.activate_user(:pwn, stub(:id => 102))
30
- @sapling.activate_user(:juggle, stub(:id => 115))
31
- end
32
-
33
- it "test user bicycle & pwn user" do
34
- output = Sapling::CssGenerator.new(@sapling).to_s(:user => stub(:id => 102))
35
- %w{ bicycle_off chat_on juggle_on pwn_off }.each do |key|
36
- output.should =~ /#{key}[^}]+?display:none/
37
- end
38
- end
39
-
40
- it "test chat & juggle user" do
41
- output = Sapling::CssGenerator.new(@sapling).to_s(:user => stub(:id => 115))
42
- %w{ bicycle_on chat_off juggle_off pwn_on }.each do |key|
43
- output.should =~ /#{key}[^}]+?display:none/
44
- end
45
- end
46
- end
47
- end