activemodel 3.0.0.beta
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 +13 -0
- data/MIT-LICENSE +21 -0
- data/README +216 -0
- data/lib/active_model.rb +61 -0
- data/lib/active_model/attribute_methods.rb +391 -0
- data/lib/active_model/callbacks.rb +133 -0
- data/lib/active_model/conversion.rb +19 -0
- data/lib/active_model/deprecated_error_methods.rb +33 -0
- data/lib/active_model/dirty.rb +164 -0
- data/lib/active_model/errors.rb +277 -0
- data/lib/active_model/lint.rb +89 -0
- data/lib/active_model/locale/en.yml +26 -0
- data/lib/active_model/naming.rb +60 -0
- data/lib/active_model/observing.rb +196 -0
- data/lib/active_model/railtie.rb +2 -0
- data/lib/active_model/serialization.rb +87 -0
- data/lib/active_model/serializers/json.rb +96 -0
- data/lib/active_model/serializers/xml.rb +204 -0
- data/lib/active_model/test_case.rb +16 -0
- data/lib/active_model/translation.rb +60 -0
- data/lib/active_model/validations.rb +168 -0
- data/lib/active_model/validations/acceptance.rb +51 -0
- data/lib/active_model/validations/confirmation.rb +49 -0
- data/lib/active_model/validations/exclusion.rb +40 -0
- data/lib/active_model/validations/format.rb +64 -0
- data/lib/active_model/validations/inclusion.rb +40 -0
- data/lib/active_model/validations/length.rb +98 -0
- data/lib/active_model/validations/numericality.rb +111 -0
- data/lib/active_model/validations/presence.rb +41 -0
- data/lib/active_model/validations/validates.rb +108 -0
- data/lib/active_model/validations/with.rb +70 -0
- data/lib/active_model/validator.rb +160 -0
- data/lib/active_model/version.rb +9 -0
- metadata +96 -0
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'active_support/json'
|
2
|
+
require 'active_support/core_ext/class/attribute_accessors'
|
3
|
+
|
4
|
+
module ActiveModel
|
5
|
+
module Serializers
|
6
|
+
module JSON
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
include ActiveModel::Serialization
|
9
|
+
|
10
|
+
included do
|
11
|
+
extend ActiveModel::Naming
|
12
|
+
|
13
|
+
cattr_accessor :include_root_in_json, :instance_writer => true
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns a JSON string representing the model. Some configuration is
|
17
|
+
# available through +options+.
|
18
|
+
#
|
19
|
+
# The option <tt>ActiveModel::Base.include_root_in_json</tt> controls the
|
20
|
+
# top-level behavior of to_json. It is true by default. When it is <tt>true</tt>,
|
21
|
+
# to_json will emit a single root node named after the object's type. For example:
|
22
|
+
#
|
23
|
+
# konata = User.find(1)
|
24
|
+
# konata.to_json
|
25
|
+
# # => { "user": {"id": 1, "name": "Konata Izumi", "age": 16,
|
26
|
+
# "created_at": "2006/08/01", "awesome": true} }
|
27
|
+
#
|
28
|
+
# ActiveRecord::Base.include_root_in_json = false
|
29
|
+
# konata.to_json
|
30
|
+
# # => {"id": 1, "name": "Konata Izumi", "age": 16,
|
31
|
+
# "created_at": "2006/08/01", "awesome": true}
|
32
|
+
#
|
33
|
+
# The remainder of the examples in this section assume include_root_in_json is set to
|
34
|
+
# <tt>false</tt>.
|
35
|
+
#
|
36
|
+
# Without any +options+, the returned JSON string will include all
|
37
|
+
# the model's attributes. For example:
|
38
|
+
#
|
39
|
+
# konata = User.find(1)
|
40
|
+
# konata.to_json
|
41
|
+
# # => {"id": 1, "name": "Konata Izumi", "age": 16,
|
42
|
+
# "created_at": "2006/08/01", "awesome": true}
|
43
|
+
#
|
44
|
+
# The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes
|
45
|
+
# included, and work similar to the +attributes+ method. For example:
|
46
|
+
#
|
47
|
+
# konata.to_json(:only => [ :id, :name ])
|
48
|
+
# # => {"id": 1, "name": "Konata Izumi"}
|
49
|
+
#
|
50
|
+
# konata.to_json(:except => [ :id, :created_at, :age ])
|
51
|
+
# # => {"name": "Konata Izumi", "awesome": true}
|
52
|
+
#
|
53
|
+
# To include any methods on the model, use <tt>:methods</tt>.
|
54
|
+
#
|
55
|
+
# konata.to_json(:methods => :permalink)
|
56
|
+
# # => {"id": 1, "name": "Konata Izumi", "age": 16,
|
57
|
+
# "created_at": "2006/08/01", "awesome": true,
|
58
|
+
# "permalink": "1-konata-izumi"}
|
59
|
+
#
|
60
|
+
# To include associations, use <tt>:include</tt>.
|
61
|
+
#
|
62
|
+
# konata.to_json(:include => :posts)
|
63
|
+
# # => {"id": 1, "name": "Konata Izumi", "age": 16,
|
64
|
+
# "created_at": "2006/08/01", "awesome": true,
|
65
|
+
# "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"},
|
66
|
+
# {"id": 2, author_id: 1, "title": "So I was thinking"}]}
|
67
|
+
#
|
68
|
+
# 2nd level and higher order associations work as well:
|
69
|
+
#
|
70
|
+
# konata.to_json(:include => { :posts => {
|
71
|
+
# :include => { :comments => {
|
72
|
+
# :only => :body } },
|
73
|
+
# :only => :title } })
|
74
|
+
# # => {"id": 1, "name": "Konata Izumi", "age": 16,
|
75
|
+
# "created_at": "2006/08/01", "awesome": true,
|
76
|
+
# "posts": [{"comments": [{"body": "1st post!"}, {"body": "Second!"}],
|
77
|
+
# "title": "Welcome to the weblog"},
|
78
|
+
# {"comments": [{"body": "Don't think too hard"}],
|
79
|
+
# "title": "So I was thinking"}]}
|
80
|
+
def encode_json(encoder)
|
81
|
+
hash = serializable_hash(encoder.options)
|
82
|
+
hash = { self.class.model_name.element => hash } if include_root_in_json
|
83
|
+
ActiveSupport::JSON.encode(hash)
|
84
|
+
end
|
85
|
+
|
86
|
+
def as_json(options = nil)
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
def from_json(json)
|
91
|
+
self.attributes = ActiveSupport::JSON.decode(json)
|
92
|
+
self
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
require 'active_support/core_ext/class/attribute_accessors'
|
2
|
+
require 'active_support/core_ext/hash/conversions'
|
3
|
+
|
4
|
+
module ActiveModel
|
5
|
+
module Serializers
|
6
|
+
module Xml
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
include ActiveModel::Serialization
|
9
|
+
|
10
|
+
class Serializer #:nodoc:
|
11
|
+
class Attribute #:nodoc:
|
12
|
+
attr_reader :name, :value, :type
|
13
|
+
|
14
|
+
def initialize(name, serializable)
|
15
|
+
@name, @serializable = name, serializable
|
16
|
+
@type = compute_type
|
17
|
+
@value = compute_value
|
18
|
+
end
|
19
|
+
|
20
|
+
# There is a significant speed improvement if the value
|
21
|
+
# does not need to be escaped, as <tt>tag!</tt> escapes all values
|
22
|
+
# to ensure that valid XML is generated. For known binary
|
23
|
+
# values, it is at least an order of magnitude faster to
|
24
|
+
# Base64 encode binary values and directly put them in the
|
25
|
+
# output XML than to pass the original value or the Base64
|
26
|
+
# encoded value to the <tt>tag!</tt> method. It definitely makes
|
27
|
+
# no sense to Base64 encode the value and then give it to
|
28
|
+
# <tt>tag!</tt>, since that just adds additional overhead.
|
29
|
+
def needs_encoding?
|
30
|
+
![ :binary, :date, :datetime, :boolean, :float, :integer ].include?(type)
|
31
|
+
end
|
32
|
+
|
33
|
+
def decorations(include_types = true)
|
34
|
+
decorations = {}
|
35
|
+
|
36
|
+
if type == :binary
|
37
|
+
decorations[:encoding] = 'base64'
|
38
|
+
end
|
39
|
+
|
40
|
+
if include_types && type != :string
|
41
|
+
decorations[:type] = type
|
42
|
+
end
|
43
|
+
|
44
|
+
if value.nil?
|
45
|
+
decorations[:nil] = true
|
46
|
+
end
|
47
|
+
|
48
|
+
decorations
|
49
|
+
end
|
50
|
+
|
51
|
+
protected
|
52
|
+
def compute_type
|
53
|
+
value = @serializable.send(name)
|
54
|
+
type = Hash::XML_TYPE_NAMES[value.class.name]
|
55
|
+
type ||= :string if value.respond_to?(:to_str)
|
56
|
+
type ||= :yaml
|
57
|
+
type
|
58
|
+
end
|
59
|
+
|
60
|
+
def compute_value
|
61
|
+
value = @serializable.send(name)
|
62
|
+
|
63
|
+
if formatter = Hash::XML_FORMATTING[type.to_s]
|
64
|
+
value ? formatter.call(value) : nil
|
65
|
+
else
|
66
|
+
value
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class MethodAttribute < Attribute #:nodoc:
|
72
|
+
protected
|
73
|
+
def compute_type
|
74
|
+
Hash::XML_TYPE_NAMES[@serializable.send(name).class.name] || :string
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
attr_reader :options
|
79
|
+
|
80
|
+
def initialize(serializable, options = nil)
|
81
|
+
@serializable = serializable
|
82
|
+
@options = options ? options.dup : {}
|
83
|
+
|
84
|
+
@options[:only] = Array.wrap(@options[:only]).map { |n| n.to_s }
|
85
|
+
@options[:except] = Array.wrap(@options[:except]).map { |n| n.to_s }
|
86
|
+
end
|
87
|
+
|
88
|
+
# To replicate the behavior in ActiveRecord#attributes,
|
89
|
+
# <tt>:except</tt> takes precedence over <tt>:only</tt>. If <tt>:only</tt> is not set
|
90
|
+
# for a N level model but is set for the N+1 level models,
|
91
|
+
# then because <tt>:except</tt> is set to a default value, the second
|
92
|
+
# level model can have both <tt>:except</tt> and <tt>:only</tt> set. So if
|
93
|
+
# <tt>:only</tt> is set, always delete <tt>:except</tt>.
|
94
|
+
def serializable_attribute_names
|
95
|
+
attribute_names = @serializable.attributes.keys.sort
|
96
|
+
|
97
|
+
if options[:only].any?
|
98
|
+
attribute_names &= options[:only]
|
99
|
+
elsif options[:except].any?
|
100
|
+
attribute_names -= options[:except]
|
101
|
+
end
|
102
|
+
|
103
|
+
attribute_names
|
104
|
+
end
|
105
|
+
|
106
|
+
def serializable_attributes
|
107
|
+
serializable_attribute_names.collect { |name| Attribute.new(name, @serializable) }
|
108
|
+
end
|
109
|
+
|
110
|
+
def serializable_method_attributes
|
111
|
+
Array(options[:methods]).inject([]) do |methods, name|
|
112
|
+
methods << MethodAttribute.new(name.to_s, @serializable) if @serializable.respond_to?(name.to_s)
|
113
|
+
methods
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def serialize
|
118
|
+
args = [root]
|
119
|
+
|
120
|
+
if options[:namespace]
|
121
|
+
args << {:xmlns => options[:namespace]}
|
122
|
+
end
|
123
|
+
|
124
|
+
if options[:type]
|
125
|
+
args << {:type => options[:type]}
|
126
|
+
end
|
127
|
+
|
128
|
+
builder.tag!(*args) do
|
129
|
+
add_attributes
|
130
|
+
procs = options.delete(:procs)
|
131
|
+
options[:procs] = procs
|
132
|
+
add_procs
|
133
|
+
yield builder if block_given?
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
def builder
|
139
|
+
@builder ||= begin
|
140
|
+
require 'builder' unless defined? ::Builder
|
141
|
+
options[:indent] ||= 2
|
142
|
+
builder = options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
|
143
|
+
|
144
|
+
unless options[:skip_instruct]
|
145
|
+
builder.instruct!
|
146
|
+
options[:skip_instruct] = true
|
147
|
+
end
|
148
|
+
|
149
|
+
builder
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def root
|
154
|
+
root = (options[:root] || @serializable.class.model_name.singular).to_s
|
155
|
+
reformat_name(root)
|
156
|
+
end
|
157
|
+
|
158
|
+
def dasherize?
|
159
|
+
!options.has_key?(:dasherize) || options[:dasherize]
|
160
|
+
end
|
161
|
+
|
162
|
+
def camelize?
|
163
|
+
options.has_key?(:camelize) && options[:camelize]
|
164
|
+
end
|
165
|
+
|
166
|
+
def reformat_name(name)
|
167
|
+
name = name.camelize if camelize?
|
168
|
+
dasherize? ? name.dasherize : name
|
169
|
+
end
|
170
|
+
|
171
|
+
def add_attributes
|
172
|
+
(serializable_attributes + serializable_method_attributes).each do |attribute|
|
173
|
+
builder.tag!(
|
174
|
+
reformat_name(attribute.name),
|
175
|
+
attribute.value.to_s,
|
176
|
+
attribute.decorations(!options[:skip_types])
|
177
|
+
)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def add_procs
|
182
|
+
if procs = options.delete(:procs)
|
183
|
+
[ *procs ].each do |proc|
|
184
|
+
if proc.arity > 1
|
185
|
+
proc.call(options, @serializable)
|
186
|
+
else
|
187
|
+
proc.call(options)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def to_xml(options = {}, &block)
|
195
|
+
Serializer.new(self, options).serialize(&block)
|
196
|
+
end
|
197
|
+
|
198
|
+
def from_xml(xml)
|
199
|
+
self.attributes = Hash.from_xml(xml).values.first
|
200
|
+
self
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module ActiveModel #:nodoc:
|
2
|
+
class TestCase < ActiveSupport::TestCase #:nodoc:
|
3
|
+
def with_kcode(kcode)
|
4
|
+
if RUBY_VERSION < '1.9'
|
5
|
+
orig_kcode, $KCODE = $KCODE, kcode
|
6
|
+
begin
|
7
|
+
yield
|
8
|
+
ensure
|
9
|
+
$KCODE = orig_kcode
|
10
|
+
end
|
11
|
+
else
|
12
|
+
yield
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'active_support/core_ext/hash/reverse_merge'
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
|
5
|
+
# ActiveModel::Translation provides integration between your object and
|
6
|
+
# the Rails internationalization (i18n) framework.
|
7
|
+
#
|
8
|
+
# A minimal implementation could be:
|
9
|
+
#
|
10
|
+
# class TranslatedPerson
|
11
|
+
# extend ActiveModel::Translation
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# TranslatedPerson.human_attribute_name('my_attribue')
|
15
|
+
# #=> "My attribute"
|
16
|
+
#
|
17
|
+
# This also provides the required class methods for hooking into the
|
18
|
+
# Rails internationalization API, including being able to define a
|
19
|
+
# class based i18n_scope and lookup_ancestors to find translations in
|
20
|
+
# parent classes.
|
21
|
+
module Translation
|
22
|
+
include ActiveModel::Naming
|
23
|
+
|
24
|
+
# Returns the i18n_scope for the class. Overwrite if you want custom lookup.
|
25
|
+
def i18n_scope
|
26
|
+
:activemodel
|
27
|
+
end
|
28
|
+
|
29
|
+
# When localizing a string, goes through the lookup returned by this method.
|
30
|
+
# Used in ActiveModel::Name#human, ActiveModel::Errors#full_messages and
|
31
|
+
# ActiveModel::Translation#human_attribute_name.
|
32
|
+
def lookup_ancestors
|
33
|
+
self.ancestors.select { |x| x.respond_to?(:model_name) }
|
34
|
+
end
|
35
|
+
|
36
|
+
# Transforms attributes names into a more human format, such as "First name" instead of "first_name".
|
37
|
+
#
|
38
|
+
# Person.human_attribute_name("first_name") # => "First name"
|
39
|
+
#
|
40
|
+
# Specify +options+ with additional translating options.
|
41
|
+
def human_attribute_name(attribute, options = {})
|
42
|
+
defaults = lookup_ancestors.map do |klass|
|
43
|
+
:"#{self.i18n_scope}.attributes.#{klass.model_name.underscore}.#{attribute}"
|
44
|
+
end
|
45
|
+
|
46
|
+
defaults << :"attributes.#{attribute}"
|
47
|
+
defaults << options.delete(:default) if options[:default]
|
48
|
+
defaults << attribute.to_s.humanize
|
49
|
+
|
50
|
+
options.reverse_merge! :count => 1, :default => defaults
|
51
|
+
I18n.translate(defaults.shift, options)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Model.human_name is deprecated. Use Model.model_name.human instead.
|
55
|
+
def human_name(*args)
|
56
|
+
ActiveSupport::Deprecation.warn("human_name has been deprecated, please use model_name.human instead", caller[0,5])
|
57
|
+
model_name.human(*args)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'active_support/core_ext/array/extract_options'
|
2
|
+
require 'active_support/core_ext/hash/keys'
|
3
|
+
require 'active_model/errors'
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
|
7
|
+
# Provides a full validation framework to your objects.
|
8
|
+
#
|
9
|
+
# A minimal implementation could be:
|
10
|
+
#
|
11
|
+
# class Person
|
12
|
+
# include ActiveModel::Validations
|
13
|
+
#
|
14
|
+
# attr_accessor :first_name, :last_name
|
15
|
+
#
|
16
|
+
# validates_each :first_name, :last_name do |record, attr, value|
|
17
|
+
# record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# Which provides you with the full standard validation stack that you
|
22
|
+
# know from ActiveRecord.
|
23
|
+
#
|
24
|
+
# person = Person.new
|
25
|
+
# person.valid?
|
26
|
+
# #=> true
|
27
|
+
# person.invalid?
|
28
|
+
# #=> false
|
29
|
+
# person.first_name = 'zoolander'
|
30
|
+
# person.valid?
|
31
|
+
# #=> false
|
32
|
+
# person.invalid?
|
33
|
+
# #=> true
|
34
|
+
# person.errors
|
35
|
+
# #=> #<OrderedHash {:first_name=>["starts with z."]}>
|
36
|
+
#
|
37
|
+
# Note that ActiveModel::Validations automatically adds an +errors+ method
|
38
|
+
# to your instances initialized with a new ActiveModel::Errors object, so
|
39
|
+
# there is no need for you to add this manually.
|
40
|
+
#
|
41
|
+
module Validations
|
42
|
+
extend ActiveSupport::Concern
|
43
|
+
include ActiveSupport::Callbacks
|
44
|
+
|
45
|
+
included do
|
46
|
+
extend ActiveModel::Translation
|
47
|
+
define_callbacks :validate, :scope => :name
|
48
|
+
end
|
49
|
+
|
50
|
+
module ClassMethods
|
51
|
+
# Validates each attribute against a block.
|
52
|
+
#
|
53
|
+
# class Person
|
54
|
+
# include ActiveModel::Validations
|
55
|
+
#
|
56
|
+
# attr_accessor :first_name, :last_name
|
57
|
+
#
|
58
|
+
# validates_each :first_name, :last_name do |record, attr, value|
|
59
|
+
# record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# Options:
|
64
|
+
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>,
|
65
|
+
# other options <tt>:create</tt>, <tt>:update</tt>).
|
66
|
+
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
|
67
|
+
# * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
|
68
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
69
|
+
# occur (e.g. <tt>:if => :allow_validation</tt>, or
|
70
|
+
# <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
|
71
|
+
# method, proc or string should return or evaluate to a true or false value.
|
72
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
|
73
|
+
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or
|
74
|
+
# <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
75
|
+
# method, proc or string should return or evaluate to a true or false value.
|
76
|
+
def validates_each(*attr_names, &block)
|
77
|
+
options = attr_names.extract_options!.symbolize_keys
|
78
|
+
validates_with BlockValidator, options.merge(:attributes => attr_names.flatten), &block
|
79
|
+
end
|
80
|
+
|
81
|
+
# Adds a validation method or block to the class. This is useful when
|
82
|
+
# overriding the +validate+ instance method becomes too unwieldly and
|
83
|
+
# you're looking for more descriptive declaration of your validations.
|
84
|
+
#
|
85
|
+
# This can be done with a symbol pointing to a method:
|
86
|
+
#
|
87
|
+
# class Comment
|
88
|
+
# include ActiveModel::Validations
|
89
|
+
#
|
90
|
+
# validate :must_be_friends
|
91
|
+
#
|
92
|
+
# def must_be_friends
|
93
|
+
# errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee)
|
94
|
+
# end
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# Or with a block which is passed the current record to be validated:
|
98
|
+
#
|
99
|
+
# class Comment
|
100
|
+
# include ActiveModel::Validations
|
101
|
+
#
|
102
|
+
# validate do |comment|
|
103
|
+
# comment.must_be_friends
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# def must_be_friends
|
107
|
+
# errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee)
|
108
|
+
# end
|
109
|
+
# end
|
110
|
+
#
|
111
|
+
# This usage applies to +validate_on_create+ and +validate_on_update as well+.
|
112
|
+
def validate(*args, &block)
|
113
|
+
options = args.last
|
114
|
+
if options.is_a?(Hash) && options.key?(:on)
|
115
|
+
options[:if] = Array(options[:if])
|
116
|
+
options[:if] << "@_on_validate == :#{options[:on]}"
|
117
|
+
end
|
118
|
+
set_callback(:validate, *args, &block)
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def _merge_attributes(attr_names)
|
124
|
+
options = attr_names.extract_options!
|
125
|
+
options.merge(:attributes => attr_names)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Returns the Errors object that holds all information about attribute error messages.
|
130
|
+
def errors
|
131
|
+
@errors ||= Errors.new(self)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Runs all the specified validations and returns true if no errors were added otherwise false.
|
135
|
+
def valid?
|
136
|
+
errors.clear
|
137
|
+
_run_validate_callbacks
|
138
|
+
errors.empty?
|
139
|
+
end
|
140
|
+
|
141
|
+
# Performs the opposite of <tt>valid?</tt>. Returns true if errors were added, false otherwise.
|
142
|
+
def invalid?
|
143
|
+
!valid?
|
144
|
+
end
|
145
|
+
|
146
|
+
# Hook method defining how an attribute value should be retieved. By default this is assumed
|
147
|
+
# to be an instance named after the attribute. Override this method in subclasses should you
|
148
|
+
# need to retrieve the value for a given attribute differently e.g.
|
149
|
+
# class MyClass
|
150
|
+
# include ActiveModel::Validations
|
151
|
+
#
|
152
|
+
# def initialize(data = {})
|
153
|
+
# @data = data
|
154
|
+
# end
|
155
|
+
#
|
156
|
+
# def read_attribute_for_validation(key)
|
157
|
+
# @data[key]
|
158
|
+
# end
|
159
|
+
# end
|
160
|
+
#
|
161
|
+
alias :read_attribute_for_validation :send
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
Dir[File.dirname(__FILE__) + "/validations/*.rb"].sort.each do |path|
|
166
|
+
filename = File.basename(path)
|
167
|
+
require "active_model/validations/#{filename}"
|
168
|
+
end
|