Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/flipper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ def groups_registry=(registry)

require 'flipper/actor'
require 'flipper/adapter'
require 'flipper/adapters/wrapper'
require 'flipper/adapters/memoizable'
require 'flipper/adapters/memory'
require 'flipper/adapters/strict'
Expand Down
98 changes: 12 additions & 86 deletions lib/flipper/adapters/operation_logger.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,108 +5,27 @@ module Adapters
# Public: Adapter that wraps another adapter and stores the operations.
#
# Useful in tests to verify calls and such. Never use outside of testing.
class OperationLogger
include Flipper::Adapter
class OperationLogger < Wrapper

class Operation
attr_reader :type, :args
attr_reader :type, :args, :kwargs

def initialize(type, args)
def initialize(type, args, kwargs = {})
@type = type
@args = args
@kwargs = kwargs
end
end

OperationTypes = [
:import,
:export,
:features,
:add,
:remove,
:clear,
:get,
:get_multi,
:get_all,
:enable,
:disable,
].freeze

# Internal: An array of the operations that have happened.
attr_reader :operations

# Public
def initialize(adapter, operations = nil)
@adapter = adapter
super(adapter)
@operations = operations || []
end

# Public: The set of known features.
def features
@operations << Operation.new(:features, [])
@adapter.features
end

# Public: Adds a feature to the set of known features.
def add(feature)
@operations << Operation.new(:add, [feature])
@adapter.add(feature)
end

# Public: Removes a feature from the set of known features and clears
# all the values for the feature.
def remove(feature)
@operations << Operation.new(:remove, [feature])
@adapter.remove(feature)
end

# Public: Clears all the gate values for a feature.
def clear(feature)
@operations << Operation.new(:clear, [feature])
@adapter.clear(feature)
end

# Public
def get(feature)
@operations << Operation.new(:get, [feature])
@adapter.get(feature)
end

# Public
def get_multi(features)
@operations << Operation.new(:get_multi, [features])
@adapter.get_multi(features)
end

# Public
def get_all
@operations << Operation.new(:get_all, [])
@adapter.get_all
end

# Public
def enable(feature, gate, thing)
@operations << Operation.new(:enable, [feature, gate, thing])
@adapter.enable(feature, gate, thing)
end

# Public
def disable(feature, gate, thing)
@operations << Operation.new(:disable, [feature, gate, thing])
@adapter.disable(feature, gate, thing)
end

# Public
def import(source)
@operations << Operation.new(:import, [source])
@adapter.import(source)
end

# Public
def export(format: :json, version: 1)
@operations << Operation.new(:export, [format, version])
@adapter.export(format: format, version: version)
end

# Public: Count the number of times a certain operation happened.
def count(type)
type(type).size
Expand All @@ -131,6 +50,13 @@ def inspect
inspect_id = ::Kernel::format "%x", (object_id * 2)
%(#<#{self.class}:0x#{inspect_id} @name=#{name.inspect}, @operations=#{@operations.inspect}, @adapter=#{@adapter.inspect}>)
end

private

def wrap(method, *args, **kwargs, &block)
@operations << Operation.new(method, args, kwargs)
block.call
end
end
end
end
45 changes: 6 additions & 39 deletions lib/flipper/adapters/read_only.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,25 @@
module Flipper
module Adapters
# Public: Adapter that wraps another adapter and raises for any writes.
class ReadOnly
include ::Flipper::Adapter
class ReadOnly < Wrapper
WriteMethods = %i[add remove clear enable disable]

class WriteAttempted < Error
def initialize(message = nil)
super(message || 'write attempted while in read only mode')
end
end

# Public
def initialize(adapter)
@adapter = adapter
end

def features
@adapter.features
end

def read_only?
true
end

def get(feature)
@adapter.get(feature)
end

def get_multi(features)
@adapter.get_multi(features)
end

def get_all
@adapter.get_all
end

def add(_feature)
raise WriteAttempted
end

def remove(_feature)
raise WriteAttempted
end

def clear(_feature)
raise WriteAttempted
end
private

def enable(_feature, _gate, _thing)
raise WriteAttempted
end
def wrap(method, *args, **kwargs)
raise WriteAttempted if WriteMethods.include?(method)

def disable(_feature, _gate, _thing)
raise WriteAttempted
yield
end
end
end
Expand Down
15 changes: 5 additions & 10 deletions lib/flipper/adapters/strict.rb
Original file line number Diff line number Diff line change
@@ -1,33 +1,28 @@
module Flipper
module Adapters
# An adapter that ensures a feature exists before checking it.
class Strict
extend Forwardable
include ::Flipper::Adapter
attr_reader :name, :adapter, :handler
class Strict < Wrapper
attr_reader :handler

class NotFound < ::Flipper::Error
def initialize(name)
super "Could not find feature #{name.inspect}. Call `Flipper.add(#{name.inspect})` to create it."
end
end

def_delegators :@adapter, :features, :get_all, :add, :remove, :clear, :enable, :disable

def initialize(adapter, handler = nil, &block)
@name = :strict
@adapter = adapter
super(adapter)
@handler = block || handler
end

def get(feature)
assert_feature_exists(feature)
@adapter.get(feature)
super
end

def get_multi(features)
features.each { |feature| assert_feature_exists(feature) }
@adapter.get_multi(features)
super
end

private
Expand Down
54 changes: 54 additions & 0 deletions lib/flipper/adapters/wrapper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
module Flipper
module Adapters
# A base class for any adapter that wraps another adapter. By default, all methods
# delegate to the wrapped adapter. Implement `#wrap` to customize the behavior of
# all delegated methods, or override individual methods as needed.
class Wrapper
include Flipper::Adapter

Methods = [
:import,
:export,
:features,
:add,
:remove,
:clear,
:get,
:get_multi,
:get_all,
:enable,
:disable,
].freeze

attr_reader :adapter

def initialize(adapter)
@adapter = adapter
end

Methods.each do |method|
if RUBY_VERSION >= '3.0'
define_method(method) do |*args, **kwargs|
wrap(method, *args, **kwargs) { @adapter.public_send(method, *args, **kwargs) }
end
else
define_method(method) do |*args|
wrap(method, *args) { @adapter.public_send(method, *args) }
end
end
end

# Override this method to customize the behavior of all delegated methods, and just yield to
# the block to call the wrapped adapter.
if RUBY_VERSION >= '3.0'
def wrap(method, *args, **kwargs, &block)
block.call
end
else
def wrap(method, *args, &block)
block.call
end
end
end
end
end