aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel
diff options
context:
space:
mode:
authorJosé Valim <jose.valim@gmail.com>2009-12-29 01:15:39 +0100
committerYehuda Katz <wycats@Yehuda-Katz.local>2009-12-28 16:19:23 -0800
commit6d390671f639b3314437054df0d393cef10493bd (patch)
tree599e7f16cecc26395b897cd9aa0c4f3c530ff930 /activemodel
parent643862e3be1bbe004e2c1a00286b12c5bdc9849a (diff)
downloadrails-6d390671f639b3314437054df0d393cef10493bd.tar.gz
rails-6d390671f639b3314437054df0d393cef10493bd.tar.bz2
rails-6d390671f639b3314437054df0d393cef10493bd.zip
Move ActiveRecord callbacks implementation to ActiveModel and make use of it.
Signed-off-by: Yehuda Katz <wycats@Yehuda-Katz.local>
Diffstat (limited to 'activemodel')
-rw-r--r--activemodel/lib/active_model.rb1
-rw-r--r--activemodel/lib/active_model/callbacks.rb91
-rw-r--r--activemodel/test/cases/callbacks_test.rb70
3 files changed, 162 insertions, 0 deletions
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index ed32a89971..f14016027c 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -30,6 +30,7 @@ module ActiveModel
extend ActiveSupport::Autoload
autoload :AttributeMethods
+ autoload :Callbacks
autoload :Conversion
autoload :DeprecatedErrorMethods
autoload :Dirty
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
new file mode 100644
index 0000000000..f66a1ddcaa
--- /dev/null
+++ b/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 \ No newline at end of file
diff --git a/activemodel/test/cases/callbacks_test.rb b/activemodel/test/cases/callbacks_test.rb
new file mode 100644
index 0000000000..b996f51d6b
--- /dev/null
+++ b/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