Skip to content

Commit

Permalink
Move ActiveRecord callbacks implementation to ActiveModel and make us…
Browse files Browse the repository at this point in the history
…e of it.

Signed-off-by: Yehuda Katz <wycats@Yehuda-Katz.local>
  • Loading branch information
josevalim authored and Yehuda Katz committed Dec 29, 2009
1 parent 643862e commit 6d39067
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 54 deletions.
1 change: 1 addition & 0 deletions activemodel/lib/active_model.rb
Expand Up @@ -30,6 +30,7 @@ module ActiveModel
extend ActiveSupport::Autoload extend ActiveSupport::Autoload


autoload :AttributeMethods autoload :AttributeMethods
autoload :Callbacks
autoload :Conversion autoload :Conversion
autoload :DeprecatedErrorMethods autoload :DeprecatedErrorMethods
autoload :Dirty autoload :Dirty
Expand Down
91 changes: 91 additions & 0 deletions activemodel/lib/active_model/callbacks.rb
@@ -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
70 changes: 70 additions & 0 deletions activemodel/test/cases/callbacks_test.rb
@@ -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
59 changes: 5 additions & 54 deletions activerecord/lib/active_record/callbacks.rb
@@ -1,5 +1,3 @@
require 'observer'

module ActiveRecord module ActiveRecord
# Callbacks are hooks into the lifecycle of an Active Record object that allow you to trigger logic # 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 # before or after an alteration of the object state. This can be used to make sure that associated and
Expand Down Expand Up @@ -210,7 +208,6 @@ module ActiveRecord
# instead of quietly returning +false+. # instead of quietly returning +false+.
module Callbacks module Callbacks
extend ActiveSupport::Concern extend ActiveSupport::Concern
include ActiveSupport::Callbacks


CALLBACKS = [ CALLBACKS = [
:after_initialize, :after_find, :before_validation, :after_validation, :after_initialize, :after_find, :before_validation, :after_validation,
Expand All @@ -224,60 +221,14 @@ module Callbacks
alias_method_chain method, :callbacks alias_method_chain method, :callbacks
end end


define_callbacks :initialize, :find, :save, :create, :update, :destroy, extend ActiveModel::Callbacks
:validation, :terminator => "result == false", :scope => [:kind, :name]
define_model_callbacks :initialize, :find, :only => :after
define_model_callbacks :save, :create, :update, :destroy
define_model_callbacks :validation, :only => [:before, :after]
end end


module ClassMethods 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) def method_added(meth)
super super
if CALLBACKS.include?(meth.to_sym) if CALLBACKS.include?(meth.to_sym)
Expand Down

0 comments on commit 6d39067

Please sign in to comment.