flipper 1.3.1 → 1.3.3

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +19 -5
  3. data/.github/workflows/examples.yml +19 -5
  4. data/Gemfile +7 -3
  5. data/Rakefile +1 -1
  6. data/examples/cloud/backoff_policy.rb +1 -1
  7. data/lib/flipper/adapters/http/error.rb +1 -1
  8. data/lib/flipper/adapters/http.rb +2 -2
  9. data/lib/flipper/adapters/poll.rb +15 -0
  10. data/lib/flipper/cloud/configuration.rb +4 -1
  11. data/lib/flipper/cloud/telemetry/backoff_policy.rb +6 -3
  12. data/lib/flipper/cloud/telemetry/submitter.rb +3 -1
  13. data/lib/flipper/cloud/telemetry.rb +2 -2
  14. data/lib/flipper/export.rb +0 -2
  15. data/lib/flipper/expressions/all.rb +0 -2
  16. data/lib/flipper/feature.rb +8 -1
  17. data/lib/flipper/instrumentation/log_subscriber.rb +0 -1
  18. data/lib/flipper/instrumentation/statsd.rb +4 -2
  19. data/lib/flipper/instrumentation/subscriber.rb +0 -4
  20. data/lib/flipper/metadata.rb +1 -0
  21. data/lib/flipper/poller.rb +2 -2
  22. data/lib/flipper/version.rb +1 -1
  23. data/lib/generators/flipper/setup_generator.rb +5 -0
  24. data/lib/generators/flipper/templates/initializer.rb +45 -0
  25. data/spec/flipper/adapters/http_spec.rb +1 -0
  26. data/spec/flipper/adapters/poll_spec.rb +41 -0
  27. data/spec/flipper/cli_spec.rb +4 -2
  28. data/spec/flipper/cloud/dsl_spec.rb +1 -1
  29. data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +3 -3
  30. data/spec/flipper/cloud/telemetry/submitter_spec.rb +4 -4
  31. data/spec/flipper/cloud/telemetry_spec.rb +6 -6
  32. data/spec/flipper/cloud_spec.rb +9 -4
  33. data/spec/flipper/dsl_spec.rb +0 -3
  34. data/spec/flipper/engine_spec.rb +1 -0
  35. data/spec/flipper/feature_spec.rb +22 -11
  36. data/spec/flipper/instrumentation/log_subscriber_spec.rb +1 -0
  37. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +1 -1
  38. data/spec/flipper/middleware/memoizer_spec.rb +4 -5
  39. data/spec/flipper/model/active_record_spec.rb +11 -0
  40. data/spec/flipper_spec.rb +1 -1
  41. data/spec/spec_helper.rb +8 -5
  42. data/test_rails/generators/flipper/setup_generator_test.rb +5 -0
  43. data/test_rails/generators/flipper/update_generator_test.rb +1 -1
  44. data/test_rails/helper.rb +3 -0
  45. data/test_rails/system/test_help_test.rb +1 -0
  46. metadata +8 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 29d129fae3234365cc1a38cbe05d41ee04ce2d7bd350ae05f2a3b1546ec757b2
4
- data.tar.gz: 184b524b7d42865e223d6018a4ff366553e54f1395723ded4ff1804d6e27f325
3
+ metadata.gz: 8078cb39434d25df42e6eb6b254386568da4eed95f519f6675b79eba2bdb74c2
4
+ data.tar.gz: 6dbefd84a3553b00d0654cdeef452404a5e25980984ee2e6a07abbb8885e2b61
5
5
  SHA512:
6
- metadata.gz: a5776ffb099fb4908635146bccd533bdae4cafe708946d1d8f9f3e0876f9bbc94d29971fd938fd471fc463dab41c2deb11c1b2f56e873fdbe0151b235974e42e
7
- data.tar.gz: 649abbcce3d804cdc141fbd74d6279ddb0a54d4ed506b6c594182adcf44ce459b4e9c3aa74005dc772da54bfcb9bcf8f324a3565c06493d090885c1f53339ed6
6
+ metadata.gz: dd0b492e592bbdf9a969d9ae027de871c8f298aaaa7be9c011d6b258918e4a45421ec4a253d1bd6eed6f246f0dae35124670e91c7c4d6e6617d7f90f4f2acb42
7
+ data.tar.gz: 70006d2e9bcddd9cf4085555b8d7cae79bdcbc93848abe1e6ee4efe0f3fd4d5dc76cae073ab1a8f981f2bd29cc5ed1e7f90795428e53acf0c571f166c1406a82
@@ -7,7 +7,7 @@ jobs:
7
7
  services:
8
8
  redis:
9
9
  image: redis
10
- ports: ['6379:6379']
10
+ ports: ["6379:6379"]
11
11
  options: >-
12
12
  --health-cmd "redis-cli ping"
13
13
  --health-interval 10s
@@ -28,21 +28,35 @@ jobs:
28
28
  strategy:
29
29
  fail-fast: false
30
30
  matrix:
31
- ruby: ['2.6', '2.7', '3.0', '3.1', '3.2', '3.3']
32
- rails: ['5.2', '6.0.0', '6.1.0', '7.0.0', '7.1.0']
31
+ ruby: ["2.6", "2.7", "3.0", "3.1", "3.2", "3.3"]
32
+ rails: ["5.2", "6.0.0", "6.1.0", "7.0.0", "7.1.0", "7.2.0", "8.0.0"]
33
33
  exclude:
34
34
  - ruby: "2.6"
35
35
  rails: "7.1.0"
36
36
  - ruby: "2.6"
37
37
  rails: "7.0.0"
38
+ - ruby: "2.6"
39
+ rails: "7.2.0"
40
+ - ruby: "2.6"
41
+ rails: "8.0.0"
38
42
  - ruby: "2.7"
39
43
  rails: "7.1.0"
44
+ - ruby: "2.7"
45
+ rails: "7.2.0"
46
+ - ruby: "2.7"
47
+ rails: "8.0.0"
40
48
  - ruby: "3.0"
41
49
  rails: "5.2"
50
+ - ruby: "3.0"
51
+ rails: "7.2.0"
52
+ - ruby: "3.0"
53
+ rails: "8.0.0"
42
54
  - ruby: "3.1"
43
55
  rails: "5.2"
44
56
  - ruby: "3.1"
45
57
  rails: "6.0.0"
58
+ - ruby: "3.1"
59
+ rails: "8.0.0"
46
60
  - ruby: "3.2"
47
61
  rails: "5.2"
48
62
  - ruby: "3.2"
@@ -56,7 +70,7 @@ jobs:
56
70
  - ruby: "3.3"
