class ActiveSupport::Callbacks::Callback
:nodoc:#
def _compile_filter(filter)
on the object.
a method is created that calls the before_foo method
Objects::
Procs:: define_method'ed into methods
Strings:: class_eval'ed into methods
Symbols:: Already methods
Arrays:: Merged together into a single filter
the same after this point:
All of these objects are compiled into methods and handled
Objects:: An object with a before_foo method on it to call
Procs:: A proc to call with the object
Strings:: Some content to evaluate
Symbols:: A method to call
merge conditions from skip_* filters
multiple conditions. Used internally to
Arrays:: Used in conditions. This is used to specify
Filters support:
def _compile_filter(filter) method_name = "_callback_#{@kind}_#{next_id}" case filter when Array filter.map {|f| _compile_filter(f)} when Symbol filter when String "(#{filter})" when Proc @klass.send(:define_method, method_name, &filter) return method_name if filter.arity <= 0 method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ") else @klass.send(:define_method, "#{method_name}_object") { filter } _normalize_legacy_filter(kind, filter) scopes = Array.wrap(chain.config[:scope]) method_to_call = scopes.map{ |s| s.is_a?(Symbol) ? send(s) : s }.join("_") @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{method_name}(&blk) #{method_name}_object.send(:#{method_to_call}, self, &blk) end RUBY_EVAL method_name end end
def _compile_options(options)
symbols, string, procs, and objects), so compile a conditional
Options support the same options as filters themselves (and support
def _compile_options(options) conditions = ["true"] unless options[:if].empty? conditions << Array.wrap(_compile_filter(options[:if])) end unless options[:unless].empty? conditions << Array.wrap(_compile_filter(options[:unless])).map {|f| "!#{f}"} end conditions.flatten.join(" && ") end
def _compile_per_key_options
def _compile_per_key_options key_options = _compile_options(@per_key) @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def _one_time_conditions_valid_#{@callback_id}? true if #{key_options} end RUBY_EVAL end
def _normalize_legacy_filter(kind, filter)
def _normalize_legacy_filter(kind, filter) if !filter.respond_to?(kind) && filter.respond_to?(:filter) filter.singleton_class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{kind}(context, &block) filter(context, &block) end RUBY_EVAL elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around def filter.around(context) should_continue = before(context) yield if should_continue after(context) end end end
def _update_filter(filter_options, new_options)
def _update_filter(filter_options, new_options) filter_options[:if].push(new_options[:unless]) if new_options.key?(:unless) filter_options[:unless].push(new_options[:if]) if new_options.key?(:if) end
def clone(chain, klass)
def clone(chain, klass) obj = super() obj.chain = chain obj.klass = klass obj.per_key = @per_key.dup obj.options = @options.dup obj.per_key[:if] = @per_key[:if].dup obj.per_key[:unless] = @per_key[:unless].dup obj.options[:if] = @options[:if].dup obj.options[:unless] = @options[:unless].dup obj end
def end(key=nil, object=nil)
This will supply contents for around and after filters, but not
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 if #{@compiled_options} #{@filter} end RUBY_EVAL when :around <<-RUBY_EVAL value end RUBY_EVAL end end
def initialize(chain, filter, kind, options, klass)
def initialize(chain, filter, kind, options, klass) @chain, @kind, @klass = chain, kind, klass normalize_options!(options) @per_key = options.delete(:per_key) @raw_filter, @options = filter, options @filter = _compile_filter(filter) @compiled_options = _compile_options(options) @callback_id = next_id _compile_per_key_options end
def matches?(_kind, _filter)
def matches?(_kind, _filter) @kind == _kind && @filter == _filter end
def name
def name chain.name end
def next_id
def next_id @@_callback_sequence += 1 end
def normalize_options!(options)
def normalize_options!(options) options[:if] = Array.wrap(options[:if]) options[:unless] = Array.wrap(options[:unless]) options[:per_key] ||= {} options[:per_key][:if] = Array.wrap(options[:per_key][:if]) options[:per_key][:unless] = Array.wrap(options[:per_key][:unless]) end
def recompile!(_options, _per_key)
def recompile!(_options, _per_key) _update_filter(self.options, _options) _update_filter(self.per_key, _per_key) @callback_id = next_id @filter = _compile_filter(@raw_filter) @compiled_options = _compile_options(@options) _compile_per_key_options end
def start(key=nil, object=nil)
This will supply contents for before and around filters, and no
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 # 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 as # the `result` variable is not always used except if the # terminator code refers to it. result = result = #{@filter} halted = (#{chain.config[:terminator]}) if halted halted_callback_hook(#{@raw_filter.inspect.inspect}) end end 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