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 CHANGED
@@ -7,6 +7,7 @@ task :default => :test
7
7
 
8
8
  task :test do
9
9
  Dir.chdir("test/testapp") do
10
+ system("bundle update")
10
11
  exec("bundle exec rspec spec")
11
12
  end
12
13
  end
@@ -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/BigFastSite/fluidfeatures-rails"
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 "persistent_http", "~>1.0.3"
18
+ s.add_dependency "fluidfeatures" unless ENV["FF_DEV"]
19
19
  end
@@ -1,5 +1,5 @@
1
1
  module FluidFeatures
2
2
  module Rails
3
- VERSION = '0.2.3'
3
+ VERSION = '0.3.0'
4
4
  end
5
5
  end
@@ -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
- @@baseuri = ENV["FLUIDFEATURES_BASEURI"]
39
- @@secret = ENV["FLUIDFEATURES_SECRET"]
40
- @@app_id = ENV["FLUIDFEATURES_APPID"]
41
- @@http = PersistentHTTP.new(
42
- :name => 'fluidfeatures',
43
- :logger => ::Rails.logger,
44
- :pool_size => 10,
45
- :warn_timeout => 0.25,
46
- :force_retry => true,
47
- :url => @@baseuri
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
@@ -8,3 +8,7 @@ gem "rspec-rails", "~> 2.0"
8
8
  gem "fakeweb"
9
9
  gem "json_pure"
10
10
  gem "json_spec"
11
+
12
+ # JRuby Rails requires this for active_support/message_encryptor.rb
13
+ gem 'jruby-openssl', :platforms => :jruby
14
+
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.2.3
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-20 00:00:00.000000000 Z
12
+ date: 2012-10-31 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: persistent_http
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: 1.0.3
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: 1.0.3
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/BigFastSite/fluidfeatures-rails
63
+ homepage: https://github.com/FluidFeatures/fluidfeatures-rails
63
64
  licenses: []
64
65
  post_install_message:
65
66
  rdoc_options: []