57
71
  rails: "6.1.0"
58
72
  env:
59
- SQLITE3_VERSION: 1.4.1
73
+ SQLITE3_VERSION: ${{ matrix.rails == '8.0.0' && '2.1.0' || '1.4.1' }}
60
74
  REDIS_URL: redis://localhost:6379/0
61
75
  CI: true
62
76
  RAILS_VERSION: ${{ matrix.rails }}
@@ -70,7 +84,7 @@ jobs:
70
84
  - name: Setup memcached
71
85
  uses: KeisukeYamashita/memcached-actions@v1
72
86
  - name: Start MongoDB
73
- uses: supercharge/mongodb-github-action@1.11.0
87
+ uses: supercharge/mongodb-github-action@1.12.0
74
88
  with:
75
89
  mongodb-version: 4.0
76
90
  - name: Check out repository code
@@ -8,7 +8,7 @@ jobs:
8
8
  services:
9
9
  redis:
10
10
  image: redis
11
- ports: ['6379:6379']
11
+ ports: ["6379:6379"]
12
12
  options: >-
13
13
  --health-cmd "redis-cli ping"
14
14
  --health-interval 10s
@@ -16,21 +16,35 @@ jobs:
16
16
  --health-retries 5
17
17
  strategy:
18
18
  matrix:
19
- ruby: ['2.6', '2.7', '3.0', '3.1', '3.2', '3.3']
20
- rails: ['5.2', '6.0.0', '6.1.0', '7.0.0', '7.1.0']
19
+ ruby: ["2.6", "2.7", "3.0", "3.1", "3.2", "3.3"]
20
+ rails: ["5.2", "6.0.0", "6.1.0", "7.0.0", "7.1.0", "7.2.0", "8.0.0"]
21
21
  exclude:
22
22
  - ruby: "2.6"
23
23
  rails: "7.1.0"
24
24
  - ruby: "2.6"
25
25
  rails: "7.0.0"
26
+ - ruby: "2.6"
27
+ rails: "7.2.0"
28
+ - ruby: "2.6"
29
+ rails: "8.0.0"
26
30
  - ruby: "2.7"
27
31
  rails: "7.1.0"
32
+ - ruby: "2.7"
33
+ rails: "7.2.0"
34
+ - ruby: "2.7"
35
+ rails: "8.0.0"
28
36
  - ruby: "3.0"
29
37
  rails: "5.2"
38
+ - ruby: "3.0"
39
+ rails: "7.2.0"
40
+ - ruby: "3.0"
41
+ rails: "8.0.0"
30
42
  - ruby: "3.1"
31
43
  rails: "5.2"
32
44
  - ruby: "3.1"
33
45
  rails: "6.0.0"
46
+ - ruby: "3.1"
47
+ rails: "8.0.0"
34
48
  - ruby: "3.2"
35
49
  rails: "5.2"
36
50
  - ruby: "3.2"
@@ -44,7 +58,7 @@ jobs:
44
58
  - ruby: "3.3"
45
59
  rails: "6.1.0"
46
60
  env:
47
- SQLITE3_VERSION: 1.4.1
61
+ SQLITE3_VERSION: ${{ matrix.rails == '8.0.0' && '2.1.0' || '1.4.1' }}
48
62
  REDIS_URL: redis://localhost:6379/0
49
63
  CI: true
50
64
  RAILS_VERSION: ${{ matrix.rails }}
@@ -52,7 +66,7 @@ jobs:
52
66
  - name: Setup memcached
53
67
  uses: KeisukeYamashita/memcached-actions@v1
54
68
  - name: Start MongoDB
55
- uses: supercharge/mongodb-github-action@1.11.0
69
+ uses: supercharge/mongodb-github-action@1.12.0
56
70
  with:
57
71
  mongodb-version: 4.0
58
72
  - name: Check out repository code
data/Gemfile CHANGED
@@ -6,16 +6,19 @@ Dir['flipper-*.gemspec'].each do |gemspec|
6
6
  gemspec(name: "flipper-#{plugin}", development_group: plugin)
7
7
  end
8
8
 
9
+ gem 'concurrent-ruby', '1.3.4'
10
+ gem 'connection_pool'
9
11
  gem 'debug'
10
12
  gem 'rake'
11
13
  gem 'statsd-ruby', '~> 1.2.1'
12
14
  gem 'rspec', '~> 3.0'
13
15
  gem 'rack-test'
14
- gem 'rackup'
15
- gem 'sqlite3', "~> #{ENV['SQLITE3_VERSION'] || '1.4.1'}"
16
- gem 'rails', "~> #{ENV['RAILS_VERSION'] || '7.1'}"
16
+ gem 'rackup', '= 1.0.0'
17
+ gem 'sqlite3', "~> #{ENV['SQLITE3_VERSION'] || '2.1.0'}"
18
+ gem 'rails', "~> #{ENV['RAILS_VERSION'] || '8.0'}"
17
19
  gem 'minitest', '~> 5.18'
18
20
  gem 'minitest-documentation'
21
+ gem 'pstore'
19
22
  gem 'webmock'
20
23
  gem 'ice_age'
21
24
  gem 'redis-namespace'
@@ -28,6 +31,7 @@ gem 'mysql2'
28
31
  gem 'pg'
29
32
  gem 'cuprite'
30
33
  gem 'puma'
34
+ gem 'warning'
31
35
 
32
36
  group(:guard) do
33
37
  gem 'guard'
data/Rakefile CHANGED
@@ -17,7 +17,7 @@ end
17
17
 
18
18
  desc 'Tags version, pushes to remote, and pushes gem'
19
19
  task release: :build do
20
- # sh 'git', 'tag', "v#{Flipper::VERSION}"
20
+ sh 'git', 'tag', "v#{Flipper::VERSION}"
21
21
  sh 'git push origin main'
22
22
  sh "git push origin v#{Flipper::VERSION}"
23
23
  puts "\nWhat OTP code should be used?"
@@ -5,7 +5,7 @@ require 'flipper/cloud/telemetry/backoff_policy'
5
5
  intervals = []
6
6
  policy = Flipper::Cloud::Telemetry::BackoffPolicy.new
7
7
 
8
- 10.times do |n|
8
+ 5.times do |n|
9
9
  intervals << policy.next_interval
10
10
  end
11
11
 
@@ -20,7 +20,7 @@ module Flipper
20
20
  if more_info = data["more_info"]
21
21
  message << "\n#{data["more_info"]}"
22
22
  end
23
- rescue => exception
23
+ rescue
24
24
  # welp we tried
25
25
  end
26
26
 
