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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
6 changes: 6 additions & 0 deletions lib/draper/collection_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 18 additions & 2 deletions lib/draper/decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/draper/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Draper
VERSION = '4.0.5'
VERSION = '4.0.6'
end
23 changes: 23 additions & 0 deletions spec/draper/collection_decorator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
28 changes: 28 additions & 0 deletions spec/draper/decorator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading