featureflow 0.6.0 → 0.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e44302ab7573a1fe36b0fac9deaac9958ab96d01b73a865e3c98d00bd71ebb3
4
- data.tar.gz: 8b641fa3bd07be005e7cf6f722a441da2544d2fa1141b40c35e1d3abb4df6db6
3
+ metadata.gz: d5b0b34f2239d222c1899b7b7af000ab6ee80dc31c481076c73a3220a8f6e76b
4
+ data.tar.gz: fc7e045b04d49220ed536cd885aa7ecdcd63f85d22feed2b41fab7ca6ea434bf
5
5
  SHA512:
6
- metadata.gz: dd22e33787e281e6a61ca62464f771c3e4d1778d6893f6689ff189c57dbdf3c433e7b49b6a881f73e113f5b5d21216c7b3a6dd67e313c8aeaf4e317ed4c858b9
7
- data.tar.gz: 679d1c6f0efbefca2497da6467109c22f93bdde4a6bc165dd5e6b0d03b35daaa02888c5aeec0296dec6f962fd0aeea5a257b701e26798b15f2d394fe324aa544
6
+ metadata.gz: 7c7834c0bd38d21c36bdbfe588491916b4d3f99c50bfce8ab33a654849fb3d58920918eada5616fc168373792c8373e86f5fbb239c210d1ce213bdd1c643002d
7
+ data.tar.gz: 362fae11135765cb677f26ee3619b57b499bc569d4af61119add4410704dfdcebae7e79d4652314f9d8d185f34dff8502383b0cc9ebf4b81f2d6003f3ea1fb8f
data/CHANGELOG.md CHANGED
@@ -1,4 +1,34 @@
1
1
  # Change log
2
+ ## [0.8.0] - 2026-08-13
3
+ ### Added
4
+ - `application` configuration option (falling back to the `FEATUREFLOW_APPLICATION`
5
+ environment variable): names this workload, sent as `X-Featureflow-Application` on
6
+ every request so the dashboard can attribute usage and flag evaluations per
7
+ application. Slug-validated client-side (case forgiven; an invalid value is dropped
8
+ with a warning and no header is sent).
9
+
10
+ ## [0.7.0] - 2026-08-11
11
+ ### Fixed
12
+ - Condition operators fail closed instead of raising into the host application: an
13
+ invalid `matches` regex, a non-string attribute against a string operator, or a
14
+ string/number type mismatch on a comparison operator is now a no-match, never an
15
+ exception.
16
+ - `before`/`after` compare parsed instants rather than strings, so rule values with
17
+ arbitrary UTC offsets compare correctly; date-only values are read as UTC midnight
18
+ on every host, not local midnight.
19
+ - Rollout bucketing hashed every user to the same bucket (`user['id']` vs
20
+ `user[:id]`); percentage splits now distribute correctly.
21
+ - `RailsClient#evaluate` and `UserBuilder#with_attributes` were broken; both work
22
+ again, and the Rails generator accepts current `sdk-srv-env-` keys.
23
+ ### Added
24
+ - `Featureflow::Client.new(api_key: '...')` accepts a configuration Hash; unknown
25
+ options raise rather than being silently dropped.
26
+ - Implicit `featureflow.date` / `featureflow.hourofday` attributes are injected at
27
+ evaluation time, matching the other server SDKs.
28
+ ### Changed
29
+ - Requires Ruby >= 3.1 (excon ~> 1.5).
30
+ - The packaged gem now contains only `lib/`, README, CHANGELOG and LICENSE.
31
+
2
32
  ## [0.6.0] - 2018-12-17
3
33
  ### Fixed
4
34
  - Use User over Context matching all other SDKs
data/README.md CHANGED
@@ -153,6 +153,22 @@ featureflow.evaluate('key-three', user).is? 'custom' # == true
153
153
 
