activemodel 3.0.pre → 3.0.0.rc

Sign up to get free protection for your applications and to get access to all the features.
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)