flipper 0.26.2 → 0.28.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +61 -0
  3. data/Gemfile +2 -3
  4. data/benchmark/enabled_multiple_actors_ips.rb +20 -0
  5. data/examples/api/basic.ru +3 -4
  6. data/examples/api/custom_memoized.ru +3 -4
  7. data/examples/api/memoized.ru +3 -4
  8. data/examples/dsl.rb +3 -3
  9. data/examples/enabled_for_actor.rb +4 -2
  10. data/examples/mirroring.rb +59 -0
  11. data/lib/flipper/adapter.rb +23 -7
  12. data/lib/flipper/adapters/http.rb +11 -3
  13. data/lib/flipper/adapters/instrumented.rb +25 -2
  14. data/lib/flipper/adapters/memoizable.rb +19 -2
  15. data/lib/flipper/adapters/memory.rb +40 -16
  16. data/lib/flipper/adapters/operation_logger.rb +16 -3
  17. data/lib/flipper/dsl.rb +5 -9
  18. data/lib/flipper/errors.rb +3 -3
  19. data/lib/flipper/export.rb +26 -0
  20. data/lib/flipper/exporter.rb +17 -0
  21. data/lib/flipper/exporters/json/export.rb +32 -0
  22. data/lib/flipper/exporters/json/v1.rb +33 -0
  23. data/lib/flipper/feature.rb +12 -10
  24. data/lib/flipper/feature_check_context.rb +8 -4
  25. data/lib/flipper/gate.rb +12 -11
  26. data/lib/flipper/gates/actor.rb +11 -8
  27. data/lib/flipper/gates/group.rb +4 -2
  28. data/lib/flipper/gates/percentage_of_actors.rb +4 -5
  29. data/lib/flipper/identifier.rb +2 -2
  30. data/lib/flipper/instrumentation/log_subscriber.rb +24 -5
  31. data/lib/flipper/instrumentation/subscriber.rb +8 -1
  32. data/lib/flipper/poller.rb +1 -1
  33. data/lib/flipper/spec/shared_adapter_specs.rb +23 -0
  34. data/lib/flipper/test/shared_adapter_test.rb +24 -0
  35. data/lib/flipper/typecast.rb +17 -0
  36. data/lib/flipper/types/actor.rb +13 -13
  37. data/lib/flipper/types/group.rb +4 -4
  38. data/lib/flipper/version.rb +1 -1
  39. data/lib/flipper.rb +5 -4
  40. data/spec/fixtures/flipper_pstore_1679087600.json +46 -0
  41. data/spec/flipper/adapter_spec.rb +29 -2
  42. data/spec/flipper/adapters/http_spec.rb +25 -3
  43. data/spec/flipper/adapters/instrumented_spec.rb +28 -10
  44. data/spec/flipper/adapters/memoizable_spec.rb +30 -10
  45. data/spec/flipper/adapters/memory_spec.rb +11 -2
  46. data/spec/flipper/adapters/operation_logger_spec.rb +29 -10
  47. data/spec/flipper/dsl_spec.rb +25 -8
  48. data/spec/flipper/export_spec.rb +13 -0
  49. data/spec/flipper/exporter_spec.rb +16 -0
  50. data/spec/flipper/exporters/json/export_spec.rb +60 -0
  51. data/spec/flipper/exporters/json/v1_spec.rb +33 -0
  52. data/spec/flipper/feature_check_context_spec.rb +5 -5
  53. data/spec/flipper/feature_spec.rb +76 -32
  54. data/spec/flipper/gates/boolean_spec.rb +1 -1
  55. data/spec/flipper/gates/group_spec.rb +2 -3
  56. data/spec/flipper/gates/percentage_of_actors_spec.rb +61 -5
  57. data/spec/flipper/gates/percentage_of_time_spec.rb +2 -2
  58. data/spec/flipper/instrumentation/log_subscriber_spec.rb +15 -5
  59. data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +10 -0
  60. data/spec/flipper/typecast_spec.rb +79 -0
  61. data/spec/flipper/types/actor_spec.rb +45 -45
  62. data/spec/flipper/types/group_spec.rb +2 -2
  63. data/spec/flipper_integration_spec.rb +62 -50
  64. data/spec/flipper_spec.rb +7 -1
  65. data/spec/support/skippable.rb +18 -0
  66. metadata +20 -2
