fluidfeatures 0.3.0 → 0.3.1
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/lib/fluidfeatures/app/feature.rb +44 -0
- data/lib/fluidfeatures/app/user.rb +180 -0
- data/lib/fluidfeatures/app.rb +53 -0
- data/lib/fluidfeatures/client.rb +47 -154
- data/lib/fluidfeatures/const.rb +4 -0
- data/lib/fluidfeatures/version.rb +1 -1
- data/lib/fluidfeatures.rb +15 -0
- metadata +6 -1
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
require "fluidfeatures/const"
|
3
|
+
|
4
|
+
module FluidFeatures
|
5
|
+
class AppFeatureVersion
|
6
|
+
|
7
|
+
attr_accessor :app, :feature_name, :version_name
|
8
|
+
|
9
|
+
DEFAULT_VERSION_NAME = "default"
|
10
|
+
|
11
|
+
def initialize(app, feature_name, version_name=DEFAULT_VERSION_NAME)
|
12
|
+
|
13
|
+
raise "app invalid : #{app}" unless app.is_a? FluidFeatures::App
|
14
|
+
raise "feature_name invalid : #{feature_name}" unless feature_name.is_a? String
|
15
|
+
version_name ||= ::FluidFeatures::DEFAULT_VERSION_NAME
|
16
|
+
raise "version_name invalid : #{version_name}" unless version_name.is_a? String
|
17
|
+
|
18
|
+
@app = client
|
19
|
+
@feature_name = feature_name
|
20
|
+
@version_name = version_name
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# This can be used to control how much of your user-base sees a
|
26
|
+
# particular feature. It may be easier to use the dashboard provided
|
27
|
+
# at https://www.fluidfeatures.com/dashboard to manage this.
|
28
|
+
#
|
29
|
+
def set_enabled_percent(percent)
|
30
|
+
|
31
|
+
unless percent.is_a? Numeric and percent >= 0.0 and percent <= 100.0
|
32
|
+
raise "percent invalid : #{percent}"
|
33
|
+
end
|
34
|
+
|
35
|
+
app.put("/feature/#{feature_name}/#{version_name}/enabled/percent", {
|
36
|
+
:enabled => {
|
37
|
+
:percent => enabled_percent
|
38
|
+
}
|
39
|
+
})
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
@@ -0,0 +1,180 @@
|
|
1
|
+
|
2
|
+
require "fluidfeatures/const"
|
3
|
+
|
4
|
+
module FluidFeatures
|
5
|
+
class AppUser
|
6
|
+
|
7
|
+
attr_accessor :app, :unique_id, :display_name, :anonymous, :unique_attrs, :cohort_attrs
|
8
|
+
|
9
|
+
def initialize(app, user_id, display_name, is_anonymous, unique_attrs, cohort_attrs)
|
10
|
+
|
11
|
+
raise "app invalid : #{app}" unless app.is_a? FluidFeatures::App
|
12
|
+
|
13
|
+
@app = app
|
14
|
+
@unique_id = user_id
|
15
|
+
@display_name = display_name
|
16
|
+
@anonymous = is_anonymous
|
17
|
+
@unique_attrs = unique_attrs
|
18
|
+
@cohort_attrs = cohort_attrs
|
19
|
+
|
20
|
+
@features = nil
|
21
|
+
@features_hit = {}
|
22
|
+
@goals_hit = {}
|
23
|
+
@unknown_features = {}
|
24
|
+
|
25
|
+
if not unique_id or is_anonymous
|
26
|
+
|
27
|
+
# We're an anonymous user
|
28
|
+
@anonymous = true
|
29
|
+
|
30
|
+
# if we were not given a user[:id] for this anonymous user, then get
|
31
|
+
# it from an existing cookie or create a new one.
|
32
|
+
unless unique_id
|
33
|
+
# Create new unique id (for cookie). Use rand + micro-seconds of current time
|
34
|
+
@unique_id = "anon-" + Random.rand(9999999999).to_s + "-" + ((Time.now.to_f * 1000000).to_i % 1000000).to_s
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# Returns all the features enabled for a specific user.
|
42
|
+
# This will depend on the user's unique_id and how many
|
43
|
+
# users each feature is enabled for.
|
44
|
+
#
|
45
|
+
def load_features
|
46
|
+
|
47
|
+
# extract just attribute ids into simple hash
|
48
|
+
attribute_ids = {
|
49
|
+
:anonymous => anonymous
|
50
|
+
}
|
51
|
+
[unique_attrs, cohort_attrs].each do |attrs|
|
52
|
+
if attrs
|
53
|
+
attrs.each do |attr_key, attr|
|
54
|
+
if attr.is_a? Hash
|
55
|
+
if attr.has_key? :id
|
56
|
+
attribute_ids[attr_key] = attr[:id]
|
57
|
+
end
|
58
|
+
else
|
59
|
+
attribute_ids[attr_key] = attr
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# normalize attributes ids as strings
|
66
|
+
attribute_ids.each do |attr_key, attr_id|
|
67
|
+
if attr_id.is_a? FalseClass or attr_id.is_a? TrueClass
|
68
|
+
attribute_ids[attr_key] = attr_id.to_s.downcase
|
69
|
+
elsif not attr_id.is_a? String
|
70
|
+
attribute_ids[attr_key] = attr_id.to_s
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
app.get("/user/#{unique_id}/features", attribute_ids) || {}
|
75
|
+
end
|
76
|
+
|
77
|
+
def features
|
78
|
+
@features ||= load_features
|
79
|
+
end
|
80
|
+
|
81
|
+
def feature_enabled?(feature_name, version_name, default_enabled)
|
82
|
+
|
83
|
+
raise "feature_name invalid : #{feature_name}" unless feature_name.is_a? String
|
84
|
+
version_name ||= ::FluidFeatures::DEFAULT_VERSION_NAME
|
85
|
+
|
86
|
+
if features.has_key? feature_name
|
87
|
+
feature = features[feature_name]
|
88
|
+
if feature.is_a? Hash
|
89
|
+
if feature.has_key? version_name
|
90
|
+
enabled = feature[version_name]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
if enabled === nil
|
96
|
+
enabled = default_enabled
|
97
|
+
|
98
|
+
# Tell FluidFeatures about this amazing new feature...
|
99
|
+
unknown_feature_hit(feature_name, version_name, default_enabled)
|
100
|
+
end
|
101
|
+
|
102
|
+
if enabled
|
103
|
+
@features_hit[feature_name] ||= {}
|
104
|
+
@features_hit[feature_name][version_name.to_s] = {}
|
105
|
+
end
|
106
|
+
|
107
|
+
enabled
|
108
|
+
end
|
109
|
+
|
110
|
+
#
|
111
|
+
# This is called when we encounter a feature_name that
|
112
|
+
# FluidFeatures has no record of for your application.
|
113
|
+
# This will be reported back to the FluidFeatures service so
|
114
|
+
# that it can populate your dashboard with this feature.
|
115
|
+
# The parameter "default_enabled" is a boolean that says whether
|
116
|
+
# this feature should be enabled to all users or no users.
|
117
|
+
# Usually, this is "true" for existing features that you are
|
118
|
+
# planning to phase out and "false" for new feature that you
|
119
|
+
# intend to phase in.
|
120
|
+
#
|
121
|
+
def unknown_feature_hit(feature_name, version_name, default_enabled)
|
122
|
+
if not @unknown_features[feature_name]
|
123
|
+
@unknown_features[feature_name] = { :versions => {} }
|
124
|
+
end
|
125
|
+
@unknown_features[feature_name][:versions][version_name] = default_enabled
|
126
|
+
end
|
127
|
+
|
128
|
+
def goal_hit(goal_name, goal_version_name)
|
129
|
+
sleep 10
|
130
|
+
raise "goal_name invalid : #{goal_name}" unless goal_name.is_a? String
|
131
|
+
goal_version_name ||= ::FluidFeatures::DEFAULT_VERSION_NAME
|
132
|
+
raise "goal_version_name invalid : #{goal_version_name}" unless goal_version_name.is_a? String
|
133
|
+
@goals_hit[goal_name.to_s] ||= {}
|
134
|
+
@goals_hit[goal_name.to_s][goal_version_name.to_s] = {}
|
135
|
+
end
|
136
|
+
|
137
|
+
#
|
138
|
+
# This reports back to FluidFeatures which features we
|
139
|
+
# encountered during this request, the request duration,
|
140
|
+
# and statistics on time spent talking to the FluidFeatures
|
141
|
+
# service. Any new features encountered will also be reported
|
142
|
+
# back with the default_enabled status (see unknown_feature_hit)
|
143
|
+
# so that FluidFeatures can auto-populate the dashboard.
|
144
|
+
#
|
145
|
+
def end_transaction(url, stats)
|
146
|
+
|
147
|
+
payload = {
|
148
|
+
:url => url,
|
149
|
+
:user => {
|
150
|
+
:id => unique_id
|
151
|
+
},
|
152
|
+
:hits => {
|
153
|
+
:feature => @features_hit,
|
154
|
+
:goal => @goals_hit
|
155
|
+
}
|
156
|
+
}
|
157
|
+
|
158
|
+
if stats
|
159
|
+
raise "stats invalid : #{stats}" unless stats.is_a? Hash
|
160
|
+
payload[:stats] = stats
|
161
|
+
end
|
162
|
+
|
163
|
+
payload_user = payload[:user] ||= {}
|
164
|
+
payload_user[:name] = display_name if display_name
|
165
|
+
payload_user[:anonymous] = anonymous if anonymous
|
166
|
+
payload_user[:unique] = unique_attrs if unique_attrs
|
167
|
+
payload_user[:cohorts] = cohort_attrs if cohort_attrs
|
168
|
+
|
169
|
+
(payload[:stats] ||= {})[:ff_latency] = app.client.last_fetch_duration
|
170
|
+
if @unknown_features.size
|
171
|
+
(payload[:features] ||= {})[:unknown] = @unknown_features
|
172
|
+
@unknown_features = {}
|
173
|
+
end
|
174
|
+
|
175
|
+
app.post("/user/#{unique_id}/features/hit", payload)
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
require "fluidfeatures/app/user"
|
3
|
+
require "fluidfeatures/app/feature"
|
4
|
+
|
5
|
+
module FluidFeatures
|
6
|
+
class App
|
7
|
+
|
8
|
+
attr_accessor :client, :app_id, :secret, :logger
|
9
|
+
|
10
|
+
def initialize(client, app_id, secret, logger)
|
11
|
+
|
12
|
+
raise "client invalid : #{client}" unless client.is_a? FluidFeatures::Client
|
13
|
+
raise "app_id invalid : #{app_id}" unless app_id.is_a? String
|
14
|
+
raise "secret invalid : #{secret}" unless secret.is_a? String
|
15
|
+
|
16
|
+
@client = client
|
17
|
+
@app_id = app_id
|
18
|
+
@secret = secret
|
19
|
+
@logger = logger
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
def get(path, params=nil)
|
24
|
+
client.get("/app/#{app_id}#{path}", secret, params)
|
25
|
+
end
|
26
|
+
|
27
|
+
def put(path, payload)
|
28
|
+
client.put("/app/#{app_id}#{path}", secret, payload)
|
29
|
+
end
|
30
|
+
|
31
|
+
def post(path, payload)
|
32
|
+
client.post("/app/#{app_id}#{path}", secret, payload)
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# Returns all the features that FluidFeatures knows about for
|
37
|
+
# your application. The enabled percentage (how much of your user-base)
|
38
|
+
# sees each feature is also provided.
|
39
|
+
#
|
40
|
+
def features
|
41
|
+
get("/features")
|
42
|
+
end
|
43
|
+
|
44
|
+
def user(user_id, display_name, is_anonymous, unique_attrs, cohort_attrs)
|
45
|
+
::FluidFeatures::AppUser.new(self, user_id, display_name, is_anonymous, unique_attrs, cohort_attrs)
|
46
|
+
end
|
47
|
+
|
48
|
+
def feature_version(feature_name, version_name)
|
49
|
+
::FluidFeatures::AppFeatureVersion.new(self, feature_name, version_name)
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
data/lib/fluidfeatures/client.rb
CHANGED
@@ -1,204 +1,97 @@
|
|
1
1
|
|
2
|
-
require
|
2
|
+
require 'net/http'
|
3
|
+
require 'persistent_http'
|
4
|
+
require "pre_ruby192/uri" if RUBY_VERSION < "1.9.2"
|
3
5
|
|
4
|
-
|
5
|
-
require "pre_ruby192/uri"
|
6
|
-
end
|
6
|
+
require "fluidfeatures/app"
|
7
7
|
|
8
8
|
module FluidFeatures
|
9
9
|
class Client
|
10
10
|
|
11
|
-
attr_accessor :logger
|
12
|
-
|
13
|
-
def initialize(base_uri, app_id, secret, options={})
|
11
|
+
attr_accessor :base_uri, :logger, :last_fetch_duration
|
14
12
|
|
15
|
-
|
13
|
+
def initialize(base_uri, logger)
|
16
14
|
|
17
|
-
@
|
18
|
-
@
|
19
|
-
@secret = secret
|
15
|
+
@logger = logger
|
16
|
+
@base_uri = base_uri
|
20
17
|
|
21
|
-
@http = PersistentHTTP.new(
|
18
|
+
@http = ::PersistentHTTP.new(
|
22
19
|
:name => 'fluidfeatures',
|
23
|
-
:logger =>
|
20
|
+
:logger => logger,
|
24
21
|
:pool_size => 10,
|
25
22
|
:warn_timeout => 0.25,
|
26
23
|
:force_retry => true,
|
27
|
-
:url =>
|
24
|
+
:url => base_uri
|
28
25
|
)
|
29
26
|
|
30
|
-
@unknown_features = {}
|
31
27
|
@last_fetch_duration = nil
|
32
28
|
|
33
29
|
end
|
34
30
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
request = Net::HTTP::Put.new uri.path
|
45
|
-
request["Content-Type"] = "application/json"
|
46
|
-
request["Accept"] = "application/json"
|
47
|
-
request['AUTHORIZATION'] = @secret
|
48
|
-
payload = {
|
49
|
-
:enabled => {
|
50
|
-
:percent => enabled_percent
|
51
|
-
}
|
52
|
-
}
|
53
|
-
request.body = JSON.dump(payload)
|
54
|
-
response = @http.request uri, request
|
55
|
-
if response.is_a?(Net::HTTPSuccess)
|
56
|
-
logger.error{"[FF] [" + response.code.to_s + "] Failed to set feature enabled percent : " + uri.to_s + " : " + response.body.to_s}
|
31
|
+
def get(path, auth_token, url_params=nil)
|
32
|
+
payload = nil
|
33
|
+
|
34
|
+
uri = URI(@base_uri + path)
|
35
|
+
url_path = uri.path
|
36
|
+
if url_params
|
37
|
+
uri.query = URI.encode_www_form( url_params )
|
38
|
+
if uri.query
|
39
|
+
url_path += "?" + uri.query
|
57
40
|
end
|
58
|
-
rescue
|
59
|
-
logger.error{"[FF] Request to set feature enabled percent failed : " + uri.to_s}
|
60
|
-
raise
|
61
41
|
end
|
62
|
-
end
|
63
42
|
|
64
|
-
#
|
65
|
-
# Returns all the features that FluidFeatures knows about for
|
66
|
-
# your application. The enabled percentage (how much of your user-base)
|
67
|
-
# sees each feature is also provided.
|
68
|
-
#
|
69
|
-
def get_feature_set
|
70
|
-
features = nil
|
71
43
|
begin
|
72
|
-
|
73
|
-
request = Net::HTTP::Get.new uri.path
|
44
|
+
request = Net::HTTP::Get.new url_path
|
74
45
|
request["Accept"] = "application/json"
|
75
|
-
request['AUTHORIZATION'] =
|
46
|
+
request['AUTHORIZATION'] = auth_token
|
47
|
+
fetch_start_time = Time.now
|
76
48
|
response = @http.request request
|
77
49
|
if response.is_a?(Net::HTTPSuccess)
|
78
|
-
|
50
|
+
payload = JSON.parse(response.body)
|
51
|
+
@last_fetch_duration = Time.now - fetch_start_time
|
79
52
|
end
|
80
53
|
rescue
|
81
|
-
logger.error{"[FF] Request failed when getting
|
54
|
+
logger.error{"[FF] Request failed when getting #{path}"}
|
82
55
|
raise
|
83
56
|
end
|
84
|
-
if not
|
85
|
-
logger.error{"[FF] Empty
|
57
|
+
if not payload
|
58
|
+
logger.error{"[FF] Empty response from #{path}"}
|
86
59
|
end
|
87
|
-
|
60
|
+
payload
|
88
61
|
end
|
89
62
|
|
90
|
-
|
91
|
-
# Returns all the features enabled for a specific user.
|
92
|
-
# This will depend on the user_id and how many users each
|
93
|
-
# feature is enabled for.
|
94
|
-
#
|
95
|
-
def get_user_features(user)
|
96
|
-
logger.debug{"[FF] get_user_features #{user}"}
|
97
|
-
if not user
|
98
|
-
raise "user object should be a Hash"
|
99
|
-
end
|
100
|
-
if not user[:id]
|
101
|
-
raise "user does not contain :id field"
|
102
|
-
end
|
103
|
-
|
104
|
-
# extract just attribute ids into simple hash
|
105
|
-
attribute_ids = {
|
106
|
-
:anonymous => !!user[:anonymous]
|
107
|
-
}
|
108
|
-
[:unique, :cohorts].each do |attr_type|
|
109
|
-
if user.has_key? attr_type
|
110
|
-
user[attr_type].each do |attr_key, attr|
|
111
|
-
if attr.is_a? Hash
|
112
|
-
if attr.has_key? :id
|
113
|
-
attribute_ids[attr_key] = attr[:id]
|
114
|
-
end
|
115
|
-
else
|
116
|
-
attribute_ids[attr_key] = attr
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
# normalize attributes ids as strings
|
123
|
-
attribute_ids.each do |attr_key, attr_id|
|
124
|
-
if attr_id.is_a? FalseClass or attr_id.is_a? TrueClass
|
125
|
-
attribute_ids[attr_key] = attr_id.to_s.downcase
|
126
|
-
elsif not attr_id.is_a? String
|
127
|
-
attribute_ids[attr_key] = attr_id.to_s
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
features = {}
|
132
|
-
fetch_start_time = Time.now
|
63
|
+
def put(path, auth_token, payload)
|
133
64
|
begin
|
134
|
-
uri = URI(
|
135
|
-
|
136
|
-
|
137
|
-
if uri.query
|
138
|
-
url_path += "?" + uri.query
|
139
|
-
end
|
140
|
-
request = Net::HTTP::Get.new url_path
|
65
|
+
uri = URI(@base_uri + path)
|
66
|
+
request = Net::HTTP::Put.new uri.path
|
67
|
+
request["Content-Type"] = "application/json"
|
141
68
|
request["Accept"] = "application/json"
|
142
|
-
request['AUTHORIZATION'] =
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
logger.error{"[FF] [#{response.code}] Failed to get user features : #{uri} : #{response.body}"}
|
69
|
+
request['AUTHORIZATION'] = auth_token
|
70
|
+
request.body = JSON.dump(payload)
|
71
|
+
response = @http.request uri, request
|
72
|
+
unless response.is_a?(Net::HTTPSuccess)
|
73
|
+
logger.error{"[FF] Request unsuccessful when putting #{path}"}
|
148
74
|
end
|
149
|
-
rescue
|
150
|
-
logger.error{"[FF] Request
|
75
|
+
rescue Exception => err
|
76
|
+
logger.error{"[FF] Request failed putting #{path} : #{err.message}"}
|
151
77
|
raise
|
152
78
|
end
|
153
|
-
@last_fetch_duration = Time.now - fetch_start_time
|
154
|
-
features
|
155
|
-
end
|
156
|
-
|
157
|
-
#
|
158
|
-
# This is called when we encounter a feature_name that
|
159
|
-
# FluidFeatures has no record of for your application.
|
160
|
-
# This will be reported back to the FluidFeatures service so
|
161
|
-
# that it can populate your dashboard with this feature.
|
162
|
-
# The parameter "default_enabled" is a boolean that says whether
|
163
|
-
# this feature should be enabled to all users or no users.
|
164
|
-
# Usually, this is "true" for existing features that you are
|
165
|
-
# planning to phase out and "false" for new feature that you
|
166
|
-
# intend to phase in.
|
167
|
-
#
|
168
|
-
def unknown_feature_hit(feature_name, version_name, defaults)
|
169
|
-
if not @unknown_features[feature_name]
|
170
|
-
@unknown_features[feature_name] = { :versions => {} }
|
171
|
-
end
|
172
|
-
@unknown_features[feature_name][:versions][version_name] = defaults
|
173
79
|
end
|
174
|
-
|
175
|
-
|
176
|
-
# This reports back to FluidFeatures which features we
|
177
|
-
# encountered during this request, the request duration,
|
178
|
-
# and statistics on time spent talking to the FluidFeatures
|
179
|
-
# service. Any new features encountered will also be reported
|
180
|
-
# back with the default_enabled status (see unknown_feature_hit)
|
181
|
-
# so that FluidFeatures can auto-populate the dashboard.
|
182
|
-
#
|
183
|
-
def log_request(user_id, payload)
|
80
|
+
|
81
|
+
def post(path, auth_token, payload)
|
184
82
|
begin
|
185
|
-
(
|
186
|
-
if @unknown_features.size
|
187
|
-
(payload[:features] ||= {})[:unknown] = @unknown_features
|
188
|
-
@unknown_features = {}
|
189
|
-
end
|
190
|
-
uri = URI(@baseuri + "/app/#{@app_id}/user/#{user_id}/features/hit")
|
83
|
+
uri = URI(@base_uri + path)
|
191
84
|
request = Net::HTTP::Post.new uri.path
|
192
85
|
request["Content-Type"] = "application/json"
|
193
86
|
request["Accept"] = "application/json"
|
194
|
-
request['AUTHORIZATION'] =
|
87
|
+
request['AUTHORIZATION'] = auth_token
|
195
88
|
request.body = JSON.dump(payload)
|
196
89
|
response = @http.request request
|
197
90
|
unless response.is_a?(Net::HTTPSuccess)
|
198
|
-
logger.error{"[FF]
|
91
|
+
logger.error{"[FF] Request unsuccessful when posting #{path}"}
|
199
92
|
end
|
200
|
-
rescue Exception =>
|
201
|
-
logger.error{"[FF] Request
|
93
|
+
rescue Exception => err
|
94
|
+
logger.error{"[FF] Request failed posting #{path} : #{err.message}"}
|
202
95
|
raise
|
203
96
|
end
|
204
97
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
require "logger"
|
3
|
+
|
4
|
+
require "fluidfeatures/client"
|
5
|
+
require "fluidfeatures/app"
|
6
|
+
|
7
|
+
module FluidFeatures
|
8
|
+
|
9
|
+
def self.app(base_uri, app_id, secret, logger=nil)
|
10
|
+
logger ||= ::Logger.new(STDERR)
|
11
|
+
client = ::FluidFeatures::Client.new(base_uri, logger)
|
12
|
+
::FluidFeatures::App.new(client, app_id, secret, logger)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fluidfeatures
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -38,7 +38,12 @@ files:
|
|
38
38
|
- Gemfile
|
39
39
|
- README.md
|
40
40
|
- fluidfeatures.gemspec
|
41
|
+
- lib/fluidfeatures.rb
|
42
|
+
- lib/fluidfeatures/app.rb
|
43
|
+
- lib/fluidfeatures/app/feature.rb
|
44
|
+
- lib/fluidfeatures/app/user.rb
|
41
45
|
- lib/fluidfeatures/client.rb
|
46
|
+
- lib/fluidfeatures/const.rb
|
42
47
|
- lib/fluidfeatures/version.rb
|
43
48
|
- lib/pre_ruby192/uri.rb
|
44
49
|
homepage: https://github.com/FluidFeatures/fluidfeatures-ruby
|