activemodel 3.0.pre → 3.0.0.rc

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.
Files changed (45) hide show
  1. data/CHANGELOG +44 -1
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +184 -0
  4. data/lib/active_model.rb +29 -19
  5. data/lib/active_model/attribute_methods.rb +167 -46
  6. data/lib/active_model/callbacks.rb +134 -0
  7. data/lib/active_model/conversion.rb +41 -1
  8. data/lib/active_model/deprecated_error_methods.rb +1 -1
  9. data/lib/active_model/dirty.rb +56 -12
  10. data/lib/active_model/errors.rb +205 -46
  11. data/lib/active_model/lint.rb +53 -17
  12. data/lib/active_model/locale/en.yml +26 -23
  13. data/lib/active_model/mass_assignment_security.rb +160 -0
  14. data/lib/active_model/mass_assignment_security/permission_set.rb +40 -0
  15. data/lib/active_model/mass_assignment_security/sanitizer.rb +23 -0
  16. data/lib/active_model/naming.rb +70 -5
  17. data/lib/active_model/observing.rb +40 -16
  18. data/lib/active_model/railtie.rb +2 -0
  19. data/lib/active_model/serialization.rb +59 -0
  20. data/lib/active_model/serializers/json.rb +17 -11
  21. data/lib/active_model/serializers/xml.rb +66 -123
  22. data/lib/active_model/test_case.rb +0 -2
  23. data/lib/active_model/translation.rb +64 -0
  24. data/lib/active_model/validations.rb +150 -68
  25. data/lib/active_model/validations/acceptance.rb +53 -33
  26. data/lib/active_model/validations/callbacks.rb +57 -0
  27. data/lib/active_model/validations/confirmation.rb +41 -23
  28. data/lib/active_model/validations/exclusion.rb +18 -13
  29. data/lib/active_model/validations/format.rb +28 -24
  30. data/lib/active_model/validations/inclusion.rb +18 -13
  31. data/lib/active_model/validations/length.rb +67 -65
  32. data/lib/active_model/validations/numericality.rb +83 -58
  33. data/lib/active_model/validations/presence.rb +10 -8
  34. data/lib/active_model/validations/validates.rb +110 -0
  35. data/lib/active_model/validations/with.rb +90 -23
  36. data/lib/active_model/validator.rb +186 -0
  37. data/lib/active_model/version.rb +3 -2
  38. metadata +79 -20
  39. data/README +0 -21
  40. data/lib/active_model/state_machine.rb +0 -70
  41. data/lib/active_model/state_machine/event.rb +0 -62
  42. data/lib/active_model/state_machine/machine.rb +0 -75
  43. data/lib/active_model/state_machine/state.rb +0 -47
  44. data/lib/active_model/state_machine/state_transition.rb +0 -40
  45. data/lib/active_model/validations_repair_helper.rb +0 -35