@@ -2,10 +2,10 @@ module Flipper
2
2
  # Top level error that all other errors inherit from.
3
3
  class Error < StandardError; end
4
4
 
5
- # Raised when gate can not be found for a thing.
5
+ # Raised when gate can not be found for an actor.
6
6
  class GateNotFound < Error
7
- def initialize(thing)
8
- super "Could not find gate for #{thing.inspect}"
7
+ def initialize(actor)
8
+ super "Could not find gate for #{actor.inspect}"
9
9
  end
10
10
  end
11
11
 
@@ -0,0 +1,26 @@
1
+ require "flipper/adapters/memory"
2
+
3
+ module Flipper
4
+ class Export
5
+ attr_reader :contents, :format, :version
6
+
7
+ def initialize(contents:, format: :json, version: 1)
8
+ @contents = contents
9
+ @format = format
10
+ @version = version
11
+ end
12
+
13
+ def features
14
+ raise NotImplementedError
15
+ end
16
+
17
+ def adapter
18
+ @adapter ||= Flipper::Adapters::Memory.new(features)
19
+ end
20
+
21
+ def eql?(other)
22
+ self.class.eql?(other.class) && @contents == other.contents && @format == other.format && @version == other.version
23
+ end
24
+ alias_method :==, :eql?
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ require "flipper/exporters/json/v1"
2
+
3
+ module Flipper
4
+ module Exporter
5
+ extend self
6
+
7
+ FORMATTERS = {
8
+ json: {
9
+ 1 => Flipper::Exporters::Json::V1,
10
+ }
11
+ }.freeze
12
+
13
+ def build(format: :json, version: 1)
14
+ FORMATTERS.fetch(format).fetch(version).new
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,32 @@
1
+ require "flipper/export"
2
+ require "flipper/typecast"
3
+
4
+ module Flipper
5
+ module Exporters
6
+ module Json
7
+ # Raised when the contents of the export are not valid.
8
+ class InvalidError < StandardError; end
9
+ class JsonError < InvalidError; end
10
+
11
+ # Internal: JSON export class that knows how to build features hash
12
+ # from data.
13
+ class Export < ::Flipper::Export
14
+ def initialize(contents:, version: 1)
15
+ super contents: contents, version: version, format: :json
16
+ end
17
+
18
+ # Public: The features hash identical to calling get_all on adapter.
19
+ def features
20
+ @features ||= begin
21
+ features = JSON.parse(contents).fetch("features")
22
+ Typecast.features_hash(features)
23
+ rescue JSON::ParserError
24
+ raise JsonError
25
+ rescue
26
+ raise InvalidError
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ require "json"
2
+ require "flipper/exporters/json/export"
3
+
4
+ module Flipper
5
+ module Exporters
6
+ module Json
7
+ class V1
8
+ VERSION = 1
9
+
10
+ def call(adapter)
11
+ features = adapter.get_all
12
+
13
+ # Convert sets to arrays for json
14
+ features.each do |feature_key, gates|
15
+ gates.each do |key, value|
16
+ case value
17
+ when Set
18
+ features[feature_key][key] = value.to_a
19
+ end
20
+ end
21
+ end
22
+
23
+ json = JSON.dump({
24
+ version: VERSION,
25
+ features: features,
26
+ })
27
+
28
+ Json::Export.new(contents: json, version: VERSION)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -96,17 +96,19 @@ module Flipper
96
96
  instrument(:clear) { adapter.clear(self) }
97
97
  end
98
98
 
99
- # Public: Check if a feature is enabled for a thing.
99
+ # Public: Check if a feature is enabled for zero or more actors.
100
100
  #
101
101
  # Returns true if enabled, false if not.
