activemodel 3.0.pre → 3.0.0.rc
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|