fluidfeatures-rails 0.2.3 → 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/Rakefile +1 -0
- data/fluidfeatures-rails.gemspec +2 -2
- data/lib/fluidfeatures/rails/version.rb +1 -1
- data/lib/fluidfeatures/rails.rb +18 -188
- data/lib/pre_ruby192/uri.rb +120 -0
- data/test/testapp/Gemfile +4 -0
- metadata +9 -8
data/Rakefile
CHANGED
data/fluidfeatures-rails.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.version = FluidFeatures::Rails::VERSION
|
8
8
|
s.authors = ["Phil Whelan"]
|
9
9
|
s.email = ["phil@fluidfeatures.com"]
|
10
|
-
s.homepage = "https://github.com/
|
10
|
+
s.homepage = "https://github.com/FluidFeatures/fluidfeatures-rails"
|
11
11
|
s.summary = %q{Ruby on Rails client for the FluidFeatures service.}
|
12
12
|
s.description = %q{Ruby on Rails client for the FluidFeatures service.}
|
13
13
|
s.rubyforge_project = s.name
|
@@ -15,5 +15,5 @@ Gem::Specification.new do |s|
|
|
15
15
|
#s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
16
|
s.require_paths = ["lib"]
|
17
17
|
#s.add_dependency "rails", "~>3.0"
|
18
|
-
s.add_dependency "
|
18
|
+
s.add_dependency "fluidfeatures" unless ENV["FF_DEV"]
|
19
19
|
end
|
data/lib/fluidfeatures/rails.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
|
2
|
+
require "fluidfeatures/client"
|
3
|
+
|
2
4
|
module FluidFeatures
|
3
5
|
module Rails
|
4
6
|
|
5
7
|
class << self
|
6
8
|
attr_accessor :enabled
|
9
|
+
attr_accessor :client
|
7
10
|
end
|
8
11
|
|
9
12
|
#
|
@@ -34,201 +37,28 @@ module FluidFeatures
|
|
34
37
|
|
35
38
|
::Rails::Application.initializer "fluidfeatures.initializer" do
|
36
39
|
ActiveSupport.on_load(:action_controller) do
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
40
|
+
api_baseuri = ENV["FLUIDFEATURES_BASEURI"]
|
41
|
+
api_appid = ENV["FLUIDFEATURES_APPID"]
|
42
|
+
api_secret = ENV["FLUIDFEATURES_SECRET"]
|
43
|
+
|
44
|
+
::FluidFeatures::Rails.client = ::FluidFeatures::Client.new(
|
45
|
+
api_baseuri,
|
46
|
+
api_appid,
|
47
|
+
api_secret,
|
48
|
+
# options
|
49
|
+
{
|
50
|
+
:logger => nil,#::Rails.logger
|
51
|
+
}
|
48
52
|
)
|
49
|
-
@@unknown_features = {}
|
50
|
-
@@last_fetch_duration = nil
|
51
53
|
|
52
54
|
ActionController::Base.append_before_filter :fluidfeatures_request_before
|
53
55
|
ActionController::Base.append_after_filter :fluidfeatures_request_after
|
54
|
-
|
55
56
|
end
|
56
57
|
end
|
57
58
|
|
58
59
|
@enabled = true
|
59
60
|
end
|
60
61
|
|
61
|
-
#
|
62
|
-
# This can be used to control how much of your user-base sees a
|
63
|
-
# particular feature. It may be easier to use the dashboard provided
|
64
|
-
# at https://www.fluidfeatures.com/dashboard to manage this, or to
|
65
|
-
# set timers to automate the gradual rollout of your new features.
|
66
|
-
#
|
67
|
-
def self.feature_set_enabled_percent(feature_name, enabled_percent)
|
68
|
-
begin
|
69
|
-
uri = URI(@@baseuri + "/app/" + @@app_id.to_s + "/features/" + feature_name.to_s)
|
70
|
-
request = Net::HTTP::Put.new uri.path
|
71
|
-
request["Content-Type"] = "application/json"
|
72
|
-
request["Accept"] = "application/json"
|
73
|
-
request['AUTHORIZATION'] = @@secret
|
74
|
-
payload = {
|
75
|
-
:enabled => {
|
76
|
-
:percent => enabled_percent
|
77
|
-
}
|
78
|
-
}
|
79
|
-
request.body = JSON.dump(payload)
|
80
|
-
response = @@http.request uri, request
|
81
|
-
if response.is_a?(Net::HTTPSuccess)
|
82
|
-
::Rails.logger.error "[" + response.code.to_s + "] Failed to set feature enabled percent : " + uri.to_s + " : " + response.body.to_s
|
83
|
-
end
|
84
|
-
rescue
|
85
|
-
::Rails.logger.error "Request to set feature enabled percent failed : " + uri.to_s
|
86
|
-
raise
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
#
|
91
|
-
# Returns all the features that FluidFeatures knows about for
|
92
|
-
# your application. The enabled percentage (how much of your user-base)
|
93
|
-
# sees each feature is also provided.
|
94
|
-
#
|
95
|
-
def self.get_feature_set
|
96
|
-
features = nil
|
97
|
-
begin
|
98
|
-
uri = URI(@@baseuri + "/app/" + @@app_id.to_s + "/features")
|
99
|
-
request = Net::HTTP::Get.new uri.path
|
100
|
-
request["Accept"] = "application/json"
|
101
|
-
request['AUTHORIZATION'] = @@secret
|
102
|
-
response = @@http.request request
|
103
|
-
if response.is_a?(Net::HTTPSuccess)
|
104
|
-
features = JSON.parse(response.body)
|
105
|
-
end
|
106
|
-
rescue
|
107
|
-
::Rails.logger.error "Request failed when getting feature set from " + uri.to_s
|
108
|
-
raise
|
109
|
-
end
|
110
|
-
if not features
|
111
|
-
::Rails.logger.error "Empty feature set returned from " + uri.to_s
|
112
|
-
end
|
113
|
-
features
|
114
|
-
end
|
115
|
-
|
116
|
-
#
|
117
|
-
# Returns all the features enabled for a specific user.
|
118
|
-
# This will depend on the user_id and how many users each
|
119
|
-
# feature is enabled for.
|
120
|
-
#
|
121
|
-
def self.get_user_features(user)
|
122
|
-
|
123
|
-
if not user
|
124
|
-
raise "user object should be a Hash"
|
125
|
-
end
|
126
|
-
if not user[:id]
|
127
|
-
raise "user does not contain :id field"
|
128
|
-
end
|
129
|
-
|
130
|
-
# extract just attribute ids into simple hash
|
131
|
-
attribute_ids = {
|
132
|
-
:anonymous => !!user[:anonymous]
|
133
|
-
}
|
134
|
-
[:unique, :cohorts].each do |attr_type|
|
135
|
-
if user.has_key? attr_type
|
136
|
-
user[attr_type].each do |attr_key, attr|
|
137
|
-
if attr.is_a? Hash
|
138
|
-
if attr.has_key? :id
|
139
|
-
attribute_ids[attr_key] = attr[:id]
|
140
|
-
end
|
141
|
-
else
|
142
|
-
attribute_ids[attr_key] = attr
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
# normalize attributes ids as strings
|
149
|
-
attribute_ids.each do |attr_key, attr_id|
|
150
|
-
if attr_id.is_a? FalseClass or attr_id.is_a? TrueClass
|
151
|
-
attribute_ids[attr_key] = attr_id.to_s.downcase
|
152
|
-
elsif not attr_id.is_a? String
|
153
|
-
attribute_ids[attr_key] = attr_id.to_s
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
features = {}
|
158
|
-
fetch_start_time = Time.now
|
159
|
-
begin
|
160
|
-
uri = URI("#{@@baseuri}/app/#{@@app_id}/user/#{user[:id]}/features")
|
161
|
-
uri.query = URI.encode_www_form( attribute_ids )
|
162
|
-
url_path = uri.path
|
163
|
-
if uri.query
|
164
|
-
url_path += "?" + uri.query
|
165
|
-
end
|
166
|
-
request = Net::HTTP::Get.new url_path
|
167
|
-
request["Accept"] = "application/json"
|
168
|
-
request['AUTHORIZATION'] = @@secret
|
169
|
-
response = @@http.request request
|
170
|
-
if response.is_a?(Net::HTTPSuccess)
|
171
|
-
features = JSON.parse(response.body)
|
172
|
-
else
|
173
|
-
::Rails.logger.error "[#{response.code}] Failed to get user features : #{uri} : #{response.body}"
|
174
|
-
end
|
175
|
-
rescue
|
176
|
-
::Rails.logger.error "Request to get user features failed : #{uri}"
|
177
|
-
raise
|
178
|
-
end
|
179
|
-
@@last_fetch_duration = Time.now - fetch_start_time
|
180
|
-
features
|
181
|
-
end
|
182
|
-
|
183
|
-
#
|
184
|
-
# This is called when we encounter a feature_name that
|
185
|
-
# FluidFeatures has no record of for your application.
|
186
|
-
# This will be reported back to the FluidFeatures service so
|
187
|
-
# that it can populate your dashboard with this feature.
|
188
|
-
# The parameter "default_enabled" is a boolean that says whether
|
189
|
-
# this feature should be enabled to all users or no users.
|
190
|
-
# Usually, this is "true" for existing features that you are
|
191
|
-
# planning to phase out and "false" for new feature that you
|
192
|
-
# intend to phase in.
|
193
|
-
#
|
194
|
-
def self.unknown_feature_hit(feature_name, version_name, defaults)
|
195
|
-
if not @@unknown_features[feature_name]
|
196
|
-
@@unknown_features[feature_name] = { :versions => {} }
|
197
|
-
end
|
198
|
-
@@unknown_features[feature_name][:versions][version_name] = defaults
|
199
|
-
end
|
200
|
-
|
201
|
-
#
|
202
|
-
# This reports back to FluidFeatures which features we
|
203
|
-
# encountered during this request, the request duration,
|
204
|
-
# and statistics on time spent talking to the FluidFeatures
|
205
|
-
# service. Any new features encountered will also be reported
|
206
|
-
# back with the default_enabled status (see unknown_feature_hit)
|
207
|
-
# so that FluidFeatures can auto-populate the dashboard.
|
208
|
-
#
|
209
|
-
def self.log_request(user_id, payload)
|
210
|
-
begin
|
211
|
-
(payload[:stats] ||= {})[:ff_latency] = @@last_fetch_duration
|
212
|
-
if @@unknown_features.size
|
213
|
-
(payload[:features] ||= {})[:unknown] = @@unknown_features
|
214
|
-
@@unknown_features = {}
|
215
|
-
end
|
216
|
-
uri = URI(@@baseuri + "/app/#{@@app_id}/user/#{user_id}/features/hit")
|
217
|
-
request = Net::HTTP::Post.new uri.path
|
218
|
-
request["Content-Type"] = "application/json"
|
219
|
-
request["Accept"] = "application/json"
|
220
|
-
request['AUTHORIZATION'] = @@secret
|
221
|
-
request.body = JSON.dump(payload)
|
222
|
-
response = @@http.request request
|
223
|
-
unless response.is_a?(Net::HTTPSuccess)
|
224
|
-
::Rails.logger.error "[" + response.code.to_s + "] Failed to log features hit : " + uri.to_s + " : " + response.body.to_s
|
225
|
-
end
|
226
|
-
rescue Exception => e
|
227
|
-
::Rails.logger.error "Request to log user features hit failed : " + uri.to_s
|
228
|
-
raise
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
62
|
end
|
233
63
|
end
|
234
64
|
|
@@ -321,7 +151,7 @@ module ActionController
|
|
321
151
|
options.remove(:version)
|
322
152
|
end
|
323
153
|
::Rails.logger.debug "fluidfeature: seeing feature '#{feature_name.to_s}' (version '#{version_name.to_s}') for the first time."
|
324
|
-
FluidFeatures::Rails.unknown_feature_hit(feature_name, version_name, options)
|
154
|
+
::FluidFeatures::Rails.client.unknown_feature_hit(feature_name, version_name, options)
|
325
155
|
end
|
326
156
|
if enabled
|
327
157
|
@ff_features_hit[feature_name] ||= {}
|
@@ -352,7 +182,7 @@ module ActionController
|
|
352
182
|
#
|
353
183
|
def fluidfeatures_retrieve_user_features
|
354
184
|
user = fluidfeatures_user
|
355
|
-
@ff_features = FluidFeatures::Rails.get_user_features(user)
|
185
|
+
@ff_features = ::FluidFeatures::Rails.client.get_user_features(user)
|
356
186
|
end
|
357
187
|
|
358
188
|
#
|
@@ -385,7 +215,7 @@ module ActionController
|
|
385
215
|
(payload[:user] ||= {})[key] = fluidfeatures_user[key]
|
386
216
|
end
|
387
217
|
end
|
388
|
-
FluidFeatures::Rails.log_request(fluidfeatures_user[:id], payload)
|
218
|
+
::FluidFeatures::Rails.client.log_request(fluidfeatures_user[:id], payload)
|
389
219
|
end
|
390
220
|
|
391
221
|
def fluidfeatures_defaults
|
@@ -0,0 +1,120 @@
|
|
1
|
+
|
2
|
+
# https://bitbucket.org/ged/ruby-axis/raw/ef212387adcbd567a39fa0d51eb6dc6051c416bf/lib/axis/monkeypatches.rb
|
3
|
+
|
4
|
+
# Backport of Ruby 1.9.2 URI methods to 1.8.7.
|
5
|
+
module URIFormEncoding
|
6
|
+
|
7
|
+
TBLENCWWWCOMP_ = {} # :nodoc:
|
8
|
+
TBLDECWWWCOMP_ = {} # :nodoc:
|
9
|
+
|
10
|
+
|
11
|
+
# Encode given +str+ to URL-encoded form data.
|
12
|
+
#
|
13
|
+
# This doesn't convert *, -, ., 0-9, A-Z, _, a-z,
|
14
|
+
# does convert SP to +, and convert others to %XX.
|
15
|
+
#
|
16
|
+
# This refers http://www.w3.org/TR/html5/forms.html#url-encoded-form-data
|
17
|
+
#
|
18
|
+
# See URI.decode_www_form_component, URI.encode_www_form
|
19
|
+
def encode_www_form_component( str )
|
20
|
+
if TBLENCWWWCOMP_.empty?
|
21
|
+
256.times do |i|
|
22
|
+
TBLENCWWWCOMP_[i.chr] = '%%%02X' % i
|
23
|
+
end
|
24
|
+
TBLENCWWWCOMP_[' '] = '+'
|
25
|
+
TBLENCWWWCOMP_.freeze
|
26
|
+
end
|
27
|
+
return str.to_s.gsub(/[^*\-.0-9A-Z_a-z]/, TBLENCWWWCOMP_)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Decode given +str+ of URL-encoded form data.
|
31
|
+
#
|
32
|
+
# This decodes + to SP.
|
33
|
+
#
|
34
|
+
# See URI.encode_www_form_component, URI.decode_www_form
|
35
|
+
def decode_www_form_component( str )
|
36
|
+
if TBLDECWWWCOMP_.empty?
|
37
|
+
256.times do |i|
|
38
|
+
h, l = i>>4, i&15
|
39
|
+
TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
|
40
|
+
TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
|
41
|
+
TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
|
42
|
+
TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
|
43
|
+
end
|
44
|
+
TBLDECWWWCOMP_['+'] = ' '
|
45
|
+
TBLDECWWWCOMP_.freeze
|
46
|
+
end
|
47
|
+
raise ArgumentError, "invalid %-encoding (#{str})" unless /\A(?:%\h\h|[^%]+)*\z/ =~ str
|
48
|
+
return str.gsub( /\+|%\h\h/, TBLDECWWWCOMP_ )
|
49
|
+
end
|
50
|
+
|
51
|
+
# Generate URL-encoded form data from given +enum+.
|
52
|
+
#
|
53
|
+
# This generates application/x-www-form-urlencoded data defined in HTML5
|
54
|
+
# from given an Enumerable object.
|
55
|
+
#
|
56
|
+
# This internally uses URI.encode_www_form_component(str).
|
57
|
+
#
|
58
|
+
# This doesn't convert encodings of give items, so convert them before call
|
59
|
+
# this method if you want to send data as other than original encoding or
|
60
|
+
# mixed encoding data. (strings which is encoded in HTML5 ASCII incompatible
|
61
|
+
# encoding is converted to UTF-8)
|
62
|
+
#
|
63
|
+
# This doesn't treat files. When you send a file, use multipart/form-data.
|
64
|
+
#
|
65
|
+
# This refers http://www.w3.org/TR/html5/forms.html#url-encoded-form-data
|
66
|
+
#
|
67
|
+
# See URI.encode_www_form_component, URI.decode_www_form
|
68
|
+
def encode_www_form( enum )
|
69
|
+
str = nil
|
70
|
+
enum.each do |k,v|
|
71
|
+
if str
|
72
|
+
str << '&'
|
73
|
+
else
|
74
|
+
str = nil.to_s
|
75
|
+
end
|
76
|
+
str << encode_www_form_component(k)
|
77
|
+
str << '='
|
78
|
+
str << encode_www_form_component(v)
|
79
|
+
end
|
80
|
+
str
|
81
|
+
end
|
82
|
+
|
83
|
+
WFKV_ = '(?:%\h\h|[^%#=;&])' # :nodoc:
|
84
|
+
|
85
|
+
# Decode URL-encoded form data from given +str+.
|
86
|
+
#
|
87
|
+
# This decodes application/x-www-form-urlencoded data
|
88
|
+
# and returns array of key-value array.
|
89
|
+
# This internally uses URI.decode_www_form_component.
|
90
|
+
#
|
91
|
+
# This refers http://www.w3.org/TR/html5/forms.html#url-encoded-form-data
|
92
|
+
#
|
93
|
+
# ary = URI.decode_www_form("a=1&a=2&b=3")
|
94
|
+
# p ary #=> [['a', '1'], ['a', '2'], ['b', '3']]
|
95
|
+
# p ary.assoc('a').last #=> '1'
|
96
|
+
# p ary.assoc('b').last #=> '3'
|
97
|
+
# p ary.rassoc('a').last #=> '2'
|
98
|
+
# p Hash[ary] # => {"a"=>"2", "b"=>"3"}
|
99
|
+
#
|
100
|
+
# See URI.decode_www_form_component, URI.encode_www_form
|
101
|
+
def decode_www_form( str )
|
102
|
+
return [] if str.empty?
|
103
|
+
unless /\A#{WFKV_}*=#{WFKV_}*(?:[;&]#{WFKV_}*=#{WFKV_}*)*\z/o =~ str
|
104
|
+
raise ArgumentError, "invalid data of application/x-www-form-urlencoded (#{str})"
|
105
|
+
end
|
106
|
+
ary = []
|
107
|
+
$&.scan(/([^=;&]+)=([^;&]*)/) do
|
108
|
+
ary << [decode_www_form_component($1, enc), decode_www_form_component($2, enc)]
|
109
|
+
end
|
110
|
+
ary
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
unless URI.methods.include?( :encode_www_form )
|
117
|
+
URI.extend( URIFormEncoding )
|
118
|
+
end
|
119
|
+
|
120
|
+
|
data/test/testapp/Gemfile
CHANGED
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
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,24 +9,24 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-10-
|
12
|
+
date: 2012-10-31 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
15
|
+
name: fluidfeatures
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
|
-
- -
|
19
|
+
- - ! '>='
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version:
|
21
|
+
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
26
26
|
requirements:
|
27
|
-
- -
|
27
|
+
- - ! '>='
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version:
|
29
|
+
version: '0'
|
30
30
|
description: Ruby on Rails client for the FluidFeatures service.
|
31
31
|
email:
|
32
32
|
- phil@fluidfeatures.com
|
@@ -44,6 +44,7 @@ files:
|
|
44
44
|
- lib/fluidfeatures/rails/app/controllers/fluidfeatures_controller.rb
|
45
45
|
- lib/fluidfeatures/rails/config/routes.rb
|
46
46
|
- lib/fluidfeatures/rails/version.rb
|
47
|
+
- lib/pre_ruby192/uri.rb
|
47
48
|
- test/testapp/.gitignore
|
48
49
|
- test/testapp/Gemfile
|
49
50
|
- test/testapp/Rakefile
|
@@ -59,7 +60,7 @@ files:
|
|
59
60
|
- test/testapp/script/rails
|
60
61
|
- test/testapp/spec/controllers/home_controller_spec.rb
|
61
62
|
- test/testapp/spec/spec_helper.rb
|
62
|
-
homepage: https://github.com/
|
63
|
+
homepage: https://github.com/FluidFeatures/fluidfeatures-rails
|
63
64
|
licenses: []
|
64
65
|
post_install_message:
|
65
66
|
rdoc_options: []
|