102
- def enabled?(thing = nil)
103
- thing = Types::Actor.wrap(thing) unless thing.nil?
102
+ def enabled?(*actors)
103
+ actors = actors.flatten.compact.map { |actor| Types::Actor.wrap(actor) }
104
+ actors = nil if actors.empty?
104
105
 
105
- instrument(:enabled?, thing: thing) do |payload|
106
+ # thing is left for backwards compatibility
107
+ instrument(:enabled?, thing: actors&.first, actors: actors) do |payload|
106
108
  context = FeatureCheckContext.new(
107
109
  feature_name: @name,
108
110
  values: gate_values,
109
- thing: thing
111
+ actors: actors
110
112
  )
111
113
 
112
114
  if open_gate = gates.detect { |gate| gate.open?(context) }
@@ -359,14 +361,14 @@ module Flipper
359
361
  gates_hash[name.to_sym]
360
362
  end
361
363
 
362
- # Public: Find the gate that protects a thing.
364
+ # Public: Find the gate that protects an actor.
363
365
  #
364
- # thing - The object for which you would like to find a gate
366
+ # actor - The object for which you would like to find a gate
365
367
  #
366
368
  # Returns a Flipper::Gate.
367
- # Raises Flipper::GateNotFound if no gate found for thing
368
- def gate_for(thing)
369
- gates.detect { |gate| gate.protects?(thing) } || raise(GateNotFound, thing)
369
+ # Raises Flipper::GateNotFound if no gate found for actor
370
+ def gate_for(actor)
371
+ gates.detect { |gate| gate.protects?(actor) } || raise(GateNotFound, actor)
370
372
  end
371
373
 
372
374
  private
@@ -7,13 +7,17 @@ module Flipper
7
7
  # gates for the feature.
8
8
  attr_reader :values
9
9
 
10
- # Public: The thing we want to know if a feature is enabled for.
11
- attr_reader :thing
10
+ # Public: The actors we want to know if a feature is enabled for.
11
+ attr_reader :actors
12
12
 
13
- def initialize(feature_name:, values:, thing:)
13
+ def initialize(feature_name:, values:, actors:)
14
14
  @feature_name = feature_name
15
15
  @values = values
16
- @thing = thing
16
+ @actors = actors
17
+ end
18
+
19
+ def actors?
20
+ !@actors.nil? && !@actors.empty?
17
21
  end
18
22
 
19
23
  # Public: Convenience method for groups value like Feature has.
data/lib/flipper/gate.rb CHANGED
@@ -18,28 +18,29 @@ module Flipper
18
18
  raise 'Not implemented'
19
19
  end
20
20
 
21
- def enabled?(_value)
21
+ def enabled?(value)
22
22
  raise 'Not implemented'
23
23
  end
24
24
 
25
- # Internal: Check if a gate is open for a thing. Implemented in subclass.
25
+ # Internal: Check if a gate is open for one or more actors. Implemented
26
+ # in subclass.
26
27
  #
27
- # Returns true if gate open for thing, false if not.
28
- def open?(_thing, _value, _options = {})
28
+ # Returns true if gate open for any actor, false if not.
29
+ def open?(actors, value, options = {})
29
30
  false
30
31
  end
31
32
 
32
- # Internal: Check if a gate is protects a thing. Implemented in subclass.
33
+ # Internal: Check if a gate is protects an actor. Implemented in subclass.
33
34
  #
34
- # Returns true if gate protects thing, false if not.
35
- def protects?(_thing)
35
+ # Returns true if gate protects actor, false if not.
36
+ def protects?(actor)
36
37
  false
37
38
  end
38
39
 
39
- # Internal: Allows gate to wrap thing using one of the supported flipper
40
- # types so adapters always get something that responds to value.
41
- def wrap(thing)
42
- thing
40
+ # Internal: Allows gate to wrap actor using one of the supported flipper
41
+ # types so adapters always get someactor that responds to value.
42
+ def wrap(actor)
43
+ actor
43
44
  end
44
45
 
45
46
  # Public: Pretty string version for debugging.
@@ -19,20 +19,23 @@ module Flipper
19
19
  !value.empty?
20
20
  end
21
21
 