@@ -0,0 +1,134 @@
1
+ require 'active_support/core_ext/array/wrap'
2
+ require 'active_support/callbacks'
3
+
4
+ module ActiveModel
5
+ # == Active Model Callbacks
6
+ #
7
+ # Provides an interface for any class to have Active Record like callbacks.
8
+ #
9
+ # Like the Active Record methods, the callback chain is aborted as soon as
10
+ # one of the methods in the chain returns false.
11
+ #
12
+ # First, extend ActiveModel::Callbacks from the class you are creating:
13
+ #
14
+ # class MyModel
15
+ # extend ActiveModel::Callbacks
16
+ # end
17
+ #
18
+ # Then define a list of methods that you want callbacks attached to:
19
+ #
20
+ # define_model_callbacks :create, :update
21
+ #
22
+ # This will provide all three standard callbacks (before, around and after) for
23
+ # both the :create and :update methods. To implement, you need to wrap the methods
24
+ # you want callbacks on in a block so that the callbacks get a chance to fire:
25
+ #
26
+ # def create
27
+ # _run_create_callbacks do
28
+ # # Your create action methods here
29
+ # end
30
+ # end
31
+ #
32
+ # The _run_<method_name>_callbacks methods are dynamically created when you extend
33
+ # the <tt>ActiveModel::Callbacks</tt> module.
34
+ #
35
+ # Then in your class, you can use the +before_create+, +after_create+ and +around_create+
36
+ # methods, just as you would in an Active Record module.
37
+ #
38
+ # before_create :action_before_create
39
+ #
40
+ # def action_before_create
41
+ # # Your code here
42
+ # end
43
+ #
44
+ # You can choose not to have all three callbacks by passing a hash to the
45
+ # define_model_callbacks method.
46
+ #
47
+ # define_model_callbacks :create, :only => :after, :before
48
+ #
49
+ # Would only create the after_create and before_create callback methods in your
50
+ # class.
51
+ module Callbacks
52
+ def self.extended(base)
53
+ base.class_eval do
54
+ include ActiveSupport::Callbacks
55
+ end
56
+ end
57
+
58
+ # define_model_callbacks accepts the same options define_callbacks does, in case
59
+ # you want to overwrite a default. Besides that, it also accepts an :only option,
60
+ # where you can choose if you want all types (before, around or after) or just some.
61
+ #
62
+ # define_model_callbacks :initializer, :only => :after
63
+ #
64
+ # Note, the <tt>:only => <type></tt> hash will apply to all callbacks defined on
65
+ # that method call. To get around this you can call the define_model_callbacks
66
+ # method as many times as you need.
67
+ #
68
+ # define_model_callbacks :create, :only => :after
69
+ # define_model_callbacks :update, :only => :before
70
+ # define_model_callbacks :destroy, :only => :around
71
+ #
72
+ # Would create +after_create+, +before_update+ and +around_destroy+ methods only.
73
+ #
74
+ # You can pass in a class to before_<type>, after_<type> and around_<type>, in which
75
+ # case the callback will call that class's <action>_<type> method passing the object
76
+ # that the callback is being called on.
77
+ #
78
+ # class MyModel
79
+ # extend ActiveModel::Callbacks
80
+ # define_model_callbacks :create
81
+ #
82
+ # before_create AnotherClass
83
+ # end
84
+ #
85
+ # class AnotherClass
86
+ # def self.before_create( obj )
87
+ # # obj is the MyModel instance that the callback is being called on
88
+ # end
89
+ # end
90
+ #
91
+ def define_model_callbacks(*callbacks)
92
+ options = callbacks.extract_options!
93
+ options = { :terminator => "result == false", :scope => [:kind, :name] }.merge(options)
94
+
95
+ types = Array.wrap(options.delete(:only))
96
+ types = [:before, :around, :after] if types.empty?
97
+
98
+ callbacks.each do |callback|
99
+ define_callbacks(callback, options)
100
+
101
+ types.each do |type|
102
+ send(:"_define_#{type}_model_callback", self, callback)
103
+ end
104
+ end
105
+ end
106
+
107
+ def _define_before_model_callback(klass, callback) #:nodoc:
108
+ klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
109
+ def self.before_#{callback}(*args, &block)
110
+ set_callback(:#{callback}, :before, *args, &block)
111
+ end
112
+ CALLBACK
113
+ end
114
+
115
+ def _define_around_model_callback(klass, callback) #:nodoc:
116
+ klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
117
+ def self.around_#{callback}(*args, &block)
118
+ set_callback(:#{callback}, :around, *args, &block)
119
+ end
120
+ CALLBACK
121
+ end
122
+
123
+ def _define_after_model_callback(klass, callback) #:nodoc:
124
+ klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
125
+ def self.after_#{callback}(*args, &block)
126
+ options = args.extract_options!
127
+ options[:prepend] = true
128
+ options[:if] = Array.wrap(options[:if]) << "!halted && value != false"
129
+ set_callback(:#{callback}, :after, *(args << options), &block)
130
+ end
131
+ CALLBACK
132
+ end
133
+ end
134
+ end
@@ -1,8 +1,48 @@
1
1
  module ActiveModel
2
- # Include ActiveModel::Conversion if your object "acts like an ActiveModel model".
2
+ # == Active Model Conversions
3
+ #
4
+ # Handles default conversions: to_model, to_key and to_param.
5
+ #
6
+ # == Example
7
+ #
8
+ # Let's take for example this non persisted object.
9
+ #
10
+ # class ContactMessage
11
+ # include ActiveModel::Conversion
12
+ #
13
+ # # ContactMessage are never persisted in the DB
14
+ # def persisted?
15
+ # false
16
+ # end
17
+ # end
18
+ #
19
+ # cm = ContactMessage.new
20
+ # cm.to_model == self #=> true
21
+ # cm.to_key #=> nil
22
+ # cm.to_param #=> nil
23
+ #
3
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
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
31
+ # your object with Active Model compliant methods.
4
32
  def to_model
5
33
  self
6
34
  end
35
+
36
+ # Returns an Enumerable of all (primary) key attributes or nil if
37
+ # persisted? is false
38
+ def to_key
39
+ persisted? ? [id] : nil
40
+ end
41
+
42
+ # Returns a string representing the object's key suitable for use in URLs,
43
+ # or nil if persisted? is false
44
+ def to_param
45
+ to_key ? to_key.join('-') : nil
46
+ end
7
47
  end
8
48
  end
@@ -16,7 +16,7 @@ module ActiveModel
16
16
  end
17
17
 
18
18
  def add_to_base(msg)
19
- ActiveSupport::Deprecation.warn "Errors#add_to_base(msg) has been deprecated, use Errors#[:base] << msg instead"
19
+ ActiveSupport::Deprecation.warn "Errors#add_to_base(msg) has been deprecated, use Errors#add(:base, msg) instead"
20
20
  self[:base] << msg
21
21
  end
22
22
 
@@ -1,5 +1,53 @@
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
+
1
6
  module ActiveModel
2
- # Track unsaved attribute changes.
7
+ # == Active Model Dirty
8
+ #
9
+ # Provides a way to track changes in your object in the same way as
10
+ # Active Record does.
11
+ #
12
+ # The requirements to implement ActiveModel::Dirty are to:
13
+ #
14
+ # * <tt>include ActiveModel::Dirty</tt> in your object
15
+ # * Call <tt>define_attribute_methods</tt> passing each method you want to
16
+ # track
17
+ # * Call <tt>attr_name_will_change!</tt> before each change to the tracked
18
+ # attribute
19
+ #
20
+ # If you wish to also track previous changes on save or update, you need to
21
+ # add
22
+ #
23
+ # @previously_changed = changes
24
+ #
25
+ # inside of your save or update method.
26
+ #
27
+ # A minimal implementation could be:
28
+ #
29
+ # class Person
30
+ #
31
+ # include ActiveModel::Dirty
32
+ #
33
+ # define_attribute_methods [:name]
34
+ #
35
+ # def name
36
+ # @name
37
+ # end
38
+ #
39
+ # def name=(val)
40
+ # name_will_change!
41
+ # @name = val
42
+ # end
43
+ #
44
+ # def save
45
+ # @previously_changed = changes
46
+ # end
47
+ #
48
+ # end
49
+ #
50
+ # == Examples:
3
51
  #
4
52
  # A newly instantiated object is unchanged:
5
53
  # person = Person.find_by_name('Uncle Bob')
@@ -69,7 +117,7 @@ module ActiveModel
69
117
  # person.name = 'bob'
70
118
  # person.changes # => { 'name' => ['bill', 'bob'] }
71
119
  def changes
72
- changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h }
120
+ changed.inject(HashWithIndifferentAccess.new){ |h, attr| h[attr] = attribute_change(attr); h }
73
121
  end
74
122
 
75
123
  # Map of attributes that were changed when the model was saved.
@@ -78,19 +126,15 @@ module ActiveModel
78
126
  # person.save
79
127
  # person.previous_changes # => {'name' => ['bob, 'robert']}
80
128
  def previous_changes
81
- previously_changed_attributes
129
+ @previously_changed
82
130
  end
83
131
 
84
- private
85
- # Map of change <tt>attr => original value</tt>.
86
- def changed_attributes
87
- @changed_attributes ||= {}
88
- end
132
+ # Map of change <tt>attr => original value</tt>.
133
+ def changed_attributes
134
+ @changed_attributes ||= {}
135
+ end
89
136
 
90
- # Map of fields that were changed when the model was saved
91
- def previously_changed_attributes
92
- @previously_changed || {}
93
- end
137
+ private
94
138
 
95
139
  # Handle <tt>*_changed?</tt> for +method_missing+.
96
140
  def attribute_changed?(attr)
@@ -1,10 +1,77 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'active_support/core_ext/array/wrap'
4
+ require 'active_support/core_ext/array/conversions'
1
5
  require 'active_support/core_ext/string/inflections'
6
+ require 'active_support/core_ext/object/blank'
7
+ require 'active_support/core_ext/hash/reverse_merge'
2
8
  require 'active_support/ordered_hash'
3
9
 
4
10
  module ActiveModel
11
+ # == Active Model Errors
12
+ #
13
+ # Provides a modified +OrderedHash+ that you can include in your object
14
+ # for handling error messages and interacting with Action Pack helpers.
15
+ #
16
+ # A minimal implementation could be:
17
+ #
18
+ # class Person
19
+ #
20
+ # # Required dependency for ActiveModel::Errors
21
+ # extend ActiveModel::Naming
22
+ #
23
+ # def initialize
24
+ # @errors = ActiveModel::Errors.new(self)
25
+ # end
26
+ #
27
+ # attr_accessor :name
28
+ # attr_reader :errors
29
+ #
30
+ # def validate!
31
+ # errors.add(:name, "can not be nil") if name == nil
32
+ # end
33
+ #
34
+ # # The following methods are needed to be minimally implemented
35
+ #
36
+ # def read_attribute_for_validation(attr)
37
+ # send(attr)
38
+ # end
39
+ #
40
+ # def ErrorsPerson.human_attribute_name(attr, options = {})
41
+ # attr
42
+ # end
43
+ #
44
+ # def ErrorsPerson.lookup_ancestors
45
+ # [self]
46
+ # end
47
+ #
48
+ # end
49
+ #
50
+ # The last three methods are required in your object for Errors to be
51
+ # able to generate error messages correctly and also handle multiple
52
+ # languages. Of course, if you extend your object with ActiveModel::Translations
53
+ # you will not need to implement the last two. Likewise, using
54
+ # ActiveModel::Validations will handle the validation related methods
55
+ # for you.
56
+ #
57
+ # The above allows you to do:
58
+ #
59
+ # p = Person.new
60
+ # p.validate! # => ["can not be nil"]
61
+ # p.errors.full_messages # => ["name can not be nil"]
62
+ # # etc..
5
63
  class Errors < ActiveSupport::OrderedHash
6
64
  include DeprecatedErrorMethods
7
65
 
66
+ CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank]
67
+
68
+ # Pass in the instance of the object that is using the errors object.
69
+ #
70
+ # class Person
71
+ # def initialize
72
+ # @errors = ActiveModel::Errors.new(self)
73
+ # end
74
+ # end
8
75
  def initialize(base)
9
76
  @base = base
10
77
  super()
@@ -13,6 +80,11 @@ module ActiveModel
13
80
  alias_method :get, :[]
14
81
  alias_method :set, :[]=
15
82
 
83
+ # When passed a symbol or a name of a method, returns an array of errors
84
+ # for the method.
85
+ #
86
+ # p.errors[:name] #=> ["can not be nil"]
87
+ # p.errors['name'] #=> ["can not be nil"]
16
88
  def [](attribute)
17
89
  if errors = get(attribute.to_sym)
18
90
  errors
@@ -21,65 +93,134 @@ module ActiveModel
21
93
  end
22
94
  end
23
95
 
96
+ # Adds to the supplied attribute the supplied error message.
97
+ #
98
+ # p.errors[:name] = "must be set"
99
+ # p.errors[:name] #=> ['must be set']
24
100
  def []=(attribute, error)
25
101
  self[attribute.to_sym] << error
26
102
  end
27
103
 
104
+ # Iterates through each error key, value pair in the error messages hash.
105
+ # Yields the attribute and the error for that attribute. If the attribute
106
+ # has more than one error message, yields once for each error message.
107
+ #
108
+ # p.errors.add(:name, "can't be blank")
109
+ # p.errors.each do |attribute, errors_array|
110
+ # # Will yield :name and "can't be blank"
111
+ # end
112
+ #
113
+ # p.errors.add(:name, "must be specified")
114
+ # p.errors.each do |attribute, errors_array|
115
+ # # Will yield :name and "can't be blank"
116
+ # # then yield :name and "must be specified"
117
+ # end
28
118
  def each
29
119
  each_key do |attribute|
30
120
  self[attribute].each { |error| yield attribute, error }
31
121
  end
32
122
  end
33
123
 
124
+ # Returns the number of error messages.
125
+ #
126
+ # p.errors.add(:name, "can't be blank")
127
+ # p.errors.size #=> 1
128
+ # p.errors.add(:name, "must be specified")
129
+ # p.errors.size #=> 2
34
130
  def size
35
131
  values.flatten.size
36
132
  end
37
133
 
134
+ # Returns an array of error messages, with the attribute name included
135
+ #
136
+ # p.errors.add(:name, "can't be blank")
137
+ # p.errors.add(:name, "must be specified")
138
+ # p.errors.to_a #=> ["name can't be blank", "name must be specified"]
38
139
  def to_a
39
140
  full_messages
40
141
  end
41
142
 
143
+ # Returns the number of error messages.
144
+ # p.errors.add(:name, "can't be blank")
145
+ # p.errors.count #=> 1
146
+ # p.errors.add(:name, "must be specified")
147
+ # p.errors.count #=> 2
42
148
  def count
43
149
  to_a.size
44
150
  end
45
151
 
152
+ # Returns true if there are any errors, false if not.
153
+ def empty?
154
+ all? { |k, v| v && v.empty? }
155
+ end
156
+
157
+ # Returns an xml formatted representation of the Errors hash.
158
+ #
159
+ # p.errors.add(:name, "can't be blank")
160
+ # p.errors.add(:name, "must be specified")
161
+ # p.errors.to_xml #=> Produces:
162
+ #
163
+ # # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
164
+ # # <errors>
165
+ # # <error>name can't be blank</error>
166
+ # # <error>name must be specified</error>
167
+ # # </errors>
46
168
  def to_xml(options={})
47
- require 'builder' unless defined? ::Builder
48
- options[:root] ||= "errors"
49
- options[:indent] ||= 2
50
- options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
51
-
52
- options[:builder].instruct! unless options.delete(:skip_instruct)
53
- options[:builder].errors do |e|
54
- to_a.each { |error| e.error(error) }
55
- end
169
+ to_a.to_xml options.reverse_merge(:root => "errors", :skip_types => true)
170
+ end
171
+
172
+ # Returns an array as JSON representation for this object.
173
+ def as_json(options=nil)
174
+ to_a
56
175
  end
57
176
 
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).
177
+ # Adds +message+ to the error messages on +attribute+, which will be returned on a call to
178
+ # <tt>on(attribute)</tt> for the same attribute. More than one error can be added to the same
179
+ # +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
180
+ # If no +message+ is supplied, <tt>:invalid</tt> is assumed.
181
+ #
182
+ # If +message+ is a symbol, it will be translated using the appropriate scope (see +translate_error+).
183
+ # If +message+ is a proc, it will be called, allowing for things like <tt>Time.now</tt> to be used within an error.
63
184
  def add(attribute, message = nil, options = {})