@@ -59,8 +59,8 @@ module Flipper
59
59
  response = @client.get("/features?exclude_gate_names=true")
60
60
  raise Error, response unless response.is_a?(Net::HTTPOK)
61
61
 
62
- parsed_response = Typecast.from_json(response.body)
63
- parsed_features = parsed_response.fetch('features')
62
+ parsed_response = response.body.empty? ? {} : Typecast.from_json(response.body)
63
+ parsed_features = parsed_response['features'] || []
64
64
  gates_by_key = parsed_features.each_with_object({}) do |parsed_feature, hash|
65
65
  hash[parsed_feature['key']] = parsed_feature['gates']
66
66
  hash
@@ -18,6 +18,21 @@ module Flipper
18
18
  @adapter = adapter
19
19
  @poller = poller
20
20
  @last_synced_at = 0
21
+
22
+ # If the adapter is empty, we need to sync before starting the poller.
23
+ # Yes, this will block the main thread, but that's better than thinking
24
+ # nothing is enabled.
25
+ if adapter.features.empty?
26
+ begin
27
+ @poller.sync
28
+ rescue
29
+ # TODO: Warn here that it's possible that no data has been synced
30
+ # and flags are being evaluated without flag data being present
31
+ # until a sync completes. We rescue to avoid flipper being down
32
+ # causing your processes to crash.
33
+ end
34
+ end
35
+
21
36
  @poller.start
22
37
  end
23
38
 
@@ -3,7 +3,6 @@ require "socket"
3
3
  require "flipper/adapters/http"
4
4
  require "flipper/adapters/poll"
5
5
  require "flipper/poller"
6
- require "flipper/adapters/memory"
7
6
  require "flipper/adapters/dual_write"
8
7
  require "flipper/adapters/sync/synchronizer"
9
8
  require "flipper/cloud/telemetry"
@@ -135,6 +134,10 @@ module Flipper
135
134
  logger.send(level, "name=flipper_cloud #{message}")
136
135
  end
137
136
 
137
+ def instrument(name, payload = {}, &block)
138
+ instrumenter.instrument(name, payload, &block)
139
+ end
140
+
138
141
  private
139
142
 
140
143
  def app_adapter
@@ -3,10 +3,10 @@ module Flipper
3
3
  class Telemetry
4
4
  class BackoffPolicy
5
5
  # Private: The default minimum timeout between intervals in milliseconds.
6
- MIN_TIMEOUT_MS = 1_000
6
+ MIN_TIMEOUT_MS = 30_000
7
7
 
8
8
  # Private: The default maximum timeout between intervals in milliseconds.
9
- MAX_TIMEOUT_MS = 30_000
9
+ MAX_TIMEOUT_MS = 120_000
10
10
 
11
11
  # Private: The value to multiply the current interval with for each
12
12
  # retry attempt.
@@ -67,7 +67,10 @@ module Flipper
67
67
 
68
68
  @attempts += 1
69
69
 
70
- [interval, @max_timeout_ms].min
70
+ # cap the interval to the max timeout
71
+ result = [interval, @max_timeout_ms].min
72
+ # jitter even when maxed out
73
+ result == @max_timeout_ms ? add_jitter(result, 0.05) : result
71
74
  end
72
75
 
73
76
  def reset
@@ -34,7 +34,7 @@ module Flipper
34
34
  return if drained.empty?
35
35
  body = to_body(drained)
36
36
  return if body.nil? || body.empty?
37
- retry_with_backoff(10) { submit(body) }
37
+ retry_with_backoff(5) { submit(body) }
38
38
  end
39
39
 
40
40
  private
@@ -51,6 +51,7 @@ module Flipper
51
51
 
52
52
  Typecast.to_gzip(json)
53
53
  rescue => exception
54
+ @cloud_configuration.instrument "telemetry_error.#{Flipper::InstrumentationNamespace}", exception: exception, request_id: request_id
54
55
  @cloud_configuration.log "action=to_body request_id=#{request_id} error=#{exception.inspect}", level: :error
55
56
  end
56
57
 
@@ -63,6 +64,7 @@ module Flipper
63
64
  result, should_retry = yield
64
65
  return [result, nil] unless should_retry
65
66
  rescue => error
67
+ @cloud_configuration.instrument "telemetry_retry.#{Flipper::InstrumentationNamespace}", attempts_remaining: attempts_remaining, exception: error
66
68
  @cloud_configuration.log "action=post_to_cloud attempts_remaining=#{attempts_remaining} error=#{error.inspect}", level: :error
67
69
  should_retry = true
68
70
  caught_exception = error
@@ -75,8 +75,8 @@ module Flipper
75
75
 
76
76
  @metric_storage = MetricStorage.new
77
77
 
