Skip to content

Commit 00fb4e6

Browse files
authored
Merge pull request rails#43713 from Shopify/as-callback-specialization
AS::Callbacks: specialize call templates for better performance
2 parents d0b53aa + eea74ec commit 00fb4e6

File tree

1 file changed

+142
-47
lines changed

1 file changed

+142
-47
lines changed

activesupport/lib/active_support/callbacks.rb

Lines changed: 142 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -372,56 +372,153 @@ def conditions_lambdas
372372

373373
# A future invocation of user-supplied code (either as a callback,
374374
# or a condition filter).
375-
class CallTemplate # :nodoc:
376-
def initialize(target, method, arguments, block)
377-
@override_target = target
378-
@method_name = method
379-
@arguments = arguments
380-
@override_block = block
375+
module CallTemplate # :nodoc:
376+
class MethodCall
377+
def initialize(method)
378+
@method_name = method
379+
end
380+
381+
# Return the parts needed to make this call, with the given
382+
# input values.
383+
#
384+
# Returns an array of the form:
385+
#
386+
# [target, block, method, *arguments]
387+
#
388+
# This array can be used as such:
389+
#
390+
# target.send(method, *arguments, &block)
391+
#
392+
# The actual invocation is left up to the caller to minimize
393+
# call stack pollution.
394+
def expand(target, value, block)
395+
[target, block, @method_name]
396+
end
397+
398+
def make_lambda
399+
lambda do |target, value, &block|
400+
target.send(@method_name, &block)
401+
end
402+
end
403+
404+
def inverted_lambda
405+
lambda do |target, value, &block|
406+
!target.send(@method_name, &block)
407+
end
408+
end
381409
end
382410

383-
# Return the parts needed to make this call, with the given
384-
# input values.
385-
#
386-
# Returns an array of the form:
387-
#
388-
# [target, block, method, *arguments]
389-
#
390-
# This array can be used as such:
391-
#
392-
# target.send(method, *arguments, &block)
393-
#
394-
# The actual invocation is left up to the caller to minimize
395-
# call stack pollution.
396-
def expand(target, value, block)
397-
expanded = [@override_target || target, @override_block || block, @method_name]
411+
class ObjectCall
412+
def initialize(target, method)
413+
@override_target = target
414+
@method_name = method
415+
end
416+
417+
def expand(target, value, block)
418+
[@override_target || target, block, @method_name, target]
419+
end
420+
421+
def make_lambda
422+
lambda do |target, value, &block|
423+
(@override_target || target).send(@method_name, target, &block)
424+
end
425+
end
426+
427+
def inverted_lambda
428+
lambda do |target, value, &block|
429+
!(@override_target || target).send(@method_name, target, &block)
430+
end
431+
end
432+
end
433+
434+
class InstanceExec0
435+
def initialize(block)
436+
@override_block = block
437+
end
438+
439+
def expand(target, value, block)
440+
[target, @override_block, :instance_exec]
441+
end
442+
443+
def make_lambda
444+
lambda do |target, value, &block|
445+
target.instance_exec(&@override_block)
446+
end
447+
end
398448

399-
@arguments.each do |arg|
400-
case arg
401-
when :value then expanded << value
402-
when :target then expanded << target
403-
when :block then expanded << (block || raise(ArgumentError))
449+
def inverted_lambda
450+
lambda do |target, value, &block|
451+
!target.instance_exec(&@override_block)
452+
end
453+
end
454+
end
455+
456+
class InstanceExec1
457+
def initialize(block)
458+
@override_block = block
459+
end
460+
461+
def expand(target, value, block)
462+
[target, @override_block, :instance_exec, target]
463+
end
464+
465+
def make_lambda
466+
lambda do |target, value, &block|
467+
target.instance_exec(target, &@override_block)
404468
end
405469
end
406470

407-
expanded
471+
def inverted_lambda
472+
lambda do |target, value, &block|
473+
!target.instance_exec(target, &@override_block)
474+
end
475+
end
408476
end
409477

410-
# Return a lambda that will make this call when given the input
411-
# values.
412-
def make_lambda
413-
lambda do |target, value, &block|
414-
target, block, method, *arguments = expand(target, value, block)
415-
target.send(method, *arguments, &block)
478+
class InstanceExec2
479+
def initialize(block)
480+
@override_block = block
481+
end
482+
483+
def expand(target, value, block)
484+
raise ArgumentError unless block
485+
[target, @override_block || block, :instance_exec, target, block]
486+
end
487+
488+
def make_lambda
489+
lambda do |target, value, &block|
490+
raise ArgumentError unless block
491+
target.instance_exec(target, block, &@override_block)
492+
end
493+
end
494+
495+
def inverted_lambda
496+
lambda do |target, value, &block|
497+
raise ArgumentError unless block
498+
!target.instance_exec(target, block, &@override_block)
499+
end
416500
end
417501
end
418502

419-
# Return a lambda that will make this call when given the input
420-
# values, but then return the boolean inverse of that result.
421-
def inverted_lambda
422-
lambda do |target, value, &block|
423-
target, block, method, *arguments = expand(target, value, block)
424-
! target.send(method, *arguments, &block)
503+
class ProcCall
504+
def initialize(target)
505+
@override_target = target
506+
end
507+
508+
def expand(target, value, block)
509+
[@override_target || target, block, :call, target, value]
510+
end
511+
512+
def make_lambda
513+
lambda do |target, value, &block|
514+
(@override_target || target).call(target, value, &block)
515+
end
516+
end
517+
518+
def inverted_lambda
519+
lambda do |target, value, &block|
520+
!(@override_target || target).call(target, value, &block)
521+
end
425522
end
426523
end
427524

@@ -436,21 +533,19 @@ def inverted_lambda
436533
def self.build(filter, callback)
437534
case filter
438535
when Symbol
439-
new(nil, filter, [], nil)
536+
MethodCall.new(filter)
440537
when Conditionals::Value
441-
new(filter, :call, [:target, :value], nil)
538+
ProcCall.new(filter)
442539
when ::Proc
443540
if filter.arity > 1
444-
new(nil, :instance_exec, [:target, :block], filter)
541+
InstanceExec2.new(filter)
445542
elsif filter.arity > 0
446-
new(nil, :instance_exec, [:target], filter)
543+
InstanceExec1.new(filter)
447544
else
448-
new(nil, :instance_exec, [], filter)
545+
InstanceExec0.new(filter)
449546
end
450547
else
451-
method_to_call = callback.current_scopes.join("_")
452-
453-
new(filter, method_to_call, [:target], nil)
548+
ObjectCall.new(filter, callback.current_scopes.join("_").to_sym)
454549
end
455550
end
456551
end

0 commit comments

Comments
 (0)