activemodel 3.0.0.rc → 3.0.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +6 -1
- data/README.rdoc +34 -34
- data/lib/active_model/attribute_methods.rb +43 -43
- data/lib/active_model/callbacks.rb +31 -28
- data/lib/active_model/conversion.rb +14 -11
- data/lib/active_model/dirty.rb +22 -24
- data/lib/active_model/errors.rb +44 -48
- data/lib/active_model/lint.rb +2 -1
- data/lib/active_model/naming.rb +11 -8
- data/lib/active_model/observing.rb +7 -7
- data/lib/active_model/serialization.rb +23 -21
- data/lib/active_model/serializers/xml.rb +1 -1
- data/lib/active_model/translation.rb +8 -8
- data/lib/active_model/validations.rb +40 -29
- data/lib/active_model/validations/acceptance.rb +11 -11
- data/lib/active_model/validations/callbacks.rb +18 -4
- data/lib/active_model/validations/confirmation.rb +10 -10
- data/lib/active_model/validations/length.rb +10 -11
- data/lib/active_model/validations/validates.rb +8 -8
- data/lib/active_model/validator.rb +14 -14
- data/lib/active_model/version.rb +1 -1
- metadata +7 -7
@@ -17,24 +17,27 @@ module ActiveModel
|
|
17
17
|
# end
|
18
18
|
#
|
19
19
|
# cm = ContactMessage.new
|
20
|
-
# cm.to_model == self
|
21
|
-
# cm.to_key
|
22
|
-
# cm.to_param
|
20
|
+
# cm.to_model == self # => true
|
21
|
+
# cm.to_key # => nil
|
22
|
+
# cm.to_param # => nil
|
23
23
|
#
|
24
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
|
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
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
|
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
31
|
# your object with Active Model compliant methods.
|
32
32
|
def to_model
|
33
33
|
self
|
34
34
|
end
|
35
35
|
|
36
|
-
# Returns an Enumerable of all
|
37
|
-
#
|
36
|
+
# Returns an Enumerable of all key attributes if any is set, regardless
|
37
|
+
# if the object is persisted or not.
|
38
|
+
#
|
39
|
+
# Note the default implementation uses persisted? just because all objects
|
40
|
+
# in Ruby 1.8.x responds to :id.
|
38
41
|
def to_key
|
39
42
|
persisted? ? [id] : nil
|
40
43
|
end
|
@@ -42,7 +45,7 @@ module ActiveModel
|
|
42
45
|
# Returns a string representing the object's key suitable for use in URLs,
|
43
46
|
# or nil if persisted? is false
|
44
47
|
def to_param
|
45
|
-
|
48
|
+
persisted? ? to_key.join('-') : nil
|
46
49
|
end
|
47
50
|
end
|
48
51
|
end
|
data/lib/active_model/dirty.rb
CHANGED
@@ -6,47 +6,48 @@ require 'active_support/core_ext/object/duplicable'
|
|
6
6
|
module ActiveModel
|
7
7
|
# == Active Model Dirty
|
8
8
|
#
|
9
|
-
# Provides a way to track changes in your object in the same way as
|
9
|
+
# Provides a way to track changes in your object in the same way as
|
10
10
|
# Active Record does.
|
11
|
-
#
|
11
|
+
#
|
12
12
|
# The requirements to implement ActiveModel::Dirty are to:
|
13
13
|
#
|
14
14
|
# * <tt>include ActiveModel::Dirty</tt> in your object
|
15
|
-
# * Call <tt>define_attribute_methods</tt> passing each method you want to
|
15
|
+
# * Call <tt>define_attribute_methods</tt> passing each method you want to
|
16
16
|
# track
|
17
|
-
# * Call <tt>attr_name_will_change!</tt> before each change to the tracked
|
17
|
+
# * Call <tt>attr_name_will_change!</tt> before each change to the tracked
|
18
18
|
# attribute
|
19
|
-
#
|
20
|
-
# If you wish to also track previous changes on save or update, you need to
|
19
|
+
#
|
20
|
+
# If you wish to also track previous changes on save or update, you need to
|
21
21
|
# add
|
22
|
-
#
|
22
|
+
#
|
23
23
|
# @previously_changed = changes
|
24
|
-
#
|
24
|
+
#
|
25
25
|
# inside of your save or update method.
|
26
|
-
#
|
26
|
+
#
|
27
27
|
# A minimal implementation could be:
|
28
|
-
#
|
28
|
+
#
|
29
29
|
# class Person
|
30
|
-
#
|
30
|
+
#
|
31
31
|
# include ActiveModel::Dirty
|
32
|
-
#
|
32
|
+
#
|
33
33
|
# define_attribute_methods [:name]
|
34
|
-
#
|
34
|
+
#
|
35
35
|
# def name
|
36
36
|
# @name
|
37
37
|
# end
|
38
|
-
#
|
38
|
+
#
|
39
39
|
# def name=(val)
|
40
|
-
# name_will_change!
|
40
|
+
# name_will_change! unless val == @name
|
41
41
|
# @name = val
|
42
42
|
# end
|
43
|
-
#
|
43
|
+
#
|
44
44
|
# def save
|
45
45
|
# @previously_changed = changes
|
46
|
+
# @changed_attributes.clear
|
46
47
|
# end
|
47
|
-
#
|
48
|
+
#
|
48
49
|
# end
|
49
|
-
#
|
50
|
+
#
|
50
51
|
# == Examples:
|
51
52
|
#
|
52
53
|
# A newly instantiated object is unchanged:
|
@@ -77,13 +78,10 @@ module ActiveModel
|
|
77
78
|
# person.changed # => ['name']
|
78
79
|
# person.changes # => { 'name' => ['Bill', 'Bob'] }
|
79
80
|
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
83
|
-
# person.name_changed? # => false
|
84
|
-
# person.name # => 'Bill'
|
81
|
+
# If an attribute is modified in-place then make use of <tt>[attribute_name]_will_change!</tt>
|
82
|
+
# to mark that the attribute is changing. Otherwise ActiveModel can't track changes to
|
83
|
+
# in-place attributes.
|
85
84
|
#
|
86
|
-
# Before modifying an attribute in-place:
|
87
85
|
# person.name_will_change!
|
88
86
|
# person.name << 'y'
|
89
87
|
# person.name_change # => ['Bill', 'Billy']
|
data/lib/active_model/errors.rb
CHANGED
@@ -12,50 +12,50 @@ module ActiveModel
|
|
12
12
|
#
|
13
13
|
# Provides a modified +OrderedHash+ that you can include in your object
|
14
14
|
# for handling error messages and interacting with Action Pack helpers.
|
15
|
-
#
|
15
|
+
#
|
16
16
|
# A minimal implementation could be:
|
17
|
-
#
|
17
|
+
#
|
18
18
|
# class Person
|
19
|
-
#
|
19
|
+
#
|
20
20
|
# # Required dependency for ActiveModel::Errors
|
21
21
|
# extend ActiveModel::Naming
|
22
|
-
#
|
22
|
+
#
|
23
23
|
# def initialize
|
24
24
|
# @errors = ActiveModel::Errors.new(self)
|
25
25
|
# end
|
26
|
-
#
|
26
|
+
#
|
27
27
|
# attr_accessor :name
|
28
28
|
# attr_reader :errors
|
29
|
-
#
|
29
|
+
#
|
30
30
|
# def validate!
|
31
31
|
# errors.add(:name, "can not be nil") if name == nil
|
32
32
|
# end
|
33
|
-
#
|
33
|
+
#
|
34
34
|
# # The following methods are needed to be minimally implemented
|
35
35
|
#
|
36
36
|
# def read_attribute_for_validation(attr)
|
37
37
|
# send(attr)
|
38
38
|
# end
|
39
|
-
#
|
40
|
-
# def
|
39
|
+
#
|
40
|
+
# def Person.human_attribute_name(attr, options = {})
|
41
41
|
# attr
|
42
42
|
# end
|
43
|
-
#
|
44
|
-
# def
|
43
|
+
#
|
44
|
+
# def Person.lookup_ancestors
|
45
45
|
# [self]
|
46
46
|
# end
|
47
|
-
#
|
47
|
+
#
|
48
48
|
# end
|
49
|
-
#
|
49
|
+
#
|
50
50
|
# The last three methods are required in your object for Errors to be
|
51
51
|
# able to generate error messages correctly and also handle multiple
|
52
52
|
# languages. Of course, if you extend your object with ActiveModel::Translations
|
53
53
|
# you will not need to implement the last two. Likewise, using
|
54
54
|
# ActiveModel::Validations will handle the validation related methods
|
55
55
|
# for you.
|
56
|
-
#
|
56
|
+
#
|
57
57
|
# The above allows you to do:
|
58
|
-
#
|
58
|
+
#
|
59
59
|
# p = Person.new
|
60
60
|
# p.validate! # => ["can not be nil"]
|
61
61
|
# p.errors.full_messages # => ["name can not be nil"]
|
@@ -66,7 +66,7 @@ module ActiveModel
|
|
66
66
|
CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank]
|
67
67
|
|
68
68
|
# Pass in the instance of the object that is using the errors object.
|
69
|
-
#
|
69
|
+
#
|
70
70
|
# class Person
|
71
71
|
# def initialize
|
72
72
|
# @errors = ActiveModel::Errors.new(self)
|
@@ -80,23 +80,19 @@ module ActiveModel
|
|
80
80
|
alias_method :get, :[]
|
81
81
|
alias_method :set, :[]=
|
82
82
|
|
83
|
-
# When passed a symbol or a name of a method, returns an array of errors
|
83
|
+
# When passed a symbol or a name of a method, returns an array of errors
|
84
84
|
# for the method.
|
85
|
-
#
|
86
|
-
# p.errors[:name]
|
87
|
-
# p.errors['name']
|
85
|
+
#
|
86
|
+
# p.errors[:name] # => ["can not be nil"]
|
87
|
+
# p.errors['name'] # => ["can not be nil"]
|
88
88
|
def [](attribute)
|
89
|
-
|
90
|
-
errors
|
91
|
-
else
|
92
|
-
set(attribute.to_sym, [])
|
93
|
-
end
|
89
|
+
get(attribute.to_sym) || set(attribute.to_sym, [])
|
94
90
|
end
|
95
91
|
|
96
92
|
# Adds to the supplied attribute the supplied error message.
|
97
|
-
#
|
93
|
+
#
|
98
94
|
# p.errors[:name] = "must be set"
|
99
|
-
# p.errors[:name]
|
95
|
+
# p.errors[:name] # => ['must be set']
|
100
96
|
def []=(attribute, error)
|
101
97
|
self[attribute.to_sym] << error
|
102
98
|
end
|
@@ -104,12 +100,12 @@ module ActiveModel
|
|
104
100
|
# Iterates through each error key, value pair in the error messages hash.
|
105
101
|
# Yields the attribute and the error for that attribute. If the attribute
|
106
102
|
# has more than one error message, yields once for each error message.
|
107
|
-
#
|
103
|
+
#
|
108
104
|
# p.errors.add(:name, "can't be blank")
|
109
105
|
# p.errors.each do |attribute, errors_array|
|
110
106
|
# # Will yield :name and "can't be blank"
|
111
107
|
# end
|
112
|
-
#
|
108
|
+
#
|
113
109
|
# p.errors.add(:name, "must be specified")
|
114
110
|
# p.errors.each do |attribute, errors_array|
|
115
111
|
# # Will yield :name and "can't be blank"
|
@@ -122,29 +118,29 @@ module ActiveModel
|
|
122
118
|
end
|
123
119
|
|
124
120
|
# Returns the number of error messages.
|
125
|
-
#
|
121
|
+
#
|
126
122
|
# p.errors.add(:name, "can't be blank")
|
127
|
-
# p.errors.size
|
123
|
+
# p.errors.size # => 1
|
128
124
|
# p.errors.add(:name, "must be specified")
|
129
|
-
# p.errors.size
|
125
|
+
# p.errors.size # => 2
|
130
126
|
def size
|
131
127
|
values.flatten.size
|
132
128
|
end
|
133
129
|
|
134
130
|
# Returns an array of error messages, with the attribute name included
|
135
|
-
#
|
131
|
+
#
|
136
132
|
# p.errors.add(:name, "can't be blank")
|
137
133
|
# p.errors.add(:name, "must be specified")
|
138
|
-
# p.errors.to_a
|
134
|
+
# p.errors.to_a # => ["name can't be blank", "name must be specified"]
|
139
135
|
def to_a
|
140
136
|
full_messages
|
141
137
|
end
|
142
138
|
|
143
139
|
# Returns the number of error messages.
|
144
140
|
# p.errors.add(:name, "can't be blank")
|
145
|
-
# p.errors.count
|
141
|
+
# p.errors.count # => 1
|
146
142
|
# p.errors.add(:name, "must be specified")
|
147
|
-
# p.errors.count
|
143
|
+
# p.errors.count # => 2
|
148
144
|
def count
|
149
145
|
to_a.size
|
150
146
|
end
|
@@ -155,11 +151,11 @@ module ActiveModel
|
|
155
151
|
end
|
156
152
|
|
157
153
|
# Returns an xml formatted representation of the Errors hash.
|
158
|
-
#
|
154
|
+
#
|
159
155
|
# p.errors.add(:name, "can't be blank")
|
160
156
|
# p.errors.add(:name, "must be specified")
|
161
|
-
# p.errors.to_xml
|
162
|
-
#
|
157
|
+
# p.errors.to_xml
|
158
|
+
# # =>
|
163
159
|
# # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
164
160
|
# # <errors>
|
165
161
|
# # <error>name can't be blank</error>
|
@@ -169,16 +165,16 @@ module ActiveModel
|
|
169
165
|
to_a.to_xml options.reverse_merge(:root => "errors", :skip_types => true)
|
170
166
|
end
|
171
167
|
|
172
|
-
# Returns an
|
168
|
+
# Returns an ActiveSupport::OrderedHash that can be used as the JSON representation for this object.
|
173
169
|
def as_json(options=nil)
|
174
|
-
|
170
|
+
self
|
175
171
|
end
|
176
172
|
|
177
173
|
# Adds +message+ to the error messages on +attribute+, which will be returned on a call to
|
178
174
|
# <tt>on(attribute)</tt> for the same attribute. More than one error can be added to the same
|
179
175
|
# +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
|
180
176
|
# If no +message+ is supplied, <tt>:invalid</tt> is assumed.
|
181
|
-
#
|
177
|
+
#
|
182
178
|
# If +message+ is a symbol, it will be translated using the appropriate scope (see +translate_error+).
|
183
179
|
# If +message+ is a proc, it will be called, allowing for things like <tt>Time.now</tt> to be used within an error.
|
184
180
|
def add(attribute, message = nil, options = {})
|
@@ -257,19 +253,19 @@ module ActiveModel
|
|
257
253
|
full_messages
|
258
254
|
end
|
259
255
|
|
260
|
-
# Translates an error message in its default scope
|
256
|
+
# Translates an error message in its default scope
|
261
257
|
# (<tt>activemodel.errors.messages</tt>).
|
262
258
|
#
|
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
|
259
|
+
# Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>,
|
260
|
+
# if it's not there, it's looked up in <tt>models.MODEL.MESSAGE</tt> and if that is not
|
261
|
+
# there also, it returns the translation of the default message
|
266
262
|
# (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model name,
|
267
263
|
# translated attribute name and the value are available for interpolation.
|
268
264
|
#
|
269
265
|
# When using inheritance in your models, it will check all the inherited
|
270
266
|
# 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+,
|
267
|
+
# <tt>class Admin < User; end</tt> and you wanted the translation for
|
268
|
+
# the <tt>:blank</tt> error +message+ for the <tt>title</tt> +attribute+,
|
273
269
|
# it looks for these translations:
|
274
270
|
#
|
275
271
|
# <ol>
|
data/lib/active_model/lint.rb
CHANGED
@@ -38,6 +38,7 @@ module ActiveModel
|
|
38
38
|
# not persisted?, then to_param should always return nil.
|
39
39
|
def test_to_param
|
40
40
|
assert model.respond_to?(:to_param), "The model should respond to to_param"
|
41
|
+
def model.to_key() [1] end
|
41
42
|
def model.persisted?() false end
|
42
43
|
assert model.to_param.nil?
|
43
44
|
end
|
@@ -79,7 +80,7 @@ module ActiveModel
|
|
79
80
|
end
|
80
81
|
|
81
82
|
# == Errors Testing
|
82
|
-
#
|
83
|
+
#
|
83
84
|
# Returns an object that has :[] and :full_messages defined on it. See below
|
84
85
|
# for more details.
|
85
86
|
#
|
data/lib/active_model/naming.rb
CHANGED
@@ -17,7 +17,10 @@ module ActiveModel
|
|
17
17
|
end
|
18
18
|
|
19
19
|
# Transform the model name into a more humane format, using I18n. By default,
|
20
|
-
# it will underscore then humanize the class name
|
20
|
+
# it will underscore then humanize the class name
|
21
|
+
#
|
22
|
+
# BlogPost.model_name.human # => "Blog post"
|
23
|
+
#
|
21
24
|
# Specify +options+ with additional translating options.
|
22
25
|
def human(options={})
|
23
26
|
return @human unless @klass.respond_to?(:lookup_ancestors) &&
|
@@ -38,16 +41,16 @@ module ActiveModel
|
|
38
41
|
# == Active Model Naming
|
39
42
|
#
|
40
43
|
# Creates a +model_name+ method on your object.
|
41
|
-
#
|
44
|
+
#
|
42
45
|
# To implement, just extend ActiveModel::Naming in your object:
|
43
|
-
#
|
46
|
+
#
|
44
47
|
# class BookCover
|
45
48
|
# extend ActiveModel::Naming
|
46
49
|
# end
|
47
|
-
#
|
48
|
-
# BookCover.model_name
|
49
|
-
# BookCover.model_name.human
|
50
|
-
#
|
50
|
+
#
|
51
|
+
# BookCover.model_name # => "BookCover"
|
52
|
+
# BookCover.model_name.human # => "Book cover"
|
53
|
+
#
|
51
54
|
# Providing the functionality that ActiveModel::Naming provides in your object
|
52
55
|
# is required to pass the Active Model Lint test. So either extending the provided
|
53
56
|
# method below, or rolling your own is required..
|
@@ -87,5 +90,5 @@ module ActiveModel
|
|
87
90
|
(record_or_class.is_a?(Class) ? record_or_class : record_or_class.class).model_name
|
88
91
|
end
|
89
92
|
end
|
90
|
-
|
93
|
+
|
91
94
|
end
|
@@ -10,7 +10,7 @@ module ActiveModel
|
|
10
10
|
|
11
11
|
module ClassMethods
|
12
12
|
# == Active Model Observers Activation
|
13
|
-
#
|
13
|
+
#
|
14
14
|
# Activates the observers assigned. Examples:
|
15
15
|
#
|
16
16
|
# # Calls PersonObserver.instance
|
@@ -22,7 +22,7 @@ module ActiveModel
|
|
22
22
|
# # Same as above, just using explicit class references
|
23
23
|
# ActiveRecord::Base.observers = Cacher, GarbageCollector
|
24
24
|
#
|
25
|
-
# Note: Setting this does not instantiate the observers yet.
|
25
|
+
# Note: Setting this does not instantiate the observers yet.
|
26
26
|
# +instantiate_observers+ is called during startup, and before
|
27
27
|
# each development request.
|
28
28
|
def observers=(*values)
|
@@ -123,9 +123,9 @@ module ActiveModel
|
|
123
123
|
#
|
124
124
|
# Observers will by default be mapped to the class with which they share a
|
125
125
|
# name. So CommentObserver will be tied to observing Comment, ProductManagerObserver
|
126
|
-
# to ProductManager, and so on. If you want to name your observer differently than
|
127
|
-
# the class you're interested in observing, you can use the Observer.observe class
|
128
|
-
# method which takes either the concrete class (Product) or a symbol for that
|
126
|
+
# to ProductManager, and so on. If you want to name your observer differently than
|
127
|
+
# the class you're interested in observing, you can use the Observer.observe class
|
128
|
+
# method which takes either the concrete class (Product) or a symbol for that
|
129
129
|
# class (:product):
|
130
130
|
#
|
131
131
|
# class AuditObserver < ActiveModel::Observer
|
@@ -136,7 +136,7 @@ module ActiveModel
|
|
136
136
|
# end
|
137
137
|
# end
|
138
138
|
#
|
139
|
-
# If the audit observer needs to watch more than one kind of object, this can be
|
139
|
+
# If the audit observer needs to watch more than one kind of object, this can be
|
140
140
|
# specified with multiple arguments:
|
141
141
|
#
|
142
142
|
# class AuditObserver < ActiveModel::Observer
|
@@ -147,7 +147,7 @@ module ActiveModel
|
|
147
147
|
# end
|
148
148
|
# end
|
149
149
|
#
|
150
|
-
# The AuditObserver will now act on both updates to Account and Balance by treating
|
150
|
+
# The AuditObserver will now act on both updates to Account and Balance by treating
|
151
151
|
# them both as records.
|
152
152
|
#
|
153
153
|
class Observer
|