From 3dc80b7fdf7f9d11c64f3de9feb78edf83a9e2a8 Mon Sep 17 00:00:00 2001
From: Bogdan Gusiev <agresso@gmail.com>
Date: Tue, 15 Nov 2011 10:33:25 +0200
Subject: AS::Callbacks::Callback refactor

In order to make Callbacks code always operate on valid peaces of code
Concatenated Callback#start and Callback#end method into #apply
method.
---
 activesupport/lib/active_support/callbacks.rb | 120 +++++++++++++-------------
 1 file changed, 62 insertions(+), 58 deletions(-)

diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index ea37355fc1..66767bfbb6 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -158,19 +158,10 @@ module ActiveSupport
         RUBY_EVAL
       end
 
-      # This will supply contents for before and around filters, and no
-      # contents for after filters (for the forward pass).
-      def start(key=nil, object=nil)
-        return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
-
-        # options[0] is the compiled form of supplied conditions
-        # options[1] is the "end" for the conditional
-        #
+      # Wraps code with filter
+      def apply(code, key=nil, object=nil)
         case @kind
         when :before
-          # if condition    # before_save :filter_name, :if => :condition
-          #   filter_name
-          # end
           <<-RUBY_EVAL
             if !halted && #{@compiled_options}
               # This double assignment is to prevent warnings in 1.9.3.  I would
@@ -180,62 +171,64 @@ module ActiveSupport
               result = result = #{@filter}
               halted = (#{chain.config[:terminator]})
             end
+            #{code}
           RUBY_EVAL
-        when :around
-          # Compile around filters with conditions into proxy methods
-          # that contain the conditions.
-          #
-          # For `around_save :filter_name, :if => :condition':
-          #
-          # def _conditional_callback_save_17
-          #   if condition
-          #     filter_name do
-          #       yield self
-          #     end
-          #   else
-          #     yield self
-          #   end
-          # end
-          #
-          name = "_conditional_callback_#{@kind}_#{next_id}"
-          @klass.class_eval <<-RUBY_EVAL,  __FILE__, __LINE__ + 1
-             def #{name}(halted)
-              if #{@compiled_options} && !halted
-                #{@filter} do
-                  yield self
-                end
-              else
-                yield self
-              end
-            end
-          RUBY_EVAL
-          "#{name}(halted) do"
-        end
-      end
-
-      # This will supply contents for around and after filters, but not
-      # before filters (for the backward pass).
-      def end(key=nil, object=nil)
-        return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
-
-        case @kind
         when :after
-          # after_save :filter_name, :if => :condition
           <<-RUBY_EVAL
+          #{code}
           if #{@compiled_options}
             #{@filter}
           end
           RUBY_EVAL
         when :around
+          name = define_conditional_callback
           <<-RUBY_EVAL
+          #{name}(halted) do
+            #{code}
             value
           end
           RUBY_EVAL
         end
       end
 
+
+      def one_time_conditions_valid?(object)
+        object.send("_one_time_conditions_valid_#{@callback_id}?")
+      end
+
       private
 
+      # Compile around filters with conditions into proxy methods
+      # that contain the conditions.
+      #
+      # For `around_save :filter_name, :if => :condition':
+      #
+      # def _conditional_callback_save_17
+      #   if condition
+      #     filter_name do
+      #       yield self
+      #     end
+      #   else
+      #     yield self
+      #   end
+      # end
+      #
+      def define_conditional_callback
+        name = "_conditional_callback_#{@kind}_#{next_id}"
+        @klass.class_eval <<-RUBY_EVAL,  __FILE__, __LINE__ + 1
+          def #{name}(halted)
+           if #{@compiled_options} && !halted
+             #{@filter} do
+               yield self
+             end
+           else
+             yield self
+           end
+         end
+        RUBY_EVAL
+        name
+      end
+
       # Options support the same options as filters themselves (and support
       # symbols, string, procs, and objects), so compile a conditional
       # expression based on the options
@@ -338,10 +331,20 @@ module ActiveSupport
         method << "value = nil"
         method << "halted = false"
 
-        each do |callback|
-          method << callback.start(key, object)
+        callbacks = yielding
+        applicable_callbacks_for(key, object).reverse_each do |callback|
+          callbacks = callback.apply(callbacks, key, object)
         end
+        method << callbacks
+
+        method << "raise rescued_error if rescued_error" if config[:rescuable]
+        method << "halted ? false : (block_given? ? value : true)"
+        method.flatten.compact.join("\n")
+      end
 
+      # Returns part of method that evaluates the callback block
+      def yielding
+        method = []
         if config[:rescuable]
           method << "rescued_error = nil"
           method << "begin"
@@ -354,14 +357,15 @@ module ActiveSupport
           method << "rescued_error = e"
           method << "end"
         end
+        method.join("\n")
+      end
 
-        reverse_each do |callback|
-          method << callback.end(key, object)
+      # Selects callbacks that have valid <tt>:per_key</tt> condition
+      def applicable_callbacks_for(key, object)
+        return self unless key
+        select do |callback|
+          callback.one_time_conditions_valid?(object)
         end
-
-        method << "raise rescued_error if rescued_error" if config[:rescuable]
-        method << "halted ? false : (block_given? ? value : true)"
-        method.compact.join("\n")
       end
     end
 
-- 
cgit v1.2.3