diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f892391..a4449c35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Draper Changelog +## 4.0.6 - 2025-11-15 + +### Fixes +* Revert breaking change from v4.0.5 + ## 4.0.5 - 2025-11-12 ### Fixes diff --git a/lib/draper/collection_decorator.rb b/lib/draper/collection_decorator.rb index 31837621..35e0b1b4 100644 --- a/lib/draper/collection_decorator.rb +++ b/lib/draper/collection_decorator.rb @@ -61,6 +61,12 @@ def decorated? alias :decorated_with? :instance_of? + def kind_of?(klass) + decorated_collection.kind_of?(klass) || super + end + + alias_method :is_a?, :kind_of? + def replace(other) decorated_collection.replace(other) self diff --git a/lib/draper/decorator.rb b/lib/draper/decorator.rb index e5baf1e2..c029bb27 100644 --- a/lib/draper/decorator.rb +++ b/lib/draper/decorator.rb @@ -33,7 +33,7 @@ def initialize(object, options = {}) options.assert_valid_keys(:context) @object = object @context = options.fetch(:context, {}) - handle_multiple_decoration(options) if object.is_a?(Draper::Decorator) + handle_multiple_decoration(options) if object.instance_of?(self.class) end class << self @@ -185,6 +185,22 @@ def hash self.class.hash ^ object.hash end + # Checks if `self.kind_of?(klass)` or `object.kind_of?(klass)` + # + # @param [Class] klass + def kind_of?(klass) + super || object.kind_of?(klass) + end + + alias :is_a? :kind_of? + + # Checks if `self.instance_of?(klass)` or `object.instance_of?(klass)` + # + # @param [Class] klass + def instance_of?(klass) + super || object.instance_of?(klass) + end + delegate :to_s # In case object is nil @@ -251,7 +267,7 @@ def handle_multiple_decoration(options) if object.applied_decorators.last == self.class @context = object.context unless options.has_key?(:context) @object = object.object - elsif object.applied_decorators.include?(self.class) + else warn "Reapplying #{self.class} decorator to target that is already decorated with it. Call stack:\n#{caller(1).join("\n")}" end end diff --git a/lib/draper/version.rb b/lib/draper/version.rb index eba6bb27..1296580d 100644 --- a/lib/draper/version.rb +++ b/lib/draper/version.rb @@ -1,3 +1,3 @@ module Draper - VERSION = '4.0.5' + VERSION = '4.0.6' end diff --git a/spec/draper/collection_decorator_spec.rb b/spec/draper/collection_decorator_spec.rb index 928b3654..e18f40dd 100644 --- a/spec/draper/collection_decorator_spec.rb +++ b/spec/draper/collection_decorator_spec.rb @@ -249,6 +249,29 @@ module Draper end end + describe '#kind_of?' do + it 'asks the kind of its decorated collection' do + decorator = ProductsDecorator.new([]) + expect(decorator.decorated_collection).to receive(:kind_of?).with(Array).and_return("true") + expect(decorator.kind_of?(Array)).to eq "true" + end + + context 'when asking the underlying collection returns false' do + it 'asks the CollectionDecorator instance itself' do + decorator = ProductsDecorator.new([]) + allow(decorator.decorated_collection).to receive(:kind_of?).with(::Draper::CollectionDecorator).and_return(false) + expect(decorator.kind_of?(::Draper::CollectionDecorator)).to be true + end + end + end + + describe '#is_a?' do + it 'aliases to #kind_of?' do + decorator = ProductsDecorator.new([]) + expect(decorator.method(:kind_of?)).to eq decorator.method(:is_a?) + end + end + describe "#replace" do it "replaces the decorated collection" do decorator = CollectionDecorator.new([Product.new]) diff --git a/spec/draper/decorator_spec.rb b/spec/draper/decorator_spec.rb index 980cd0fa..5536c696 100644 --- a/spec/draper/decorator_spec.rb +++ b/spec/draper/decorator_spec.rb @@ -800,6 +800,34 @@ def hello_world end end + describe "class spoofing" do + it "pretends to be a kind of the object class" do + decorator = Decorator.new(Model.new) + + expect(decorator.kind_of?(Model)).to be_truthy + expect(decorator.is_a?(Model)).to be_truthy + end + + it "is still a kind of its own class" do + decorator = Decorator.new(Model.new) + + expect(decorator.kind_of?(Decorator)).to be_truthy + expect(decorator.is_a?(Decorator)).to be_truthy + end + + it "pretends to be an instance of the object class" do + decorator = Decorator.new(Model.new) + + expect(decorator.instance_of?(Model)).to be_truthy + end + + it "is still an instance of its own class" do + decorator = Decorator.new(Model.new) + + expect(decorator.instance_of?(Decorator)).to be_truthy + end + end + describe ".decorates_finders" do protect_class Decorator