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 +21 -0
- data/Gemfile +4 -0
- data/README.md +4 -0
- data/Rakefile +12 -0
- data/fluidfeatures-rails.gemspec +19 -0
- data/lib/fluidfeatures/rails/version.rb +5 -0
- data/lib/fluidfeatures/rails.rb +288 -0
- metadata +84 -0
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
data/README.md
ADDED
data/Rakefile
ADDED
@@ -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,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: []
|