78
- @pool = Concurrent::FixedThreadPool.new(2, {
79
- max_queue: 5,
78
+ @pool = Concurrent::FixedThreadPool.new(1, {
79
+ max_queue: 20, # ~ 20 minutes of data at 1 minute intervals
80
80
  fallback_policy: :discard,
81
81
  name: "flipper-telemetry-post-to-cloud-pool".freeze,
82
82
  })
@@ -1,5 +1,3 @@
1
- require "flipper/adapters/memory"
2
-
3
1
  module Flipper
4
2
  class Export
5
3
  attr_reader :contents, :format, :version
@@ -1,5 +1,3 @@
1
- require "flipper/expression"
2
-
3
1
  module Flipper
4
2
  module Expressions
5
3
  class All
@@ -100,7 +100,14 @@ module Flipper
100
100
  #
101
101
  # Returns true if enabled, false if not.
102
102
  def enabled?(*actors)
103
- actors = actors.flatten.compact.map { |actor| Types::Actor.wrap(actor) }
103
+ actors = Array(actors).
104
+ # Avoids to_ary warning that happens when passing DelegateClass of an
105
+ # ActiveRecord object and using flatten here. This is tested in
106
+ # spec/flipper/model/active_record_spec.rb.
107
+ flat_map { |actor| actor.is_a?(Array) ? actor : [actor] }.
108
+ # Allows null object pattern. See PR for more. https://github.com/flippercloud/flipper/pull/887
109
+ reject(&:nil?).
110
+ map { |actor| Types::Actor.wrap(actor) }
104
111
  actors = nil if actors.empty?
105
112
 
106
113
  # thing is left for backwards compatibility
@@ -53,7 +53,6 @@ module Flipper
53
53
 
54
54
  feature_name = event.payload[:feature_name]
55
55
  adapter_name = event.payload[:adapter_name]
56
- gate_name = event.payload[:gate_name]
57
56
  operation = event.payload[:operation]
58
57
  result = event.payload[:result]
59
58
 
@@ -2,5 +2,7 @@ require 'securerandom'
2
2
  require 'active_support/notifications'
3
3
  require 'flipper/instrumentation/statsd_subscriber'
4
4
 
5
- ActiveSupport::Notifications.subscribe /\.flipper$/,
6
- Flipper::Instrumentation::StatsdSubscriber
5
+ ActiveSupport::Notifications.subscribe(
6
+ /\.flipper$/,
7
+ Flipper::Instrumentation::StatsdSubscriber
8
+ )
@@ -42,7 +42,6 @@ module Flipper
42
42
  # Private
43
43
  def update_feature_operation_metrics
44
44
  feature_name = @payload[:feature_name]
45
- gate_name = @payload[:gate_name]
46
45
  operation = strip_trailing_question_mark(@payload[:operation])
47
46
  result = @payload[:result]
48
47
 
@@ -64,9 +63,6 @@ module Flipper
64
63
  def update_adapter_operation_metrics
65
64
  adapter_name = @payload[:adapter_name]
66
65
  operation = @payload[:operation]
67
- result = @payload[:result]
68
- value = @payload[:value]
69
- key = @payload[:key]
70
66
 
71
67
  update_timer "flipper.adapter.#{adapter_name}.#{operation}"
72
68
  end
@@ -7,5 +7,6 @@ module Flipper
7
7
  "source_code_uri" => "https://github.com/flippercloud/flipper",
8
8
  "bug_tracker_uri" => "https://github.com/flippercloud/flipper/issues",
9
9
  "changelog_uri" => "https://github.com/flippercloud/flipper/releases/tag/v#{Flipper::VERSION}",
10
+ "funding_uri" => "https://github.com/sponsors/flippercloud",
10
11
  }.freeze
11
12
  end
@@ -59,10 +59,10 @@ module Flipper
59
59
  def run
60
60
  loop do
61
61
  sleep jitter
62
- start = Concurrent.monotonic_time
62
+
63
63
  begin
64
64
  sync
65
- rescue => exception
65
+ rescue
66
66
  # you can instrument these using poller.flipper
67
67
  end
68
68
 
@@ -1,5 +1,5 @@
1
1
  module Flipper
2
- VERSION = '1.3.1'.freeze
2
+ VERSION = '1.3.3'.freeze
3
3
 
4
4
  REQUIRED_RUBY_VERSION = '2.6'.freeze
5
5
  NEXT_REQUIRED_RUBY_VERSION = '3.0'.freeze
@@ -4,10 +4,15 @@ module Flipper
4
4
  module Generators
5
5
  class SetupGenerator < ::Rails::Generators::Base
6
6
  desc 'Peform any necessary steps to install Flipper'
7
+ source_paths << File.expand_path('templates', __dir__)
7
8
 
8
9
  class_option :token, type: :string, default: nil, aliases: '-t',
9
10
  desc: "Your personal environment token for Flipper Cloud"
10
11
 
12
+ def generate_initializer
13
+ template 'initializer.rb', 'config/initializers/flipper.rb'
14
+ end
15
+
11
16
  def generate_active_record
12
17
  invoke 'flipper:active_record' if defined?(Flipper::Adapters::ActiveRecord)
13
18
  end
@@ -0,0 +1,45 @@
1
+ Rails.application.configure do
2
+ ## Memoization ensures that only one adapter call is made per feature per request.
3
+ ## For more info, see https://www.flippercloud.io/docs/optimization#memoization
4
+ # config.flipper.memoize = true
5
+
6
+ ## Flipper preloads all features before each request, which is recommended if:
7
+ ## * you have a limited number of features (< 100?)
8
+ ## * most of your requests depend on most of your features
9
+ ## * you have limited gate data combined across all features (< 1k enabled gates, like individual actors, across all features)
10
+ ##
11
+ ## For more info, see https://www.flippercloud.io/docs/optimization#preloading
12
+ # config.flipper.preload = true
13
+
14
+ ## Warn or raise an error if an unknown feature is checked
15
+ ## Can be set to `:warn`, `:raise`, or `false`
16
+ # config.flipper.strict = Rails.env.development? && :warn
17
+
18
+ ## Show Flipper checks in logs
19
+ # config.flipper.log = true
20
+
21
+ ## Reconfigure Flipper to use the Memory adaper and disable Cloud in tests
22
+ # config.flipper.test_help = true
23
+
24
+ ## The path that Flipper Cloud will use to sync features
25
+ # config.flipper.cloud_path = "_flipper"
26
+
27
+ ## The instrumenter that Flipper will use. Defaults to ActiveSupport::Notifications.
28
+ # config.flipper.instrumenter = ActiveSupport::Notifications
29
+ end
30
+
31
+ Flipper.configure do |config|
32
+ ## Configure other adapters that you want to use here:
33
+ ## See http://flippercloud.io/docs/adapters
34
+ # config.use Flipper::Adapters::ActiveSupportCacheStore, Rails.cache, expires_in: 5.minutes
35
+ end
36
+
37
+ ## Register a group that can be used for enabling features.
38
+ ##
39
+ ## Flipper.enable_group :my_feature, :admins
40
+ ##
41
+ ## See https://www.flippercloud.io/docs/features#enablement-group
42
+ #
43
+ # Flipper.register(:admins) do |actor|
44
+ # actor.respond_to?(:admin?) && actor.admin?
45
+ # end
@@ -28,6 +28,7 @@ RSpec.describe Flipper::Adapters::Http do
28
28
  end
29
29
 
30
30
  before :all do
31
+ @started = false
31
32
  dir = FlipperRoot.join('tmp').tap(&:mkpath)
32
33
  log_path = dir.join('flipper_adapters_http_spec.log')
33
34
  @pstore_file = dir.join('flipper.pstore')
@@ -0,0 +1,41 @@
1
+ require 'flipper/adapters/poll'
2
+
3
+ RSpec.describe Flipper::Adapters::Poll do
4
+ let(:remote_adapter) {
5
+ adapter = Flipper::Adapters::Memory.new(threadsafe: true)
6
+ flipper = Flipper.new(adapter)
7
+ flipper.enable(:search)
8
+ flipper.enable(:analytics)
9
+ adapter
10
+ }
11
+ let(:local_adapter) { Flipper::Adapters::Memory.new(threadsafe: true) }
12
+ let(:poller) {
13
+ Flipper::Poller.get("for_spec", {
14
+ start_automatically: false,
15
+ remote_adapter: remote_adapter,
16
+ })
17
+ }
18
+
19
+ it "syncs in main thread if local adapter is empty" do
20
+ instance = described_class.new(poller, local_adapter)
21
+ instance.features # call something to force sync
22
+ expect(local_adapter.features).to eq(remote_adapter.features)
23
+ end
24
+
25
+ it "does not sync in main thread if local adapter is not empty" do
26
+ # make local not empty by importing remote
27
+ flipper = Flipper.new(local_adapter)
28
+ flipper.import(remote_adapter)
29
+
30
+ # make a fake poller to verify calls
31
+ poller = double("Poller", last_synced_at: Concurrent::AtomicFixnum.new(0))
32
+ expect(poller).to receive(:start).twice
33
+ expect(poller).not_to receive(:sync)
34
+
35
+ # create new instance and call something to force sync
36
+ instance = described_class.new(poller, local_adapter)
37
+ instance.features # call something to force sync
38
+
39
+ expect(local_adapter.features).to eq(remote_adapter.features)
40
+ end
41
+ end
@@ -5,6 +5,8 @@ RSpec.describe Flipper::CLI do
5
5
  let(:stderr) { StringIO.new }
6
6
  let(:cli) { Flipper::CLI.new(stdout: stdout, stderr: stderr) }
7
7
 
8
+ Result = Struct.new(:status, :stdout, :stderr, keyword_init: true)
9
+
8
10
  before do
9
11
  # Prentend stdout/stderr a TTY to test colorization
10
12
  allow(stdout).to receive(:tty?).and_return(true)
@@ -12,7 +14,7 @@ RSpec.describe Flipper::CLI do
12
14
  end
13
15
 
14
16
  # Infer the command from the description
15
- subject(:argv) do
17
+ let(:argv) do
16
18
  descriptions = self.class.parent_groups.map {|g| g.metadata[:description_args] }.reverse.flatten.drop(1)
17
19
  descriptions.map { |arg| Shellwords.split(arg) }.flatten
18
20
  end
@@ -26,7 +28,7 @@ RSpec.describe Flipper::CLI do
26
28
  status = e.status
27
29
  end
28
30
 
29
- OpenStruct.new(status: status, stdout: stdout.string, stderr: stderr.string)
31
+ Result.new(status: status, stdout: stdout.string, stderr: stderr.string)
30
32
  end
31
33
 
32
34
  before do
@@ -45,7 +45,7 @@ RSpec.describe Flipper::Cloud::DSL do
45
45
  end
46
46
 
47
47
  let(:cloud_configuration) do
48
- cloud_configuration = Flipper::Cloud::Configuration.new({
48
+ Flipper::Cloud::Configuration.new({
49
49
  token: "asdf",
50
50
  sync_secret: "tasty",
51
51
  local_adapter: local_adapter
@@ -4,8 +4,8 @@ RSpec.describe Flipper::Cloud::Telemetry::BackoffPolicy do
4
4
  context "#initialize" do
5
5
  it "with no options" do
6
6
  policy = described_class.new
7
- expect(policy.min_timeout_ms).to eq(1_000)
8
- expect(policy.max_timeout_ms).to eq(30_000)
7
+ expect(policy.min_timeout_ms).to eq(30_000)
8
+ expect(policy.max_timeout_ms).to eq(120_000)
9
9
  expect(policy.multiplier).to eq(1.5)
10
10
  expect(policy.randomization_factor).to eq(0.5)
11
11
  end
@@ -87,7 +87,7 @@ RSpec.describe Flipper::Cloud::Telemetry::BackoffPolicy do
87
87
  randomization_factor: 0.5,
88
88
  })
89
89
  10.times { policy.next_interval }
90
- expect(policy.next_interval).to eq(10_000)
90
+ expect(policy.next_interval).to be_within(10_000*0.1).of(10_000)
91
91
  end
92
92
  end
93
93
 
@@ -71,15 +71,15 @@ RSpec.describe Flipper::Cloud::Telemetry::Submitter do
71
71
  to_return(status: 429, body: "{}").
72
72
  to_return(status: 200, body: "{}")
73
73
  instance = described_class.new(cloud_configuration)
74
- expect(instance.backoff_policy.min_timeout_ms).to eq(1_000)
75
- expect(instance.backoff_policy.max_timeout_ms).to eq(30_000)
74
+ expect(instance.backoff_policy.min_timeout_ms).to eq(30_000)
75
+ expect(instance.backoff_policy.max_timeout_ms).to eq(120_000)
76
76
  end
77
77
 
78
78
  it "tries 10 times by default" do
79
79
  stub_request(:post, "https://www.flippercloud.io/adapter/telemetry").
80
80
  to_return(status: 500, body: "{}")
81
81
  subject.call(enabled_metrics)
82
- expect(subject.backoff_policy.retries).to eq(9) # 9 retries + 1 initial attempt
82
+ expect(subject.backoff_policy.retries).to eq(4) # 4 retries + 1 initial attempt
83
83
  end
84
84
 
85
85
  [
@@ -105,7 +105,7 @@ RSpec.describe Flipper::Cloud::Telemetry::Submitter do
105
105
  stub_request(:post, "https://www.flippercloud.io/adapter/telemetry").
106
106
  to_raise(error_class)
107
107
  subject.call(enabled_metrics)
108
- expect(subject.backoff_policy.retries).to eq(9)
108
+ expect(subject.backoff_policy.retries).to eq(4)
109
109
  end
110
110
  end
111
111
 
@@ -25,7 +25,7 @@ RSpec.describe Flipper::Cloud::Telemetry do
25
25
 
26
26
  expect(telemetry.interval).to eq(60)
27
27
  expect(telemetry.timer.execution_interval).to eq(60)
28
- expect(stub).to have_been_requested
28
+ expect(stub).to have_been_requested.at_least_once
29
29
  end
30
30
 
31
31
  it "phones home and updates telemetry interval if present" do
@@ -45,7 +45,7 @@ RSpec.describe Flipper::Cloud::Telemetry do
45
45
 
46
46
  expect(telemetry.interval).to eq(120)
47
47
  expect(telemetry.timer.execution_interval).to eq(120)
48
- expect(stub).to have_been_requested
48
+ expect(stub).to have_been_requested.at_least_once
49
49
  end
50
50
 
51
51
  it "phones home and requests shutdown if telemetry-shutdown header is true" do
@@ -67,7 +67,7 @@ RSpec.describe Flipper::Cloud::Telemetry do
67
67
  result: true,
68
68
  })
69
69
  telemetry.stop
70
- expect(stub).to have_been_requested
70
+ expect(stub).to have_been_requested.at_least_once
71
71
  expect(output.string).to match(/action=telemetry_shutdown message=The server has requested that telemetry be shut down./)
72
72
  end
73
73
 
@@ -90,7 +90,7 @@ RSpec.describe Flipper::Cloud::Telemetry do
90
90
  result: true,
91
91
  })