64
185
  message ||= :invalid
65
- message = generate_message(attribute, message, options) if message.is_a?(Symbol)
186
+
187
+ if message.is_a?(Symbol)
188
+ message = generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
189
+ elsif message.is_a?(Proc)
190
+ message = message.call
191
+ end
192
+
66
193
  self[attribute] << message
67
194
  end
68
195
 
69
196
  # Will add an error message to each of the attributes in +attributes+ that is empty.
70
- def add_on_empty(attributes, custom_message = nil)
197
+ def add_on_empty(attributes, options = {})
198
+ if options && !options.is_a?(Hash)
199
+ options = { :message => options }
200
+ ActiveSupport::Deprecation.warn \
201
+ "ActiveModel::Errors#add_on_empty(attributes, custom_message) has been deprecated.\n" +
202
+ "Instead of passing a custom_message pass an options Hash { :message => custom_message }."
203
+ end
204
+
71
205
  [attributes].flatten.each do |attribute|
72
206
  value = @base.send(:read_attribute_for_validation, attribute)
73
207
  is_empty = value.respond_to?(:empty?) ? value.empty? : false
74
- add(attribute, :empty, :default => custom_message) unless !value.nil? && !is_empty
208
+ add(attribute, :empty, options) if value.nil? || is_empty
75
209
  end
