aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/lib/active_model/validations/callbacks.rb
blob: 11a8b2b2292f7b74fe36818db227c8f2d91b18ab (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# frozen_string_literal: true

module ActiveModel
  module Validations
    # == Active \Model \Validation \Callbacks
    #
    # Provides an interface for any class to have +before_validation+ and
    # +after_validation+ callbacks.
    #
    # First, include ActiveModel::Validations::Callbacks from the class you are
    # creating:
    #
    #   class MyModel
    #     include ActiveModel::Validations::Callbacks
    #
    #     before_validation :do_stuff_before_validation
    #     after_validation  :do_stuff_after_validation
    #   end
    #
    # Like other <tt>before_*</tt> callbacks if +before_validation+ throws
    # +:abort+ then <tt>valid?</tt> will not be called.
    module Callbacks
      extend ActiveSupport::Concern

      included do
        include ActiveSupport::Callbacks
        define_callbacks :validation,
                         skip_after_callbacks_if_terminated: true,
                         scope: [:kind, :name]
      end

      module ClassMethods
        # Defines a callback that will get called right before validation.
        #
        #   class Person
        #     include ActiveModel::Validations
        #     include ActiveModel::Validations::Callbacks
        #
        #     attr_accessor :name
        #
        #     validates_length_of :name, maximum: 6
        #
        #     before_validation :remove_whitespaces
        #
        #     private
        #
        #     def remove_whitespaces
        #       name.strip!
        #     end
        #   end
        #
        #   person = Person.new
        #   person.name = '  bob  '
        #   person.valid? # => true
        #   person.name   # => "bob"
        def before_validation(*args, &block)
          options = args.extract_options!
          options[:if] = Array(options[:if])

          if options.key?(:on)
            options[:if].unshift ->(o) {
              !(Array(options[:on]) & Array(o.validation_context)).empty?
            }
          end

          args << options
          set_callback(:validation, :before, *args, &block)
        end

        # Defines a callback that will get called right after validation.
        #
        #   class Person
        #     include ActiveModel::Validations
        #     include ActiveModel::Validations::Callbacks
        #
        #     attr_accessor :name, :status
        #
        #     validates_presence_of :name
        #
        #     after_validation :set_status
        #
        #     private
        #
        #     def set_status
        #       self.status = errors.empty?
        #     end
        #   end
        #
        #   person = Person.new
        #   person.name = ''
        #   person.valid? # => false
        #   person.status # => false
        #   person.name = 'bob'
        #   person.valid? # => true
        #   person.status # => true
        def after_validation(*args, &block)
          options = args.extract_options!
          options[:prepend] = true
          options[:if] = Array(options[:if])

          if options.key?(:on)
            options[:if].unshift ->(o) {
              !(Array(options[:on]) & Array(o.validation_context)).empty?
            }
          end

          args << options
          set_callback(:validation, :after, *args, &block)
        end
      end

    private

      # Overwrite run validations to include callbacks.
      def run_validations!
        _run_validation_callbacks { super }
      end
    end
  end
end