92
92
  telemetry.stop
93
- expect(stub).to have_been_requested
93
+ expect(stub).to have_been_requested.at_least_once
94
94
  expect(output.string).not_to match(/action=telemetry_shutdown message=The server has requested that telemetry be shut down./)
95
95
  end
96
96
 
@@ -122,7 +122,7 @@ RSpec.describe Flipper::Cloud::Telemetry do
122
122
  # Check the conig interval and the timer interval.
123
123
  expect(telemetry.interval).to eq(120)
124
124
  expect(telemetry.timer.execution_interval).to eq(120)
125
- expect(stub).to have_been_requested.times(10)
125
+ expect(stub).to have_been_requested.times(5)
126
126
  end
127
127
 
128
128
  it "doesn't try to update telemetry interval from error if not response error" do
@@ -152,7 +152,7 @@ RSpec.describe Flipper::Cloud::Telemetry do
152
152
 
153
153
  expect(telemetry.interval).to eq(60)
154
154
  expect(telemetry.timer.execution_interval).to eq(60)
155
- expect(stub).to have_been_requested.times(10)
155
+ expect(stub).to have_been_requested.times(5)
156
156
  end
157
157
 
158
158
  describe '#record' do
@@ -43,6 +43,7 @@ RSpec.describe Flipper::Cloud do
43
43
 
