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,133 @@
|
|
1
|
+
require 'active_support/callbacks'
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
# == Active Model Callbacks
|
5
|
+
#
|
6
|
+
# Provides an interface for any class to have Active Record like callbacks.
|
7
|
+
#
|
8
|
+
# Like the Active Record methods, the call back chain is aborted as soon as
|
9
|
+
# one of the methods in the chain returns false.
|
10
|
+
#
|
11
|
+
# First, extend ActiveModel::Callbacks from the class you are creating:
|
12
|
+
#
|
13
|
+
# class MyModel
|
14
|
+
# extend ActiveModel::Callbacks
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# Then define a list of methods that you want call backs attached to:
|
18
|
+
#
|
19
|
+
# define_model_callbacks :create, :update
|
20
|
+
#
|
21
|
+
# This will provide all three standard callbacks (before, around and after) around
|
22
|
+
# both the :create and :update methods. To implement, you need to wrap the methods
|
23
|
+
# you want call backs on in a block so that the call backs get a chance to fire:
|
24
|
+
#
|
25
|
+
# def create
|
26
|
+
# _run_create_callbacks do
|
27
|
+
# # Your create action methods here
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# The _run_<method_name>_callbacks methods are dynamically created when you extend
|
32
|
+
# the <tt>ActiveModel::Callbacks</tt> module.
|
33
|
+
#
|
34
|
+
# Then in your class, you can use the +before_create+, +after_create+ and +around_create+
|
35
|
+
# methods, just as you would in an Active Record module.
|
36
|
+
#
|
37
|
+
# before_create :action_before_create
|
38
|
+
#
|
39
|
+
# def action_before_create
|
40
|
+
# # Your code here
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# You can choose not to have all three callbacks by passing an hash to the
|
44
|
+
# define_model_callbacks method.
|
45
|
+
#
|
46
|
+
# define_model_callbacks :create, :only => :after, :before
|
47
|
+
#
|
48
|
+
# Would only create the after_create and before_create callback methods in your
|
49
|
+
# class.
|
50
|
+
module Callbacks
|
51
|
+
def self.extended(base)
|
52
|
+
base.class_eval do
|
53
|
+
include ActiveSupport::Callbacks
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# define_model_callbacks accepts all options define_callbacks does, in case you
|
58
|
+
# want to overwrite a default. Besides that, it also accepts an :only option,
|
59
|
+
# where you can choose if you want all types (before, around or after) or just some.
|
60
|
+
#
|
61
|
+
# define_model_callbacks :initializer, :only => :after
|
62
|
+
#
|
63
|
+
# Note, the <tt>:only => <type></tt> hash will apply to all callbacks defined on
|
64
|
+
# that method call. To get around this you can call the define_model_callbacks
|
65
|
+
# method as many times as you need.
|
66
|
+
#
|
67
|
+
# define_model_callbacks :create, :only => :after
|
68
|
+
# define_model_callbacks :update, :only => :before
|
69
|
+
# define_model_callbacks :destroy, :only => :around
|
70
|
+
#
|
71
|
+
# Would create +after_create+, +before_update+ and +around_destroy+ methods only.
|
72
|
+
#
|
73
|
+
# You can pass in a class to before_<type>, after_<type> and around_<type>, in which
|
74
|
+
# case the call back will call that class's <action>_<type> method passing the object
|
75
|
+
# that the callback is being called on.
|
76
|
+
#
|
77
|
+
# class MyModel
|
78
|
+
# extend ActiveModel::Callbacks
|
79
|
+
# define_model_callbacks :create
|
80
|
+
#
|
81
|
+
# before_create AnotherClass
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# class AnotherClass
|
85
|
+
# def self.before_create( obj )
|
86
|
+
# # obj is the MyModel instance that the callback is being called on
|
87
|
+
# end
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
def define_model_callbacks(*callbacks)
|
91
|
+
options = callbacks.extract_options!
|
92
|
+
options = { :terminator => "result == false", :scope => [:kind, :name] }.merge(options)
|
93
|
+
|
94
|
+
types = Array(options.delete(:only))
|
95
|
+
types = [:before, :around, :after] if types.empty?
|
96
|
+
|
97
|
+
callbacks.each do |callback|
|
98
|
+
define_callbacks(callback, options)
|
99
|
+
|
100
|
+
types.each do |type|
|
101
|
+
send(:"_define_#{type}_model_callback", self, callback)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def _define_before_model_callback(klass, callback) #:nodoc:
|
107
|
+
klass.class_eval <<-CALLBACK, __FILE__, __LINE__
|
108
|
+
def self.before_#{callback}(*args, &block)
|
109
|
+
set_callback(:#{callback}, :before, *args, &block)
|
110
|
+
end
|
111
|
+
CALLBACK
|
112
|
+
end
|
113
|
+
|
114
|
+
def _define_around_model_callback(klass, callback) #:nodoc:
|
115
|
+
klass.class_eval <<-CALLBACK, __FILE__, __LINE__
|
116
|
+
def self.around_#{callback}(*args, &block)
|
117
|
+
set_callback(:#{callback}, :around, *args, &block)
|
118
|
+
end
|
119
|
+
CALLBACK
|
120
|
+
end
|
121
|
+
|
122
|
+
def _define_after_model_callback(klass, callback) #:nodoc:
|
123
|
+
klass.class_eval <<-CALLBACK, __FILE__, __LINE__
|
124
|
+
def self.after_#{callback}(*args, &block)
|
125
|
+
options = args.extract_options!
|
126
|
+
options[:prepend] = true
|
127
|
+
options[:if] = Array(options[:if]) << "!halted && value != false"
|
128
|
+
set_callback(:#{callback}, :after, *(args << options), &block)
|
129
|
+
end
|
130
|
+
CALLBACK
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
# If your object is already designed to implement all of the Active Model featurs
|
3
|
+
# include this module in your Class.
|
4
|
+
#
|
5
|
+
# class MyClass
|
6
|
+
# include ActiveModel::Conversion
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
# Returns self to the <tt>:to_model</tt> method.
|
10
|
+
#
|
11
|
+
# If your model does not act like an Active Model object, then you should define
|
12
|
+
# <tt>:to_model</tt> yourself returning a proxy object that wraps your object
|
13
|
+
# with Active Model compliant methods.
|
14
|
+
module Conversion
|
15
|
+
def to_model
|
16
|
+
self
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module DeprecatedErrorMethods
|
3
|
+
def on(attribute)
|
4
|
+
message = "Errors#on have been deprecated, use Errors#[] instead.\n"
|
5
|
+
message << "Also note that the behaviour of Errors#[] has changed. Errors#[] now always returns an Array. An empty Array is "
|
6
|
+
message << "returned when there are no errors on the specified attribute."
|
7
|
+
ActiveSupport::Deprecation.warn(message)
|
8
|
+
|
9
|
+
errors = self[attribute]
|
10
|
+
errors.size < 2 ? errors.first : errors
|
11
|
+
end
|
12
|
+
|
13
|
+
def on_base
|
14
|
+
ActiveSupport::Deprecation.warn "Errors#on_base have been deprecated, use Errors#[:base] instead"
|
15
|
+
ActiveSupport::Deprecation.silence { on(:base) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_to_base(msg)
|
19
|
+
ActiveSupport::Deprecation.warn "Errors#add_to_base(msg) has been deprecated, use Errors#[:base] << msg instead"
|
20
|
+
self[:base] << msg
|
21
|
+
end
|
22
|
+
|
23
|
+
def invalid?(attribute)
|
24
|
+
ActiveSupport::Deprecation.warn "Errors#invalid?(attribute) has been deprecated, use Errors#[attribute].any? instead"
|
25
|
+
self[attribute].any?
|
26
|
+
end
|
27
|
+
|
28
|
+
def each_full
|
29
|
+
ActiveSupport::Deprecation.warn "Errors#each_full has been deprecated, use Errors#to_a.each instead"
|
30
|
+
to_a.each { |error| yield error }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
# <tt>ActiveModel::Dirty</tt> provides a way to track changes in your
|
3
|
+
# object in the same way as ActiveRecord does.
|
4
|
+
#
|
5
|
+
# The requirements to implement ActiveModel::Dirty are:
|
6
|
+
#
|
7
|
+
# * <tt>include ActiveModel::Dirty</tt> in your object
|
8
|
+
# * Call <tt>define_attribute_methods</tt> passing each method you want to track
|
9
|
+
# * Call <tt>attr_name_will_change!</tt> before each change to the tracked attribute
|
10
|
+
#
|
11
|
+
# If you wish to also track previous changes on save or update, you need to add
|
12
|
+
#
|
13
|
+
# @previously_changed = changes
|
14
|
+
#
|
15
|
+
# inside of your save or update method.
|
16
|
+
#
|
17
|
+
# A minimal implementation could be:
|
18
|
+
#
|
19
|
+
# class Person
|
20
|
+
#
|
21
|
+
# include ActiveModel::Dirty
|
22
|
+
#
|
23
|
+
# define_attribute_methods [:name]
|
24
|
+
#
|
25
|
+
# def name
|
26
|
+
# @name
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# def name=(val)
|
30
|
+
# name_will_change!
|
31
|
+
# @name = val
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# def save
|
35
|
+
# @previously_changed = changes
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# == Examples:
|
41
|
+
#
|
42
|
+
# A newly instantiated object is unchanged:
|
43
|
+
# person = Person.find_by_name('Uncle Bob')
|
44
|
+
# person.changed? # => false
|
45
|
+
#
|
46
|
+
# Change the name:
|
47
|
+
# person.name = 'Bob'
|
48
|
+
# person.changed? # => true
|
49
|
+
# person.name_changed? # => true
|
50
|
+
# person.name_was # => 'Uncle Bob'
|
51
|
+
# person.name_change # => ['Uncle Bob', 'Bob']
|
52
|
+
# person.name = 'Bill'
|
53
|
+
# person.name_change # => ['Uncle Bob', 'Bill']
|
54
|
+
#
|
55
|
+
# Save the changes:
|
56
|
+
# person.save
|
57
|
+
# person.changed? # => false
|
58
|
+
# person.name_changed? # => false
|
59
|
+
#
|
60
|
+
# Assigning the same value leaves the attribute unchanged:
|
61
|
+
# person.name = 'Bill'
|
62
|
+
# person.name_changed? # => false
|
63
|
+
# person.name_change # => nil
|
64
|
+
#
|
65
|
+
# Which attributes have changed?
|
66
|
+
# person.name = 'Bob'
|
67
|
+
# person.changed # => ['name']
|
68
|
+
# person.changes # => { 'name' => ['Bill', 'Bob'] }
|
69
|
+
#
|
70
|
+
# Resetting an attribute returns it to its original state:
|
71
|
+
# person.reset_name! # => 'Bill'
|
72
|
+
# person.changed? # => false
|
73
|
+
# person.name_changed? # => false
|
74
|
+
# person.name # => 'Bill'
|
75
|
+
#
|
76
|
+
# Before modifying an attribute in-place:
|
77
|
+
# person.name_will_change!
|
78
|
+
# person.name << 'y'
|
79
|
+
# person.name_change # => ['Bill', 'Billy']
|
80
|
+
module Dirty
|
81
|
+
extend ActiveSupport::Concern
|
82
|
+
include ActiveModel::AttributeMethods
|
83
|
+
|
84
|
+
included do
|
85
|
+
attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
|
86
|
+
attribute_method_affix :prefix => 'reset_', :suffix => '!'
|
87
|
+
end
|
88
|
+
|
89
|
+
# Do any attributes have unsaved changes?
|
90
|
+
# person.changed? # => false
|
91
|
+
# person.name = 'bob'
|
92
|
+
# person.changed? # => true
|
93
|
+
def changed?
|
94
|
+
!changed_attributes.empty?
|
95
|
+
end
|
96
|
+
|
97
|
+
# List of attributes with unsaved changes.
|
98
|
+
# person.changed # => []
|
99
|
+
# person.name = 'bob'
|
100
|
+
# person.changed # => ['name']
|
101
|
+
def changed
|
102
|
+
changed_attributes.keys
|
103
|
+
end
|
104
|
+
|
105
|
+
# Map of changed attrs => [original value, new value].
|
106
|
+
# person.changes # => {}
|
107
|
+
# person.name = 'bob'
|
108
|
+
# person.changes # => { 'name' => ['bill', 'bob'] }
|
109
|
+
def changes
|
110
|
+
changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h }
|
111
|
+
end
|
112
|
+
|
113
|
+
# Map of attributes that were changed when the model was saved.
|
114
|
+
# person.name # => 'bob'
|
115
|
+
# person.name = 'robert'
|
116
|
+
# person.save
|
117
|
+
# person.previous_changes # => {'name' => ['bob, 'robert']}
|
118
|
+
def previous_changes
|
119
|
+
previously_changed_attributes
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
# Map of change <tt>attr => original value</tt>.
|
124
|
+
def changed_attributes
|
125
|
+
@changed_attributes ||= {}
|
126
|
+
end
|
127
|
+
|
128
|
+
# Map of fields that were changed when the model was saved
|
129
|
+
def previously_changed_attributes
|
130
|
+
@previously_changed || {}
|
131
|
+
end
|
132
|
+
|
133
|
+
# Handle <tt>*_changed?</tt> for +method_missing+.
|
134
|
+
def attribute_changed?(attr)
|
135
|
+
changed_attributes.include?(attr)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Handle <tt>*_change</tt> for +method_missing+.
|
139
|
+
def attribute_change(attr)
|
140
|
+
[changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Handle <tt>*_was</tt> for +method_missing+.
|
144
|
+
def attribute_was(attr)
|
145
|
+
attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Handle <tt>*_will_change!</tt> for +method_missing+.
|
149
|
+
def attribute_will_change!(attr)
|
150
|
+
begin
|
151
|
+
value = __send__(attr)
|
152
|
+
value = value.duplicable? ? value.clone : value
|
153
|
+
rescue TypeError, NoMethodError
|
154
|
+
end
|
155
|
+
|
156
|
+
changed_attributes[attr] = value
|
157
|
+
end
|
158
|
+
|
159
|
+
# Handle <tt>reset_*!</tt> for +method_missing+.
|
160
|
+
def reset_attribute!(attr)
|
161
|
+
__send__("#{attr}=", changed_attributes[attr]) if attribute_changed?(attr)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,277 @@
|
|
1
|
+
require 'active_support/core_ext/string/inflections'
|
2
|
+
require 'active_support/ordered_hash'
|
3
|
+
|
4
|
+
module ActiveModel
|
5
|
+
# Provides a modified +OrderedHash+ that you can include in your object
|
6
|
+
# for handling error messages and interacting with Action Pack helpers.
|
7
|
+
#
|
8
|
+
# A minimal implementation could be:
|
9
|
+
#
|
10
|
+
# class Person
|
11
|
+
#
|
12
|
+
# # Required dependency for ActiveModel::Errors
|
13
|
+
# extend ActiveModel::Naming
|
14
|
+
#
|
15
|
+
# def initialize
|
16
|
+
# @errors = ActiveModel::Errors.new(self)
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# attr_accessor :name
|
20
|
+
# attr_reader :errors
|
21
|
+
#
|
22
|
+
# def validate!
|
23
|
+
# errors.add(:name, "can not be nil") if name == nil
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# # The following methods are needed to be minimally implemented
|
27
|
+
#
|
28
|
+
# def read_attribute_for_validation(attr)
|
29
|
+
# send(attr)
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# def ErrorsPerson.human_attribute_name(attr, options = {})
|
33
|
+
# attr
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# def ErrorsPerson.lookup_ancestors
|
37
|
+
# [self]
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# The last three methods are required in your object for Errors to be
|
43
|
+
# able to generate error messages correctly and also handle multiple
|
44
|
+
# languages. Of course, if you extend your object with ActiveModel::Translations
|
45
|
+
# you will not need to implement the last two. Likewise, using
|
46
|
+
# ActiveModel::Validations will handle the validation related methods
|
47
|
+
# for you.
|
48
|
+
#
|
49
|
+
# The above allows you to do:
|
50
|
+
#
|
51
|
+
# p = Person.new
|
52
|
+
# p.validate! # => ["can not be nil"]
|
53
|
+
# p.errors.full_messages # => ["name can not be nil"]
|
54
|
+
# # etc..
|
55
|
+
class Errors < ActiveSupport::OrderedHash
|
56
|
+
include DeprecatedErrorMethods
|
57
|
+
|
58
|
+
# Pass in the instance of the object that is using the errors object.
|
59
|
+
#
|
60
|
+
# class Person
|
61
|
+
# def initialize
|
62
|
+
# @errors = ActiveModel::Errors.new(self)
|
63
|
+
# end
|
64
|
+
# end
|
65
|
+
def initialize(base)
|
66
|
+
@base = base
|
67
|
+
super()
|
68
|
+
end
|
69
|
+
|
70
|
+
alias_method :get, :[]
|
71
|
+
alias_method :set, :[]=
|
72
|
+
|
73
|
+
# When passed a symbol or a name of a method, returns an array of errors for the method.
|
74
|
+
#
|
75
|
+
# p.errors[:name] #=> ["can not be nil"]
|
76
|
+
# p.errors['name'] #=> ["can not be nil"]
|
77
|
+
def [](attribute)
|
78
|
+
if errors = get(attribute.to_sym)
|
79
|
+
errors
|
80
|
+
else
|
81
|
+
set(attribute.to_sym, [])
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Adds to the supplied attribute the supplied error message.
|
86
|
+
#
|
87
|
+
# p.errors[:name] = "must be set"
|
88
|
+
# p.errors[:name] #=> ['must be set']
|
89
|
+
def []=(attribute, error)
|
90
|
+
self[attribute.to_sym] << error
|
91
|
+
end
|
92
|
+
|
93
|
+
# Iterates through each error key, value pair in the error messages hash.
|
94
|
+
# Yields the attribute and the error for that attribute. If the attribute
|
95
|
+
# has more than one error message, yields once for each error message.
|
96
|
+
#
|
97
|
+
# p.errors.add(:name, "can't be blank")
|
98
|
+
# p.errors.each do |attribute, errors_array|
|
99
|
+
# # Will yield :name and "can't be blank"
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
# p.errors.add(:name, "must be specified")
|
103
|
+
# p.errors.each do |attribute, errors_array|
|
104
|
+
# # Will yield :name and "can't be blank"
|
105
|
+
# # then yield :name and "must be specified"
|
106
|
+
# end
|
107
|
+
def each
|
108
|
+
each_key do |attribute|
|
109
|
+
self[attribute].each { |error| yield attribute, error }
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Returns the number of error messages.
|
114
|
+
#
|
115
|
+
# p.errors.add(:name, "can't be blank")
|
116
|
+
# p.errors.size #=> 1
|
117
|
+
# p.errors.add(:name, "must be specified")
|
118
|
+
# p.errors.size #=> 2
|
119
|
+
def size
|
120
|
+
values.flatten.size
|
121
|
+
end
|
122
|
+
|
123
|
+
# Returns an array of error messages, with the attribute name included
|
124
|
+
#
|
125
|
+
# p.errors.add(:name, "can't be blank")
|
126
|
+
# p.errors.add(:name, "must be specified")
|
127
|
+
# p.errors.to_a #=> ["name can't be blank", "name must be specified"]
|
128
|
+
def to_a
|
129
|
+
full_messages
|
130
|
+
end
|
131
|
+
|
132
|
+
# Returns the number of error messages.
|
133
|
+
# p.errors.add(:name, "can't be blank")
|
134
|
+
# p.errors.count #=> 1
|
135
|
+
# p.errors.add(:name, "must be specified")
|
136
|
+
# p.errors.count #=> 2
|
137
|
+
def count
|
138
|
+
to_a.size
|
139
|
+
end
|
140
|
+
|
141
|
+
# Returns an xml formatted representation of the Errors hash.
|
142
|
+
#
|
143
|
+
# p.errors.add(:name, "can't be blank")
|
144
|
+
# p.errors.add(:name, "must be specified")
|
145
|
+
# p.errors.to_xml #=> Produces:
|
146
|
+
#
|
147
|
+
# # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
148
|
+
# # <errors>
|
149
|
+
# # <error>name can't be blank</error>
|
150
|
+
# # <error>name must be specified</error>
|
151
|
+
# # </errors>
|
152
|
+
def to_xml(options={})
|
153
|
+
require 'builder' unless defined? ::Builder
|
154
|
+
options[:root] ||= "errors"
|
155
|
+
options[:indent] ||= 2
|
156
|
+
options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
|
157
|
+
|
158
|
+
options[:builder].instruct! unless options.delete(:skip_instruct)
|
159
|
+
options[:builder].errors do |e|
|
160
|
+
to_a.each { |error| e.error(error) }
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Adds an error message (+messsage+) to the +attribute+, which will be returned on a call to <tt>on(attribute)</tt>
|
165
|
+
# for the same attribute and ensure that this error object returns false when asked if <tt>empty?</tt>. More than one
|
166
|
+
# error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
|
167
|
+
# If no +messsage+ is supplied, :invalid is assumed.
|
168
|
+
#
|
169
|
+
# If +message+ is a Symbol, it will be translated, using the appropriate scope (see translate_error).
|
170
|
+
# If +message+ is a Proc, it will be called, allowing for things like Time.now to be used within an error
|
171
|
+
def add(attribute, message = nil, options = {})
|
172
|
+
message ||= :invalid
|
173
|
+
message = generate_message(attribute, message, options) if message.is_a?(Symbol)
|
174
|
+
message = message.call if message.is_a?(Proc)
|
175
|
+
self[attribute] << message
|
176
|
+
end
|
177
|
+
|
178
|
+
# Will add an error message to each of the attributes in +attributes+ that is empty.
|
179
|
+
def add_on_empty(attributes, custom_message = nil)
|
180
|
+
[attributes].flatten.each do |attribute|
|
181
|
+
value = @base.send(:read_attribute_for_validation, attribute)
|
182
|
+
is_empty = value.respond_to?(:empty?) ? value.empty? : false
|
183
|
+
add(attribute, :empty, :default => custom_message) unless !value.nil? && !is_empty
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?).
|
188
|
+
def add_on_blank(attributes, custom_message = nil)
|
189
|
+
[attributes].flatten.each do |attribute|
|
190
|
+
value = @base.send(:read_attribute_for_validation, attribute)
|
191
|
+
add(attribute, :blank, :default => custom_message) if value.blank?
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Returns all the full error messages in an array.
|
196
|
+
#
|
197
|
+
# class Company
|
198
|
+
# validates_presence_of :name, :address, :email
|
199
|
+
# validates_length_of :name, :in => 5..30
|
200
|
+
# end
|
201
|
+
#
|
202
|
+
# company = Company.create(:address => '123 First St.')
|
203
|
+
# company.errors.full_messages # =>
|
204
|
+
# ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
|
205
|
+
def full_messages
|
206
|
+
full_messages = []
|
207
|
+
|
208
|
+
each do |attribute, messages|
|
209
|
+
messages = Array(messages)
|
210
|
+
next if messages.empty?
|
211
|
+
|
212
|
+
if attribute == :base
|
213
|
+
messages.each {|m| full_messages << m }
|
214
|
+
else
|
215
|
+
attr_name = attribute.to_s.gsub('.', '_').humanize
|
216
|
+
attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
|
217
|
+
options = { :default => "{{attribute}} {{message}}", :attribute => attr_name }
|
218
|
+
|
219
|
+
messages.each do |m|
|
220
|
+
full_messages << I18n.t(:"errors.format", options.merge(:message => m))
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
full_messages
|
226
|
+
end
|
227
|
+
|
228
|
+
# Translates an error message in its default scope (<tt>activemodel.errors.messages</tt>).
|
229
|
+
# Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>, if it's not there,
|
230
|
+
# it's looked up in <tt>models.MODEL.MESSAGE</tt> and if that is not there it returns the translation of the
|
231
|
+
# default message (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model name,
|
232
|
+
# translated attribute name and the value are available for interpolation.
|
233
|
+
#
|
234
|
+
# When using inheritence in your models, it will check all the inherited models too, but only if the model itself
|
235
|
+
# hasn't been found. Say you have <tt>class Admin < User; end</tt> and you wanted the translation for the <tt>:blank</tt>
|
236
|
+
# error +message+ for the <tt>title</tt> +attribute+, it looks for these translations:
|
237
|
+
#
|
238
|
+
# <ol>
|
239
|
+
# <li><tt>activemodel.errors.models.admin.attributes.title.blank</tt></li>
|
240
|
+
# <li><tt>activemodel.errors.models.admin.blank</tt></li>
|
241
|
+
# <li><tt>activemodel.errors.models.user.attributes.title.blank</tt></li>
|
242
|
+
# <li><tt>activemodel.errors.models.user.blank</tt></li>
|
243
|
+
# <li>any default you provided through the +options+ hash (in the activemodel.errors scope)</li>
|
244
|
+
# <li><tt>activemodel.errors.messages.blank</tt></li>
|
245
|
+
# <li><tt>errors.attributes.title.blank</tt></li>
|
246
|
+
# <li><tt>errors.messages.blank</tt></li>
|
247
|
+
# </ol>
|
248
|
+
def generate_message(attribute, message = :invalid, options = {})
|
249
|
+
message, options[:default] = options[:default], message if options[:default].is_a?(Symbol)
|
250
|
+
|
251
|
+
defaults = @base.class.lookup_ancestors.map do |klass|
|
252
|
+
[ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.underscore}.attributes.#{attribute}.#{message}",
|
253
|
+
:"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.underscore}.#{message}" ]
|
254
|
+
end
|
255
|
+
|
256
|
+
defaults << options.delete(:default)
|
257
|
+
defaults << :"#{@base.class.i18n_scope}.errors.messages.#{message}"
|
258
|
+
defaults << :"errors.attributes.#{attribute}.#{message}"
|
259
|
+
defaults << :"errors.messages.#{message}"
|
260
|
+
|
261
|
+
defaults.compact!
|
262
|
+
defaults.flatten!
|
263
|
+
|
264
|
+
key = defaults.shift
|
265
|
+
value = @base.send(:read_attribute_for_validation, attribute)
|
266
|
+
|
267
|
+
options = {
|
268
|
+
:default => defaults,
|
269
|
+
:model => @base.class.model_name.human,
|
270
|
+
:attribute => @base.class.human_attribute_name(attribute),
|
271
|
+
:value => value
|
272
|
+
}.merge(options)
|
273
|
+
|
274
|
+
I18n.translate(key, options)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|