activemodel 3.0.pre → 3.0.0.rc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +44 -1
- data/MIT-LICENSE +1 -1
- data/README.rdoc +184 -0
- data/lib/active_model.rb +29 -19
- data/lib/active_model/attribute_methods.rb +167 -46
- data/lib/active_model/callbacks.rb +134 -0
- data/lib/active_model/conversion.rb +41 -1
- data/lib/active_model/deprecated_error_methods.rb +1 -1
- data/lib/active_model/dirty.rb +56 -12
- data/lib/active_model/errors.rb +205 -46
- data/lib/active_model/lint.rb +53 -17
- data/lib/active_model/locale/en.yml +26 -23
- data/lib/active_model/mass_assignment_security.rb +160 -0
- data/lib/active_model/mass_assignment_security/permission_set.rb +40 -0
- data/lib/active_model/mass_assignment_security/sanitizer.rb +23 -0
- data/lib/active_model/naming.rb +70 -5
- data/lib/active_model/observing.rb +40 -16
- data/lib/active_model/railtie.rb +2 -0
- data/lib/active_model/serialization.rb +59 -0
- data/lib/active_model/serializers/json.rb +17 -11
- data/lib/active_model/serializers/xml.rb +66 -123
- data/lib/active_model/test_case.rb +0 -2
- data/lib/active_model/translation.rb +64 -0
- data/lib/active_model/validations.rb +150 -68
- data/lib/active_model/validations/acceptance.rb +53 -33
- data/lib/active_model/validations/callbacks.rb +57 -0
- data/lib/active_model/validations/confirmation.rb +41 -23
- data/lib/active_model/validations/exclusion.rb +18 -13
- data/lib/active_model/validations/format.rb +28 -24
- data/lib/active_model/validations/inclusion.rb +18 -13
- data/lib/active_model/validations/length.rb +67 -65
- data/lib/active_model/validations/numericality.rb +83 -58
- data/lib/active_model/validations/presence.rb +10 -8
- data/lib/active_model/validations/validates.rb +110 -0
- data/lib/active_model/validations/with.rb +90 -23
- data/lib/active_model/validator.rb +186 -0
- data/lib/active_model/version.rb +3 -2
- metadata +79 -20
- data/README +0 -21
- data/lib/active_model/state_machine.rb +0 -70
- data/lib/active_model/state_machine/event.rb +0 -62
- data/lib/active_model/state_machine/machine.rb +0 -75
- data/lib/active_model/state_machine/state.rb +0 -47
- data/lib/active_model/state_machine/state_transition.rb +0 -40
- data/lib/active_model/validations_repair_helper.rb +0 -35
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'active_support/core_ext/array/wrap'
|
2
|
+
require 'active_support/callbacks'
|
3
|
+
|
4
|
+
module ActiveModel
|
5
|
+
# == Active Model Callbacks
|
6
|
+
#
|
7
|
+
# Provides an interface for any class to have Active Record like callbacks.
|
8
|
+
#
|
9
|
+
# Like the Active Record methods, the callback chain is aborted as soon as
|
10
|
+
# one of the methods in the chain returns false.
|
11
|
+
#
|
12
|
+
# First, extend ActiveModel::Callbacks from the class you are creating:
|
13
|
+
#
|
14
|
+
# class MyModel
|
15
|
+
# extend ActiveModel::Callbacks
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# Then define a list of methods that you want callbacks attached to:
|
19
|
+
#
|
20
|
+
# define_model_callbacks :create, :update
|
21
|
+
#
|
22
|
+
# This will provide all three standard callbacks (before, around and after) for
|
23
|
+
# both the :create and :update methods. To implement, you need to wrap the methods
|
24
|
+
# you want callbacks on in a block so that the callbacks get a chance to fire:
|
25
|
+
#
|
26
|
+
# def create
|
27
|
+
# _run_create_callbacks do
|
28
|
+
# # Your create action methods here
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# The _run_<method_name>_callbacks methods are dynamically created when you extend
|
33
|
+
# the <tt>ActiveModel::Callbacks</tt> module.
|
34
|
+
#
|
35
|
+
# Then in your class, you can use the +before_create+, +after_create+ and +around_create+
|
36
|
+
# methods, just as you would in an Active Record module.
|
37
|
+
#
|
38
|
+
# before_create :action_before_create
|
39
|
+
#
|
40
|
+
# def action_before_create
|
41
|
+
# # Your code here
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# You can choose not to have all three callbacks by passing a hash to the
|
45
|
+
# define_model_callbacks method.
|
46
|
+
#
|
47
|
+
# define_model_callbacks :create, :only => :after, :before
|
48
|
+
#
|
49
|
+
# Would only create the after_create and before_create callback methods in your
|
50
|
+
# class.
|
51
|
+
module Callbacks
|
52
|
+
def self.extended(base)
|
53
|
+
base.class_eval do
|
54
|
+
include ActiveSupport::Callbacks
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# define_model_callbacks accepts the same options define_callbacks does, in case
|
59
|
+
# you want to overwrite a default. Besides that, it also accepts an :only option,
|
60
|
+
# where you can choose if you want all types (before, around or after) or just some.
|
61
|
+
#
|
62
|
+
# define_model_callbacks :initializer, :only => :after
|
63
|
+
#
|
64
|
+
# Note, the <tt>:only => <type></tt> hash will apply to all callbacks defined on
|
65
|
+
# that method call. To get around this you can call the define_model_callbacks
|
66
|
+
# method as many times as you need.
|
67
|
+
#
|
68
|
+
# define_model_callbacks :create, :only => :after
|
69
|
+
# define_model_callbacks :update, :only => :before
|
70
|
+
# define_model_callbacks :destroy, :only => :around
|
71
|
+
#
|
72
|
+
# Would create +after_create+, +before_update+ and +around_destroy+ methods only.
|
73
|
+
#
|
74
|
+
# You can pass in a class to before_<type>, after_<type> and around_<type>, in which
|
75
|
+
# case the callback will call that class's <action>_<type> method passing the object
|
76
|
+
# that the callback is being called on.
|
77
|
+
#
|
78
|
+
# class MyModel
|
79
|
+
# extend ActiveModel::Callbacks
|
80
|
+
# define_model_callbacks :create
|
81
|
+
#
|
82
|
+
# before_create AnotherClass
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# class AnotherClass
|
86
|
+
# def self.before_create( obj )
|
87
|
+
# # obj is the MyModel instance that the callback is being called on
|
88
|
+
# end
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
def define_model_callbacks(*callbacks)
|
92
|
+
options = callbacks.extract_options!
|
93
|
+
options = { :terminator => "result == false", :scope => [:kind, :name] }.merge(options)
|
94
|
+
|
95
|
+
types = Array.wrap(options.delete(:only))
|
96
|
+
types = [:before, :around, :after] if types.empty?
|
97
|
+
|
98
|
+
callbacks.each do |callback|
|
99
|
+
define_callbacks(callback, options)
|
100
|
+
|
101
|
+
types.each do |type|
|
102
|
+
send(:"_define_#{type}_model_callback", self, callback)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def _define_before_model_callback(klass, callback) #:nodoc:
|
108
|
+
klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
|
109
|
+
def self.before_#{callback}(*args, &block)
|
110
|
+
set_callback(:#{callback}, :before, *args, &block)
|
111
|
+
end
|
112
|
+
CALLBACK
|
113
|
+
end
|
114
|
+
|
115
|
+
def _define_around_model_callback(klass, callback) #:nodoc:
|
116
|
+
klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
|
117
|
+
def self.around_#{callback}(*args, &block)
|
118
|
+
set_callback(:#{callback}, :around, *args, &block)
|
119
|
+
end
|
120
|
+
CALLBACK
|
121
|
+
end
|
122
|
+
|
123
|
+
def _define_after_model_callback(klass, callback) #:nodoc:
|
124
|
+
klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
|
125
|
+
def self.after_#{callback}(*args, &block)
|
126
|
+
options = args.extract_options!
|
127
|
+
options[:prepend] = true
|
128
|
+
options[:if] = Array.wrap(options[:if]) << "!halted && value != false"
|
129
|
+
set_callback(:#{callback}, :after, *(args << options), &block)
|
130
|
+
end
|
131
|
+
CALLBACK
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -1,8 +1,48 @@
|
|
1
1
|
module ActiveModel
|
2
|
-
#
|
2
|
+
# == Active Model Conversions
|
3
|
+
#
|
4
|
+
# Handles default conversions: to_model, to_key and to_param.
|
5
|
+
#
|
6
|
+
# == Example
|
7
|
+
#
|
8
|
+
# Let's take for example this non persisted object.
|
9
|
+
#
|
10
|
+
# class ContactMessage
|
11
|
+
# include ActiveModel::Conversion
|
12
|
+
#
|
13
|
+
# # ContactMessage are never persisted in the DB
|
14
|
+
# def persisted?
|
15
|
+
# false
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# cm = ContactMessage.new
|
20
|
+
# cm.to_model == self #=> true
|
21
|
+
# cm.to_key #=> nil
|
22
|
+
# cm.to_param #=> nil
|
23
|
+
#
|
3
24
|
module Conversion
|
25
|
+
# If your object is already designed to implement all of the Active Model
|
26
|
+
# you can use the default to_model implementation, which simply returns
|
27
|
+
# self.
|
28
|
+
#
|
29
|
+
# If your model does not act like an Active Model object, then you should
|
30
|
+
# define <tt>:to_model</tt> yourself returning a proxy object that wraps
|
31
|
+
# your object with Active Model compliant methods.
|
4
32
|
def to_model
|
5
33
|
self
|
6
34
|
end
|
35
|
+
|
36
|
+
# Returns an Enumerable of all (primary) key attributes or nil if
|
37
|
+
# persisted? is false
|
38
|
+
def to_key
|
39
|
+
persisted? ? [id] : nil
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns a string representing the object's key suitable for use in URLs,
|
43
|
+
# or nil if persisted? is false
|
44
|
+
def to_param
|
45
|
+
to_key ? to_key.join('-') : nil
|
46
|
+
end
|
7
47
|
end
|
8
48
|
end
|
@@ -16,7 +16,7 @@ module ActiveModel
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def add_to_base(msg)
|
19
|
-
ActiveSupport::Deprecation.warn "Errors#add_to_base(msg) has been deprecated, use Errors#
|
19
|
+
ActiveSupport::Deprecation.warn "Errors#add_to_base(msg) has been deprecated, use Errors#add(:base, msg) instead"
|
20
20
|
self[:base] << msg
|
21
21
|
end
|
22
22
|
|
data/lib/active_model/dirty.rb
CHANGED
@@ -1,5 +1,53 @@
|
|
1
|
+
require 'active_model/attribute_methods'
|
2
|
+
require 'active_support/concern'
|
3
|
+
require 'active_support/hash_with_indifferent_access'
|
4
|
+
require 'active_support/core_ext/object/duplicable'
|
5
|
+
|
1
6
|
module ActiveModel
|
2
|
-
#
|
7
|
+
# == Active Model Dirty
|
8
|
+
#
|
9
|
+
# Provides a way to track changes in your object in the same way as
|
10
|
+
# Active Record does.
|
11
|
+
#
|
12
|
+
# The requirements to implement ActiveModel::Dirty are to:
|
13
|
+
#
|
14
|
+
# * <tt>include ActiveModel::Dirty</tt> in your object
|
15
|
+
# * Call <tt>define_attribute_methods</tt> passing each method you want to
|
16
|
+
# track
|
17
|
+
# * Call <tt>attr_name_will_change!</tt> before each change to the tracked
|
18
|
+
# attribute
|
19
|
+
#
|
20
|
+
# If you wish to also track previous changes on save or update, you need to
|
21
|
+
# add
|
22
|
+
#
|
23
|
+
# @previously_changed = changes
|
24
|
+
#
|
25
|
+
# inside of your save or update method.
|
26
|
+
#
|
27
|
+
# A minimal implementation could be:
|
28
|
+
#
|
29
|
+
# class Person
|
30
|
+
#
|
31
|
+
# include ActiveModel::Dirty
|
32
|
+
#
|
33
|
+
# define_attribute_methods [:name]
|
34
|
+
#
|
35
|
+
# def name
|
36
|
+
# @name
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# def name=(val)
|
40
|
+
# name_will_change!
|
41
|
+
# @name = val
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# def save
|
45
|
+
# @previously_changed = changes
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# == Examples:
|
3
51
|
#
|
4
52
|
# A newly instantiated object is unchanged:
|
5
53
|
# person = Person.find_by_name('Uncle Bob')
|
@@ -69,7 +117,7 @@ module ActiveModel
|
|
69
117
|
# person.name = 'bob'
|
70
118
|
# person.changes # => { 'name' => ['bill', 'bob'] }
|
71
119
|
def changes
|
72
|
-
changed.inject(
|
120
|
+
changed.inject(HashWithIndifferentAccess.new){ |h, attr| h[attr] = attribute_change(attr); h }
|
73
121
|
end
|
74
122
|
|
75
123
|
# Map of attributes that were changed when the model was saved.
|
@@ -78,19 +126,15 @@ module ActiveModel
|
|
78
126
|
# person.save
|
79
127
|
# person.previous_changes # => {'name' => ['bob, 'robert']}
|
80
128
|
def previous_changes
|
81
|
-
|
129
|
+
@previously_changed
|
82
130
|
end
|
83
131
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
end
|
132
|
+
# Map of change <tt>attr => original value</tt>.
|
133
|
+
def changed_attributes
|
134
|
+
@changed_attributes ||= {}
|
135
|
+
end
|
89
136
|
|
90
|
-
|
91
|
-
def previously_changed_attributes
|
92
|
-
@previously_changed || {}
|
93
|
-
end
|
137
|
+
private
|
94
138
|
|
95
139
|
# Handle <tt>*_changed?</tt> for +method_missing+.
|
96
140
|
def attribute_changed?(attr)
|
data/lib/active_model/errors.rb
CHANGED
@@ -1,10 +1,77 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'active_support/core_ext/array/wrap'
|
4
|
+
require 'active_support/core_ext/array/conversions'
|
1
5
|
require 'active_support/core_ext/string/inflections'
|
6
|
+
require 'active_support/core_ext/object/blank'
|
7
|
+
require 'active_support/core_ext/hash/reverse_merge'
|
2
8
|
require 'active_support/ordered_hash'
|
3
9
|
|
4
10
|
module ActiveModel
|
11
|
+
# == Active Model Errors
|
12
|
+
#
|
13
|
+
# Provides a modified +OrderedHash+ that you can include in your object
|
14
|
+
# for handling error messages and interacting with Action Pack helpers.
|
15
|
+
#
|
16
|
+
# A minimal implementation could be:
|
17
|
+
#
|
18
|
+
# class Person
|
19
|
+
#
|
20
|
+
# # Required dependency for ActiveModel::Errors
|
21
|
+
# extend ActiveModel::Naming
|
22
|
+
#
|
23
|
+
# def initialize
|
24
|
+
# @errors = ActiveModel::Errors.new(self)
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# attr_accessor :name
|
28
|
+
# attr_reader :errors
|
29
|
+
#
|
30
|
+
# def validate!
|
31
|
+
# errors.add(:name, "can not be nil") if name == nil
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# # The following methods are needed to be minimally implemented
|
35
|
+
#
|
36
|
+
# def read_attribute_for_validation(attr)
|
37
|
+
# send(attr)
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# def ErrorsPerson.human_attribute_name(attr, options = {})
|
41
|
+
# attr
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# def ErrorsPerson.lookup_ancestors
|
45
|
+
# [self]
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# The last three methods are required in your object for Errors to be
|
51
|
+
# able to generate error messages correctly and also handle multiple
|
52
|
+
# languages. Of course, if you extend your object with ActiveModel::Translations
|
53
|
+
# you will not need to implement the last two. Likewise, using
|
54
|
+
# ActiveModel::Validations will handle the validation related methods
|
55
|
+
# for you.
|
56
|
+
#
|
57
|
+
# The above allows you to do:
|
58
|
+
#
|
59
|
+
# p = Person.new
|
60
|
+
# p.validate! # => ["can not be nil"]
|
61
|
+
# p.errors.full_messages # => ["name can not be nil"]
|
62
|
+
# # etc..
|
5
63
|
class Errors < ActiveSupport::OrderedHash
|
6
64
|
include DeprecatedErrorMethods
|
7
65
|
|
66
|
+
CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank]
|
67
|
+
|
68
|
+
# Pass in the instance of the object that is using the errors object.
|
69
|
+
#
|
70
|
+
# class Person
|
71
|
+
# def initialize
|
72
|
+
# @errors = ActiveModel::Errors.new(self)
|
73
|
+
# end
|
74
|
+
# end
|
8
75
|
def initialize(base)
|
9
76
|
@base = base
|
10
77
|
super()
|
@@ -13,6 +80,11 @@ module ActiveModel
|
|
13
80
|
alias_method :get, :[]
|
14
81
|
alias_method :set, :[]=
|
15
82
|
|
83
|
+
# When passed a symbol or a name of a method, returns an array of errors
|
84
|
+
# for the method.
|
85
|
+
#
|
86
|
+
# p.errors[:name] #=> ["can not be nil"]
|
87
|
+
# p.errors['name'] #=> ["can not be nil"]
|
16
88
|
def [](attribute)
|
17
89
|
if errors = get(attribute.to_sym)
|
18
90
|
errors
|
@@ -21,65 +93,134 @@ module ActiveModel
|
|
21
93
|
end
|
22
94
|
end
|
23
95
|
|
96
|
+
# Adds to the supplied attribute the supplied error message.
|
97
|
+
#
|
98
|
+
# p.errors[:name] = "must be set"
|
99
|
+
# p.errors[:name] #=> ['must be set']
|
24
100
|
def []=(attribute, error)
|
25
101
|
self[attribute.to_sym] << error
|
26
102
|
end
|
27
103
|
|
104
|
+
# Iterates through each error key, value pair in the error messages hash.
|
105
|
+
# Yields the attribute and the error for that attribute. If the attribute
|
106
|
+
# has more than one error message, yields once for each error message.
|
107
|
+
#
|
108
|
+
# p.errors.add(:name, "can't be blank")
|
109
|
+
# p.errors.each do |attribute, errors_array|
|
110
|
+
# # Will yield :name and "can't be blank"
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# p.errors.add(:name, "must be specified")
|
114
|
+
# p.errors.each do |attribute, errors_array|
|
115
|
+
# # Will yield :name and "can't be blank"
|
116
|
+
# # then yield :name and "must be specified"
|
117
|
+
# end
|
28
118
|
def each
|
29
119
|
each_key do |attribute|
|
30
120
|
self[attribute].each { |error| yield attribute, error }
|
31
121
|
end
|
32
122
|
end
|
33
123
|
|
124
|
+
# Returns the number of error messages.
|
125
|
+
#
|
126
|
+
# p.errors.add(:name, "can't be blank")
|
127
|
+
# p.errors.size #=> 1
|
128
|
+
# p.errors.add(:name, "must be specified")
|
129
|
+
# p.errors.size #=> 2
|
34
130
|
def size
|
35
131
|
values.flatten.size
|
36
132
|
end
|
37
133
|
|
134
|
+
# Returns an array of error messages, with the attribute name included
|
135
|
+
#
|
136
|
+
# p.errors.add(:name, "can't be blank")
|
137
|
+
# p.errors.add(:name, "must be specified")
|
138
|
+
# p.errors.to_a #=> ["name can't be blank", "name must be specified"]
|
38
139
|
def to_a
|
39
140
|
full_messages
|
40
141
|
end
|
41
142
|
|
143
|
+
# Returns the number of error messages.
|
144
|
+
# p.errors.add(:name, "can't be blank")
|
145
|
+
# p.errors.count #=> 1
|
146
|
+
# p.errors.add(:name, "must be specified")
|
147
|
+
# p.errors.count #=> 2
|
42
148
|
def count
|
43
149
|
to_a.size
|
44
150
|
end
|
45
151
|
|
152
|
+
# Returns true if there are any errors, false if not.
|
153
|
+
def empty?
|
154
|
+
all? { |k, v| v && v.empty? }
|
155
|
+
end
|
156
|
+
|
157
|
+
# Returns an xml formatted representation of the Errors hash.
|
158
|
+
#
|
159
|
+
# p.errors.add(:name, "can't be blank")
|
160
|
+
# p.errors.add(:name, "must be specified")
|
161
|
+
# p.errors.to_xml #=> Produces:
|
162
|
+
#
|
163
|
+
# # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
164
|
+
# # <errors>
|
165
|
+
# # <error>name can't be blank</error>
|
166
|
+
# # <error>name must be specified</error>
|
167
|
+
# # </errors>
|
46
168
|
def to_xml(options={})
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
options[:builder].errors do |e|
|
54
|
-
to_a.each { |error| e.error(error) }
|
55
|
-
end
|
169
|
+
to_a.to_xml options.reverse_merge(:root => "errors", :skip_types => true)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Returns an array as JSON representation for this object.
|
173
|
+
def as_json(options=nil)
|
174
|
+
to_a
|
56
175
|
end
|
57
176
|
|
58
|
-
# Adds
|
59
|
-
# for the same attribute
|
60
|
-
#
|
61
|
-
# If no +
|
62
|
-
#
|
177
|
+
# Adds +message+ to the error messages on +attribute+, which will be returned on a call to
|
178
|
+
# <tt>on(attribute)</tt> for the same attribute. More than one error can be added to the same
|
179
|
+
# +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
|
180
|
+
# If no +message+ is supplied, <tt>:invalid</tt> is assumed.
|
181
|
+
#
|
182
|
+
# If +message+ is a symbol, it will be translated using the appropriate scope (see +translate_error+).
|
183
|
+
# If +message+ is a proc, it will be called, allowing for things like <tt>Time.now</tt> to be used within an error.
|
63
184
|
def add(attribute, message = nil, options = {})
|
64
185
|
message ||= :invalid
|
65
|
-
|
186
|
+
|
187
|
+
if message.is_a?(Symbol)
|
188
|
+
message = generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
|
189
|
+
elsif message.is_a?(Proc)
|
190
|
+
message = message.call
|
191
|
+
end
|
192
|
+
|
66
193
|
self[attribute] << message
|
67
194
|
end
|
68
195
|
|
69
196
|
# Will add an error message to each of the attributes in +attributes+ that is empty.
|
70
|
-
def add_on_empty(attributes,
|
197
|
+
def add_on_empty(attributes, options = {})
|
198
|
+
if options && !options.is_a?(Hash)
|
199
|
+
options = { :message => options }
|
200
|
+
ActiveSupport::Deprecation.warn \
|
201
|
+
"ActiveModel::Errors#add_on_empty(attributes, custom_message) has been deprecated.\n" +
|
202
|
+
"Instead of passing a custom_message pass an options Hash { :message => custom_message }."
|
203
|
+
end
|
204
|
+
|
71
205
|
[attributes].flatten.each do |attribute|
|
72
206
|
value = @base.send(:read_attribute_for_validation, attribute)
|
73
207
|
is_empty = value.respond_to?(:empty?) ? value.empty? : false
|
74
|
-
add(attribute, :empty,
|
208
|
+
add(attribute, :empty, options) if value.nil? || is_empty
|
75
209
|
end
|
76
210
|
end
|
77
211
|
|
78
212
|
# Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?).
|
79
|
-
def add_on_blank(attributes,
|
213
|
+
def add_on_blank(attributes, options = {})
|
214
|
+
if options && !options.is_a?(Hash)
|
215
|
+
options = { :message => options }
|
216
|
+
ActiveSupport::Deprecation.warn \
|
217
|
+
"ActiveModel::Errors#add_on_blank(attributes, custom_message) has been deprecated.\n" +
|
218
|
+
"Instead of passing a custom_message pass an options Hash { :message => custom_message }."
|
219
|
+
end
|
220
|
+
|
80
221
|
[attributes].flatten.each do |attribute|
|
81
222
|
value = @base.send(:read_attribute_for_validation, attribute)
|
82
|
-
add(attribute, :blank,
|
223
|
+
add(attribute, :blank, options) if value.blank?
|
83
224
|
end
|
84
225
|
end
|
85
226
|
|
@@ -93,7 +234,7 @@ module ActiveModel
|
|
93
234
|
# company = Company.create(:address => '123 First St.')
|
94
235
|
# company.errors.full_messages # =>
|
95
236
|
# ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
|
96
|
-
def full_messages
|
237
|
+
def full_messages
|
97
238
|
full_messages = []
|
98
239
|
|
99
240
|
each do |attribute, messages|
|
@@ -103,10 +244,12 @@ module ActiveModel
|
|
103
244
|
if attribute == :base
|
104
245
|
messages.each {|m| full_messages << m }
|
105
246
|
else
|
106
|
-
attr_name = attribute.to_s.humanize
|
107
|
-
|
247
|
+
attr_name = attribute.to_s.gsub('.', '_').humanize
|
248
|
+
attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
|
249
|
+
options = { :default => "%{attribute} %{message}", :attribute => attr_name }
|
250
|
+
|
108
251
|
messages.each do |m|
|
109
|
-
full_messages <<
|
252
|
+
full_messages << I18n.t(:"errors.format", options.merge(:message => m))
|
110
253
|
end
|
111
254
|
end
|
112
255
|
end
|
@@ -114,46 +257,62 @@ module ActiveModel
|
|
114
257
|
full_messages
|
115
258
|
end
|
116
259
|
|
117
|
-
# Translates an error message in its default scope
|
118
|
-
#
|
119
|
-
#
|
120
|
-
#
|
260
|
+
# Translates an error message in its default scope
|
261
|
+
# (<tt>activemodel.errors.messages</tt>).
|
262
|
+
#
|
263
|
+
# Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>,
|
264
|
+
# if it's not there, it's looked up in <tt>models.MODEL.MESSAGE</tt> and if that is not
|
265
|
+
# there also, it returns the translation of the default message
|
266
|
+
# (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model name,
|
121
267
|
# translated attribute name and the value are available for interpolation.
|
122
268
|
#
|
123
|
-
# When using
|
124
|
-
# hasn't been found. Say you have
|
125
|
-
#
|
269
|
+
# When using inheritance in your models, it will check all the inherited
|
270
|
+
# models too, but only if the model itself hasn't been found. Say you have
|
271
|
+
# <tt>class Admin < User; end</tt> and you wanted the translation for
|
272
|
+
# the <tt>:blank</tt> error +message+ for the <tt>title</tt> +attribute+,
|
273
|
+
# it looks for these translations:
|
126
274
|
#
|
127
275
|
# <ol>
|
128
276
|
# <li><tt>activemodel.errors.models.admin.attributes.title.blank</tt></li>
|
129
277
|
# <li><tt>activemodel.errors.models.admin.blank</tt></li>
|
130
278
|
# <li><tt>activemodel.errors.models.user.attributes.title.blank</tt></li>
|
131
279
|
# <li><tt>activemodel.errors.models.user.blank</tt></li>
|
132
|
-
# <li><tt>activemodel.errors.messages.blank</tt></li>
|
133
280
|
# <li>any default you provided through the +options+ hash (in the activemodel.errors scope)</li>
|
281
|
+
# <li><tt>activemodel.errors.messages.blank</tt></li>
|
282
|
+
# <li><tt>errors.attributes.title.blank</tt></li>
|
283
|
+
# <li><tt>errors.messages.blank</tt></li>
|
134
284
|
# </ol>
|
135
|
-
def generate_message(attribute,
|
136
|
-
|
285
|
+
def generate_message(attribute, type = :invalid, options = {})
|
286
|
+
type = options.delete(:message) if options[:message].is_a?(Symbol)
|
137
287
|
|
138
|
-
|
139
|
-
|
288
|
+
if options[:default]
|
289
|
+
ActiveSupport::Deprecation.warn \
|
290
|
+
"ActiveModel::Errors#generate_message(attributes, custom_message) has been deprecated.\n" +
|
291
|
+
"Use ActiveModel::Errors#generate_message(attributes, :message => 'your message') instead."
|
292
|
+
options[:message] = options.delete(:default)
|
293
|
+
end
|
140
294
|
|
141
|
-
defaults =
|
142
|
-
[ :"models.#{klass.
|
143
|
-
:"models.#{klass.
|
295
|
+
defaults = @base.class.lookup_ancestors.map do |klass|
|
296
|
+
[ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.underscore}.attributes.#{attribute}.#{type}",
|
297
|
+
:"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.underscore}.#{type}" ]
|
144
298
|
end
|
145
299
|
|
146
|
-
defaults << options.delete(:
|
147
|
-
defaults
|
300
|
+
defaults << options.delete(:message)
|
301
|
+
defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}"
|
302
|
+
defaults << :"errors.attributes.#{attribute}.#{type}"
|
303
|
+
defaults << :"errors.messages.#{type}"
|
304
|
+
|
305
|
+
defaults.compact!
|
306
|
+
defaults.flatten!
|
148
307
|
|
149
308
|
key = defaults.shift
|
150
|
-
value = @base.send(:read_attribute_for_validation, attribute)
|
309
|
+
value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil)
|
151
310
|
|
152
|
-
options = {
|
153
|
-
:
|
154
|
-
:
|
155
|
-
:
|
156
|
-
:
|
311
|
+
options = {
|
312
|
+
:default => defaults,
|
313
|
+
:model => @base.class.model_name.human,
|
314
|
+
:attribute => @base.class.human_attribute_name(attribute),
|
315
|
+
:value => value
|
157
316
|
}.merge(options)
|
158
317
|
|
159
318
|
I18n.translate(key, options)
|