From f002be148e1377709ed28b8e80c5db76ee2fa410 Mon Sep 17 00:00:00 2001
From: Matt Tanous <mtanous22@gmail.com>
Date: Wed, 25 Jul 2018 17:04:34 -0400
Subject: Add :allow_nil option to delegate_missing_to; use in ActiveStorage
 attachment

---
 activesupport/CHANGELOG.md                                 |  7 ++++++-
 .../lib/active_support/core_ext/module/delegation.rb       | 11 ++++++++---
 activesupport/test/core_ext/module_test.rb                 | 14 ++++++++++++++
 3 files changed, 28 insertions(+), 4 deletions(-)

(limited to 'activesupport')

diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index e494f66c5a..3d1dd41085 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,9 @@
+*   `delegate_missing_to` would raise a `DelegationError` if the object
+    delegated to was `nil`. Now the `allow_nil` option has been added to enable
+    the user to specify they want `nil` returned in this case.
+
+    *Matthew Tanous*
+
 *   `truncate` would return the original string if it was too short to be truncated
     and a frozen string if it were long enough to be truncated. Now truncate will
     consistently return an unfrozen string regardless. This behavior is consistent
@@ -19,5 +25,4 @@
 
     *Jordan Thomas*
 
-
 Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/activesupport/CHANGELOG.md) for previous changes.
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 9fe7f8fe01..2f88010d27 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -274,8 +274,9 @@ class Module
   # variables, methods, constants, etc.
   #
   # The delegated method must be public on the target, otherwise it will
-  # raise +NoMethodError+.
-  def delegate_missing_to(target)
+  # raise +DelegationError+. If you wish to instead return +nil+,
+  # use the <tt>:allow_nil</tt> option.
+  def delegate_missing_to(target, allow_nil: nil)
     target = target.to_s
     target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
 
@@ -295,7 +296,11 @@ class Module
             super
           rescue NoMethodError
             if #{target}.nil?
-              raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil"
+              if #{allow_nil == true}
+                return nil
+              else
+                raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil"
+              end
             else
               raise
             end
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index 04692f1484..6e341480d1 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -92,6 +92,16 @@ DecoratedTester = Struct.new(:client) do
   delegate_missing_to :client
 end
 
+class DecoratedMissingAllowNil
+  delegate_missing_to :case, allow_nil: true
+
+  attr_reader :case
+
+  def initialize(kase)
+    @case = kase
+  end
+end
+
 class DecoratedReserved
   delegate_missing_to :case
 
@@ -382,6 +392,10 @@ class ModuleTest < ActiveSupport::TestCase
     assert_equal "name delegated to client, but client is nil", e.message
   end
 
+  def test_delegate_missing_to_returns_nil_if_allow_nil_and_nil_target
+    assert_nil DecoratedMissingAllowNil.new(nil).name
+  end
+
   def test_delegate_missing_to_affects_respond_to
     assert_respond_to DecoratedTester.new(@david), :name
     assert_not_respond_to DecoratedTester.new(@david), :private_name
-- 
cgit v1.2.3