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

## Unreleased

### New Features
* Delegate missing constants to the object class via `const_missing` when using `delegate_all` [#955](https://github.com/drapergem/draper/pull/955)

## 4.0.6 - 2025-11-15

### Fixes
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,21 @@ the decorator will first try to call the method on the parent decorator class. I
the method does not exist on the parent decorator class, it will then try to call
the method on the decorated `object`. This is a very permissive interface.

Constants defined on the source model class are also accessible via the decorator
class when using `delegate_all`:

```ruby
class Car
WHEELS_COUNT = 4
end

class CarDecorator < ApplicationDecorator
delegate_all
end

CarDecorator::WHEELS_COUNT # => 4
```

If you want to strictly control which methods are called within views, you can
choose to only delegate certain methods from the decorator to the source model:

Expand Down
7 changes: 7 additions & 0 deletions lib/draper/automatic_delegation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ def respond_to_missing?(method, include_private = false)
super || delegatable?(method)
end

# Proxies missing constants to the source class.
def const_missing(name)
return object_class.const_get(name) if object_class? && object_class.const_defined?(name)

super
end

# @private
def delegatable?(method)
object_class? && object_class.respond_to?(method)
Expand Down
24 changes: 24 additions & 0 deletions spec/draper/decorator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,30 @@ def hello_world
end
end

context ".const_missing" do
context "without an object class" do
it "raises a NameError on missing constants" do
expect{Decorator::HELLO_WORLD}.to raise_error NameError, /HELLO_WORLD/
end
end

context "with an object class" do
it "delegates constants that exist on the object class" do
object_class = Class.new
object_class.const_set(:HELLO_WORLD, :delegated)
allow(Decorator).to receive_messages object_class: object_class

expect(Decorator::HELLO_WORLD).to be :delegated
end

it "raises a NameError for constants that do not exist on the object class" do
allow(Decorator).to receive_messages object_class: Class.new

expect{Decorator::HELLO_WORLD}.to raise_error NameError, /HELLO_WORLD/
end
end
end

describe "#respond_to?" do
it "returns true for its own methods" do
Decorator.class_eval{def hello_world; end}
Expand Down