22
- # Internal: Checks if the gate is open for a thing.
22
+ # Internal: Checks if the gate is open for an actor.
23
23
  #
24
- # Returns true if gate open for thing, false if not.
24
+ # Returns true if gate open for actor, false if not.
25
25
  def open?(context)
26
- return false if context.thing.nil?
27
- context.values.actors.include?(context.thing.value)
26
+ return false unless context.actors?
27
+
28
+ context.actors.any? do |actor|
29
+ context.values.actors.include?(actor.value)
30
+ end
28
31
  end
29
32
 
30
- def wrap(thing)
31
- Types::Actor.wrap(thing)
33
+ def wrap(actor)
34
+ Types::Actor.wrap(actor)
32
35
  end
33
36
 
34
- def protects?(thing)
35
- Types::Actor.wrappable?(thing)
37
+ def protects?(actor)
38
+ Types::Actor.wrappable?(actor)
36
39
  end
37
40
  end
38
41
  end
@@ -23,10 +23,12 @@ module Flipper
23
23
  #
24
24
  # Returns true if gate open for thing, false if not.
25
25
  def open?(context)
26
- return false if context.thing.nil?
26
+ return false unless context.actors?
27
27
 
28
28
  context.values.groups.any? do |name|
29
- Flipper.group(name).match?(context.thing, context)
29
+ context.actors.any? do |actor|
30
+ Flipper.group(name).match?(actor, context)
31
+ end
30
32
  end
31
33
  end
32
34
 
@@ -26,13 +26,12 @@ module Flipper
26
26
  SCALING_FACTOR = 1_000
27
27
  private_constant :SCALING_FACTOR
28
28
 
29
- # Internal: Checks if the gate is open for a thing.
29
+ # Internal: Checks if the gate is open for one or more actors.
30
30
  #
31
- # Returns true if gate open for thing, false if not.
31
+ # Returns true if gate open for any actors, false if not.
32
32
  def open?(context)
33
- return false if context.thing.nil?
34
-
35
- id = "#{context.feature_name}#{context.thing.value}"
33
+ return false unless context.actors?
34
+ id = "#{context.feature_name}#{context.actors.map(&:value).sort.join}"
36
35
  Zlib.crc32(id) % (100 * SCALING_FACTOR) < context.values.percentage_of_actors * SCALING_FACTOR
37
36
  end
38
37
 
@@ -6,8 +6,8 @@ module Flipper
6
6
  # end
7
7
  #
8
8
  # user = User.new(99)
9
- # Flipper.enable :thing, user
10
- # Flipper.enabled? :thing, user #=> true
9
+ # Flipper.enable :some_feature, user
10
+ # Flipper.enabled? :some_feature, user #=> true
11
11
  #
12
12
  module Identifier
13
13
  def flipper_id
@@ -10,7 +10,7 @@ module Flipper
10
10
  # Example Output
11
11
  #
12
12
  # flipper[:search].enabled?(user)
13
- # # Flipper feature(search) enabled? false (1.2ms) [ thing=... ]
13
+ # # Flipper feature(search) enabled? false (1.2ms) [ actors=... ]
14
14
  #
15
15
  # Returns nothing.
16
16
  def feature_operation(event)
@@ -20,15 +20,19 @@ module Flipper
20
20
  gate_name = event.payload[:gate_name]
21
21
  operation = event.payload[:operation]
22
22
  result = event.payload[:result]
23
- thing = event.payload[:thing]
24
23
 
25
24
  description = "Flipper feature(#{feature_name}) #{operation} #{result.inspect}"
26
- details = "thing=#{thing.inspect}"
25
+
26
+ details = if event.payload.key?(:actors)
27
+ "actors=#{event.payload[:actors].inspect}"
28
+ else
29
+ "thing=#{event.payload[:thing].inspect}"
30
+ end
27
31
 
28
32
  details += " gate_name=#{gate_name}" unless gate_name.nil?
29
33
 
30
34
  name = '%s (%.1fms)' % [description, event.duration]
31
- debug " #{color(name, CYAN, true)} [ #{details} ]"
35
+ debug " #{color_name(name)} [ #{details} ]"
32
36
  end
