Skip to content

Commit

Permalink
Merge branch 'polycb'
Browse files Browse the repository at this point in the history
* polycb:
  separate identification computation
  separate filters from source code
  if the callbacks are not the same class, they cannot be duplicates
  fix object comparison case
  polymorphic comparison operator
  • Loading branch information
tenderlove committed May 8, 2013
2 parents f4c96fe + 9e323e7 commit cecef59
Showing 1 changed file with 48 additions and 22 deletions.
70 changes: 48 additions & 22 deletions activesupport/lib/active_support/callbacks.rb
Expand Up @@ -91,18 +91,42 @@ def halted_callback_hook(filter)
class Callback #:nodoc:#
@@_callback_sequence = 0

attr_accessor :chain, :filter, :kind, :options, :klass, :raw_filter
class Basic < Callback
end

class Object < Callback
def duplicates?(other)
false
end
end

def self.build(chain, filter, kind, options, _klass)
klass = case filter
when Array, Symbol, String
Callback::Basic
else
Callback::Object
end
klass.new chain, filter, kind, options, _klass
end

attr_accessor :chain, :kind, :options, :klass, :raw_filter

def initialize(chain, filter, kind, options, klass)
@chain, @kind, @klass = chain, kind, klass
deprecate_per_key_option(options)
normalize_options!(options)

@raw_filter, @options = filter, options
@filter = _compile_filter(filter)
@key = compute_identifier filter
@source = _compile_source(filter)
recompile_options!
end

def filter
@key
end

def deprecate_per_key_option(options)
if options[:per_key]
raise NotImplementedError, ":per_key option is no longer supported. Use generic :if and :unless options instead."
Expand Down Expand Up @@ -133,16 +157,12 @@ def next_id
end

def matches?(_kind, _filter)
if @_is_object_filter && !_filter.is_a?(String)
_filter_matches = @filter.to_s.start_with?(_method_name_for_object_filter(_kind, _filter, false))
else
_filter_matches = (@filter == _filter)
end

@kind == _kind && _filter_matches
@kind == _kind && filter == _filter
end

def duplicates?(other)
return false unless self.class == other.class

matches?(other.kind, other.filter)
end

Expand All @@ -167,7 +187,7 @@ def apply(code)
# This double assignment is to prevent warnings in 1.9.3 as
# the `result` variable is not always used except if the
# terminator code refers to it.
result = result = #{@filter}
result = result = #{@source}
halted = (#{chain.config[:terminator]})
if halted
halted_callback_hook(#{@raw_filter.inspect.inspect})
Expand All @@ -179,7 +199,7 @@ def apply(code)
<<-RUBY_EVAL
#{code}
if #{!chain.config[:skip_after_callbacks_if_terminated] || "!halted"} && #{@compiled_options}
#{@filter}
#{@source}
end
RUBY_EVAL
when :around
Expand All @@ -195,6 +215,15 @@ def apply(code)

private

def compute_identifier(filter)
case filter
when String, ::Proc
filter.object_id
else
filter
end
end

# Compile around filters with conditions into proxy methods
# that contain the conditions.
#
Expand All @@ -214,7 +243,7 @@ def define_conditional_callback
@klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{name}(halted)
if #{@compiled_options} && !halted
#{@filter} do
#{@source} do
yield self
end
else
Expand All @@ -232,11 +261,11 @@ def recompile_options!
conditions = ["true"]

unless options[:if].empty?
conditions << Array(_compile_filter(options[:if]))
conditions << Array(_compile_source(options[:if]))
end

unless options[:unless].empty?
conditions << Array(_compile_filter(options[:unless])).map {|f| "!#{f}"}
conditions << Array(_compile_source(options[:unless])).map {|f| "!#{f}"}
end

@compiled_options = conditions.flatten.join(" && ")
Expand Down Expand Up @@ -272,25 +301,22 @@ def _method_name_for_object_filter(kind, filter, append_next_id = true)
# Objects::
# a method is created that calls the before_foo method
# on the object.
def _compile_filter(filter)
@_is_object_filter = false

def _compile_source(filter)
case filter
when Array
filter.map {|f| _compile_filter(f)}
filter.map {|f| _compile_source(f)}
when Symbol
filter
when String
"(#{filter})"
when Proc
when ::Proc
method_name = "_callback_#{@kind}_#{next_id}"
@klass.send(:define_method, method_name, &filter)
return method_name if filter.arity <= 0

method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ")
method_name << (filter.arity == 1 ? "(self)" : " self, ::Proc.new ")
else
method_name = _method_name_for_object_filter(kind, filter)
@_is_object_filter = true
@klass.send(:define_method, "#{method_name}_object") { filter }

_normalize_legacy_filter(kind, filter)
Expand Down Expand Up @@ -465,7 +491,7 @@ def set_callback(name, *filter_list, &block)

__update_callbacks(name, filter_list, block) do |target, chain, type, filters, options|
mapped ||= filters.map do |filter|
Callback.new(chain, filter, type, options.dup, self)
Callback.build(chain, filter, type, options.dup, self)
end

options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped)
Expand Down

0 comments on commit cecef59

Please sign in to comment.