154
154
  ```
155
155
 
156
+ #### Naming your application
157
+
158
+ Optionally tag this workload with an application name so the Featureflow dashboard can
159
+ attribute SDK usage and flag evaluations to it (Admin → SDKs, and the "Evaluated by"
160
+ panel on each feature's statistics tab):
161
+
162
+ ```ruby
163
+ featureflow = Featureflow::Client.new(api_key: FEATUREFLOW_SERVER_KEY,
164
+ application: 'checkout-api')
165
+ ```
166
+
167
+ The name is a slug — lowercase letters, numbers, `.`, `_` and `-`, at most 64
168
+ characters. An invalid value is dropped with a warning and no tag is sent. The
169
+ `FEATUREFLOW_APPLICATION` environment variable is used when the option is not set in
170
+ code.
171
+
156
172
  #### Further documentation
157
173
  Further documentation can be found [here](http://docs.featureflow.io/docs)
158
174
 
@@ -181,4 +197,14 @@ rvm install 2.5.1
181
197
  rvm use --default 2.5.1
182
198
  bundle install
183
199
  ruby test.rb
184
- ```
200
+ ```
201
+
202
+ ## Manual test harness (example server)
203
+
204
+ To try the SDK against a real Featureflow environment without writing any code, run the small local harness in [examples/harness](examples/harness):
205
+
206
+ ```
207
+ FEATUREFLOW_SERVER_KEY=sdk-srv-env-<your_key> bundle exec ruby examples/harness/server.rb
208
+ ```
209
+
210
+ Then open `http://127.0.0.1:3456/` to evaluate features in the browser, or hit `GET /api/evaluate?feature=<key>&userId=<id>` directly. See [examples/harness/README.md](examples/harness/README.md) for all endpoints and environment variables (including pointing at staging via `FEATUREFLOW_BASE_URL`).
@@ -0,0 +1,28 @@
1
+ module Featureflow
2
+ # Validates the configured application tag, sent as the X-Featureflow-Application
3
+ # header on every request so the dashboard can attribute usage per workload.
4
+ #
5
+ # Contract slug (featureflow-client-sdk-testbed/CONTRACT.md): lowercase [a-z0-9._-],
6
+ # max 64 chars. The server sanitises defensively; the SDK validates strictly so a
7
+ # typo is a visible warning here rather than a silently mangled tag there.
8
+ class Application
9
+ PATTERN = /\A[a-z0-9._-]{1,64}\z/.freeze
10
+
11
+ # Case is forgiven (lowercased); anything else invalid is dropped with a warning
12
+ # and no X-Featureflow-Application header is sent.
13
+ def self.sanitise(raw)
14
+ return nil if raw.nil? || raw == ''
15
+ unless raw.is_a?(String)
16
+ Featureflow.logger.warn "ignoring application #{raw.inspect} - must be a string"
17
+ return nil
18
+ end
19
+ value = raw.strip.downcase
20
+ unless PATTERN.match?(value)
21
+ Featureflow.logger.warn "ignoring application #{raw.inspect} - must be lowercase " \
22
+ 'a-z, 0-9, dot, underscore or hyphen, max 64 chars'
23
+ return nil
24
+ end
25
+ value
26
+ end
27
+ end
28
+ end
@@ -1,3 +1,5 @@
1
+ require 'time'
2
+ require 'featureflow/application'
1
3
  require 'featureflow/evaluate'
2
4
  require 'featureflow/polling_client'
3
5
  require 'featureflow/events_client'
@@ -5,10 +7,14 @@ require 'featureflow/events_client'
5
7
  module Featureflow
6
8
  class Client
7
9
  def initialize(configuration = nil)
8
- @configuration = configuration || Featureflow.configuration
10
+ @configuration = build_configuration(configuration)
9
11
  @configuration.validate!
10
12
  @features = {}
11
13
 
14
+ # Optional workload name sent as X-Featureflow-Application on every request so
15
+ # usage and flag evaluations are attributable per application in the dashboard.
16
+ @application = Application.sanitise(@configuration.application)
17
+
12
18
  Featureflow.logger.info 'initializing client'
13
19
 
14
20
  @failover_variants = {}
@@ -19,7 +25,7 @@ module Featureflow
19
25
  end
20
26
 
21
27
  unless @configuration.disable_events
22
- @events_client = EventsClient.new @configuration.event_endpoint, @configuration.api_key
28
+ @events_client = EventsClient.new @configuration.event_endpoint, @configuration.api_key, @application
23
29
  @events_client.register_features @configuration.with_features