33
37
 
34
38
  # Logs an adapter operation. If operation is for a feature, then that
@@ -60,12 +64,27 @@ module Flipper
60
64
  details = "result=#{result.inspect}"
61
65
 
62
66
  name = '%s (%.1fms)' % [description, event.duration]
63
- debug " #{color(name, CYAN, true)} [ #{details} ]"
67
+ debug " #{color_name(name)} [ #{details} ]"
64
68
  end
65
69
 
66
70
  def logger
67
71
  self.class.logger
68
72
  end
73
+
74
+ private
75
+
76
+ # Rails 7.1 changed the signature of this function.
77
+ # Checking if > 7.0.99 rather than >= 7.1 so that 7.1 pre-release versions are included.
78
+ COLOR_OPTIONS = if Rails.gem_version > Gem::Version.new('7.0.99')
79
+ { bold: true }.freeze
80
+ else
81
+ true
82
+ end
83
+ private_constant :COLOR_OPTIONS
84
+
85
+ def color_name(name)
86
+ color(name, CYAN, COLOR_OPTIONS)
87
+ end
69
88
  end
70
89
  end
71
90
 
@@ -45,7 +45,6 @@ module Flipper
45
45
  gate_name = @payload[:gate_name]
46
46
  operation = strip_trailing_question_mark(@payload[:operation])
47
47
  result = @payload[:result]
48
- thing = @payload[:thing]
49
48
 
50
49
  update_timer "flipper.feature_operation.#{operation}"
51
50
 
@@ -72,6 +71,14 @@ module Flipper
72
71
  update_timer "flipper.adapter.#{adapter_name}.#{operation}"
73
72
  end
74
73
 
74
+ def update_poller_metrics
75
+ # noop
76
+ end
77
+
78
+ def update_synchronizer_call_metrics
79
+ # noop
80
+ end
81
+
75
82
  QUESTION_MARK = '?'.freeze
76
83
 
77
84
  # Private
@@ -28,7 +28,7 @@ module Flipper
28
28
  @remote_adapter = options.fetch(:remote_adapter)
29
29
  @interval = options.fetch(:interval, 10).to_f
30
30
  @last_synced_at = Concurrent::AtomicFixnum.new(0)
31
- @adapter = Adapters::Memory.new
31
+ @adapter = Adapters::Memory.new(nil, threadsafe: true)
32
32
 
33
33
  if @interval < 1
34
34
  warn "Flipper::Cloud poll interval must be greater than or equal to 1 but was #{@interval}. Setting @interval to 1."
@@ -33,7 +33,15 @@ RSpec.shared_examples_for 'a flipper adapter' do
33
33
  expect(subject.class.ancestors).to include(Flipper::Adapter)
34
34
  end
35
35
 
36
+ it 'knows how to get adapter from source' do
37
+ adapter = Flipper::Adapters::Memory.new
38
+ flipper = Flipper.new(adapter)
39
+ expect(subject.class.from(adapter).class.ancestors).to include(Flipper::Adapter)
40
+ expect(subject.class.from(flipper).class.ancestors).to include(Flipper::Adapter)
41
+ end
42
+
36
43
  it 'returns correct default values for the gates if none are enabled' do
44
+ expect(subject.get(feature)).to eq(subject.class.default_config)
37
45
  expect(subject.get(feature)).to eq(subject.default_config)
38
46
  end
39
47
 
@@ -304,4 +312,19 @@ RSpec.shared_examples_for 'a flipper adapter' do
304
312
  subject.enable(feature, boolean_gate, flipper.boolean(true))
305
313
  expect(subject.get(feature)).to eq(subject.default_config.merge(boolean: "true"))
306
314
  end
315
+
316
+ it 'can import and export' do
317
+ adapter = Flipper::Adapters::Memory.new
318
+ source_flipper = Flipper.new(adapter)
319
+ source_flipper.enable(:stats)
320
+ export = adapter.export
321
+
322
+ # some adapters cannot import so if they return false lets assert it
323
+ # didn't happen
324
+ if subject.import(export)
325
+ expect(flipper[:stats]).to be_enabled
326
+ else
327
+ expect(flipper[:stats]).not_to be_enabled
328
+ end
329
+ end
307
330
  end
