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