24
30
  end
25
31
 
@@ -38,7 +44,8 @@ module Featureflow
38
44
  @configuration.endpoint,
39
45
  @configuration.api_key,
40
46
  poll_interval: 10,
41
- timeout: 30
47
+ timeout: 30,
48
+ application: @application
42
49
  )
43
50
  end
44
51
 
@@ -61,7 +68,16 @@ module Featureflow
61
68
  user = UserBuilder.new(user).build if user.is_a?(String)
62
69
 
63
70
  user = user.dup
64
- user[:attributes] = user[:attributes].merge('featureflow.user.id' => user[:id])
71
+ # Implicit attributes injected at evaluation time (matching the Node SDK's
72
+ # EvaluateHelpers): featureflow.date/hourofday let rules target the current
73
+ # moment; sdk-server's partial evaluation defers these to the JS client, but
74
+ # server SDKs evaluate them locally against "now".
75
+ now = Time.now
76
+ user[:attributes] = user[:attributes].merge(
77
+ 'featureflow.user.id' => user[:id],
78
+ 'featureflow.date' => now.utc.iso8601,
79
+ 'featureflow.hourofday' => now.hour
80
+ )
65
81
 
66
82
  Evaluate.new(
67
83
  feature_key: key,
@@ -72,5 +88,26 @@ module Featureflow
72
88
  events_client: @events_client
73
89
  )
74
90
  end
91
+
92
+ # Accepts either a Featureflow::Configuration, or a Hash of configuration
93
+ # options — `Featureflow::Client.new(api_key: '...')` is the form the README
94
+ # leads with and the natural Ruby idiom. Passing nothing keeps using the
95
+ # process-wide Featureflow.configuration (which reads FEATUREFLOW_SERVER_KEY).
96
+ private def build_configuration(configuration)
97
+ return Featureflow.configuration if configuration.nil?
98
+ return configuration unless configuration.is_a?(Hash)
99
+
100
+ config = Configuration.new
101
+ configuration.each do |option, value|
102
+ setter = "#{option}="
103
+ # Fail loudly rather than silently dropping a mistyped option — a
104
+ # misspelt `endpoint:` would otherwise quietly poll production.
105
+ unless config.respond_to?(setter)
106
+ raise ArgumentError, "Unknown Featureflow configuration option: #{option}"
107
+ end
108
+ config.public_send(setter, value)
109
+ end
110
+ config
111
+ end
75
112
  end
76
113
  end
@@ -1,33 +1,104 @@
1
+ require 'date'
2
+ require 'time'
3
+
1
4
  module Featureflow
2
5
  class Conditions
6
+ # Mirrors the Node SDK's dateParse: dates compare by instant, not by string,
7
+ # so ISO values with different UTC offsets (e.g. "+04:00" vs "Z") compare correctly.
8
+ #
9
+ # A value carrying no timezone information is read as UTC, per the SDK contract
10
+ # ("A date-only value denotes UTC midnight"): the dashboard's date picker emits
11
+ # date-only values like "2026-07-03", and reading those as *local* midnight would
12
+ # make a scheduled rollout fire at a different instant on every host in a fleet.
13
+ # Values with an explicit offset or "Z" keep their instant, unshifted.
14
+ def self.to_epoch(value)
15
+ case value
16
+ when Time then value.to_f
17
+ when Numeric then value.to_f
18
+ when String
19
+ begin
20
+ parsed = Time.parse(value)
21
+ # Time.parse silently falls back to the process's timezone when the value
22
+ # carries no zone; adding back the offset it applied re-reads the same
23
+ # wall-clock components as UTC. Date._parse only reports :offset when the
24
+ # value actually stated a zone.
25
+ parsed += parsed.utc_offset unless Date._parse(value).key?(:offset)
26
+ parsed.to_f
27
+ rescue ArgumentError, TypeError, RangeError
28
+ nil
29
+ end
30
+ end
31
+ end
32
+
33
+ # Both operands must be strings for a string operator to mean anything.
34
+ def self.both_strings?(a, b)
35
+ a.is_a?(String) && b.is_a?(String)
36
+ end
37
+
38
+ # Numeric-to-numeric or string-to-string only. Mixing the two raises in Ruby, and
39
+ # coercing would answer version comparisons wrongly.
40
+ def self.comparable?(a, b)
41
+ (a.is_a?(Numeric) && b.is_a?(Numeric)) || (a.is_a?(String) && b.is_a?(String))
42
+ end
43
+
3
44
  def self.test(op, a, b)