76
210
  end
77
211
 
78
212
  # Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?).
79
- def add_on_blank(attributes, custom_message = nil)
213
+ def add_on_blank(attributes, options = {})
214
+ if options && !options.is_a?(Hash)
215
+ options = { :message => options }
216
+ ActiveSupport::Deprecation.warn \
217
+ "ActiveModel::Errors#add_on_blank(attributes, custom_message) has been deprecated.\n" +
218
+ "Instead of passing a custom_message pass an options Hash { :message => custom_message }."
219
+ end
220
+
80
221
  [attributes].flatten.each do |attribute|
81
222
  value = @base.send(:read_attribute_for_validation, attribute)
82
- add(attribute, :blank, :default => custom_message) if value.blank?
223
+ add(attribute, :blank, options) if value.blank?
83
224
  end
84
225
  end
85
226
 
@@ -93,7 +234,7 @@ module ActiveModel
93
234
  # company = Company.create(:address => '123 First St.')
94
235
  # company.errors.full_messages # =>
95
236
  # ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
96
- def full_messages(options = {})
237
+ def full_messages
97
238
  full_messages = []
98
239
 
99
240
  each do |attribute, messages|
@@ -103,10 +244,12 @@ module ActiveModel
103
244
  if attribute == :base
104
245
  messages.each {|m| full_messages << m }
105
246
  else
106
- attr_name = attribute.to_s.humanize
107
- prefix = attr_name + I18n.t('activemodel.errors.format.separator', :default => ' ')
247
+ attr_name = attribute.to_s.gsub('.', '_').humanize
248
+ attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
249
+ options = { :default => "%{attribute} %{message}", :attribute => attr_name }
250
+
108
251
  messages.each do |m|
109
- full_messages << "#{prefix}#{m}"
252
+ full_messages << I18n.t(:"errors.format", options.merge(:message => m))
110
253
  end
111
254
  end
112
255
  end
@@ -114,46 +257,62 @@ module ActiveModel
114
257
  full_messages
115
258
  end
116
259
 
117
- # Translates an error message in its default scope (<tt>activemodel.errors.messages</tt>).
118
- # Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>, if it's not there,
119
- # it's looked up in <tt>models.MODEL.MESSAGE</tt> and if that is not there it returns the translation of the
120
- # default message (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model name,
260
+ # Translates an error message in its default scope
261
+ # (<tt>activemodel.errors.messages</tt>).
262
+ #
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
266
+ # (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model name,
121
267
  # translated attribute name and the value are available for interpolation.
122
268
  #
123
- # When using inheritence in your models, it will check all the inherited models too, but only if the model itself
124
- # hasn't been found. Say you have <tt>class Admin < User; end</tt> and you wanted the translation for the <tt>:blank</tt>
125
- # error +message+ for the <tt>title</tt> +attribute+, it looks for these translations:
269
+ # When using inheritance in your models, it will check all the inherited
270
+ # 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+,
273
+ # it looks for these translations:
126
274
  #