44
44
  context 'initialize with token and options' do
45
45
  it 'sets correct url' do
46
+ stub_request(:any, %r{fakeflipper.com}).to_return(status: 200)
46
47
  instance = described_class.new(token: 'asdf', url: 'https://www.fakeflipper.com/sadpanda')
47
48
  # pardon the nesting...
48
49
  memoized = instance.adapter
@@ -78,27 +79,31 @@ RSpec.describe Flipper::Cloud do
78
79
  end
79
80
 
80
81
  it 'can set debug_output' do
82
+ instance = Flipper::Adapters::Http::Client.new(token: 'asdf', url: 'https://www.flippercloud.io/adapter')
81
83
  expect(Flipper::Adapters::Http::Client).to receive(:new)
82
- .with(hash_including(debug_output: STDOUT)).at_least(:once)
84
+ .with(hash_including(debug_output: STDOUT)).at_least(:once).and_return(instance)
83
85
  described_class.new(token: 'asdf', debug_output: STDOUT)
84
86
  end
85
87
 
86
88
  it 'can set read_timeout' do
89
+ instance = Flipper::Adapters::Http::Client.new(token: 'asdf', url: 'https://www.flippercloud.io/adapter')
87
90
  expect(Flipper::Adapters::Http::Client).to receive(:new)
88
- .with(hash_including(read_timeout: 1)).at_least(:once)
91
+ .with(hash_including(read_timeout: 1)).at_least(:once).and_return(instance)
89
92
  described_class.new(token: 'asdf', read_timeout: 1)
90
93
  end
91
94
 
92
95
  it 'can set open_timeout' do
96
+ instance = Flipper::Adapters::Http::Client.new(token: 'asdf', url: 'https://www.flippercloud.io/adapter')
93
97
  expect(Flipper::Adapters::Http::Client).to receive(:new)
94
- .with(hash_including(open_timeout: 1)).at_least(:once)
98
+ .with(hash_including(open_timeout: 1)).at_least(:once).and_return(instance)
95
99
  described_class.new(token: 'asdf', open_timeout: 1)
96
100
  end
97
101
 
98
102
  if RUBY_VERSION >= '2.6.0'
99
103
  it 'can set write_timeout' do
104
+ instance = Flipper::Adapters::Http::Client.new(token: 'asdf', url: 'https://www.flippercloud.io/adapter')
100
105
  expect(Flipper::Adapters::Http::Client).to receive(:new)
101
- .with(hash_including(open_timeout: 1)).at_least(:once)
106
+ .with(hash_including(open_timeout: 1)).at_least(:once).and_return(instance)
102
107
  described_class.new(token: 'asdf', open_timeout: 1)
103
108
  end
104
109
  end
@@ -225,9 +225,6 @@ RSpec.describe Flipper::DSL do
225
225
 
226
226
  describe '#enable_group/disable_group' do
227
227
  it 'enables and disables the feature for group' do
228
- actor = Flipper::Actor.new(5)
229
- group = Flipper.register(:fives) { |actor| actor.flipper_id == 5 }
230
-
231
228
  expect(subject[:stats].groups_value).to be_empty
232
229
  subject.enable_group(:stats, :fives)
233
230
  expect(subject[:stats].groups_value).to eq(Set['fives'])
@@ -12,6 +12,7 @@ RSpec.describe Flipper::Engine do
12
12
  end
13
13
 
14
14
  before do
15
+ stub_request(:get, /flippercloud\.io/).to_return(status: 200, body: "{}")
15
16
  Rails.application = nil
16
17
  ActiveSupport::Dependencies.autoload_paths = ActiveSupport::Dependencies.autoload_paths.dup
17
18
  ActiveSupport::Dependencies.autoload_once_paths = ActiveSupport::Dependencies.autoload_once_paths.dup
@@ -76,6 +76,26 @@ RSpec.describe Flipper::Feature do
76
76
  expect(subject.enabled?(actors)).to be(false)
77
77
  end
78
78
  end
79
+
80
+ context "for an object that implements .nil? == true" do
81
+ let(:actor) { Flipper::Actor.new("User;1") }
82
+
83
+ before do
84
+ def actor.nil?
85
+ true
86
+ end
87
+ end
88
+
89
+ it 'returns true if feature is enabled' do
90
+ subject.enable
91
+ expect(subject.enabled?(actor)).to be(true)
92
+ end
93
+
94
+ it 'returns false if feature is disabled' do
95
+ subject.disable
96
+ expect(subject.enabled?(actor)).to be(false)
97
+ end
98
+ end
79
99
  end
80
100
 
81
101
  describe '#to_s' do
@@ -195,7 +215,7 @@ RSpec.describe Flipper::Feature do
195
215
 
196
216
  it 'is recorded for enable' do
197
217
  actor = Flipper::Types::Actor.new(Flipper::Actor.new('1'))
198
- gate = subject.gate_for(actor)
218
+ subject.gate_for(actor)
199
219
 
200
220
  subject.enable(actor)
201
221
 
@@ -210,7 +230,7 @@ RSpec.describe Flipper::Feature do
210
230
 
211
231
  it 'always instruments flipper type instance for enable' do
212
232
  actor = Flipper::Actor.new('1')