@@ -34,7 +34,16 @@ module Flipper
34
34
  assert_includes @adapter.class.ancestors, Flipper::Adapter
35
35
  end
36
36
 
37
+ def test_knows_how_to_get_adapter_from_source
38
+ adapter = Flipper::Adapters::Memory.new
39
+ flipper = Flipper.new(adapter)
40
+
41
+ assert_includes adapter.class.from(adapter).class.ancestors, Flipper::Adapter
42
+ assert_includes adapter.class.from(flipper).class.ancestors, Flipper::Adapter
43
+ end
44
+
37
45
  def test_returns_correct_default_values_for_gates_if_none_are_enabled
46
+ assert_equal @adapter.class.default_config, @adapter.get(@feature)
38
47
  assert_equal @adapter.default_config, @adapter.get(@feature)
39
48
  end
40
49
 
@@ -300,6 +309,21 @@ module Flipper
300
309
  assert_equal true, @adapter.enable(@feature, @boolean_gate, @flipper.boolean(true))
301
310
  assert_equal @adapter.default_config.merge(boolean: "true"), @adapter.get(@feature)
302
311
  end
312
+
313
+ def test_can_import_and_export
314
+ adapter = Flipper::Adapters::Memory.new
315
+ source_flipper = Flipper.new(adapter)
316
+ source_flipper.enable(:stats)
317
+ export = adapter.export
318
+
319
+ # some adapters cannot import so if they return false lets assert it
320
+ # didn't happen
321
+ if @adapter.import(export)
322
+ assert @flipper[:stats].enabled?
323
+ else
324
+ refute @flipper[:stats].enabled?
325
+ end
326
+ end
303
327
  end
304
328
  end
305
329
  end
@@ -62,5 +62,22 @@ module Flipper
62
62
  raise ArgumentError, "#{value.inspect} cannot be converted to a set"
63
63
  end
64
64
  end
65
+
66
+ def self.features_hash(source)
67
+ normalized_source = {}
68
+ (source || {}).each do |feature_key, gates|
69
+ normalized_source[feature_key] ||= {}
70
+ gates.each do |gate_key, value|
71
+ normalized_value = case value
72
+ when Array, Set
73
+ value.to_set
74
+ else
75
+ value ? value.to_s : value
76
+ end
77
+ normalized_source[feature_key][gate_key.to_sym] = normalized_value
78
+ end
79
+ end
80
+ normalized_source
81
+ end
65
82
  end
66
83
  end
@@ -1,35 +1,35 @@
1
1
  module Flipper
2
2
  module Types
3
3
  class Actor < Type
4
- def self.wrappable?(thing)
5
- return false if thing.nil?
6
- thing.respond_to?(:flipper_id)
4
+ def self.wrappable?(actor)
5
+ return false if actor.nil?
6
+ actor.respond_to?(:flipper_id)
7
7
  end
8
8
 
9
- attr_reader :thing
9
+ attr_reader :actor
10
10
 
11
- def initialize(thing)
12
- raise ArgumentError, 'thing cannot be nil' if thing.nil?
11
+ def initialize(actor)
12
+ raise ArgumentError, 'actor cannot be nil' if actor.nil?
13
13
 
14
- unless thing.respond_to?(:flipper_id)
15
- raise ArgumentError, "#{thing.inspect} must respond to flipper_id, but does not"
14
+ unless actor.respond_to?(:flipper_id)
15
+ raise ArgumentError, "#{actor.inspect} must respond to flipper_id, but does not"
16
16
  end
17
17
 
18
- @thing = thing
19
- @value = thing.flipper_id.to_s
18
+ @actor = actor
19
+ @value = actor.flipper_id.to_s
20
20
  end
21
21
 
22
22
  def respond_to?(*args)
23
- super || @thing.respond_to?(*args)
23
+ super || @actor.respond_to?(*args)
24
24
  end
25
25
 
