Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move ActiveRecord callbacks implementation to ActiveModel and make us…
…e of it. Signed-off-by: Yehuda Katz <wycats@Yehuda-Katz.local>
- Loading branch information
Showing
4 changed files
with
167 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters