activemodel 3.0.0.beta4 → 3.0.pre
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 +1 -39
- data/MIT-LICENSE +1 -1
- data/README +16 -200
- data/lib/active_model.rb +19 -28
- data/lib/active_model/attribute_methods.rb +27 -142
- data/lib/active_model/conversion.rb +1 -37
- data/lib/active_model/dirty.rb +12 -51
- data/lib/active_model/errors.rb +22 -146
- data/lib/active_model/lint.rb +14 -48
- data/lib/active_model/locale/en.yml +23 -26
- data/lib/active_model/naming.rb +5 -41
- data/lib/active_model/observing.rb +16 -35
- data/lib/active_model/serialization.rb +0 -57
- data/lib/active_model/serializers/json.rb +8 -13
- data/lib/active_model/serializers/xml.rb +123 -63
- data/lib/active_model/state_machine.rb +70 -0
- data/lib/active_model/state_machine/event.rb +62 -0
- data/lib/active_model/state_machine/machine.rb +75 -0
- data/lib/active_model/state_machine/state.rb +47 -0
- data/lib/active_model/state_machine/state_transition.rb +40 -0
- data/lib/active_model/test_case.rb +2 -0
- data/lib/active_model/validations.rb +62 -125
- data/lib/active_model/validations/acceptance.rb +18 -23
- data/lib/active_model/validations/confirmation.rb +10 -14
- data/lib/active_model/validations/exclusion.rb +13 -15
- data/lib/active_model/validations/format.rb +24 -26
- data/lib/active_model/validations/inclusion.rb +13 -15
- data/lib/active_model/validations/length.rb +65 -61
- data/lib/active_model/validations/numericality.rb +58 -76
- data/lib/active_model/validations/presence.rb +8 -8
- data/lib/active_model/validations/with.rb +22 -90
- data/lib/active_model/validations_repair_helper.rb +35 -0
- data/lib/active_model/version.rb +2 -3
- metadata +19 -63
- data/lib/active_model/callbacks.rb +0 -134
- data/lib/active_model/railtie.rb +0 -2
- data/lib/active_model/translation.rb +0 -60
- data/lib/active_model/validations/validates.rb +0 -108
- data/lib/active_model/validator.rb +0 -183
@@ -1,44 +1,8 @@
|
|
1
1
|
module ActiveModel
|
2
|
-
#
|
3
|
-
#
|
4
|
-
# == Example
|
5
|
-
#
|
6
|
-
# Let's take for example this non persisted object.
|
7
|
-
#
|
8
|
-
# class ContactMessage
|
9
|
-
# include ActiveModel::Conversion
|
10
|
-
#
|
11
|
-
# # ContactMessage are never persisted in the DB
|
12
|
-
# def persisted?
|
13
|
-
# false
|
14
|
-
# end
|
15
|
-
# end
|
16
|
-
#
|
17
|
-
# cm = ContactMessage.new
|
18
|
-
# cm.to_model == self #=> true
|
19
|
-
# cm.to_key #=> nil
|
20
|
-
# cm.to_param #=> nil
|
21
|
-
#
|
2
|
+
# Include ActiveModel::Conversion if your object "acts like an ActiveModel model".
|
22
3
|
module Conversion
|
23
|
-
# If your object is already designed to implement all of the Active Model you can use
|
24
|
-
# the default to_model implementation, which simply returns self.
|
25
|
-
#
|
26
|
-
# If your model does not act like an Active Model object, then you should define
|
27
|
-
# <tt>:to_model</tt> yourself returning a proxy object that wraps your object
|
28
|
-
# with Active Model compliant methods.
|
29
4
|
def to_model
|
30
5
|
self
|
31
6
|
end
|
32
|
-
|
33
|
-
# Returns an Enumerable of all (primary) key attributes or nil if persisted? is fakse
|
34
|
-
def to_key
|
35
|
-
persisted? ? [id] : nil
|
36
|
-
end
|
37
|
-
|
38
|
-
# Returns a string representing the object's key suitable for use in URLs,
|
39
|
-
# or nil if persisted? is false
|
40
|
-
def to_param
|
41
|
-
to_key ? to_key.join('-') : nil
|
42
|
-
end
|
43
7
|
end
|
44
8
|
end
|
data/lib/active_model/dirty.rb
CHANGED
@@ -1,48 +1,5 @@
|
|
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
|
-
|
6
1
|
module ActiveModel
|
7
|
-
#
|
8
|
-
# object in the same way as ActiveRecord does.
|
9
|
-
#
|
10
|
-
# The requirements to implement ActiveModel::Dirty are:
|
11
|
-
#
|
12
|
-
# * <tt>include ActiveModel::Dirty</tt> in your object
|
13
|
-
# * Call <tt>define_attribute_methods</tt> passing each method you want to track
|
14
|
-
# * Call <tt>attr_name_will_change!</tt> before each change to the tracked attribute
|
15
|
-
#
|
16
|
-
# If you wish to also track previous changes on save or update, you need to add
|
17
|
-
#
|
18
|
-
# @previously_changed = changes
|
19
|
-
#
|
20
|
-
# inside of your save or update method.
|
21
|
-
#
|
22
|
-
# A minimal implementation could be:
|
23
|
-
#
|
24
|
-
# class Person
|
25
|
-
#
|
26
|
-
# include ActiveModel::Dirty
|
27
|
-
#
|
28
|
-
# define_attribute_methods [:name]
|
29
|
-
#
|
30
|
-
# def name
|
31
|
-
# @name
|
32
|
-
# end
|
33
|
-
#
|
34
|
-
# def name=(val)
|
35
|
-
# name_will_change!
|
36
|
-
# @name = val
|
37
|
-
# end
|
38
|
-
#
|
39
|
-
# def save
|
40
|
-
# @previously_changed = changes
|
41
|
-
# end
|
42
|
-
#
|
43
|
-
# end
|
44
|
-
#
|
45
|
-
# == Examples:
|
2
|
+
# Track unsaved attribute changes.
|
46
3
|
#
|
47
4
|
# A newly instantiated object is unchanged:
|
48
5
|
# person = Person.find_by_name('Uncle Bob')
|
@@ -112,7 +69,7 @@ module ActiveModel
|
|
112
69
|
# person.name = 'bob'
|
113
70
|
# person.changes # => { 'name' => ['bill', 'bob'] }
|
114
71
|
def changes
|
115
|
-
changed.inject(
|
72
|
+
changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h }
|
116
73
|
end
|
117
74
|
|
118
75
|
# Map of attributes that were changed when the model was saved.
|
@@ -121,15 +78,19 @@ module ActiveModel
|
|
121
78
|
# person.save
|
122
79
|
# person.previous_changes # => {'name' => ['bob, 'robert']}
|
123
80
|
def previous_changes
|
124
|
-
|
125
|
-
end
|
126
|
-
|
127
|
-
# Map of change <tt>attr => original value</tt>.
|
128
|
-
def changed_attributes
|
129
|
-
@changed_attributes ||= {}
|
81
|
+
previously_changed_attributes
|
130
82
|
end
|
131
83
|
|
132
84
|
private
|
85
|
+
# Map of change <tt>attr => original value</tt>.
|
86
|
+
def changed_attributes
|
87
|
+
@changed_attributes ||= {}
|
88
|
+
end
|
89
|
+
|
90
|
+
# Map of fields that were changed when the model was saved
|
91
|
+
def previously_changed_attributes
|
92
|
+
@previously_changed || {}
|
93
|
+
end
|
133
94
|
|
134
95
|
# Handle <tt>*_changed?</tt> for +method_missing+.
|
135
96
|
def attribute_changed?(attr)
|
data/lib/active_model/errors.rb
CHANGED
@@ -1,71 +1,10 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
|
3
|
-
require 'active_support/core_ext/array/wrap'
|
4
1
|
require 'active_support/core_ext/string/inflections'
|
5
|
-
require 'active_support/core_ext/object/blank'
|
6
2
|
require 'active_support/ordered_hash'
|
7
3
|
|
8
4
|
module ActiveModel
|
9
|
-
# Provides a modified +OrderedHash+ that you can include in your object
|
10
|
-
# for handling error messages and interacting with Action Pack helpers.
|
11
|
-
#
|
12
|
-
# A minimal implementation could be:
|
13
|
-
#
|
14
|
-
# class Person
|
15
|
-
#
|
16
|
-
# # Required dependency for ActiveModel::Errors
|
17
|
-
# extend ActiveModel::Naming
|
18
|
-
#
|
19
|
-
# def initialize
|
20
|
-
# @errors = ActiveModel::Errors.new(self)
|
21
|
-
# end
|
22
|
-
#
|
23
|
-
# attr_accessor :name
|
24
|
-
# attr_reader :errors
|
25
|
-
#
|
26
|
-
# def validate!
|
27
|
-
# errors.add(:name, "can not be nil") if name == nil
|
28
|
-
# end
|
29
|
-
#
|
30
|
-
# # The following methods are needed to be minimally implemented
|
31
|
-
#
|
32
|
-
# def read_attribute_for_validation(attr)
|
33
|
-
# send(attr)
|
34
|
-
# end
|
35
|
-
#
|
36
|
-
# def ErrorsPerson.human_attribute_name(attr, options = {})
|
37
|
-
# attr
|
38
|
-
# end
|
39
|
-
#
|
40
|
-
# def ErrorsPerson.lookup_ancestors
|
41
|
-
# [self]
|
42
|
-
# end
|
43
|
-
#
|
44
|
-
# end
|
45
|
-
#
|
46
|
-
# The last three methods are required in your object for Errors to be
|
47
|
-
# able to generate error messages correctly and also handle multiple
|
48
|
-
# languages. Of course, if you extend your object with ActiveModel::Translations
|
49
|
-
# you will not need to implement the last two. Likewise, using
|
50
|
-
# ActiveModel::Validations will handle the validation related methods
|
51
|
-
# for you.
|
52
|
-
#
|
53
|
-
# The above allows you to do:
|
54
|
-
#
|
55
|
-
# p = Person.new
|
56
|
-
# p.validate! # => ["can not be nil"]
|
57
|
-
# p.errors.full_messages # => ["name can not be nil"]
|
58
|
-
# # etc..
|
59
5
|
class Errors < ActiveSupport::OrderedHash
|
60
6
|
include DeprecatedErrorMethods
|
61
7
|
|
62
|
-
# Pass in the instance of the object that is using the errors object.
|
63
|
-
#
|
64
|
-
# class Person
|
65
|
-
# def initialize
|
66
|
-
# @errors = ActiveModel::Errors.new(self)
|
67
|
-
# end
|
68
|
-
# end
|
69
8
|
def initialize(base)
|
70
9
|
@base = base
|
71
10
|
super()
|
@@ -74,10 +13,6 @@ module ActiveModel
|
|
74
13
|
alias_method :get, :[]
|
75
14
|
alias_method :set, :[]=
|
76
15
|
|
77
|
-
# When passed a symbol or a name of a method, returns an array of errors for the method.
|
78
|
-
#
|
79
|
-
# p.errors[:name] #=> ["can not be nil"]
|
80
|
-
# p.errors['name'] #=> ["can not be nil"]
|
81
16
|
def [](attribute)
|
82
17
|
if errors = get(attribute.to_sym)
|
83
18
|
errors
|
@@ -86,78 +21,28 @@ module ActiveModel
|
|
86
21
|
end
|
87
22
|
end
|
88
23
|
|
89
|
-
# Adds to the supplied attribute the supplied error message.
|
90
|
-
#
|
91
|
-
# p.errors[:name] = "must be set"
|
92
|
-
# p.errors[:name] #=> ['must be set']
|
93
24
|
def []=(attribute, error)
|
94
25
|
self[attribute.to_sym] << error
|
95
26
|
end
|
96
27
|
|
97
|
-
# Iterates through each error key, value pair in the error messages hash.
|
98
|
-
# Yields the attribute and the error for that attribute. If the attribute
|
99
|
-
# has more than one error message, yields once for each error message.
|
100
|
-
#
|
101
|
-
# p.errors.add(:name, "can't be blank")
|
102
|
-
# p.errors.each do |attribute, errors_array|
|
103
|
-
# # Will yield :name and "can't be blank"
|
104
|
-
# end
|
105
|
-
#
|
106
|
-
# p.errors.add(:name, "must be specified")
|
107
|
-
# p.errors.each do |attribute, errors_array|
|
108
|
-
# # Will yield :name and "can't be blank"
|
109
|
-
# # then yield :name and "must be specified"
|
110
|
-
# end
|
111
28
|
def each
|
112
29
|
each_key do |attribute|
|
113
30
|
self[attribute].each { |error| yield attribute, error }
|
114
31
|
end
|
115
32
|
end
|
116
33
|
|
117
|
-
# Returns the number of error messages.
|
118
|
-
#
|
119
|
-
# p.errors.add(:name, "can't be blank")
|
120
|
-
# p.errors.size #=> 1
|
121
|
-
# p.errors.add(:name, "must be specified")
|
122
|
-
# p.errors.size #=> 2
|
123
34
|
def size
|
124
35
|
values.flatten.size
|
125
36
|
end
|
126
37
|
|
127
|
-
# Returns an array of error messages, with the attribute name included
|
128
|
-
#
|
129
|
-
# p.errors.add(:name, "can't be blank")
|
130
|
-
# p.errors.add(:name, "must be specified")
|
131
|
-
# p.errors.to_a #=> ["name can't be blank", "name must be specified"]
|
132
38
|
def to_a
|
133
39
|
full_messages
|
134
40
|
end
|
135
41
|
|
136
|
-
# Returns the number of error messages.
|
137
|
-
# p.errors.add(:name, "can't be blank")
|
138
|
-
# p.errors.count #=> 1
|
139
|
-
# p.errors.add(:name, "must be specified")
|
140
|
-
# p.errors.count #=> 2
|
141
42
|
def count
|
142
43
|
to_a.size
|
143
44
|
end
|
144
45
|
|
145
|
-
# Returns true if there are any errors, false if not.
|
146
|
-
def empty?
|
147
|
-
all? { |k, v| v && v.empty? }
|
148
|
-
end
|
149
|
-
|
150
|
-
# Returns an xml formatted representation of the Errors hash.
|
151
|
-
#
|
152
|
-
# p.errors.add(:name, "can't be blank")
|
153
|
-
# p.errors.add(:name, "must be specified")
|
154
|
-
# p.errors.to_xml #=> Produces:
|
155
|
-
#
|
156
|
-
# # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
157
|
-
# # <errors>
|
158
|
-
# # <error>name can't be blank</error>
|
159
|
-
# # <error>name must be specified</error>
|
160
|
-
# # </errors>
|
161
46
|
def to_xml(options={})
|
162
47
|
require 'builder' unless defined? ::Builder
|
163
48
|
options[:root] ||= "errors"
|
@@ -170,17 +55,14 @@ module ActiveModel
|
|
170
55
|
end
|
171
56
|
end
|
172
57
|
|
173
|
-
# Adds
|
174
|
-
#
|
175
|
-
# +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
|
176
|
-
# If no +
|
177
|
-
#
|
178
|
-
# If +message+ is a symbol, it will be translated using the appropriate scope (see +translate_error+).
|
179
|
-
# If +message+ is a proc, it will be called, allowing for things like <tt>Time.now</tt> to be used within an error.
|
58
|
+
# Adds an error message (+messsage+) to the +attribute+, which will be returned on a call to <tt>on(attribute)</tt>
|
59
|
+
# for the same attribute and ensure that this error object returns false when asked if <tt>empty?</tt>. More than one
|
60
|
+
# error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
|
61
|
+
# If no +messsage+ is supplied, :invalid is assumed.
|
62
|
+
# If +message+ is a Symbol, it will be translated, using the appropriate scope (see translate_error).
|
180
63
|
def add(attribute, message = nil, options = {})
|
181
64
|
message ||= :invalid
|
182
65
|
message = generate_message(attribute, message, options) if message.is_a?(Symbol)
|
183
|
-
message = message.call if message.is_a?(Proc)
|
184
66
|
self[attribute] << message
|
185
67
|
end
|
186
68
|
|
@@ -211,7 +93,7 @@ module ActiveModel
|
|
211
93
|
# company = Company.create(:address => '123 First St.')
|
212
94
|
# company.errors.full_messages # =>
|
213
95
|
# ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
|
214
|
-
def full_messages
|
96
|
+
def full_messages(options = {})
|
215
97
|
full_messages = []
|
216
98
|
|
217
99
|
each do |attribute, messages|
|
@@ -221,12 +103,10 @@ module ActiveModel
|
|
221
103
|
if attribute == :base
|
222
104
|
messages.each {|m| full_messages << m }
|
223
105
|
else
|
224
|
-
attr_name = attribute.to_s.
|
225
|
-
|
226
|
-
options = { :default => "%{attribute} %{message}", :attribute => attr_name }
|
227
|
-
|
106
|
+
attr_name = attribute.to_s.humanize
|
107
|
+
prefix = attr_name + I18n.t('activemodel.errors.format.separator', :default => ' ')
|
228
108
|
messages.each do |m|
|
229
|
-
full_messages <<
|
109
|
+
full_messages << "#{prefix}#{m}"
|
230
110
|
end
|
231
111
|
end
|
232
112
|
end
|
@@ -249,35 +129,31 @@ module ActiveModel
|
|
249
129
|
# <li><tt>activemodel.errors.models.admin.blank</tt></li>
|
250
130
|
# <li><tt>activemodel.errors.models.user.attributes.title.blank</tt></li>
|
251
131
|
# <li><tt>activemodel.errors.models.user.blank</tt></li>
|
252
|
-
# <li>any default you provided through the +options+ hash (in the activemodel.errors scope)</li>
|
253
132
|
# <li><tt>activemodel.errors.messages.blank</tt></li>
|
254
|
-
# <li
|
255
|
-
# <li><tt>errors.messages.blank</tt></li>
|
133
|
+
# <li>any default you provided through the +options+ hash (in the activemodel.errors scope)</li>
|
256
134
|
# </ol>
|
257
135
|
def generate_message(attribute, message = :invalid, options = {})
|
258
136
|
message, options[:default] = options[:default], message if options[:default].is_a?(Symbol)
|
259
137
|
|
260
|
-
|
261
|
-
|
262
|
-
|
138
|
+
klass_ancestors = [@base.class]
|
139
|
+
klass_ancestors += @base.class.ancestors.reject {|x| x.is_a?(Module)}
|
140
|
+
|
141
|
+
defaults = klass_ancestors.map do |klass|
|
142
|
+
[ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}",
|
143
|
+
:"models.#{klass.name.underscore}.#{message}" ]
|
263
144
|
end
|
264
145
|
|
265
146
|
defaults << options.delete(:default)
|
266
|
-
defaults << :"
|
267
|
-
defaults << :"errors.attributes.#{attribute}.#{message}"
|
268
|
-
defaults << :"errors.messages.#{message}"
|
269
|
-
|
270
|
-
defaults.compact!
|
271
|
-
defaults.flatten!
|
147
|
+
defaults = defaults.compact.flatten << :"messages.#{message}"
|
272
148
|
|
273
149
|
key = defaults.shift
|
274
150
|
value = @base.send(:read_attribute_for_validation, attribute)
|
275
151
|
|
276
|
-
options = {
|
277
|
-
:
|
278
|
-
:
|
279
|
-
:
|
280
|
-
:
|
152
|
+
options = { :default => defaults,
|
153
|
+
:model => @base.class.name.humanize,
|
154
|
+
:attribute => attribute.to_s.humanize,
|
155
|
+
:value => value,
|
156
|
+
:scope => [:activemodel, :errors]
|
281
157
|
}.merge(options)
|
282
158
|
|
283
159
|
I18n.translate(key, options)
|
data/lib/active_model/lint.rb
CHANGED
@@ -13,34 +13,8 @@
|
|
13
13
|
module ActiveModel
|
14
14
|
module Lint
|
15
15
|
module Tests
|
16
|
-
|
17
|
-
#
|
18
|
-
#
|
19
|
-
# Returns an Enumerable of all (primary) key attributes
|
20
|
-
# or nil if model.persisted? is false
|
21
|
-
def test_to_key
|
22
|
-
assert model.respond_to?(:to_key), "The model should respond to to_key"
|
23
|
-
def model.persisted?() false end
|
24
|
-
assert model.to_key.nil?
|
25
|
-
end
|
26
|
-
|
27
|
-
# == Responds to <tt>to_param</tt>
|
28
|
-
#
|
29
|
-
# Returns a string representing the object's key suitable for use in URLs
|
30
|
-
# or nil if model.persisted? is false.
|
31
|
-
#
|
32
|
-
# Implementers can decide to either raise an exception or provide a default
|
33
|
-
# in case the record uses a composite primary key. There are no tests for this
|
34
|
-
# behavior in lint because it doesn't make sense to force any of the possible
|
35
|
-
# implementation strategies on the implementer. However, if the resource is
|
36
|
-
# not persisted?, then to_param should always return nil.
|
37
|
-
def test_to_param
|
38
|
-
assert model.respond_to?(:to_param), "The model should respond to to_param"
|
39
|
-
def model.persisted?() false end
|
40
|
-
assert model.to_param.nil?
|
41
|
-
end
|
42
|
-
|
43
|
-
# == Responds to <tt>valid?</tt>
|
16
|
+
# valid?
|
17
|
+
# ------
|
44
18
|
#
|
45
19
|
# Returns a boolean that specifies whether the object is in a valid or invalid
|
46
20
|
# state.
|
@@ -49,38 +23,30 @@ module ActiveModel
|
|
49
23
|
assert_boolean model.valid?, "valid?"
|
50
24
|
end
|
51
25
|
|
52
|
-
#
|
26
|
+
# new_record?
|
27
|
+
# -----------
|
53
28
|
#
|
54
29
|
# Returns a boolean that specifies whether the object has been persisted yet.
|
55
30
|
# This is used when calculating the URL for an object. If the object is
|
56
31
|
# not persisted, a form for that object, for instance, will be POSTed to the
|
57
32
|
# collection. If it is persisted, a form for the object will put PUTed to the
|
58
33
|
# URL for the object.
|
59
|
-
def
|
60
|
-
assert model.respond_to?(:
|
61
|
-
assert_boolean model.
|
34
|
+
def test_new_record?
|
35
|
+
assert model.respond_to?(:new_record?), "The model should respond to new_record?"
|
36
|
+
assert_boolean model.new_record?, "new_record?"
|
62
37
|
end
|
63
38
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
# :human and :partial_path. Check ActiveModel::Naming for more information.
|
68
|
-
#
|
69
|
-
def test_model_naming
|
70
|
-
assert model.class.respond_to?(:model_name), "The model should respond to model_name"
|
71
|
-
model_name = model.class.model_name
|
72
|
-
assert_kind_of String, model_name
|
73
|
-
assert_kind_of String, model_name.human
|
74
|
-
assert_kind_of String, model_name.partial_path
|
75
|
-
assert_kind_of String, model_name.singular
|
76
|
-
assert_kind_of String, model_name.plural
|
39
|
+
def test_destroyed?
|
40
|
+
assert model.respond_to?(:destroyed?), "The model should respond to destroyed?"
|
41
|
+
assert_boolean model.destroyed?, "destroyed?"
|
77
42
|
end
|
78
43
|
|
79
|
-
#
|
80
|
-
#
|
44
|
+
# errors
|
45
|
+
# ------
|
46
|
+
#
|
81
47
|
# Returns an object that has :[] and :full_messages defined on it. See below
|
82
48
|
# for more details.
|
83
|
-
|
49
|
+
|
84
50
|
# Returns an Array of Strings that are the errors for the attribute in
|
85
51
|
# question. If localization is used, the Strings should be localized
|
86
52
|
# for the current locale. If no error is present, this method should
|