127
275
  # <ol>
128
276
  # <li><tt>activemodel.errors.models.admin.attributes.title.blank</tt></li>
129
277
  # <li><tt>activemodel.errors.models.admin.blank</tt></li>
130
278
  # <li><tt>activemodel.errors.models.user.attributes.title.blank</tt></li>
131
279
  # <li><tt>activemodel.errors.models.user.blank</tt></li>
132
- # <li><tt>activemodel.errors.messages.blank</tt></li>
133
280
  # <li>any default you provided through the +options+ hash (in the activemodel.errors scope)</li>
281
+ # <li><tt>activemodel.errors.messages.blank</tt></li>
282
+ # <li><tt>errors.attributes.title.blank</tt></li>
283
+ # <li><tt>errors.messages.blank</tt></li>
134
284
  # </ol>
135
- def generate_message(attribute, message = :invalid, options = {})
136
- message, options[:default] = options[:default], message if options[:default].is_a?(Symbol)
285
+ def generate_message(attribute, type = :invalid, options = {})
286
+ type = options.delete(:message) if options[:message].is_a?(Symbol)
137
287
 
138
- klass_ancestors = [@base.class]
139
- klass_ancestors += @base.class.ancestors.reject {|x| x.is_a?(Module)}
288
+ if options[:default]
289
+ ActiveSupport::Deprecation.warn \
290
+ "ActiveModel::Errors#generate_message(attributes, custom_message) has been deprecated.\n" +
291
+ "Use ActiveModel::Errors#generate_message(attributes, :message => 'your message') instead."
292
+ options[:message] = options.delete(:default)
293
+ end
140
294
 
