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.
Files changed (36) hide show
  1. data/CHANGELOG +17 -73
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +5 -5
  4. data/lib/active_model.rb +2 -2
  5. data/lib/active_model/attribute_methods.rb +46 -53
  6. data/lib/active_model/callbacks.rb +2 -5
  7. data/lib/active_model/conversion.rb +0 -2
  8. data/lib/active_model/dirty.rb +3 -4
  9. data/lib/active_model/errors.rb +55 -56
  10. data/lib/active_model/lint.rb +2 -2
  11. data/lib/active_model/mass_assignment_security.rb +96 -47
  12. data/lib/active_model/naming.rb +55 -13
  13. data/lib/active_model/observer_array.rb +104 -0
  14. data/lib/active_model/observing.rb +53 -18
  15. data/lib/active_model/secure_password.rb +67 -0
  16. data/lib/active_model/serialization.rb +4 -11
  17. data/lib/active_model/serializers/json.rb +18 -18
  18. data/lib/active_model/serializers/xml.rb +26 -5
  19. data/lib/active_model/translation.rb +4 -11
  20. data/lib/active_model/validations.rb +23 -23
  21. data/lib/active_model/validations/acceptance.rb +3 -5
  22. data/lib/active_model/validations/callbacks.rb +5 -19
  23. data/lib/active_model/validations/confirmation.rb +6 -5
  24. data/lib/active_model/validations/exclusion.rb +27 -3
  25. data/lib/active_model/validations/format.rb +38 -12
  26. data/lib/active_model/validations/inclusion.rb +30 -23
  27. data/lib/active_model/validations/length.rb +3 -1
  28. data/lib/active_model/validations/numericality.rb +4 -2
  29. data/lib/active_model/validations/presence.rb +3 -2
  30. data/lib/active_model/validations/validates.rb +23 -9
  31. data/lib/active_model/validations/with.rb +14 -2
  32. data/lib/active_model/validator.rb +16 -18
  33. data/lib/active_model/version.rb +3 -3
  34. metadata +71 -58
  35. checksums.yaml +0 -7
  36. data/lib/active_model/deprecated_error_methods.rb +0 -33
@@ -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 < ActiveSupport::OrderedHash
64
- include DeprecatedErrorMethods
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 = base
77
- super()
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
- alias_method :get, :[]
81
- alias_method :set, :[]=
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
- hash = ActiveSupport::OrderedHash.new
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
- full_messages = []
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
- messages.each {|m| full_messages << m }
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
- messages.each do |m|
254
- full_messages << I18n.t(:"errors.format", options.merge(:message => m))
255
- end
260
+ I18n.t(:"errors.format", {
261
+ :default => "%{attribute} %{message}",
262
+ :attribute => attr_name,
263
+ :message => message
264
+ })
256
265
  end
257
- end
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 +message+ for the <tt>title</tt> +attribute+,
281
+ # the <tt>:blank</tt> error message for the <tt>title</tt> attribute,
275
282
  # it looks for these translations:
276
283
  #
277
- # <ol>
278
- # <li><tt>activemodel.errors.models.admin.attributes.title.blank</tt></li>
279
- # <li><tt>activemodel.errors.models.admin.blank</tt></li>
280
- # <li><tt>activemodel.errors.models.user.attributes.title.blank</tt></li>
281
- # <li><tt>activemodel.errors.models.user.blank</tt></li>
282
- # <li>any default you provided through the +options+ hash (in the activemodel.errors scope)</li>
283
- # <li><tt>activemodel.errors.messages.blank</tt></li>
284
- # <li><tt>errors.attributes.title.blank</tt></li>
285
- # <li><tt>errors.messages.blank</tt></li>
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}" ]
@@ -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
- # class AccountsController < ApplicationController
24
- # include ActiveModel::MassAssignmentSecurity
23
+ # class AccountsController < ApplicationController
24
+ # include ActiveModel::MassAssignmentSecurity
25
25
  #
26
- # attr_accessible :first_name, :last_name
26
+ # attr_accessible :first_name, :last_name
27
+ # attr_accessible :first_name, :last_name, :plan_id, :as => :admin
27
28
  #
28
- # def self.admin_accessible_attributes
29
- # accessible_attributes + [ :plan_id ]
30
- # end
31
- #
32
- # def update
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
- # protected
35
+ # protected
39
36
  #
40
- # def account_params
41
- # sanitize_for_mass_assignment(params[:account])
42
- # end
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
- # def attributes=(values)
68
- # sanitize_for_mass_assignment(values).each do |k, v|
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.attributes = { "name" => "David", "credit_rating" => "Excellent" }
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(*names)
88
- self._protected_attributes = self.protected_attributes + names
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 attributes=(values)
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.attributes = { :name => "David", :credit_rating => "Excellent" }
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(*names)
127
- self._accessible_attributes = self.accessible_attributes + names
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
- self._protected_attributes ||= BlackList.new(attributes_protected_by_default).tap do |w|
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
- self._accessible_attributes ||= WhiteList.new.tap { |w| w.logger = self.logger if self.respond_to?(:logger) }
167
+ def accessible_attributes(scope = :default)
168
+ accessible_attributes_configs[scope]
139
169
  end
140
170
 
141
- def active_authorizer
142
- self._active_authorizer ||= protected_attributes
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
@@ -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 = ActiveSupport::Inflector.underscore(self).tr('/', '_').freeze
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
- @i18n_key = ActiveSupport::Inflector.underscore(self).tr('/', '.').to_sym
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
- [klass.model_name.i18n_key,
32
- klass.model_name.i18n_key.to_s.tr('.', '/').to_sym]
33
- end.flatten
37
+ klass.model_name.i18n_key
38
+ end
34
39
 
35
- defaults << options.delete(:default) if options[:default]
40
+ defaults << options[:default] if options[:default]
36
41
  defaults << @human
37
42
 
38
- options.reverse_merge! :scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults
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. So either extending the provided
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 ||= ActiveModel::Name.new(self)
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