4
45
  case op
5
46
  when 'equals'
6
47
  a.eql? b[0]
48
+ # The string operators assume both sides are strings. A numeric attribute against
49
+ # `contains` raised NoMethodError straight into the host application — the same
50
+ # fail-closed violation as the invalid regex below, and reachable the same way,
51
+ # from a rule somebody typed into the dashboard. Python already guards these with
52
+ # an explicit type check; this brings Ruby into line. A type mismatch is a
53
+ # no-match, not an exception.
7
54
  when 'contains'
8
- a.include? b[0]
55
+ both_strings?(a, b[0]) && a.include?(b[0])
9
56
  when 'startsWith'
10
- a.start_with? b[0]
57
+ both_strings?(a, b[0]) && a.start_with?(b[0])
11
58
  when 'endsWith'
12
- a.end_with? b[0]
59
+ both_strings?(a, b[0]) && a.end_with?(b[0])
13
60
  when 'matches'
14
- a.match? Regexp.new(b[0])
61
+ # The pattern is untrusted configuration, not code: it comes from a targeting
62
+ # rule somebody typed into the dashboard, so a malformed one like "[unclosed"
63
+ # is a user's mistake rather than a bug in the SDK. The contract
64
+ # (featureflow-client-sdk-testbed/CONTRACT.md, "Operators") therefore requires
65
+ # an invalid regex to return false — a flag SDK must degrade rather than take
66
+ # the host application down with it.
67
+ #
68
+ # Only the regex failure is rescued, never everything: RegexpError for a
69
+ # pattern that will not compile, TypeError for a value that is not a string at
70
+ # all. A genuine bug anywhere else still surfaces.
71
+ begin
72
+ a.match? Regexp.new(b[0])
73
+ rescue RegexpError, TypeError
74
+ false
75
+ end
15
76
  when 'in'
16
77
  b.include? a
17
78
  when 'notIn'
18
79
  !b.include? a
80
+ # Comparing a String with a Numeric raises ArgumentError in Ruby, so an attribute
81
+ # whose type does not match the rule's took the host application down. The
82
+ # contract requires a numeric operator to return false on non-numeric input
83
+ # rather than coercing — "1.10.0" > "1.9.0" must not silently succeed — so a
84
+ # mismatch is simply a no-match. String-to-string comparison is kept, since Ruby
85
+ # orders strings meaningfully and the server behaves the same way.
19
86
  when 'greaterThan'
20
- a > b[0]
87
+ comparable?(a, b[0]) && a > b[0]
21
88
  when 'greaterThanOrEqual'
22
- a >= b[0]
89
+ comparable?(a, b[0]) && a >= b[0]
23
90
  when 'lessThan'
24
- a < b[0]
91
+ comparable?(a, b[0]) && a < b[0]
25
92
  when 'lessThanOrEqual'
26
- a <= b[0]
93
+ comparable?(a, b[0]) && a <= b[0]
27
94
  when 'before'
28
- a < b[0]
95
+ a_time = to_epoch(a)
96
+ b_time = to_epoch(b[0])
97
+ !!(a_time && b_time && a_time < b_time)
29
98
  when 'after'
30
- a > b[0]
99
+ a_time = to_epoch(a)
100
+ b_time = to_epoch(b[0])
101
+ !!(a_time && b_time && a_time > b_time)
31
102
  else
32
103
  false
33
104
  end
@@ -5,12 +5,16 @@ class Featureflow::Configuration
5
5
  attr_accessor :disable_events
6
6
  attr_accessor :with_features
7
7
  attr_accessor :logger
8
+ # Optional workload name for per-application usage attribution in the dashboard;
9
+ # sent as X-Featureflow-Application on every request. See Featureflow::Application.
10
+ attr_accessor :application
8
11
 