141
- defaults = klass_ancestors.map do |klass|
142
- [ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}",
143
- :"models.#{klass.name.underscore}.#{message}" ]
295
+ defaults = @base.class.lookup_ancestors.map do |klass|
296
+ [ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.underscore}.attributes.#{attribute}.#{type}",
297
+ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.underscore}.#{type}" ]
144
298
  end
145
299
 
146
- defaults << options.delete(:default)
147
- defaults = defaults.compact.flatten << :"messages.#{message}"
300
+ defaults << options.delete(:message)
301
+ defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}"
302
+ defaults << :"errors.attributes.#{attribute}.#{type}"
303
+ defaults << :"errors.messages.#{type}"
304
+
305
+ defaults.compact!
306
+ defaults.flatten!
148
307
 
149
308
  key = defaults.shift
150
- value = @base.send(:read_attribute_for_validation, attribute)
309
+ value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil)
151
310
 
152
- options = { :default => defaults,
153
- :model => @base.class.name.humanize,
154
- :attribute => attribute.to_s.humanize,
155
- :value => value,
156
- :scope => [:activemodel, :errors]
311
+ options = {
312
+ :default => defaults,
313
+ :model => @base.class.model_name.human,
314
+ :attribute => @base.class.human_attribute_name(attribute),
315
+ :value => value
157
316
  }.merge(options)
158
317
 
159
318
  I18n.translate(key, options)