activemodel 3.0.20 → 3.1.0.beta1

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