Permalink
Browse files

Move ActiveRecord callbacks implementation to ActiveModel and make us…

…e of it.

Signed-off-by: Yehuda Katz <wycats@Yehuda-Katz.local>
  • Loading branch information...
1 parent 643862e commit 6d390671f639b3314437054df0d393cef10493bd @josevalim josevalim committed with Yehuda Katz Dec 29, 2009
@@ -30,6 +30,7 @@ module ActiveModel
extend ActiveSupport::Autoload
autoload :AttributeMethods
+ autoload :Callbacks
autoload :Conversion
autoload :DeprecatedErrorMethods
autoload :Dirty
@@ -0,0 +1,91 @@
+require 'active_support/callbacks'
+
+module ActiveModel
+ module Callbacks
+ def self.extended(base)
+ base.class_eval do
+ include ActiveSupport::Callbacks
+ end
+ end
+
+ # Define callbacks similar to ActiveRecord ones. It means:
+ #
+ # * The callback chain is aborted whenever the block given to
+ # _run_callbacks returns false.
+ #
+ # * If a class is given to the fallback, it will search for
+ # before_create, around_create and after_create methods.
+ #
+ # == Usage
+ #
+ # First you need to define which callbacks your model will have:
+ #
+ # class MyModel
+ # define_model_callbacks :create
+ # end
+ #
+ # This will define three class methods: before_create, around_create,
+ # and after_create. They accept a symbol, a string, an object or a block.
+ #
+ # After you create a callback, you need to tell when they are executed.
+ # For example, you could do:
+ #
+ # def create
+ # _run_create_callbacks do
+ # super
+ # end
+ # end
+ #
+ # == Options
+ #
+ # define_model_callbacks accepts all options define_callbacks does, in
+ # case you want to overwrite a default. Besides that, it also accepts
+ # an :only option, where you can choose if you want all types (before,
+ # around or after) or just some:
+ #
+ # define_model_callbacks :initializer, :only => :after
+ #
+ def define_model_callbacks(*callbacks)
+ options = callbacks.extract_options!
+ options = { :terminator => "result == false", :scope => [:kind, :name] }.merge(options)
+
+ types = Array(options.delete(:only))
+ types = [:before, :around, :after] if types.empty?
+
+ callbacks.each do |callback|
+ define_callbacks(callback, options)
+
+ types.each do |type|
+ send(:"_define_#{type}_model_callback", self, callback)
+ end
+ end
+ end
+
+ def _define_before_model_callback(klass, callback) #:nodoc:
+ klass.class_eval <<-CALLBACK, __FILE__, __LINE__
+ def self.before_#{callback}(*args, &block)
+ set_callback(:#{callback}, :before, *args, &block)
+ end
+ CALLBACK
+ end
+
+ def _define_around_model_callback(klass, callback) #:nodoc:
+ klass.class_eval <<-CALLBACK, __FILE__, __LINE__
+ def self.around_#{callback}(*args, &block)
+ set_callback(:#{callback}, :around, *args, &block)
+ end
+ CALLBACK
+ end
+
+ def _define_after_model_callback(klass, callback) #:nodoc:
+ klass.class_eval <<-CALLBACK, __FILE__, __LINE__
+ def self.after_#{callback}(*args, &block)
+ options = args.extract_options!
+ options[:prepend] = true
+ options[:if] = Array(options[:if]) << "!halted && value != false"
+ set_callback(:#{callback}, :after, *(args << options), &block)
+ end
+ CALLBACK
+ end
+ end
+end
@@ -0,0 +1,70 @@
+require "cases/helper"
+
+class CallbacksTest < ActiveModel::TestCase
+
+ class CallbackValidator
+ def around_create(model)
+ model.callbacks << :before_around_create
+ yield
+ model.callbacks << :after_around_create
+ end
+ end
+
+ class ModelCallbacks
+ attr_reader :callbacks
+ extend ActiveModel::Callbacks
+
+ define_model_callbacks :create
+ define_model_callbacks :initialize, :only => :after
+
+ before_create :before_create
+ around_create CallbackValidator.new
+
+ after_create do |model|
+ model.callbacks << :after_create
+ end
+
+ after_create "@callbacks << :final_callback"
+
+ def initialize(valid=true)
+ @callbacks, @valid = [], valid
+ end
+
+ def before_create
+ @callbacks << :before_create
+ end
+
+ def create
+ _run_create_callbacks do
+ @callbacks << :create
+ @valid
+ end
+ end
+ end
+
+ test "complete callback chain" do
+ model = ModelCallbacks.new
+ model.create
+ assert_equal model.callbacks, [ :before_create, :before_around_create, :create,
+ :after_around_create, :after_create, :final_callback]
+ end
+
+ test "after callbacks are always appended" do
+ model = ModelCallbacks.new
+ model.create
+ assert_equal model.callbacks.last, :final_callback
+ end
+
+ test "after callbacks are not executed if the block returns false" do
+ model = ModelCallbacks.new(false)
+ model.create
+ assert_equal model.callbacks, [ :before_create, :before_around_create,
+ :create, :after_around_create]
+ end
+
+ test "only selects which types of callbacks should be created" do
+ assert !ModelCallbacks.respond_to?(:before_initialize)
+ assert !ModelCallbacks.respond_to?(:around_initialize)
+ assert ModelCallbacks.respond_to?(:after_initialize)
+ end
+end
@@ -1,5 +1,3 @@
-require 'observer'
-
module ActiveRecord
# Callbacks are hooks into the lifecycle of an Active Record object that allow you to trigger logic
# before or after an alteration of the object state. This can be used to make sure that associated and
@@ -210,7 +208,6 @@ module ActiveRecord
# instead of quietly returning +false+.
module Callbacks
extend ActiveSupport::Concern
- include ActiveSupport::Callbacks
CALLBACKS = [
:after_initialize, :after_find, :before_validation, :after_validation,
@@ -224,60 +221,14 @@ module Callbacks
alias_method_chain method, :callbacks
end
- define_callbacks :initialize, :find, :save, :create, :update, :destroy,
- :validation, :terminator => "result == false", :scope => [:kind, :name]
+ extend ActiveModel::Callbacks
+
+ define_model_callbacks :initialize, :find, :only => :after
+ define_model_callbacks :save, :create, :update, :destroy
+ define_model_callbacks :validation, :only => [:before, :after]
end
module ClassMethods
- def after_initialize(*args, &block)
- options = args.extract_options!
- options[:prepend] = true
- set_callback(:initialize, :after, *(args << options), &block)
- end
-
- def after_find(*args, &block)
- options = args.extract_options!
- options[:prepend] = true
- set_callback(:find, :after, *(args << options), &block)
- end
-
- [:save, :create, :update, :destroy].each do |callback|
- module_eval <<-CALLBACKS, __FILE__, __LINE__
- def before_#{callback}(*args, &block)
- set_callback(:#{callback}, :before, *args, &block)
- end
-
- def around_#{callback}(*args, &block)
- set_callback(:#{callback}, :around, *args, &block)
- end
-
- def after_#{callback}(*args, &block)
- options = args.extract_options!
- options[:prepend] = true
- options[:if] = Array(options[:if]) << "!halted && value != false"
- set_callback(:#{callback}, :after, *(args << options), &block)
- end
- CALLBACKS
- end
-
- def before_validation(*args, &block)
- options = args.extract_options!
- if options[:on]
- options[:if] = Array(options[:if])
- options[:if] << "@_on_validate == :#{options[:on]}"
- end
- set_callback(:validation, :before, *(args << options), &block)
- end
-
- def after_validation(*args, &block)
- options = args.extract_options!
- options[:if] = Array(options[:if])
- options[:if] << "!halted"
- options[:if] << "@_on_validate == :#{options[:on]}" if options[:on]
- options[:prepend] = true
- set_callback(:validation, :after, *(args << options), &block)
- end
-
def method_added(meth)
super
if CALLBACKS.include?(meth.to_sym)

0 comments on commit 6d39067

Please sign in to comment.