Skip to content

Commit

Permalink
API: added Decoratable
Browse files Browse the repository at this point in the history
This module adds three methods to decorate an object. Decorator class is
being inferred automatically. When no decorator is found,
* `#decorate` returns `nil`,
* `#decorate!` raises `Magic::Lookup::Error`,
* `#decorated` returns the original object.

One can test if the object is actually decorated with `#decorated?`.

`Decoratable` is mixed into `Object` by default.
  • Loading branch information
Alexander-Senko committed Oct 13, 2024
1 parent 1c12ea7 commit 59a20b1
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 0 deletions.
14 changes: 14 additions & 0 deletions lib/magic/decoratable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

module Magic
module Decoratable
def decorate = decorator&.new self
def decorate! = decorate || raise(Lookup::Error.for self, Decorator)
def decorated = decorate || self
def decorated? = false

private

def decorator = Decorator.for self.class
end
end
4 changes: 4 additions & 0 deletions lib/magic/decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
require_relative 'decorator/version'

module Magic
autoload :Decoratable, 'magic/decoratable'

Object.include Decoratable

module Decorator
autoload :Base, 'magic/decorator/base'

Expand Down
2 changes: 2 additions & 0 deletions lib/magic/decorator/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ class Base < SimpleDelegator
class << self
def name_for(object_class) = "#{object_class}Decorator"
end

def decorated? = true
end
end
end
13 changes: 13 additions & 0 deletions sig/magic/decoratable.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Magic
module Decoratable
def decorate: -> Decorator?
def decorate!: -> Decorator
def decorated: -> (Decorator | self)

def decorated?: -> bool

private

def decorator: -> Class?
end
end
2 changes: 2 additions & 0 deletions sig/magic/decorator/base.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ module Magic
module Decorator
class Base
extend Lookup

def decorated?: -> bool
end
end
end
61 changes: 61 additions & 0 deletions spec/magic/decoratable_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# frozen_string_literal: true

module Magic
RSpec.describe Decoratable do
subject { object }

let(:object) { 2.times.map { rand } }

def decorator_class = Class.new Decorator::Base

before do # HACK: decorators persist across examples somehow otherwise
stub_const 'Magic::Decorator::Base', Class.new(Decorator::Base)
end

after { Decorator::Base.clear_memery_cache! }

shared_context :decoratable do
before { stub_const 'ArrayDecorator', decorator_class }
end

shared_examples 'returns decorated object' do
its([]) { is_expected.to eq object }
its([]) { is_expected.to be_decorated }
end

describe '#decorate', :method do
it_behaves_like :decoratable do
include_examples 'returns decorated object'
end

its([]) { is_expected.to be_nil }
end

describe '#decorate!', :method do
it_behaves_like :decoratable do
include_examples 'returns decorated object'
end

it { expect { subject[] }.to raise_error Lookup::Error }
end

describe '#decorated', :method do
it_behaves_like :decoratable do
include_examples 'returns decorated object'
end

its([]) { is_expected.to eq object }
its([]) { is_expected.not_to be_decorated }
end

describe '#decorated?', :method do
let(:object) { super().decorated }

it_behaves_like :decoratable do
its([]) { is_expected.to be true }
end

its([]) { is_expected.to be false }
end
end
end

0 comments on commit 59a20b1

Please sign in to comment.