Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement interface and proxy features #1

Merged
merged 3 commits into from
Jun 23, 2024
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
## [Unreleased]

### Added

- Add `Solid::Adapters::Interface`
- Add `Solid::Adapters::Proxy`
- Add `Solid::Adapters::Configurable`
- Add `Solid::Adapters.config`, `Solid::Adapters.configuration` (alias `.configure`) to toggle the gem's features.
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ gem "standard", "~> 1.37"

gem "minitest", "~> 5.24"

gem "mocha", "~> 2.4", require: false

gem "simplecov", "~> 0.22.0", require: false
20 changes: 20 additions & 0 deletions lib/solid/adapters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,25 @@

module Solid
module Adapters
module Core
require_relative "adapters/core/config"
require_relative "adapters/core/proxy"
end

require_relative "adapters/configurable"
require_relative "adapters/interface"
require_relative "adapters/proxy"

def self.config
Core::Config.instance
end

def self.configuration(freeze: true)
yield(config)

config.tap { _1.freeze if freeze }
end

singleton_class.send(:alias_method, :configure, :configuration)
end
end
19 changes: 19 additions & 0 deletions lib/solid/adapters/configurable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

module Solid::Adapters
module Configurable
require_relative "configurable/options"

def config
@config ||= Options.new
end

def configuration(freeze: true)
yield(config)

config.tap { _1.freeze if freeze }
end

alias_method :configure, :configuration
end
end
44 changes: 44 additions & 0 deletions lib/solid/adapters/configurable/options.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

class Solid::Adapters::Configurable::Options
MapOption = ->(key) { key.end_with?("=") ? key[0..-2].to_sym : key }
MapValue = ->(value) { value.is_a?(::Proc) ? value.call : value }

def initialize(**options)
@options = options
end

def to_h
@options.dup
end

def key?(name)
@options.key?(name)
end

def [](name)
@options[name].then(&MapValue)
end

def fetch(name, &block)
@options.fetch(name, &block).then(&MapValue)
end

def method_missing(method_name, value = nil, &block)
return fetch(method_name) { super } if !method_name.end_with?("=") && !block

option_name = MapOption[method_name]

@options[option_name] = block || value
end

def respond_to_missing?(method_name, include_private = false)
(method_name.end_with?("=") || key?(method_name)) || super
end

def freeze
@options.freeze

super
end
end
35 changes: 35 additions & 0 deletions lib/solid/adapters/core/config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

module Solid::Adapters::Core
class Config
attr_accessor :proxy_enabled, :interface_enabled

def initialize(proxy_enabled: true, interface_enabled: true)
self.proxy_enabled = proxy_enabled
self.interface_enabled = interface_enabled
end

def proxy_enabled?
proxy_enabled
end

def interface_enabled?
interface_enabled
end

def options
{
proxy_enabled: proxy_enabled,
interface_enabled: interface_enabled
}
end

def inspect
"#<#{self.class.name} proxy_enabled=#{proxy_enabled}, interface_enabled=#{interface_enabled}>"
end

@instance = new

singleton_class.send(:attr_reader, :instance)
end
end
25 changes: 25 additions & 0 deletions lib/solid/adapters/core/proxy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module Solid::Adapters::Core
module Proxy
module ClassMethods
def [](object)
new(object)
end

def to_proc
->(object) { new(object) }
end
end

class Base
extend ClassMethods

attr_reader :object

def initialize(object)
@object = object
end
end
end
end
57 changes: 57 additions & 0 deletions lib/solid/adapters/interface.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# frozen_string_literal: true

require "delegate"

module Solid::Adapters
module Interface
module Callbacks
def extended(impl)
impl.singleton_class.prepend(self::Methods)
end

def included(impl)
impl.prepend(self::Methods)
end
end

module ClassMethods
def [](object)
const_get(:Proxy, false).new(object).extend(self)
end
end

module ProxyDisabled
extend Core::Proxy::ClassMethods

def self.new(object)
object
end
end

DEFINE = lambda do |interface, enabled:|
proxy = ProxyDisabled

if enabled
proxy = ::Class.new(::SimpleDelegator)
proxy.extend(Core::Proxy::ClassMethods)

interface.extend(Callbacks)
end

interface.const_set(:Proxy, proxy)
interface.extend(ClassMethods)
end

def self.included(interface)
DEFINE[interface, enabled: Core::Config.instance.interface_enabled]
end

module AlwaysEnabled
def self.included(interface)
DEFINE[interface, enabled: true]
end
end

private_constant :Callbacks, :ClassMethods, :ProxyDisabled, :DEFINE
end
end
11 changes: 11 additions & 0 deletions lib/solid/adapters/proxy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

module Solid::Adapters
class Proxy < Core::Proxy::Base
AlwaysEnabled = ::Class.new(Core::Proxy::Base)

def self.new(object)
Core::Config.instance.proxy_enabled ? super : object
end
end
end
101 changes: 101 additions & 0 deletions test/solid/adapters/configurable_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# frozen_string_literal: true

require "test_helper"

class Solid::Adapters::ConfigurableTest < Minitest::Test
class Foo
extend Solid::Adapters::Configurable

NUMBERS = [1, 2, 3].freeze

config.bar = nil
config.biz = nil
config.number { NUMBERS.sample }
end

module Bar
extend Solid::Adapters::Configurable

config.foo = nil
end

test ".config" do
assert_nil Foo.config.bar

refute Foo.config.key?(:foo)
assert Foo.config.key?(:bar)

number = Foo.config.number

assert_includes Foo::NUMBERS, number

assert_equal([:bar, :biz, :number], Foo.config.to_h.keys.sort)
assert_nil(Foo.config.to_h[:bar])
assert_nil(Foo.config.to_h[:biz])
assert_kind_of(Proc, Foo.config.to_h[:number])

assert_raises(KeyError) { Foo.config.fetch(:foo) }
assert_equal "default", Foo.config.fetch(:foo) { "default" }

assert_nil Foo.config[:bar]

assert_nil Foo.config.fetch(:bar)
assert_nil(Foo.config.fetch(:bar) { "default" })

assert_nil Foo.config.bar
assert_nil Foo.config.biz

assert_respond_to Foo.config, :bar
assert_respond_to Foo.config, :bar=
assert_respond_to Foo.config, :biz
assert_respond_to Foo.config, :biz=

assert_respond_to Foo.config, :foo=
refute_respond_to Foo.config, :foo

Foo.config.bar = "BAR"

assert_equal "BAR", Foo.config.bar
assert_equal "BAR", Foo.config[:bar]
assert_equal "BAR", Foo.config.fetch(:bar)

assert_equal("BAR", Foo.config.to_h[:bar])
assert_nil(Foo.config.to_h[:biz])
assert_kind_of(Proc, Foo.config.to_h[:number])
end

test ".configuration(freeze: false)" do
Foo.configuration(freeze: false) do |config|
refute_predicate config, :frozen?

config.bar = "BAR"
config.biz = "BIZ"
end

assert_equal "BAR", Foo.config.bar
assert_equal "BIZ", Foo.config.biz

refute_predicate Foo.config, :frozen?
ensure
Foo.config.bar = nil
Foo.config.biz = nil
end

test ".configuration(freeze: true)" do
Bar.configuration(freeze: true) do |config|
refute_predicate config, :frozen?

config.foo = "FOO"
end

assert_equal "FOO", Bar.config.foo

assert_predicate Bar.config, :frozen?
end

test ".configure" do
assert_equal(Foo.method(:configure), Foo.method(:configuration))

assert_equal(Bar.method(:configure), Bar.method(:configuration))
end
end
Loading