213
- gate = subject.gate_for(actor)
233
+ subject.gate_for(actor)
214
234
 
215
235
  subject.enable(actor)
216
236
 
@@ -221,7 +241,6 @@ RSpec.describe Flipper::Feature do
221
241
 
222
242
  it 'is recorded for disable' do
223
243
  thing = Flipper::Types::Boolean.new
224
- gate = subject.gate_for(thing)
225
244
 
226
245
  subject.disable(thing)
227
246
 
@@ -266,7 +285,6 @@ RSpec.describe Flipper::Feature do
266
285
 
267
286
  it 'always instruments flipper type instance for disable' do
268
287
  actor = Flipper::Actor.new('1')
269
- gate = subject.gate_for(actor)
270
288
 
271
289
  subject.disable(actor)
272
290
 
@@ -709,7 +727,6 @@ RSpec.describe Flipper::Feature do
709
727
  context "with expression instance" do
710
728
  it "updates gate values to equal expression or clears expression" do
711
729
  expression = Flipper.property(:plan).eq("basic")
712
- other_expression = Flipper.property(:age).gte(21)
713
730
  expect(subject.gate_values.expression).to be(nil)
714
731
  subject.enable_expression(expression)
715
732
  expect(subject.gate_values.expression).to eq(expression.value)
@@ -721,7 +738,6 @@ RSpec.describe Flipper::Feature do
721
738
  context "with Hash" do
722
739
  it "updates gate values to equal expression or clears expression" do
723
740
  expression = Flipper.property(:plan).eq("basic")
724
- other_expression = Flipper.property(:age).gte(21)
725
741
  expect(subject.gate_values.expression).to be(nil)
726
742
  subject.enable_expression(expression.value)
727
743
  expect(subject.gate_values.expression).to eq(expression.value)
@@ -1078,8 +1094,6 @@ RSpec.describe Flipper::Feature do
1078
1094
  describe '#enable_group/disable_group' do
1079
1095
  context 'with symbol group name' do
1080
1096
  it 'updates the gate values to include the group' do
1081
- actor = Flipper::Actor.new(5)
1082
- group = Flipper.register(:five_only) { |actor| actor.flipper_id == 5 }
1083
1097
  expect(subject.gate_values.groups).to be_empty
1084
1098
  subject.enable_group(:five_only)
1085
1099
  expect(subject.gate_values.groups).to eq(Set['five_only'])
@@ -1090,8 +1104,6 @@ RSpec.describe Flipper::Feature do
1090
1104
 
1091
1105
  context 'with string group name' do
1092
1106
  it 'updates the gate values to include the group' do
1093
- actor = Flipper::Actor.new(5)
1094
- group = Flipper.register(:five_only) { |actor| actor.flipper_id == 5 }
1095
1107
  expect(subject.gate_values.groups).to be_empty
1096
1108
  subject.enable_group('five_only')
1097
1109
  expect(subject.gate_values.groups).to eq(Set['five_only'])
@@ -1102,7 +1114,6 @@ RSpec.describe Flipper::Feature do
1102
1114
 
1103
1115
  context 'with group instance' do
1104
1116
  it 'updates the gate values for the group' do
1105
- actor = Flipper::Actor.new(5)
1106
1117
  group = Flipper.register(:five_only) { |actor| actor.flipper_id == 5 }
1107
1118
  expect(subject.gate_values.groups).to be_empty
1108
1119
  subject.enable_group(group)
@@ -1,4 +1,5 @@
1
1
  require 'logger'
2
+ require 'active_support/core_ext/object/blank'
2
3
  require 'flipper/instrumentation/log_subscriber'
3
4
  require 'flipper/adapters/instrumented'
4
5
 
@@ -18,7 +18,7 @@ RSpec.describe Flipper::Instrumentation::StatsdSubscriber do
18
18
  Flipper.new(adapter, instrumenter: ActiveSupport::Notifications)
19
19
  end
20
20
 
21
- let(:user) { user = Flipper::Actor.new('1') }
21
+ let(:user) { Flipper::Actor.new('1') }
22
22
 
23
23
  before do
24
24
  described_class.client = statsd_client
@@ -80,7 +80,7 @@ RSpec.describe Flipper::Middleware::Memoizer do
80
80
  context 'with preload: true' do
81
81
  let(:app) do
82
82
  # ensure scoped for builder block, annoying...
83
- instance = flipper
83
+ flipper
84
84
  middleware = described_class
85
85
 
86
86
  Rack::Builder.new do
@@ -141,7 +141,7 @@ RSpec.describe Flipper::Middleware::Memoizer do
141
141
  context 'with preload specific' do
142
142
  let(:app) do
143
143
  # ensure scoped for builder block, annoying...
144
- instance = flipper
144
+ flipper
145
145
  middleware = described_class
146
146
 
147
147
  Rack::Builder.new do
@@ -266,7 +266,7 @@ RSpec.describe Flipper::Middleware::Memoizer do
266
266
  context 'with multiple instances' do
267
267
  let(:app) do
268
268
  # ensure scoped for builder block, annoying...
269
- instance = flipper
269
+ flipper
270
270
  middleware = described_class
271
271
 
272
272
  Rack::Builder.new do
@@ -316,7 +316,7 @@ RSpec.describe Flipper::Middleware::Memoizer do
316
316
  context 'with flipper setup in env' do
317
317
  let(:app) do
318
318
  # ensure scoped for builder block, annoying...
319
- instance = flipper
319
+ flipper
320
320
  middleware = described_class
321
321
 
322
322
  Rack::Builder.new do
@@ -460,7 +460,6 @@ RSpec.describe Flipper::Middleware::Memoizer do
460
460
  cache.clear
461
461
  cached = Flipper::Adapters::ActiveSupportCacheStore.new(logged_memory, cache)
462
462
  logged_cached = Flipper::Adapters::OperationLogger.new(cached)
463
- memo = {}
464
463
  flipper = Flipper.new(logged_cached)
465
464
  flipper[:stats].enable
466
465
  flipper[:shiny].enable
@@ -30,9 +30,20 @@ RSpec.describe Flipper::Model::ActiveRecord do
30
30
  include Flipper::Model::ActiveRecord
31
31
  end
32
32
 
33
+ class DelegatedUser < DelegateClass(User)
34
+ end
35
+
33
36
  class Admin < User
34
37
  end
35
38
 
39
+ it "doesn't warn for to_ary" do
40
+ # looks like we should remove this but you are wrong, we have specs that
41
+ # fail if there are warnings and if this regresses it will print a warning
42
+ # so it is in fact testing something
43
+ user = User.create!(name: "Test")
44
+ Flipper.enabled?(:something, DelegatedUser.new(user))
45
+ end
46
+
36
47
  describe "flipper_id" do
