fluidfeatures-rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.swp
3
+ .bundle
4
+ Gemfile.lock
5
+ pkg/*
6
+ *.rbc
7
+ *.sassc
8
+ .sass-cache
9
+ capybara-*.html
10
+ .rspec
11
+ /.bundle
12
+ /vendor/bundle
13
+ /log/*
14
+ /tmp/*
15
+ /db/*.sqlite3
16
+ /public/system/*
17
+ /coverage/
18
+ /spec/tmp/*
19
+ **.orig
20
+ rerun.txt
21
+ pickle-email-*.html
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in fluidfeatures.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,4 @@
1
+ fluidfeatures-rails
2
+ ===================
3
+
4
+ Rails client for API of FluidFeatures.com
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake/testtask'
5
+
6
+ namespace :test do
7
+ Rake::TestTask.new(:all) do |t|
8
+ t.libs << "test"
9
+ t.pattern = 'test/**/*_test.rb'
10
+ t.verbose = true
11
+ end
12
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "fluidfeatures/rails/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "fluidfeatures-rails"
7
+ s.version = FluidFeatures::Rails::VERSION
8
+ s.authors = ["Phil Whelan"]
9
+ s.email = ["phil@fluidfeatures.com"]
10
+ s.homepage = "https://github.com/BigFastSite/fluidfeatures-rails"
11
+ s.summary = %q{Ruby on Rails client for the FluidFeatures service.}
12
+ s.description = %q{Ruby on Rails client for the FluidFeatures service.}
13
+ s.rubyforge_project = s.name
14
+ s.files = `git ls-files`.split("\n")
15
+ #s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.require_paths = ["lib"]
17
+ s.add_dependency "rails", "~>3.0"
18
+ s.add_dependency "persistent_http", "~>1.0.3"
19
+ end
@@ -0,0 +1,5 @@
1
+ module FluidFeatures
2
+ module Rails
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1,288 @@
1
+
2
+ require 'rails'
3
+ require 'net/http'
4
+ require 'persistent_http'
5
+
6
+ #
7
+ # Without these FluidFeatures credentials we cannot talk to
8
+ # the FluidFeatures service.
9
+ #
10
+ %w[FLUIDFEATURES_BASEURI FLUIDFEATURES_SECRET FLUIDFEATURES_APPID].each do |key|
11
+ unless ENV[key]
12
+ raise "Environment variable #{key} expected"
13
+ end
14
+ end
15
+
16
+ module FluidFeatures
17
+ module Rails
18
+
19
+ #
20
+ # This is called once, when your Rails application fires up.
21
+ # It sets up the before and after request hooks
22
+ #
23
+ def self.initializer
24
+ ::Rails::Application.initializer "fluidfeatures.initializer" do
25
+ ActiveSupport.on_load(:action_controller) do
26
+
27
+ @@baseuri = ENV["FLUIDFEATURES_BASEURI"]
28
+ @@secret = ENV["FLUIDFEATURES_SECRET"]
29
+ @@app_id = ENV["FLUIDFEATURES_APPID"]
30
+ @@http = PersistentHTTP.new(
31
+ :name => 'fluidfeatures',
32
+ :logger => ::Rails.logger,
33
+ :pool_size => 10,
34
+ :warn_timeout => 0.25,
35
+ :force_retry => true,
36
+ :url => @@baseuri
37
+ )
38
+ @@unknown_features = {}
39
+ @@last_fetch_duration = nil
40
+
41
+ ActionController::Base.append_before_filter :fluidfeatures_request_init
42
+ ActionController::Base.append_after_filter :fluidfeatures_store_features_hit
43
+
44
+ end
45
+ end
46
+ end
47
+
48
+ #
49
+ # This can be used to control how much of your user-base sees a
50
+ # particular feature. It may be easier to use the dashboard provided
51
+ # at https://www.fluidfeatures.com/dashboard to manage this, or to
52
+ # set timers to automate the gradual rollout of your new features.
53
+ #
54
+ def self.feature_set_enabled_percent(feature_name, enabled_percent)
55
+ begin
56
+ uri = URI(@@baseuri + "/app/" + @@app_id.to_s + "/features/" + feature_name.to_s)
57
+ request = Net::HTTP::Put.new uri.path
58
+ request["Content-Type"] = "application/json"
59
+ request["Accept"] = "application/json"
60
+ request['AUTHORIZATION'] = @@secret
61
+ payload = {
62
+ :enabled => {
63
+ :percent => enabled_percent
64
+ }
65
+ }
66
+ request.body = JSON.dump(payload)
67
+ response = @@http.request uri, request
68
+ if response.is_a?(Net::HTTPSuccess)
69
+ ::Rails.logger.error "[" + response.code.to_s + "] Failed to set feature enabled percent : " + uri.to_s + " : " + response.body.to_s
70
+ end
71
+ rescue
72
+ ::Rails.logger.error "Request to set feature enabled percent failed : " + uri.to_s
73
+ raise
74
+ end
75
+ end
76
+
77
+ #
78
+ # Returns all the features that FluidFeatures knows about for
79
+ # your application. The enabled percentage (how much of your user-base)
80
+ # sees each feature is also provided.
81
+ #
82
+ def self.get_feature_set
83
+ features = nil
84
+ begin
85
+ uri = URI(@@baseuri + "/app/" + @@app_id.to_s + "/features")
86
+ request = Net::HTTP::Get.new uri.path
87
+ request["Accept"] = "application/json"
88
+ request['AUTHORIZATION'] = @@secret
89
+ response = @@http.request request
90
+ if response.is_a?(Net::HTTPSuccess)
91
+ features = JSON.parse(response.body)
92
+ end
93
+ rescue
94
+ ::Rails.logger.error "Request failed when getting feature set from " + uri.to_s
95
+ raise
96
+ end
97
+ if not features
98
+ ::Rails.logger.error "Empty feature set returned from " + uri.to_s
99
+ end
100
+ features
101
+ end
102
+
103
+ #
104
+ # Returns all the features enabled for a specific user.
105
+ # This will depend on the user_id and how many users each
106
+ # feature is enabled for.
107
+ #
108
+ def self.get_user_features(user_id)
109
+ if not user_id
110
+ raise "user_id is not given for get_user_features"
111
+ end
112
+ features = {}
113
+ fetch_start_time = Time.now
114
+ begin
115
+ uri = URI(@@baseuri + "/app/" + @@app_id.to_s + "/user/" + user_id.to_s + "/features")
116
+ request = Net::HTTP::Get.new uri.path
117
+ request["Accept"] = "application/json"
118
+ request['AUTHORIZATION'] = @@secret
119
+ response = @@http.request request
120
+ if response.is_a?(Net::HTTPSuccess)
121
+ features = JSON.parse(response.body)
122
+ else
123
+ ::Rails.logger.error "[" + response.code.to_s + "] Failed to get user features : " + uri.to_s + " : " + response.body.to_s
124
+ end
125
+ rescue
126
+ ::Rails.logger.error "Request to get user features failed : " + uri.to_s
127
+ raise
128
+ end
129
+ @@last_fetch_duration = Time.now - fetch_start_time
130
+ features
131
+ end
132
+
133
+ #
134
+ # This is called when we encounter a feature_name that
135
+ # FluidFeatures has no record of for your application.
136
+ # This will be reported back to the FluidFeatures service so
137
+ # that it can populate your dashboard with this feature.
138
+ # The parameter "default_enabled" is a boolean that says whether
139
+ # this feature should be enabled to all users or no users.
140
+ # Usually, this is "true" for existing features that you are
141
+ # planning to phase out and "false" for new feature that you
142
+ # intend to phase in.
143
+ #
144
+ def self.unknown_feature_hit(feature_name, version_name, defaults)
145
+ if not @@unknown_features[feature_name]
146
+ @@unknown_features[feature_name] = { :versions => {} }
147
+ end
148
+ @@unknown_features[feature_name][:versions][version_name] = defaults
149
+ end
150
+
151
+ #
152
+ # This reports back to FluidFeatures which features we
153
+ # encountered during this request, the request duration,
154
+ # and statistics on time spent talking to the FluidFeatures
155
+ # service. Any new features encountered will also be reported
156
+ # back with the default_enabled status (see unknown_feature_hit)
157
+ # so that FluidFeatures can auto-populate the dashboard.
158
+ #
159
+ def self.log_features_hit(user_id, features_hit, request_duration)
160
+ begin
161
+ uri = URI(@@baseuri + "/app/" + @@app_id.to_s + "/user/" + user_id.to_s + "/features/hit")
162
+ request = Net::HTTP::Post.new uri.path
163
+ request["Content-Type"] = "application/json"
164
+ request["Accept"] = "application/json"
165
+ request['AUTHORIZATION'] = @@secret
166
+ payload = {
167
+ :stats => {
168
+ :fetch => {
169
+ :duration => @@last_fetch_duration
170
+ },
171
+ :request => {
172
+ :duration => request_duration
173
+ }
174
+ },
175
+ :features => {
176
+ :hit => features_hit
177
+ }
178
+ }
179
+ if @@unknown_features.size
180
+ payload[:features][:unknown] = @@unknown_features
181
+ @@unknown_features = {}
182
+ end
183
+ request.body = JSON.dump(payload)
184
+ response = @@http.request request
185
+ unless response.is_a?(Net::HTTPSuccess)
186
+ ::Rails.logger.error "[" + response.code.to_s + "] Failed to log features hit : " + uri.to_s + " : " + response.body.to_s
187
+ end
188
+ rescue Exception => e
189
+ ::Rails.logger.error "Request to log user features hit failed : " + uri.to_s
190
+ raise
191
+ end
192
+ end
193
+
194
+ end
195
+ end
196
+
197
+ module ActionController
198
+ class Base
199
+
200
+ #
201
+ # Here is how we know what your user_id is for the user
202
+ # making the current request.
203
+ # This must be overriden within the user application.
204
+ # We recommend doing this in application_controller.rb
205
+ #
206
+ def fluidfeatures_set_user_id(user_id)
207
+ @ff_user_id = user_id
208
+ end
209
+
210
+ #
211
+ # This is called by the developer's code to determine if the
212
+ # feature, specified by "feature_name" is enabled for the
213
+ # current user.
214
+ # We call user_id to get the current user's unique id.
215
+ #
216
+ def fluidfeature(feature_name, defaults={})
217
+ @features_hit ||= []
218
+ @features_hit << feature_name
219
+ if defaults === true or defaults === false
220
+ defaults = { :enabled => defaults }
221
+ end
222
+ global_defaults = fluidfeatures_defaults || {}
223
+ version = (defaults[:version] || global_defaults[:version]).to_s
224
+ if not @features
225
+ fluidfeatures_retrieve_user_features
226
+ end
227
+ if @features.has_key? feature_name
228
+ if @features[feature_name].is_a? FalseClass or @features[feature_name].is_a? TrueClass
229
+ enabled = @features[feature_name]
230
+ elsif @features[feature_name].is_a? Hash
231
+ if @features[feature_name].has_key? version
232
+ enabled = @features[feature_name][version]
233
+ end
234
+ end
235
+ end
236
+ if enabled === nil
237
+ enabled = defaults[:enabled] || global_defaults[:enabled]
238
+
239
+ # Tell FluidFeatures about this amazing new feature...
240
+ options = Hash.new(defaults)
241
+ options[:enabled] = enabled
242
+ options[:version] = version
243
+ ::Rails.logger.error "fluidfeature: #{feature_name.to_s} #{version}"
244
+ FluidFeatures::Rails.unknown_feature_hit(feature_name, version, options)
245
+ end
246
+ enabled
247
+ end
248
+
249
+ #
250
+ # Initialize the FluidFeatures state for this request.
251
+ #
252
+ def fluidfeatures_request_init
253
+ @ff_request_start_time = Time.now
254
+ @features = nil
255
+ end
256
+
257
+ #
258
+ # Returns the features enabled for this request's user.
259
+ #
260
+ def fluidfeatures_retrieve_user_features
261
+ @features = FluidFeatures::Rails.get_user_features(@ff_user_id)
262
+ end
263
+
264
+ #
265
+ # After the rails request is complete we will log which features we
266
+ # encountered, including the default settings (eg. enabled) for each
267
+ # feature.
268
+ # This helps the FluidFeatures database prepopulate the feature set
269
+ # without requiring the developer to do it manually.
270
+ #
271
+ def fluidfeatures_store_features_hit
272
+ if @features
273
+ request_duration = Time.now - @ff_request_start_time
274
+ FluidFeatures::Rails.log_features_hit(@ff_user_id, @features_hit, request_duration)
275
+ end
276
+ end
277
+
278
+ def fluidfeatures_defaults
279
+ # By default unknown features are disabled.
280
+ {
281
+ :enabled => false,
282
+ :version => 1
283
+ }
284
+ end
285
+
286
+ end
287
+ end
288
+
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluidfeatures-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Phil Whelan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3.0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: persistent_http
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 1.0.3
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 1.0.3
46
+ description: Ruby on Rails client for the FluidFeatures service.
47
+ email:
48
+ - phil@fluidfeatures.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - Gemfile
55
+ - README.md
56
+ - Rakefile
57
+ - fluidfeatures-rails.gemspec
58
+ - lib/fluidfeatures/rails.rb
59
+ - lib/fluidfeatures/rails/version.rb
60
+ homepage: https://github.com/BigFastSite/fluidfeatures-rails
61
+ licenses: []
62
+ post_install_message:
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ! '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubyforge_project: fluidfeatures-rails
80
+ rubygems_version: 1.8.24
81
+ signing_key:
82
+ specification_version: 3
83
+ summary: Ruby on Rails client for the FluidFeatures service.
84
+ test_files: []