9
12
  DEFAULT_ENDPOINT = 'https://app.featureflow.io'
10
13
  DEFAULT_EVENT_ENDPOINT = 'https://events.featureflow.io'
11
14
 
12
15
  def initialize
13
16
  self.api_key = ENV["FEATUREFLOW_SERVER_KEY"]
17
+ self.application = ENV["FEATUREFLOW_APPLICATION"]
14
18
  self.endpoint = DEFAULT_ENDPOINT
15
19
  self.event_endpoint = DEFAULT_EVENT_ENDPOINT
16
20
  self.disable_events = false
@@ -48,7 +48,7 @@ module Featureflow
48
48
 
49
49
  @feature['rules'].each do |rule|
50
50
  next unless EvaluateHelpers.rule_matches(rule, @user)
51
- hash = EvaluateHelpers.calculate_hash(@salt, @feature['key'], @user['id'])
51
+ hash = EvaluateHelpers.calculate_hash(@salt, @feature['key'], @user[:id])
52
52
  variant_value = EvaluateHelpers.get_variant_value(hash)
53
53
  return EvaluateHelpers.get_variant_split_key(rule['variantSplits'], variant_value)
54
54
  end
@@ -7,9 +7,11 @@ module Featureflow
7
7
  #LOCK = Mutex.new
8
8
 
9
9
  class EventsClient
10
- def initialize(url, api_key)
10
+ def initialize(url, api_key, application = nil)
11
11
  @url = url
12
12
  @api_key = api_key
13
+ # Already sanitised by the Client (see lib/featureflow/application.rb).
14
+ @application = application
13
15
  @eventsQueue = Queue.new
14
16
  @scheduler = start_scheduler
15
17
  end
@@ -104,15 +106,17 @@ module Featureflow
104
106
  =end
105
107
 
106
108
  private def send_event(event_name, method, path, body)
109
+ headers = {
110
+ 'Authorization' => "Bearer #{@api_key}",
111
+ 'Content-Type' => 'application/json;charset=UTF-8',
112
+ 'Accept' => 'Application/Json',
113
+ 'X-Featureflow-Client' => 'RubyClient/' + Featureflow::VERSION
114
+ }
115
+ headers['X-Featureflow-Application'] = @application if @application
107
116
  connection = Excon.new(@url)
108
117
  response = connection.request(method: method,
109
118
  path: path,
110
- headers: {
111
- 'Authorization' => "Bearer #{@api_key}",
112
- 'Content-Type' => 'application/json;charset=UTF-8',
113
- 'Accept' => 'Application/Json',
114
- 'X-Featureflow-Client' => 'RubyClient/' + Featureflow::VERSION
115
- },
119
+ headers: headers,
116
120
  omit_default_port: true,
117
121
  body: JSON.generate(body))
118
122
  if response.status >= 400
@@ -5,7 +5,8 @@ module Featureflow
5
5
  class PollingClient
6
6
  DEFAULT_OPTIONS = {
7
7
  poll_interval: 30,
8
- timeout: 30
8
+ timeout: 30,
9
+ application: nil
9
10
  }.freeze
10
11
  LOCK = Mutex.new
11
12
 
@@ -35,12 +36,16 @@ module Featureflow
35
36
  end
36
37
 
37
38
  def load_features
38
- response = Excon.get(@url + + '/api/sdk/v1/features', headers: {
39
+ headers = {
39
40
  'Authorization' => "Bearer #{@api_key}",
40
41
  'Accept' => 'Application/Json',
41
42
  'If-None-Match' => @etag,
42
43
  'X-Featureflow-Client' => 'RubyClient/' + Featureflow::VERSION
43
- }, omit_default_port: true, read_timeout: @options[:timeout])
44
+ }
45
+ # Write-only telemetry: never affects the response, so it cannot fragment the CDN cache.
46
+ headers['X-Featureflow-Application'] = @options[:application] if @options[:application]
47
+ response = Excon.get(@url + + '/api/sdk/v1/features', headers: headers,
48
+ omit_default_port: true, read_timeout: @options[:timeout])
44
49
 
45
50
  if response.status == 200
46
51
  Featureflow.logger.debug "updating features"