37
48
  it "returns class name and id" do
38
49
  expect(User.new(id: 1).flipper_id).to eq("User;1")
data/spec/flipper_spec.rb CHANGED
@@ -313,7 +313,7 @@ RSpec.describe Flipper do
313
313
 
314
314
  describe '.group_exists' do
315
315
  it 'returns true if the group is already created' do
316
- group = described_class.register('admins', &:admin?)
316
+ described_class.register('admins', &:admin?)
317
317
  expect(described_class.group_exists?(:admins)).to eq(true)
318
318
  end
319
319
 
data/spec/spec_helper.rb CHANGED
@@ -2,12 +2,14 @@ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
2
2
 
3
3
  require 'pp'
4
4
  require 'pathname'
5
- FlipperRoot = Pathname(__FILE__).dirname.join('..').expand_path
6
-
7
- require 'rubygems'
8
- require 'bundler'
5
+ require 'bundler/setup'
9
6
 
10
- Bundler.setup(:default)
7
+ require 'warning'
8
+ Warning.ignore(/lib\/statsd/)
9
+ Warning.ignore(/lib\/debug\//)
10
+ Warning.ignore(/lib\/ice_age\//)
11
+ Warning.ignore(/lib\/moneta\//)
12
+ Warning.ignore(/lib\/mongo\//)
11
13
 
12
14
  require 'debug'
13
15
  require 'statsd'
@@ -20,6 +22,7 @@ require 'flipper/spec/shared_adapter_specs'
20
22
  require 'flipper/ui'
21
23
  require 'flipper/test_help'
22
24
 
25
+ FlipperRoot = Pathname(__FILE__).dirname.join('..').expand_path
23
26
  Dir[FlipperRoot.join('spec/support/**/*.rb')].sort.each { |f| require f }
24
27
 
25
28
  # Disable telemetry logging in specs.
@@ -17,6 +17,11 @@ class SetupGeneratorTest < Rails::Generators::TestCase
17
17
  end
18
18
  end
19
19
 
20
+ test "generates an initializer" do
21
+ run_generator
22
+ assert_file 'config/initializers/flipper.rb', /Flipper\.configure/
23
+ end
24
+
20
25
  test "does not invoke flipper:active_record generator if ActiveRecord adapter not defined" do
21
26
  # Ensure adapter not defined
22
27
  Flipper::Adapters.send(:remove_const, :ActiveRecord) rescue nil
@@ -65,7 +65,7 @@ class UpdateGeneratorTest < Rails::Generators::TestCase
65
65
 
66
66
  assert_migration "db/migrate/create_flipper_tables.rb" do |migration|
67
67
  assert_method :up, migration do |up|
68
- assert_match /text :value/, up
68
+ assert_match(/text :value/, up)
69
69
  end
70
70
  end
71
71
 
data/test_rails/helper.rb CHANGED
@@ -4,6 +4,9 @@ require 'minitest/autorun'
4
4
  require 'rails'
5
5
  require 'rails/test_help'
6
6
 
7
+ require 'warning'
8
+ Warning.ignore(/lib\/capybara\//)
9
+
7
10
  begin
8
11
  ActiveSupport::TestCase.test_order = :random
9
12
  rescue NoMethodError
@@ -6,6 +6,7 @@ return unless Rails::VERSION::MAJOR >= 7
6
6
  require "capybara/cuprite"
7
7
  require "flipper"
8
8
  require "flipper/test_help"
9
+ require "action_controller/railtie"
9
10
 
10
11
  require 'action_dispatch/system_testing/server'
11
12
  ActionDispatch::SystemTesting::Server.silence_puma = true
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flipper
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Nunemaker
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-09-04 00:00:00.000000000 Z
10
+ date: 2025-02-24 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: concurrent-ruby
@@ -24,7 +23,6 @@ dependencies:
24
23
  - - "<"
25
24
  - !ruby/object:Gem::Version
26
25
  version: '2'
27
- description:
28
26
  email: support@flippercloud.io
29
27
  executables:
30
28
  - flipper
@@ -193,6 +191,7 @@ files:
193
191
  - lib/flipper/types/percentage_of_time.rb
194
192
  - lib/flipper/version.rb
195
193
  - lib/generators/flipper/setup_generator.rb
194
+ - lib/generators/flipper/templates/initializer.rb
196
195
  - lib/generators/flipper/templates/update/migrations/01_create_flipper_tables.rb.erb
197
196
  - lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb
198
197
  - lib/generators/flipper/update_generator.rb
@@ -214,6 +213,7 @@ files:
214
213
  - spec/flipper/adapters/memoizable_spec.rb
215
214
  - spec/flipper/adapters/memory_spec.rb
216
215
  - spec/flipper/adapters/operation_logger_spec.rb
216
+ - spec/flipper/adapters/poll_spec.rb
217
217
  - spec/flipper/adapters/pstore_spec.rb
218
218
  - spec/flipper/adapters/read_only_spec.rb
219
219
  - spec/flipper/adapters/strict_spec.rb
@@ -314,8 +314,8 @@ metadata:
314
314
  homepage_uri: https://www.flippercloud.io
315
315
  source_code_uri: https://github.com/flippercloud/flipper
316
316
  bug_tracker_uri: https://github.com/flippercloud/flipper/issues
317
- changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.3.1
318
- post_install_message:
317
+ changelog_uri: https://github.com/flippercloud/flipper/releases/tag/v1.3.3
318
+ funding_uri: https://github.com/sponsors/flippercloud
319
319
  rdoc_options: []
320
320
  require_paths:
321
321
  - lib
@@ -330,8 +330,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
330
330
  - !ruby/object:Gem::Version
331
331
  version: '0'
332
332
  requirements: []
333
- rubygems_version: 3.5.18
334
- signing_key:
333
+ rubygems_version: 3.6.5
335
334
  specification_version: 4
336
335
  summary: Beautiful, performant feature flags for Ruby and Rails.
337
336
  test_files:
@@ -351,6 +350,7 @@ test_files:
351
350
  - spec/flipper/adapters/memoizable_spec.rb
352
351
  - spec/flipper/adapters/memory_spec.rb
353
352
  - spec/flipper/adapters/operation_logger_spec.rb
353
+ - spec/flipper/adapters/poll_spec.rb
354
354
  - spec/flipper/adapters/pstore_spec.rb
355
355
  - spec/flipper/adapters/read_only_spec.rb
356
356
  - spec/flipper/adapters/strict_spec.rb