Skip to content
This repository
Browse code

moving before_validation and after_validation functionality from Acti…

…veRecord to ActiveModel

[#4653 state:resolved]

Signed-off-by: José Valim <jose.valim@gmail.com>
  • Loading branch information...
commit 51739d3228d12907d60fb1b0a2b1ef96c55f66a3 1 parent 312f433
Neeraj Singh authored June 16, 2010 josevalim committed June 19, 2010
14  activemodel/lib/active_model/validations.rb
@@ -3,6 +3,7 @@
3 3
 require 'active_support/core_ext/class/attribute'
4 4
 require 'active_support/core_ext/hash/keys'
5 5
 require 'active_model/errors'
  6
+require 'active_model/validations/callbacks'
6 7
 
7 8
 module ActiveModel
8 9
 
@@ -45,6 +46,7 @@ module ActiveModel
45 46
   module Validations
46 47
     extend ActiveSupport::Concern
47 48
     include ActiveSupport::Callbacks
  49
+    include ActiveModel::Validations::Callbacks
48 50
 
49 51
     included do
50 52
       extend ActiveModel::Translation
@@ -158,18 +160,6 @@ def errors
158 160
       @errors ||= Errors.new(self)
159 161
     end
160 162
 
161  
-    # Runs all the specified validations and returns true if no errors were added
162  
-    # otherwise false. Context can optionally be supplied to define which callbacks
163  
-    # to test against (the context is defined on the validations using :on).
164  
-    def valid?(context = nil)
165  
-      current_context, self.validation_context = validation_context, context
166  
-      errors.clear
167  
-      _run_validate_callbacks
168  
-      errors.empty?
169  
-    ensure
170  
-      self.validation_context = current_context
171  
-    end
172  
-
173 163
     # Performs the opposite of <tt>valid?</tt>. Returns true if errors were added, 
174 164
     # false otherwise.
175 165
     def invalid?(context = nil)
64  activemodel/lib/active_model/validations/callbacks.rb
... ...
@@ -0,0 +1,64 @@
  1
+require 'active_support/callbacks'
  2
+
  3
+module ActiveModel
  4
+  module Validations
  5
+    module Callbacks
  6
+      # == Active Model Validation callbacks
  7
+      #
  8
+      # Provides an interface for any class to have <tt>before_validation</tt> and
  9
+      # <tt>after_validation</tt> callbacks.
  10
+      #
  11
+      # First, extend ActiveModel::Callbacks from the class you are creating:
  12
+      #
  13
+      #   class MyModel
  14
+      #     include ActiveModel::Validations::Callbacks
  15
+      #
  16
+      #     before_validation :do_stuff_before_validation
  17
+      #     after_validation  :do_tuff_after_validation
  18
+      #   end
  19
+      #
  20
+      #   Like other before_* callbacks if <tt>before_validation</tt> returns false
  21
+      #   then <tt>valid?</tt> will not be called.
  22
+      extend ActiveSupport::Concern
  23
+
  24
+      included do
  25
+        include ActiveSupport::Callbacks
  26
+        define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
  27
+      end
  28
+
  29
+      module ClassMethods
  30
+        def before_validation(*args, &block)
  31
+          options = args.last
  32
+          if options.is_a?(Hash) && options[:on]
  33
+            options[:if] = Array.wrap(options[:if])
  34
+            options[:if] << "self.validation_context == :#{options[:on]}"
  35
+          end
  36
+          set_callback(:validation, :before, *args, &block)
  37
+        end
  38
+
  39
+        def after_validation(*args, &block)
  40
+          options = args.extract_options!
  41
+          options[:prepend] = true
  42
+          options[:if] = Array.wrap(options[:if])
  43
+          options[:if] << "!halted && value != false"
  44
+          options[:if] << "self.validation_context == :#{options[:on]}" if options[:on]
  45
+          set_callback(:validation, :after, *(args << options), &block)
  46
+        end
  47
+      end
  48
+
  49
+      # Runs all the specified validations and returns true if no errors were added
  50
+      # otherwise false. Context can optionally be supplied to define which callbacks
  51
+      # to test against (the context is defined on the validations using :on).
  52
+      def valid?(context = nil)
  53
+        current_context, self.validation_context = validation_context, context
  54
+        errors.clear
  55
+        @validate_callback_result = nil
  56
+        validation_callback_result = _run_validation_callbacks { @validate_callback_result = _run_validate_callbacks }
  57
+        (validation_callback_result && @validate_callback_result) ? errors.empty? : false
  58
+      ensure
  59
+        self.validation_context = current_context
  60
+      end
  61
+
  62
+    end
  63
+  end
  64
+end
76  activemodel/test/cases/validations/callbacks_test.rb
... ...
@@ -0,0 +1,76 @@
  1
+# encoding: utf-8
  2
+require 'cases/helper'
  3
+
  4
+class Dog
  5
+  include ActiveModel::Validations
  6
+
  7
+  attr_accessor :name, :history
  8
+
  9
+  def history
  10
+    @history ||= []
  11
+  end
  12
+end
  13
+
  14
+class DogWithMethodCallbacks < Dog
  15
+  before_validation :set_before_validation_marker
  16
+  after_validation :set_after_validation_marker
  17
+
  18
+  def set_before_validation_marker; self.history << 'before_validation_marker'; end
  19
+  def set_after_validation_marker;  self.history << 'after_validation_marker' ; end
  20
+end
  21
+
  22
+class DogValidtorsAreProc < Dog
  23
+  before_validation { self.history << 'before_validation_marker' }
  24
+  after_validation  { self.history << 'after_validation_marker' }
  25
+end
  26
+
  27
+class DogWithTwoValidators < Dog
  28
+  before_validation { self.history << 'before_validation_marker1' }
  29
+  before_validation { self.history << 'before_validation_marker2' }
  30
+end
  31
+
  32
+class DogValidatorReturningFalse < Dog
  33
+  before_validation { false }
  34
+  before_validation { self.history << 'before_validation_marker2' }
  35
+end
  36
+
  37
+class DogWithMissingName < Dog
  38
+  before_validation { self.history << 'before_validation_marker' }
  39
+  validates_presence_of :name
  40
+end
  41
+
  42
+class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase
  43
+
  44
+  def test_before_validation_and_after_validation_callbacks_should_be_called
  45
+    d = DogWithMethodCallbacks.new
  46
+    d.valid?
  47
+    assert_equal ['before_validation_marker', 'after_validation_marker'], d.history
  48
+  end
  49
+
  50
+  def test_before_validation_and_after_validation_callbacks_should_be_called_with_proc
  51
+    d = DogValidtorsAreProc.new
  52
+    d.valid?
  53
+    assert_equal ['before_validation_marker', 'after_validation_marker'], d.history
  54
+  end
  55
+
  56
+  def test_before_validation_and_after_validation_callbacks_should_be_called_in_declared_order
  57
+    d = DogWithTwoValidators.new
  58
+    d.valid?
  59
+    assert_equal ['before_validation_marker1', 'before_validation_marker2'], d.history
  60
+  end
  61
+
  62
+  def test_further_callbacks_should_not_be_called_if_before_validation_returns_false
  63
+    d = DogValidatorReturningFalse.new
  64
+    output = d.valid?
  65
+    assert_equal [], d.history
  66
+    assert_equal false, output
  67
+  end
  68
+
  69
+  def test_validation_test_should_be_done
  70
+    d = DogWithMissingName.new
  71
+    output = d.valid?
  72
+    assert_equal ['before_validation_marker'], d.history
  73
+    assert_equal false, output
  74
+  end
  75
+
  76
+end
1  activerecord/lib/active_record/base.rb
@@ -1874,6 +1874,7 @@ def object_from_yaml(string)
1874 1874
     extend ActiveSupport::DescendantsTracker
1875 1875
 
1876 1876
     include ActiveModel::Conversion
  1877
+    include ActiveModel::Validations::Callbacks
1877 1878
     include Validations
1878 1879
     extend CounterCache
1879 1880
     include Locking::Optimistic, Locking::Pessimistic
23  activerecord/lib/active_record/callbacks.rb
@@ -235,7 +235,7 @@ module Callbacks
235 235
     included do
236 236
       extend ActiveModel::Callbacks
237 237
 
238  
-      define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
  238
+      attr_accessor :validation_context
239 239
 
240 240
       define_model_callbacks :initialize, :find, :only => :after
241 241
       define_model_callbacks :save, :create, :update, :destroy
@@ -250,28 +250,11 @@ def method_added(meth)
250 250
         end
251 251
       end
252 252
 
253  
-      def before_validation(*args, &block)
254  
-        options = args.last
255  
-        if options.is_a?(Hash) && options[:on]
256  
-          options[:if] = Array.wrap(options[:if])
257  
-          options[:if] << "@_on_validate == :#{options[:on]}"
258  
-        end
259  
-        set_callback(:validation, :before, *args, &block)
260  
-      end
261  
-
262  
-      def after_validation(*args, &block)
263  
-        options = args.extract_options!
264  
-        options[:prepend] = true
265  
-        options[:if] = Array.wrap(options[:if])
266  
-        options[:if] << "!halted && value != false"
267  
-        options[:if] << "@_on_validate == :#{options[:on]}" if options[:on]
268  
-        set_callback(:validation, :after, *(args << options), &block)
269  
-      end
270 253
     end
271 254
 
272 255
     def valid?(*) #:nodoc:
273  
-      @_on_validate = new_record? ? :create : :update
274  
-      _run_validation_callbacks { super }
  256
+      self.validation_context = new_record? ? :create : :update
  257
+      super 
275 258
     end
276 259
 
277 260
     def destroy #:nodoc:
4  activerecord/lib/active_record/validations.rb
@@ -49,12 +49,12 @@ def save!(options={})
49 49
     # Runs all the specified validations and returns true if no errors were added otherwise false.
50 50
     def valid?(context = nil)
51 51
       context ||= (new_record? ? :create : :update)
52  
-      super(context)
  52
+      output = super(context)
53 53
 
54 54
       deprecated_callback_method(:validate)
55 55
       deprecated_callback_method(:"validate_on_#{context}")
56 56
 
57  
-      errors.empty?
  57
+      errors.empty? && output
58 58
     end
59 59
 
60 60
   protected

0 notes on commit 51739d3

Please sign in to comment.
Something went wrong with that request. Please try again.