fluidfeatures-rails 0.1.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/.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: []