@@ -1,4 +1,5 @@
1
1
  require 'featureflow/client'
2
+ require 'featureflow/user_builder'
2
3
 
3
4
  module Featureflow
4
5
  class RailsClient
@@ -6,13 +7,31 @@ module Featureflow
6
7
  @request = request
7
8
  end
8
9
 
9
- def evaluate(key, context)
10
- request_context_values = {
10
+ # `user` is whatever Client#evaluate accepts: a String user id, or a user
11
+ # Hash built with Featureflow::UserBuilder ({id:, attributes:}). The current
12
+ # request's ip and url are merged in as implicit attributes so targeting
13
+ # rules can match on them; anything the caller set explicitly wins.
14
+ def evaluate(key, user)
15
+ Featureflow.evaluate(key, with_request_attributes(user))
16
+ end
17
+
18
+ private
19
+
20
+ def with_request_attributes(user)
21
+ user = UserBuilder.new(user).build if user.is_a?(String)
22
+ # Anything else is left alone so Client#evaluate raises its own ArgumentError.
23
+ return user unless user.is_a?(Hash)
24
+
25
+ user.merge(attributes: request_attributes.merge(user[:attributes] || {}))
26
+ end
27
+
28
+ # Attribute keys are Strings throughout the evaluation path (UserBuilder
29
+ # normalises them), so the request attributes are keyed as Strings too.
30
+ def request_attributes
31
+ {
11
32
  'featureflow.ip' => @request.remote_ip,
12
33
  'featureflow.url' => @request.original_url
13
34
  }
14
- context = {key: context, values: {}} if context.is_a?(String)
15
- Featureflow.evaluate(key, key: context[:key], values: request_context_values.merge(context[:values]))
16
35
  end
17
36
  end
18
- end
37
+ end
@@ -9,12 +9,12 @@ module Featureflow
9
9
 
10
10
  def with_attributes(hash)
11
11
  raise ArgumentError, 'Parameter hash must be a Hash' unless hash.is_a?(Hash)
12
- hash = hash.dup
12
+ normalized = {}
13
13
  hash.each do |k, v|
14
- raise ArgumentError, "Value for #{k} must be a valid 'primitive' JSON datatype" unless valid_value?(v)
15
- hash[k.to_s] = h.delete(k) unless k.is_a?(String)
14
+ raise ArgumentError, "Value for #{k} must be a valid 'primitive' JSON datatype" unless valid_attribute?(v)
15
+ normalized[k.to_s] = v
16
16
  end
17
- @attributes = @attributes.merge(hash)
17
+ @attributes = @attributes.merge(normalized)
18
18
  self
19
19
  end
20
20
 
@@ -1,3 +1,3 @@
1
1
  module Featureflow
2
- VERSION = "0.6.0"
2
+ VERSION = "0.8.0"
3
3
  end
@@ -9,7 +9,7 @@ class FeatureflowGenerator < Rails::Generators::Base
9
9
  desc "Configures the featureflow with your API key"
10
10
 
11
11
  def create_initializer_file
12
- unless /^srv-env-[a-f0-9]{32}$/ =~ api_key
12
+ unless /^sdk-srv-env-[a-f0-9]{32}$/ =~ api_key
13
13
  raise Thor::Error, "Invalid featureflow environment api key #{api_key.inspect}\nYou can find your environment api key on your featureflow dashboard at https://[APP-NAME].featureflow.io/"
14
14
  end
15
15
 
metadata CHANGED
@@ -1,85 +1,99 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: featureflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oliver Oldfield-Hodge
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-12-17 00:00:00.000000000 Z
11
+ date: 2026-08-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: bundler
14
+ name: rake
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.15'
19
+ version: '13.0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.15'
26
+ version: '13.0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rake
28
+ name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '3.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '3.0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rspec
42
+ name: cucumber
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '3.0'
47
+ version: '11.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '3.0'
54
+ version: '11.0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: cucumber
56
+ name: webrick
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.8'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.8'
69
+ - !ruby/object:Gem::Dependency
70
+ name: debug
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
73
  - - "~>"
60
74
  - !ruby/object:Gem::Version
61
- version: 2.4.0
75
+ version: '1.9'
62
76
  type: :development
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
80
  - - "~>"
67
81
  - !ruby/object:Gem::Version
68
- version: 2.4.0
82
+ version: '1.9'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: excon
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
87
  - - "~>"
74
88
  - !ruby/object:Gem::Version
75
- version: 0.57.0
89
+ version: '1.5'
76
90
  type: :runtime
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
- version: 0.57.0
96
+ version: '1.5'
83
97
  description: Connects your server with Featureflow. Create features at https://featureflow.io.
84
98
  email:
85
99
  - contact@featureflow.io
@@ -87,16 +101,11 @@ executables: []
87
101
  extensions: []
88
102
  extra_rdoc_files: []
89
103
  files:
90
- - ".gitignore"
91
- - ".rubocop.yml"
92
104
  - CHANGELOG.md
93
- - Gemfile
94
- - Gemfile.lock
95
105
  - LICENSE
96
106
  - README.md
97
- - Rakefile
98
- - featureflow.gemspec
99
107
  - lib/featureflow.rb
108
+ - lib/featureflow/application.rb
100
109
  - lib/featureflow/client.rb
101
110
  - lib/featureflow/conditions.rb
102
111
  - lib/featureflow/configuration.rb
@@ -110,13 +119,15 @@ files:
110
119
  - lib/featureflow/user_builder.rb
111
120
  - lib/featureflow/version.rb
112
121
  - lib/generators/featureflow_generator.rb
113
- - publish-gem.sh
114
- - test.rb
115
122
  homepage: https://github.com/featureflow/featureflow-ruby-sdk
116
123
  licenses:
117
- - MIT
118
- metadata: {}
119
- post_install_message:
124
+ - Apache-2.0
125
+ metadata:
126
+ homepage_uri: https://github.com/featureflow/featureflow-ruby-sdk
127
+ source_code_uri: https://github.com/featureflow/featureflow-ruby-sdk
128
+ changelog_uri: https://github.com/featureflow/featureflow-ruby-sdk/blob/master/CHANGELOG.md
129
+ bug_tracker_uri: https://github.com/featureflow/featureflow-ruby-sdk/issues
130
+ post_install_message:
120
131
  rdoc_options: []
121
132
  require_paths:
122
133
  - lib
@@ -124,16 +135,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
124
135
  requirements:
125
136
  - - ">="
126
137
  - !ruby/object:Gem::Version
127
- version: '0'
138
+ version: '3.1'
128
139
  required_rubygems_version: !ruby/object:Gem::Requirement
129
140
  requirements:
130
141
  - - ">="
131
142
  - !ruby/object:Gem::Version
132
143
  version: '0'
133
144
  requirements: []
134
- rubyforge_project:
135
- rubygems_version: 2.7.8
136
- signing_key:
145
+ rubygems_version: 3.5.22
146
+ signing_key:
137
147
  specification_version: 4
138
148
  summary: Ruby SDK for Featureflow
139
149
  test_files: []
data/.gitignore DELETED
@@ -1,2 +0,0 @@
1
- pkg
2
- .idea
data/.rubocop.yml DELETED
@@ -1,5 +0,0 @@
1
- Metrics/LineLength:
2
- Max: 120
3
-
4
- Metrics/MethodLength:
5
- Max: 20
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- # Specify your gem's dependencies in featureflow.gemspec
4
- gemspec
data/Gemfile.lock DELETED
@@ -1,53 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- featureflow (0.6.0)
5
- excon (~> 0.57.0)
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- builder (3.2.3)
11
- cucumber (2.4.0)
12
- builder (>= 2.1.2)
13
- cucumber-core (~> 1.5.0)
14
- cucumber-wire (~> 0.0.1)
15
- diff-lcs (>= 1.1.3)
16
- gherkin (~> 4.0)
17
- multi_json (>= 1.7.5, < 2.0)
18
- multi_test (>= 0.1.2)
19
- cucumber-core (1.5.0)
20
- gherkin (~> 4.0)
21
- cucumber-wire (0.0.1)
22
- diff-lcs (1.3)
23
- excon (0.57.1)
24
- gherkin (4.1.3)
25
- multi_json (1.12.1)
26
- multi_test (0.1.2)
27
- rake (10.5.0)
28
- rspec (3.6.0)
29
- rspec-core (~> 3.6.0)
30
- rspec-expectations (~> 3.6.0)
31
- rspec-mocks (~> 3.6.0)
32
- rspec-core (3.6.0)
33
- rspec-support (~> 3.6.0)
34
- rspec-expectations (3.6.0)
35
- diff-lcs (>= 1.2.0, < 2.0)
36
- rspec-support (~> 3.6.0)
37
- rspec-mocks (3.6.0)
38
- diff-lcs (>= 1.2.0, < 2.0)
39
- rspec-support (~> 3.6.0)
40
- rspec-support (3.6.0)
41
-
42
- PLATFORMS
43
- ruby
44
-
45
- DEPENDENCIES
46
- bundler (~> 1.15)
47
- cucumber (~> 2.4.0)
48
- featureflow!
49
- rake (~> 10.0)
50
- rspec (~> 3.0)
51
-
52
- BUNDLED WITH
53
- 1.15.3
data/Rakefile DELETED
@@ -1,6 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
-
4
- RSpec::Core::RakeTask.new(:spec)
5
-
6
- task :default => :spec
data/featureflow.gemspec DELETED
@@ -1,38 +0,0 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'featureflow/version'
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "featureflow"
8
- spec.version = Featureflow::VERSION
9
- spec.authors = ["Oliver Oldfield-Hodge"]
10
- spec.email = ["contact@featureflow.io"]
11
-
12
- spec.summary = "Ruby SDK for Featureflow"
13
- spec.description = "Connects your server with Featureflow. Create features at https://featureflow.io."
14
- spec.homepage = "https://github.com/featureflow/featureflow-ruby-sdk"
15
- spec.license = "MIT"
16
-
17
- # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
- # to allow pushing to a single host or delete this section to allow pushing to any host.
19
- # if spec.respond_to?(:metadata)
20
- # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
21
- # else
22
- # raise "RubyGems 2.0 or newer is required to protect against " \
23
- # "public gem pushes."
24
- # end
25
-
26
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
27
- f.match(%r{^(test|spec|features)/})
28
- end
29
- spec.bindir = "exe"
30
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
- spec.require_paths = ["lib"]
32
-
33
- spec.add_development_dependency "bundler", "~> 1.15"
34
- spec.add_development_dependency "rake", "~> 10.0"
35
- spec.add_development_dependency "rspec", "~> 3.0"
36
- spec.add_development_dependency "cucumber", "~> 2.4.0"
37
- spec.add_dependency "excon", "~> 0.57.0"
38
- end
data/publish-gem.sh DELETED
@@ -1 +0,0 @@
1
- gem push
data/test.rb DELETED
@@ -1,33 +0,0 @@
1
- $:.unshift(File.dirname(__FILE__) + "/lib/")
2
-
3
- require 'featureflow'
4
- with_features = [
5
- Featureflow::Feature.create('default', 'variant'),
6
- Featureflow::Feature.create('oli-f1', 'off')
7
- ]
8
-
9
-
10
-
11
- api_key = 'srv-env-'
12
- config = Featureflow::Configuration.new
13
- config.api_key = api_key
14
- #config.endpoint = 'http://localhost:8081'
15
- #config.event_endpoint = 'http://localhost:8081'
16
- config.with_features = with_features
17
-
18
- featureflow_client = Featureflow::Client.new(config)
19
- =begin
20
- featureflow_client = Featureflow::Client.new(api_key: api_key, with_features: with_features, url: 'http://localhost:8081')
21
- =end
22
- user = Featureflow::UserBuilder.new('user1').build
23
- puts('test-integration is on? ' + featureflow_client.evaluate('test-integration', user).on?.to_s)
24
- featureflow_client.evaluate('nooooo', user).on?
25
- featureflow_client.evaluate('default', user).on?
26
-
27
- #
28
- loop do
29
- sleep 10
30
- puts(featureflow_client.evaluate('oli-f1', user).value)
31
- puts(featureflow_client.evaluate('oli-f1', user).is?('extended'))
32
- puts(featureflow_client.evaluate('default', user).on?)
33
- end