fluidfeatures-rails 0.4.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,6 +1,177 @@
1
+ [![Build Status](https://secure.travis-ci.org/FluidFeatures/fluidfeatures-rails.png)](http://travis-ci.org/FluidFeatures/fluidfeatures-rails)
1
2
 
2
- fluidfeatures-rails
3
- ===================
3
+ Rails graceful feature rollout and simple A/B testing
4
+ =====================================================
5
+
6
+ `gem fluidfeatures-rails` is a Ruby on Rails client for the API of FluidFeatures.com, which provides an elegant way to wrap new code so you have real-time control over rolling out new features to your user-base.
7
+
8
+ Rollout new versions of features
9
+ --------------------------------
10
+
11
+ Using `if ff? "foo"` you can easily rollout new code to production and then use the FluidFeatures dashboard to rollout this code to yourself, your team or your users.
12
+
13
+ Use `if ff? "foo", "v2"` to wrap the code for a new version of the "foo" feature.
14
+
15
+ `if ff? "foo"` defaults to version `"default"`, so anyone seeing "v2" will not also see "v2". This is a great way to test new features in production.
16
+
17
+ Once you have moved all your users over to the newer "v2" version you can factor out code from the older version.
18
+
19
+ Tracking goals
20
+ --------------
21
+
22
+ Sometimes referred to as "conversions", goals are a simple way of flagging that an condition has been met. You can do this in your Rails controllers with `fluidgoal(<goal-name>)`. A statement such as `fluidgoal "upgraded-to-pro-account"` tells FluidFeatures that your user upgraded their account.
23
+
24
+ You can do this in controller, or in your view and you will immediately be able to see statistics within the FluidFeatures dashboard. Statistics show you will versions of which features play the greatest roll in driving your users towards these goals.
25
+
26
+ A/B testing
27
+ -----------
28
+
29
+ By combining 2 versions of a feature and one goal (see above) you can start to easily perform A/B testing. FluidFeatures will keep track of which version is winning in terms of statistically hitting the goal more often.
30
+
31
+ If you only roll out one of the versions to a small percentage, say 2%, of your user base, then this is taken into account when calculating the version success statistics. This means you can easily trial experimental features or new versions of existing features.
32
+
33
+ Multi-variant testing and beyond
34
+ --------------------------------
35
+
36
+ While A/B testing might be enough for some, you have the power to do much more.
37
+
38
+ You can define any number of versions for a feature and track them all.
39
+
40
+ You can also compare the performance of different features against each other over time.
41
+
42
+ On your server
43
+ --------------
44
+
45
+ A/B testing on the server gives you the ability to test things at all levels of your stack. Test alternate SQL statements, different versions of emails sent to users, or different backend 3rd party API services.
46
+
47
+ In your templates
48
+ -----------------
49
+
50
+ fluidfeatures-rails exposes `def ff?` (alias to `def fluidfeature`) and `def fluidgoal` to your templates, so can also wrap versioned code there.
51
+
52
+ User definition and cohorts
53
+ ---------------------------
54
+
55
+ In your `application_controller.rb` you will define a function called `def fluidfeatures_current_user` which can be called by `gem fluidfeatures-rails` to determine who the current user is.
56
+
57
+ This important for FluidFeatures to determine which feature versions to enable for the user, but is also the place where you can define any cohorts you wish to use for rolling out features.
58
+
59
+ ```ruby
60
+ def fluidfeatures_current_user(verbose)
61
+ if current_user
62
+ if verbose
63
+ {
64
+ :id => @current_user[:id],
65
+ :name => @current_user[:name],
66
+ :uniques => {
67
+ :twitter => @current_user[:twitter_id]
68
+ },
69
+ :cohorts => {
70
+ # Example attributes for the user.
71
+ # These can be any fields you wish to select users by.
72
+ :company => {
73
+ # "display" is used to help you find it in the dashboard.
74
+ # Highly recommended unless you work with ids.
75
+ # This display name can change over time without consequence.
76
+ :display => @current_user[:company_name],
77
+ # "id" should be unique this this cohort and must be static
78
+ # over time.
79
+ :id => @current_user[:company_id]
80
+ },
81
+ # For this boolean cohort "true"|"false" is the "display"
82
+ # and the "id"
83
+ :admin => @current_user[:admin]
84
+ }
85
+ }
86
+ else
87
+ {
88
+ :id => @current_user.id
89
+ }
90
+ end
91
+ else
92
+ nil
93
+ end
94
+ end
95
+ ```
96
+
97
+ The above is an example of `fluidfeatures_current_user` that you might implement. The `verbose` is a boolean that indicates whether all details are required, or just the minimal identifying attributes (unique `:id`). `verbose` is an optimization that will be used at the discretion of `gem fluidfeatures-rails`.
98
+
99
+ `:name` allows you to pass the human-readable string, such as `"John Doe"`, that represents the user. This is then used in the FluidFeatures dashboard for display and search purposes. You can very quickly search for this user and enable a new feature for them.
100
+
101
+ `:uniques` are other unique attributes of the user that you can pass to FluidFeatures. For instance, their Twitter handle. These can also be used for search in the dashboard. FluidFeatures may use these for further integrate, such as displaying Twitter profile pictures.
102
+
103
+ `:cohorts` are non-unique attributes of the user. You can define any attribute you wish. Common ones are `:admin` (`true` or `false`), but could be anything from the month when they joined (`:joined => "2011-04"`), to whether or not they drink coffee (`"coffee-drinker" => false`). Again, you can search on these attributes and enable specific versions of features based on these attributes.
104
+
105
+ Anonymous users
106
+ ---------------
107
+
108
+ If your `ApplicationController` method `fluidfeatures_current_user` returns `nil` fluidfeatures-rails assumes that your user is anonymous and it will set a cookie in the user's browser. The cookie enables FluidFeatures to provide a consistent experience for that anonymous user. This is mostly important when you have feature versions enabled on a percentage of random users, when two anonymous user may have different features shown to them, depending on which percentage they random fall into.
109
+
110
+ It is possible handle anonymous users yourself by managing the cookies yourself, generating a unique `:id` for each anonymous user and additionally passing `:anonymous => true` in the `Hash` of `fluidfeatures_current_user` for both verbose and non-verbose calls. This basically what `gem fluidfeatures-rails` does under the hood for your convenience, so you do not have to.
111
+
112
+ Integration
113
+ -----------
114
+
115
+ Add the gem to your application
116
+
117
+ ```ruby
118
+ gem "fluidfeatures-rails"
119
+ ```
120
+
121
+ Call the initializer when your application starts
122
+
123
+ ```ruby
124
+ module MyRailsApp
125
+ class Application < Rails::Application
126
+ FluidFeatures::Rails.initializer
127
+ end
128
+ end
129
+ ```
130
+
131
+ Create a `fluidfeature_current_user` method in your `ApplicationController`. See [User definition and cohorts](#user-definition-and-cohorts).
132
+
133
+ Start adding your features and goals using `if ff?` (or `if fluidfeature`) and `fluidgoal`.
134
+
135
+ In your controllers or your views...
136
+
137
+ ```ruby
138
+ # "theme" is simply an example feature name, used to represent
139
+ # a migration to a styling of your website
140
+ if ff? "theme", "default"
141
+ # wrap code related to your default theme, so it is
142
+ # only executed when the user is allocated this version
143
+ # of the feature "theme".
144
+ end
145
+ # Alternate verison of the "theme" feature.
146
+ # FluidFeatures will only assign a user to one version
147
+ # of a feature.
148
+ if ff? "theme", "tropical"
149
+ # implement code specifically related to your new theme
150
+ end
151
+
152
+ fluidgoal "bought-bieber-dvd"
153
+
154
+ fluidgoal "added-a-comment"
155
+
156
+ fluidgoal "upgraded-to-pro-account"
157
+
158
+ fluidgoal "general-engagement"
159
+ ```
160
+
161
+ Dashboard
162
+ ---------
163
+
164
+ If you log into your FluidFeatures account and visit [fluidfeatures.com/dashboard](https://www.fluidfeatures.com/dashboard) you will see your feature and goal dashboard.
165
+
166
+ ![Example dashboard view](http://commondatastorage.googleapis.com/philwhln/blog/images/ab-test-rails/full-dashboard.png)
167
+
168
+ This shows one features `"ab-test"` with two versions simply named `"a"` and `"b"`.
169
+
170
+ There are two goals called `"yes"` and `"no"`.
171
+
172
+ Version `"a"` is seen by 75% of the user-base, whether they are anonymous or not, and version `"b"` is seen by the remaining 25%.
173
+
174
+ You can read more about this example in a blog post here...
175
+ http://www.bigfastblog.com/ab-testing-in-ruby-on-rails
4
176
 
5
- Rails client for API of FluidFeatures.com
6
177
 
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
15
15
  s.test_files = `git ls-files -- {spec}/*`.split("\n")
16
16
  s.require_paths = ["lib"]
17
17
 
18
- s.add_dependency "fluidfeatures", "~>0.4.4" unless ENV["FF_DEV"]
18
+ s.add_dependency "fluidfeatures", "~> 0.5.0" unless ENV["FF_DEV"]
19
19
 
20
20
  s.add_development_dependency('rake', '~> 10.0.2')
21
21
  s.add_development_dependency('rspec', '~> 2.12.0')
@@ -1,4 +1,3 @@
1
-
2
1
  require "fluidfeatures"
3
2
 
4
3
  module FluidFeatures
@@ -20,12 +19,12 @@ module FluidFeatures
20
19
  # Without these FluidFeatures credentials we cannot talk to
21
20
  # the FluidFeatures service.
22
21
  #
23
- %w[FLUIDFEATURES_BASEURI FLUIDFEATURES_SECRET FLUIDFEATURES_APPID].each do |key|
24
- unless ENV[key]
25
- $stderr.puts "!! fluidfeatures-rails requires ENV[\"#{key}\"] (fluidfeatures is disabled)"
26
- return
27
- end
28
- end
22
+ #%w[FLUIDFEATURES_BASEURI FLUIDFEATURES_SECRET FLUIDFEATURES_APPID].each do |key|
23
+ # unless ENV[key]
24
+ # $stderr.puts "!! fluidfeatures-rails requires ENV[\"#{key}\"] (fluidfeatures is disabled)"
25
+ # return
26
+ # end
27
+ #end
29
28
  unless defined? ::Rails
30
29
  $stderr.puts "!! fluidfeatures-rails requires rails (fluidfeatures is disabled)"
31
30
  return
@@ -34,17 +33,12 @@ module FluidFeatures
34
33
 
35
34
  ::Rails::Application.initializer "fluidfeatures.initializer" do
36
35
  ActiveSupport.on_load(:action_controller) do
37
- api_baseuri = ENV["FLUIDFEATURES_BASEURI"]
38
- api_appid = ENV["FLUIDFEATURES_APPID"]
39
- api_secret = ENV["FLUIDFEATURES_SECRET"]
40
-
41
- ::FluidFeatures::Rails.ff_app = ::FluidFeatures.app(
42
- api_baseuri,
43
- api_appid,
44
- api_secret,
45
- #::Rails.logger
46
- )
47
-
36
+ replacements = {}
37
+ replacements["baseuri"] = ENV["FLUIDFEATURES_BASEURI"] if ENV["FLUIDFEATURES_BASEURI"]
38
+ replacements["appid"] = ENV["FLUIDFEATURES_APPID"] if ENV["FLUIDFEATURES_APPID"]
39
+ replacements["secret"] = ENV["FLUIDFEATURES_SECRET"] if ENV["FLUIDFEATURES_SECRET"]
40
+ config = ::FluidFeatures::Config.new("#{::Rails.root}/config/stormpath.yml", ::Rails.env, replacements)
41
+ ::FluidFeatures::Rails.ff_app = ::FluidFeatures.app(config)
48
42
  ActionController::Base.append_before_filter :fluidfeatures_request_before
49
43
  ActionController::Base.append_after_filter :fluidfeatures_request_after
50
44
  end
@@ -62,7 +56,9 @@ module ActionController
62
56
  attr_accessor :ff_transaction
63
57
 
64
58
  # allow fluidfeature to be called from templates
59
+ helper_method :ff?
65
60
  helper_method :fluidfeature
61
+ helper_method :fluidgoal
66
62
 
67
63
  #
68
64
  # Here is how we know what your user_id is for the user
@@ -1,5 +1,5 @@
1
1
  module FluidFeatures
2
2
  module Rails
3
- VERSION = '0.4.4'
3
+ VERSION = '0.5.0'
4
4
  end
5
5
  end
@@ -0,0 +1,29 @@
1
+ module FluidFeatures
2
+ module Rails
3
+ module Generators
4
+ class InstallGenerator < ::Rails::Generators::Base
5
+ def create_initializer_file
6
+ create_file "config/fluidfeatures.yml", "common:
7
+ baseuri: https://www.fluidfeatures.com/service
8
+ cache:
9
+ enable: false
10
+ dir: tmp/fluidfeatures
11
+ limit: 2mb
12
+
13
+ development:
14
+ appid:
15
+ secret:
16
+
17
+ test:
18
+ appid:
19
+ secret:
20
+
21
+ production:
22
+ appid:
23
+ secret:
24
+ "
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,9 @@
1
+ require "spec_helper"
2
+ require "generators/fluidfeatures/rails/install/install_generator"
3
+
4
+ describe FluidFeatures::Rails::Generators::InstallGenerator do
5
+ it "creates a configuration file" do
6
+ subject.should_receive(:create_file).with("config/fluidfeatures.yml", kind_of(String))
7
+ subject.create_initializer_file
8
+ end
9
+ end
@@ -8,15 +8,6 @@ describe "FluidFeatures Rails initialization" do
8
8
  CONFIG_KEYS.each {|k| ENV[k] = k}
9
9
  end
10
10
 
11
- CONFIG_KEYS.each do |key|
12
- it "should require #{key} environment variable" do
13
- ENV[key] = nil
14
- $stderr.should_receive(:puts).with("!! fluidfeatures-rails requires ENV[\"#{key}\"] (fluidfeatures is disabled)")
15
- FluidFeatures::Rails.initializer
16
- FluidFeatures::Rails.enabled.should be_false
17
- end
18
- end
19
-
20
11
  it "should start application if Rails defined" do
21
12
  defined?(Rails).should be_true
22
13
  $stderr.should_receive(:puts).with("=> fluidfeatures-rails initializing as app #{ENV["FLUIDFEATURES_APPID"]} with #{ENV["FLUIDFEATURES_BASEURI"]}")
@@ -32,12 +23,20 @@ describe "FluidFeatures Rails initialization" do
32
23
  FluidFeatures.stub!(:app)
33
24
  ActionController::Base.stub!(:append_before_filter)
34
25
  ActionController::Base.stub!(:append_after_filter)
26
+ FluidFeatures::Config.stub(:new).and_return({})
27
+ Rails.stub!(:root).and_return(".")
28
+ Rails.stub!(:env).and_return(:development)
35
29
  FluidFeatures::Rails.initializer
36
30
  end
37
31
 
32
+ it "should create config" do
33
+ FluidFeatures::Config.should_receive(:new).and_return({})
34
+ instance_eval &on_load
35
+ end
36
+
38
37
  it "should create app with passed credentials" do
39
38
  @app = mock("app")
40
- FluidFeatures.should_receive(:app).with(ENV["FLUIDFEATURES_BASEURI"], ENV["FLUIDFEATURES_APPID"], ENV["FLUIDFEATURES_SECRET"]).and_return(@app)
39
+ FluidFeatures.should_receive(:app).with({}).and_return(@app)
41
40
  instance_eval &on_load
42
41
  FluidFeatures::Rails.ff_app.should == @app
43
42
  end
@@ -26,4 +26,9 @@ module Rails
26
26
  def self.logger
27
27
  @@logger ||= Object.new
28
28
  end
29
+
30
+ module Generators
31
+ class Base
32
+ end
33
+ end
29
34
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluidfeatures-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4
4
+ version: 0.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-05 00:00:00.000000000 Z
12
+ date: 2013-01-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: fluidfeatures
@@ -18,7 +18,7 @@ dependencies:
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: 0.4.4
21
+ version: 0.5.0
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
- version: 0.4.4
29
+ version: 0.5.0
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: rake
32
32
  requirement: !ruby/object:Gem::Requirement
@@ -111,9 +111,11 @@ files:
111
111
  - lib/fluidfeatures/rails/app/controllers/fluidfeatures_controller.rb
112
112
  - lib/fluidfeatures/rails/config/routes.rb
113
113
  - lib/fluidfeatures/rails/version.rb
114
+ - lib/generators/fluidfeatures/rails/install/install_generator.rb
114
115
  - lib/pre_ruby192/uri.rb
115
116
  - spec/controller_extentions_spec.rb
116
117
  - spec/controller_spec.rb
118
+ - spec/generators/fluidfeatures/rails/install/install_generator_spec.rb
117
119
  - spec/initializer_spec.rb
118
120
  - spec/routes_spec.rb
119
121
  - spec/spec_helper.rb