26
26
  if RUBY_VERSION >= '3.0'
27
27
  def method_missing(name, *args, **kwargs, &block)
28
- @thing.send name, *args, **kwargs, &block
28
+ @actor.send name, *args, **kwargs, &block
29
29
  end
30
30
  else
31
31
  def method_missing(name, *args, &block)
32
- @thing.send name, *args, &block
32
+ @actor.send name, *args, &block
33
33
  end
34
34
  end
35
35
  end
@@ -16,16 +16,16 @@ module Flipper
16
16
  @block = block
17
17
  @single_argument = call_with_no_context?(@block)
18
18
  else
19
- @block = ->(_thing, _context) { false }
19
+ @block = ->(actor, context) { false }
20
20
  @single_argument = false
21
21
  end
22
22
  end
23
23
 
24
- def match?(thing, context)
24
+ def match?(actor, context)
25
25
  if @single_argument
26
- @block.call(thing)
26
+ @block.call(actor)
27
27
  else
28
- @block.call(thing, context)
28
+ @block.call(actor, context)
29
29
  end
30
30
  end
31
31
 
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = '0.26.2'.freeze
2
+ VERSION = '0.28.3'.freeze
3
3
  end
data/lib/flipper.rb CHANGED
@@ -64,7 +64,7 @@ module Flipper
64
64
  :enable_percentage_of_time, :disable_percentage_of_time,
65
65
  :time, :percentage_of_time,
66
66
  :features, :feature, :[], :preload, :preload_all,
67
- :adapter, :add, :exist?, :remove, :import,
67
+ :adapter, :add, :exist?, :remove, :import, :export,
68
68
  :memoize=, :memoizing?,
69
69
  :sync, :sync_secret # For Flipper::Cloud. Will error for OSS Flipper.
70
70
 
@@ -72,12 +72,12 @@ module Flipper
72
72
  #
73
73
  # name - The Symbol name of the group.
74
74
  # block - The block that should be used to determine if the group matches a
75
- # given thing.
75
+ # given actor.
76
76
  #
77
77
  # Examples
78
78
  #
79
- # Flipper.register(:admins) { |thing|
80
- # thing.respond_to?(:admin?) && thing.admin?
79
+ # Flipper.register(:admins) { |actor|
80
+ # actor.respond_to?(:admin?) && actor.admin?
81
81
  # }
82
82
  #
83
83
  # Returns a Flipper::Group.
@@ -165,5 +165,6 @@ require 'flipper/types/percentage'
165
165
  require 'flipper/types/percentage_of_actors'
166
166
  require 'flipper/types/percentage_of_time'
167
167
  require 'flipper/typecast'
168
+ require 'flipper/version'
168
169
 
169
170
  require "flipper/railtie" if defined?(Rails::Railtie)
@@ -0,0 +1,46 @@
1
+ {
2
+ "version": 1,
3
+ "features": {
4
+ "search": {
5
+ "boolean": null,
6
+ "actors": [
7
+ "john",
8
+ "another",
9
+ "testing"
10
+ ],
11
+ "percentage_of_actors": null,
12
+ "percentage_of_time": null,
13
+ "groups": [
14
+ "admins"
15
+ ]
16
+ },
17
+ "new_pricing": {
18
+ "boolean": "true",
19
+ "actors": [],
20
+ "percentage_of_actors": null,
21
+ "percentage_of_time": null,
22
+ "groups": []
23
+ },
24
+ "google_analytics_tag": {
25
+ "boolean": null,
26
+ "actors": [],
27
+ "percentage_of_actors": "100",
28
+ "percentage_of_time": null,
29
+ "groups": []
30
+ },
31
+ "help_scout_tag": {
32
+ "boolean": null,
33
+ "actors": [],
34
+ "percentage_of_actors": null,
35
+ "percentage_of_time": "50",
36
+ "groups": []
37
+ },
38
+ "nope": {
39
+ "boolean": null,
40
+ "actors": [],
41
+ "percentage_of_actors": null,
42
+ "percentage_of_time": null,
43
+ "groups": []
44
+ }
45
+ }
46
+ }