activemodel 3.0.20 → 3.1.0.beta1
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 +17 -73
- data/MIT-LICENSE +1 -1
- data/README.rdoc +5 -5
- data/lib/active_model.rb +2 -2
- data/lib/active_model/attribute_methods.rb +46 -53
- data/lib/active_model/callbacks.rb +2 -5
- data/lib/active_model/conversion.rb +0 -2
- data/lib/active_model/dirty.rb +3 -4
- data/lib/active_model/errors.rb +55 -56
- data/lib/active_model/lint.rb +2 -2
- data/lib/active_model/mass_assignment_security.rb +96 -47
- data/lib/active_model/naming.rb +55 -13
- data/lib/active_model/observer_array.rb +104 -0
- data/lib/active_model/observing.rb +53 -18
- data/lib/active_model/secure_password.rb +67 -0
- data/lib/active_model/serialization.rb +4 -11
- data/lib/active_model/serializers/json.rb +18 -18
- data/lib/active_model/serializers/xml.rb +26 -5
- data/lib/active_model/translation.rb +4 -11
- data/lib/active_model/validations.rb +23 -23
- data/lib/active_model/validations/acceptance.rb +3 -5
- data/lib/active_model/validations/callbacks.rb +5 -19
- data/lib/active_model/validations/confirmation.rb +6 -5
- data/lib/active_model/validations/exclusion.rb +27 -3
- data/lib/active_model/validations/format.rb +38 -12
- data/lib/active_model/validations/inclusion.rb +30 -23
- data/lib/active_model/validations/length.rb +3 -1
- data/lib/active_model/validations/numericality.rb +4 -2
- data/lib/active_model/validations/presence.rb +3 -2
- data/lib/active_model/validations/validates.rb +23 -9
- data/lib/active_model/validations/with.rb +14 -2
- data/lib/active_model/validator.rb +16 -18
- data/lib/active_model/version.rb +3 -3
- metadata +71 -58
- checksums.yaml +0 -7
- data/lib/active_model/deprecated_error_methods.rb +0 -33
data/lib/active_model/errors.rb
CHANGED
@@ -60,11 +60,13 @@ module ActiveModel
|
|
60
60
|
# p.validate! # => ["can not be nil"]
|
61
61
|
# p.errors.full_messages # => ["name can not be nil"]
|
62
62
|
# # etc..
|
63
|
-
class Errors
|
64
|
-
include
|
63
|
+
class Errors
|
64
|
+
include Enumerable
|
65
65
|
|
66
66
|
CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank]
|
67
67
|
|
68
|
+
attr_reader :messages
|
69
|
+
|
68
70
|
# Pass in the instance of the object that is using the errors object.
|
69
71
|
#
|
70
72
|
# class Person
|
@@ -73,12 +75,29 @@ module ActiveModel
|
|
73
75
|
# end
|
74
76
|
# end
|
75
77
|
def initialize(base)
|
76
|
-
@base
|
77
|
-
|
78
|
+
@base = base
|
79
|
+
@messages = ActiveSupport::OrderedHash.new
|
80
|
+
end
|
81
|
+
|
82
|
+
# Clear the messages
|
83
|
+
def clear
|
84
|
+
messages.clear
|
85
|
+
end
|
86
|
+
|
87
|
+
# Do the error messages include an error with key +error+?
|
88
|
+
def include?(error)
|
89
|
+
messages.include? error
|
90
|
+
end
|
91
|
+
|
92
|
+
# Get messages for +key+
|
93
|
+
def get(key)
|
94
|
+
messages[key]
|
78
95
|
end
|
79
96
|
|
80
|
-
|
81
|
-
|
97
|
+
# Set messages for +key+ to +value+
|
98
|
+
def set(key, value)
|
99
|
+
messages[key] = value
|
100
|
+
end
|
82
101
|
|
83
102
|
# When passed a symbol or a name of a method, returns an array of errors
|
84
103
|
# for the method.
|
@@ -112,7 +131,7 @@ module ActiveModel
|
|
112
131
|
# # then yield :name and "must be specified"
|
113
132
|
# end
|
114
133
|
def each
|
115
|
-
each_key do |attribute|
|
134
|
+
messages.each_key do |attribute|
|
116
135
|
self[attribute].each { |error| yield attribute, error }
|
117
136
|
end
|
118
137
|
end
|
@@ -127,6 +146,16 @@ module ActiveModel
|
|
127
146
|
values.flatten.size
|
128
147
|
end
|
129
148
|
|
149
|
+
# Returns all message values
|
150
|
+
def values
|
151
|
+
messages.values
|
152
|
+
end
|
153
|
+
|
154
|
+
# Returns all message keys
|
155
|
+
def keys
|
156
|
+
messages.keys
|
157
|
+
end
|
158
|
+
|
130
159
|
# Returns an array of error messages, with the attribute name included
|
131
160
|
#
|
132
161
|
# p.errors.add(:name, "can't be blank")
|
@@ -171,9 +200,7 @@ module ActiveModel
|
|
171
200
|
end
|
172
201
|
|
173
202
|
def to_hash
|
174
|
-
|
175
|
-
each { |k, v| (hash[k] ||= []) << v }
|
176
|
-
hash
|
203
|
+
messages.dup
|
177
204
|
end
|
178
205
|
|
179
206
|
# Adds +message+ to the error messages on +attribute+, which will be returned on a call to
|
@@ -197,13 +224,6 @@ module ActiveModel
|
|
197
224
|
|
198
225
|
# Will add an error message to each of the attributes in +attributes+ that is empty.
|
199
226
|
def add_on_empty(attributes, options = {})
|
200
|
-
if options && !options.is_a?(Hash)
|
201
|
-
options = { :message => options }
|
202
|
-
ActiveSupport::Deprecation.warn \
|
203
|
-
"ActiveModel::Errors#add_on_empty(attributes, custom_message) has been deprecated.\n" +
|
204
|
-
"Instead of passing a custom_message pass an options Hash { :message => custom_message }."
|
205
|
-
end
|
206
|
-
|
207
227
|
[attributes].flatten.each do |attribute|
|
208
228
|
value = @base.send(:read_attribute_for_validation, attribute)
|
209
229
|
is_empty = value.respond_to?(:empty?) ? value.empty? : false
|
@@ -213,13 +233,6 @@ module ActiveModel
|
|
213
233
|
|
214
234
|
# Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?).
|
215
235
|
def add_on_blank(attributes, options = {})
|
216
|
-
if options && !options.is_a?(Hash)
|
217
|
-
options = { :message => options }
|
218
|
-
ActiveSupport::Deprecation.warn \
|
219
|
-
"ActiveModel::Errors#add_on_blank(attributes, custom_message) has been deprecated.\n" +
|
220
|
-
"Instead of passing a custom_message pass an options Hash { :message => custom_message }."
|
221
|
-
end
|
222
|
-
|
223
236
|
[attributes].flatten.each do |attribute|
|
224
237
|
value = @base.send(:read_attribute_for_validation, attribute)
|
225
238
|
add(attribute, :blank, options) if value.blank?
|
@@ -237,26 +250,20 @@ module ActiveModel
|
|
237
250
|
# company.errors.full_messages # =>
|
238
251
|
# ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
|
239
252
|
def full_messages
|
240
|
-
|
241
|
-
|
242
|
-
each do |attribute, messages|
|
243
|
-
messages = Array.wrap(messages)
|
244
|
-
next if messages.empty?
|
245
|
-
|
253
|
+
map { |attribute, message|
|
246
254
|
if attribute == :base
|
247
|
-
|
255
|
+
message
|
248
256
|
else
|
249
257
|
attr_name = attribute.to_s.gsub('.', '_').humanize
|
250
258
|
attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
|
251
|
-
options = { :default => "%{attribute} %{message}", :attribute => attr_name }
|
252
259
|
|
253
|
-
|
254
|
-
|
255
|
-
|
260
|
+
I18n.t(:"errors.format", {
|
261
|
+
:default => "%{attribute} %{message}",
|
262
|
+
:attribute => attr_name,
|
263
|
+
:message => message
|
264
|
+
})
|
256
265
|
end
|
257
|
-
|
258
|
-
|
259
|
-
full_messages
|
266
|
+
}
|
260
267
|
end
|
261
268
|
|
262
269
|
# Translates an error message in its default scope
|
@@ -271,29 +278,21 @@ module ActiveModel
|
|
271
278
|
# When using inheritance in your models, it will check all the inherited
|
272
279
|
# models too, but only if the model itself hasn't been found. Say you have
|
273
280
|
# <tt>class Admin < User; end</tt> and you wanted the translation for
|
274
|
-
# the <tt>:blank</tt> error
|
281
|
+
# the <tt>:blank</tt> error message for the <tt>title</tt> attribute,
|
275
282
|
# it looks for these translations:
|
276
283
|
#
|
277
|
-
# <
|
278
|
-
# <
|
279
|
-
# <
|
280
|
-
# <
|
281
|
-
# <
|
282
|
-
# <
|
283
|
-
# <
|
284
|
-
# <
|
285
|
-
#
|
286
|
-
# </ol>
|
284
|
+
# * <tt>activemodel.errors.models.admin.attributes.title.blank</tt>
|
285
|
+
# * <tt>activemodel.errors.models.admin.blank</tt>
|
286
|
+
# * <tt>activemodel.errors.models.user.attributes.title.blank</tt>
|
287
|
+
# * <tt>activemodel.errors.models.user.blank</tt>
|
288
|
+
# * any default you provided through the +options+ hash (in the <tt>activemodel.errors</tt> scope)
|
289
|
+
# * <tt>activemodel.errors.messages.blank</tt>
|
290
|
+
# * <tt>errors.attributes.title.blank</tt>
|
291
|
+
# * <tt>errors.messages.blank</tt>
|
292
|
+
#
|
287
293
|
def generate_message(attribute, type = :invalid, options = {})
|
288
294
|
type = options.delete(:message) if options[:message].is_a?(Symbol)
|
289
295
|
|
290
|
-
if options[:default]
|
291
|
-
ActiveSupport::Deprecation.warn \
|
292
|
-
"Giving :default as validation option to errors.add has been deprecated.\n" +
|
293
|
-
"Please use :message instead."
|
294
|
-
options[:message] = options.delete(:default)
|
295
|
-
end
|
296
|
-
|
297
296
|
defaults = @base.class.lookup_ancestors.map do |klass|
|
298
297
|
[ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
|
299
298
|
:"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
|
data/lib/active_model/lint.rb
CHANGED
@@ -23,7 +23,7 @@ module ActiveModel
|
|
23
23
|
def test_to_key
|
24
24
|
assert model.respond_to?(:to_key), "The model should respond to to_key"
|
25
25
|
def model.persisted?() false end
|
26
|
-
assert model.to_key.nil
|
26
|
+
assert model.to_key.nil?, "to_key should return nil when `persisted?` returns false"
|
27
27
|
end
|
28
28
|
|
29
29
|
# == Responds to <tt>to_param</tt>
|
@@ -40,7 +40,7 @@ module ActiveModel
|
|
40
40
|
assert model.respond_to?(:to_param), "The model should respond to to_param"
|
41
41
|
def model.to_key() [1] end
|
42
42
|
def model.persisted?() false end
|
43
|
-
assert model.to_param.nil
|
43
|
+
assert model.to_param.nil?, "to_param should return nil when `persisted?` returns false"
|
44
44
|
end
|
45
45
|
|
46
46
|
# == Responds to <tt>valid?</tt>
|
@@ -20,78 +20,94 @@ module ActiveModel
|
|
20
20
|
# For example, a logged in user may need to assign additional attributes depending
|
21
21
|
# on their role:
|
22
22
|
#
|
23
|
-
#
|
24
|
-
#
|
23
|
+
# class AccountsController < ApplicationController
|
24
|
+
# include ActiveModel::MassAssignmentSecurity
|
25
25
|
#
|
26
|
-
#
|
26
|
+
# attr_accessible :first_name, :last_name
|
27
|
+
# attr_accessible :first_name, :last_name, :plan_id, :as => :admin
|
27
28
|
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
# ...
|
34
|
-
# @account.update_attributes(account_params)
|
35
|
-
# ...
|
36
|
-
# end
|
29
|
+
# def update
|
30
|
+
# ...
|
31
|
+
# @account.update_attributes(account_params)
|
32
|
+
# ...
|
33
|
+
# end
|
37
34
|
#
|
38
|
-
#
|
35
|
+
# protected
|
39
36
|
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
37
|
+
# def account_params
|
38
|
+
# scope = admin ? :admin : :default
|
39
|
+
# sanitize_for_mass_assignment(params[:account], scope)
|
40
|
+
# end
|
43
41
|
#
|
44
|
-
# def mass_assignment_authorizer
|
45
|
-
# admin ? admin_accessible_attributes : super
|
46
42
|
# end
|
47
43
|
#
|
48
|
-
# end
|
49
|
-
#
|
50
44
|
module ClassMethods
|
51
45
|
# Attributes named in this macro are protected from mass-assignment
|
52
|
-
# whenever attributes are sanitized before assignment.
|
46
|
+
# whenever attributes are sanitized before assignment. A scope for the
|
47
|
+
# attributes is optional, if no scope is provided then :default is used.
|
48
|
+
# A scope can be defined by using the :as option.
|
53
49
|
#
|
54
50
|
# Mass-assignment to these attributes will simply be ignored, to assign
|
55
51
|
# to them you can use direct writer methods. This is meant to protect
|
56
52
|
# sensitive attributes from being overwritten by malicious users
|
57
|
-
# tampering with URLs or forms.
|
58
|
-
#
|
59
|
-
# == Example
|
53
|
+
# tampering with URLs or forms. Example:
|
60
54
|
#
|
61
55
|
# class Customer
|
62
56
|
# include ActiveModel::MassAssignmentSecurity
|
63
57
|
#
|
64
58
|
# attr_accessor :name, :credit_rating
|
65
|
-
# attr_protected :credit_rating
|
66
59
|
#
|
67
|
-
#
|
68
|
-
#
|
60
|
+
# attr_protected :credit_rating, :last_login
|
61
|
+
# attr_protected :last_login, :as => :admin
|
62
|
+
#
|
63
|
+
# def assign_attributes(values, options = {})
|
64
|
+
# sanitize_for_mass_assignment(values, options[:as]).each do |k, v|
|
69
65
|
# send("#{k}=", v)
|
70
66
|
# end
|
71
67
|
# end
|
72
68
|
# end
|
73
69
|
#
|
70
|
+
# When using a :default scope :
|
71
|
+
#
|
74
72
|
# customer = Customer.new
|
75
|
-
# customer.
|
73
|
+
# customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default)
|
76
74
|
# customer.name # => "David"
|
77
75
|
# customer.credit_rating # => nil
|
76
|
+
# customer.last_login # => nil
|
78
77
|
#
|
79
78
|
# customer.credit_rating = "Average"
|
80
79
|
# customer.credit_rating # => "Average"
|
81
80
|
#
|
81
|
+
# And using the :admin scope :
|
82
|
+
#
|
83
|
+
# customer = Customer.new
|
84
|
+
# customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin)
|
85
|
+
# customer.name # => "David"
|
86
|
+
# customer.credit_rating # => "Excellent"
|
87
|
+
# customer.last_login # => nil
|
88
|
+
#
|
82
89
|
# To start from an all-closed default and enable attributes as needed,
|
83
90
|
# have a look at +attr_accessible+.
|
84
91
|
#
|
85
92
|
# Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of +attr_protected+
|
86
93
|
# to sanitize attributes won't provide sufficient protection.
|
87
|
-
def attr_protected(*
|
88
|
-
|
94
|
+
def attr_protected(*args)
|
95
|
+
options = args.extract_options!
|
96
|
+
scope = options[:as] || :default
|
97
|
+
|
98
|
+
self._protected_attributes = protected_attributes_configs.dup
|
99
|
+
self._protected_attributes[scope] = self.protected_attributes(scope) + args
|
100
|
+
|
89
101
|
self._active_authorizer = self._protected_attributes
|
90
102
|
end
|
91
103
|
|
92
104
|
# Specifies a white list of model attributes that can be set via
|
93
105
|
# mass-assignment.
|
94
106
|
#
|
107
|
+
# Like +attr_protected+, a scope for the attributes is optional,
|
108
|
+
# if no scope is provided then :default is used. A scope can be defined by
|
109
|
+
# using the :as option.
|
110
|
+
#
|
95
111
|
# This is the opposite of the +attr_protected+ macro: Mass-assignment
|
96
112
|
# will only set attributes in this list, to assign to the rest of
|
97
113
|
# attributes you can use direct writer methods. This is meant to protect
|
@@ -104,57 +120,90 @@ module ActiveModel
|
|
104
120
|
# include ActiveModel::MassAssignmentSecurity
|
105
121
|
#
|
106
122
|
# attr_accessor :name, :credit_rating
|
123
|
+
#
|
107
124
|
# attr_accessible :name
|
125
|
+
# attr_accessible :name, :credit_rating, :as => :admin
|
108
126
|
#
|
109
|
-
# def
|
110
|
-
# sanitize_for_mass_assignment(values).each do |k, v|
|
127
|
+
# def assign_attributes(values, options = {})
|
128
|
+
# sanitize_for_mass_assignment(values, options[:as]).each do |k, v|
|
111
129
|
# send("#{k}=", v)
|
112
130
|
# end
|
113
131
|
# end
|
114
132
|
# end
|
115
133
|
#
|
134
|
+
# When using a :default scope :
|
135
|
+
#
|
116
136
|
# customer = Customer.new
|
117
|
-
# customer.
|
137
|
+
# customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default)
|
118
138
|
# customer.name # => "David"
|
119
139
|
# customer.credit_rating # => nil
|
120
140
|
#
|
121
141
|
# customer.credit_rating = "Average"
|
122
142
|
# customer.credit_rating # => "Average"
|
123
143
|
#
|
144
|
+
# And using the :admin scope :
|
145
|
+
#
|
146
|
+
# customer = Customer.new
|
147
|
+
# customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin)
|
148
|
+
# customer.name # => "David"
|
149
|
+
# customer.credit_rating # => "Excellent"
|
150
|
+
#
|
124
151
|
# Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of +attr_accessible+
|
125
152
|
# to sanitize attributes won't provide sufficient protection.
|
126
|
-
def attr_accessible(*
|
127
|
-
|
153
|
+
def attr_accessible(*args)
|
154
|
+
options = args.extract_options!
|
155
|
+
scope = options[:as] || :default
|
156
|
+
|
157
|
+
self._accessible_attributes = accessible_attributes_configs.dup
|
158
|
+
self._accessible_attributes[scope] = self.accessible_attributes(scope) + args
|
159
|
+
|
128
160
|
self._active_authorizer = self._accessible_attributes
|
129
161
|
end
|
130
162
|
|
131
|
-
def protected_attributes
|
132
|
-
|
133
|
-
w.logger = self.logger if self.respond_to?(:logger)
|
134
|
-
end
|
163
|
+
def protected_attributes(scope = :default)
|
164
|
+
protected_attributes_configs[scope]
|
135
165
|
end
|
136
166
|
|
137
|
-
def accessible_attributes
|
138
|
-
|
167
|
+
def accessible_attributes(scope = :default)
|
168
|
+
accessible_attributes_configs[scope]
|
139
169
|
end
|
140
170
|
|
141
|
-
def
|
142
|
-
self._active_authorizer ||=
|
171
|
+
def active_authorizers
|
172
|
+
self._active_authorizer ||= protected_attributes_configs
|
143
173
|
end
|
174
|
+
alias active_authorizer active_authorizers
|
144
175
|
|
145
176
|
def attributes_protected_by_default
|
146
177
|
[]
|
147
178
|
end
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
def protected_attributes_configs
|
183
|
+
self._protected_attributes ||= begin
|
184
|
+
default_black_list = BlackList.new(attributes_protected_by_default).tap do |w|
|
185
|
+
w.logger = self.logger if self.respond_to?(:logger)
|
186
|
+
end
|
187
|
+
Hash.new(default_black_list)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def accessible_attributes_configs
|
192
|
+
self._accessible_attributes ||= begin
|
193
|
+
default_white_list = WhiteList.new.tap { |w| w.logger = self.logger if self.respond_to?(:logger) }
|
194
|
+
Hash.new(default_white_list)
|
195
|
+
end
|
196
|
+
end
|
148
197
|
end
|
149
198
|
|
150
199
|
protected
|
151
200
|
|
152
|
-
def sanitize_for_mass_assignment(attributes)
|
153
|
-
mass_assignment_authorizer.sanitize(attributes)
|
201
|
+
def sanitize_for_mass_assignment(attributes, scope = :default)
|
202
|
+
mass_assignment_authorizer(scope).sanitize(attributes)
|
154
203
|
end
|
155
204
|
|
156
|
-
def mass_assignment_authorizer
|
157
|
-
self.class.active_authorizer
|
205
|
+
def mass_assignment_authorizer(scope = :default)
|
206
|
+
self.class.active_authorizer[scope]
|
158
207
|
end
|
159
208
|
end
|
160
209
|
end
|
data/lib/active_model/naming.rb
CHANGED
@@ -1,20 +1,26 @@
|
|
1
1
|
require 'active_support/inflector'
|
2
|
+
require 'active_support/core_ext/hash/except'
|
3
|
+
require 'active_support/core_ext/module/introspection'
|
2
4
|
|
3
5
|
module ActiveModel
|
4
6
|
class Name < String
|
5
|
-
attr_reader :singular, :plural, :element, :collection, :partial_path, :i18n_key
|
7
|
+
attr_reader :singular, :plural, :element, :collection, :partial_path, :route_key, :param_key, :i18n_key
|
6
8
|
alias_method :cache_key, :collection
|
7
9
|
|
8
|
-
def initialize(klass)
|
10
|
+
def initialize(klass, namespace = nil)
|
9
11
|
super(klass.name)
|
12
|
+
@unnamespaced = self.sub(/^#{namespace.name}::/, '') if namespace
|
13
|
+
|
10
14
|
@klass = klass
|
11
|
-
@singular =
|
15
|
+
@singular = _singularize(self).freeze
|
12
16
|
@plural = ActiveSupport::Inflector.pluralize(@singular).freeze
|
13
17
|
@element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self)).freeze
|
14
18
|
@human = ActiveSupport::Inflector.humanize(@element).freeze
|
15
19
|
@collection = ActiveSupport::Inflector.tableize(self).freeze
|
16
20
|
@partial_path = "#{@collection}/#{@element}".freeze
|
17
|
-
@
|
21
|
+
@param_key = (namespace ? _singularize(@unnamespaced) : @singular).freeze
|
22
|
+
@route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural).freeze
|
23
|
+
@i18n_key = self.underscore.to_sym
|
18
24
|
end
|
19
25
|
|
20
26
|
# Transform the model name into a more humane format, using I18n. By default,
|
@@ -28,16 +34,21 @@ module ActiveModel
|
|
28
34
|
@klass.respond_to?(:i18n_scope)
|
29
35
|
|
30
36
|
defaults = @klass.lookup_ancestors.map do |klass|
|
31
|
-
|
32
|
-
|
33
|
-
end.flatten
|
37
|
+
klass.model_name.i18n_key
|
38
|
+
end
|
34
39
|
|
35
|
-
defaults << options
|
40
|
+
defaults << options[:default] if options[:default]
|
36
41
|
defaults << @human
|
37
42
|
|
38
|
-
options
|
43
|
+
options = {:scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults}.merge(options.except(:default))
|
39
44
|
I18n.translate(defaults.shift, options)
|
40
45
|
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def _singularize(string, replacement='_')
|
50
|
+
ActiveSupport::Inflector.underscore(string).tr('/', replacement)
|
51
|
+
end
|
41
52
|
end
|
42
53
|
|
43
54
|
# == Active Model Naming
|
@@ -57,13 +68,16 @@ module ActiveModel
|
|
57
68
|
# BookModule::BookCover.model_name.i18n_key # => "book_module.book_cover"
|
58
69
|
#
|
59
70
|
# Providing the functionality that ActiveModel::Naming provides in your object
|
60
|
-
# is required to pass the Active Model Lint test.
|
61
|
-
# method below, or rolling your own is required
|
71
|
+
# is required to pass the Active Model Lint test. So either extending the provided
|
72
|
+
# method below, or rolling your own is required.
|
62
73
|
module Naming
|
63
74
|
# Returns an ActiveModel::Name object for module. It can be
|
64
75
|
# used to retrieve all kinds of naming-related information.
|
65
76
|
def model_name
|
66
|
-
@_model_name ||=
|
77
|
+
@_model_name ||= begin
|
78
|
+
namespace = self.parents.detect { |n| n.respond_to?(:_railtie) }
|
79
|
+
ActiveModel::Name.new(self, namespace)
|
80
|
+
end
|
67
81
|
end
|
68
82
|
|
69
83
|
# Returns the plural class name of a record or class. Examples:
|
@@ -90,9 +104,37 @@ module ActiveModel
|
|
90
104
|
plural(record_or_class) == singular(record_or_class)
|
91
105
|
end
|
92
106
|
|
107
|
+
# Returns string to use while generating route names. It differs for
|
108
|
+
# namespaced models regarding whether it's inside isolated engine.
|
109
|
+
#
|
110
|
+
# For isolated engine:
|
111
|
+
# ActiveModel::Naming.route_key(Blog::Post) #=> posts
|
112
|
+
#
|
113
|
+
# For shared engine:
|
114
|
+
# ActiveModel::Naming.route_key(Blog::Post) #=> blog_posts
|
115
|
+
def self.route_key(record_or_class)
|
116
|
+
model_name_from_record_or_class(record_or_class).route_key
|
117
|
+
end
|
118
|
+
|
119
|
+
# Returns string to use for params names. It differs for
|
120
|
+
# namespaced models regarding whether it's inside isolated engine.
|
121
|
+
#
|
122
|
+
# For isolated engine:
|
123
|
+
# ActiveModel::Naming.param_key(Blog::Post) #=> post
|
124
|
+
#
|
125
|
+
# For shared engine:
|
126
|
+
# ActiveModel::Naming.param_key(Blog::Post) #=> blog_post
|
127
|
+
def self.param_key(record_or_class)
|
128
|
+
model_name_from_record_or_class(record_or_class).param_key
|
129
|
+
end
|
130
|
+
|
93
131
|
private
|
94
132
|
def self.model_name_from_record_or_class(record_or_class)
|
95
|
-
(record_or_class.is_a?(Class) ? record_or_class : record_or_class.class).model_name
|
133
|
+
(record_or_class.is_a?(Class) ? record_or_class : convert_to_model(record_or_class).class).model_name
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.convert_to_model(object)
|
137
|
+
object.respond_to?(:to_model) ? object.to_model : object
|
96
138
|
end
|
97
139
|
end
|
98
140
|
|