activemodel 3.2.22.5 → 4.0.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +85 -64
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +61 -24
  5. data/lib/active_model.rb +21 -11
  6. data/lib/active_model/attribute_methods.rb +150 -125
  7. data/lib/active_model/callbacks.rb +49 -34
  8. data/lib/active_model/conversion.rb +39 -19
  9. data/lib/active_model/deprecated_mass_assignment_security.rb +21 -0
  10. data/lib/active_model/dirty.rb +48 -32
  11. data/lib/active_model/errors.rb +176 -88
  12. data/lib/active_model/forbidden_attributes_protection.rb +27 -0
  13. data/lib/active_model/lint.rb +42 -55
  14. data/lib/active_model/locale/en.yml +3 -1
  15. data/lib/active_model/model.rb +97 -0
  16. data/lib/active_model/naming.rb +191 -51
  17. data/lib/active_model/railtie.rb +11 -1
  18. data/lib/active_model/secure_password.rb +55 -25
  19. data/lib/active_model/serialization.rb +51 -27
  20. data/lib/active_model/serializers/json.rb +83 -46
  21. data/lib/active_model/serializers/xml.rb +46 -12
  22. data/lib/active_model/test_case.rb +0 -12
  23. data/lib/active_model/translation.rb +9 -10
  24. data/lib/active_model/validations.rb +154 -52
  25. data/lib/active_model/validations/absence.rb +31 -0
  26. data/lib/active_model/validations/acceptance.rb +10 -22
  27. data/lib/active_model/validations/callbacks.rb +78 -25
  28. data/lib/active_model/validations/clusivity.rb +41 -0
  29. data/lib/active_model/validations/confirmation.rb +13 -23
  30. data/lib/active_model/validations/exclusion.rb +26 -55
  31. data/lib/active_model/validations/format.rb +44 -34
  32. data/lib/active_model/validations/inclusion.rb +22 -52
  33. data/lib/active_model/validations/length.rb +48 -49
  34. data/lib/active_model/validations/numericality.rb +30 -32
  35. data/lib/active_model/validations/presence.rb +12 -22
  36. data/lib/active_model/validations/validates.rb +68 -36
  37. data/lib/active_model/validations/with.rb +28 -23
  38. data/lib/active_model/validator.rb +22 -22
  39. data/lib/active_model/version.rb +4 -4
  40. metadata +23 -24
  41. data/lib/active_model/mass_assignment_security.rb +0 -237
  42. data/lib/active_model/mass_assignment_security/permission_set.rb +0 -40
  43. data/lib/active_model/mass_assignment_security/sanitizer.rb +0 -59
  44. data/lib/active_model/observer_array.rb +0 -147
  45. data/lib/active_model/observing.rb +0 -252
@@ -1,25 +1,31 @@
1
1
  module ActiveModel
2
2
  module Lint
3
- # == Active Model Lint Tests
3
+ # == Active \Model \Lint \Tests
4
4
  #
5
- # You can test whether an object is compliant with the Active Model API by
6
- # including <tt>ActiveModel::Lint::Tests</tt> in your TestCase. It will include
7
- # tests that tell you whether your object is fully compliant, or if not,
8
- # which aspects of the API are not implemented.
5
+ # You can test whether an object is compliant with the Active \Model API by
6
+ # including <tt>ActiveModel::Lint::Tests</tt> in your TestCase. It will
7
+ # include tests that tell you whether your object is fully compliant,
8
+ # or if not, which aspects of the API are not implemented.
9
+ #
10
+ # Note an object is not required to implement all APIs in order to work
11
+ # with Action Pack. This module only intends to provide guidance in case
12
+ # you want all features out of the box.
9
13
  #
10
14
  # These tests do not attempt to determine the semantic correctness of the
11
- # returned values. For instance, you could implement valid? to always
12
- # return true, and the tests would pass. It is up to you to ensure that
13
- # the values are semantically meaningful.
15
+ # returned values. For instance, you could implement <tt>valid?</tt> to
16
+ # always return true, and the tests would pass. It is up to you to ensure
17
+ # that the values are semantically meaningful.
14
18
  #
15
- # Objects you pass in are expected to return a compliant object from a
16
- # call to to_model. It is perfectly fine for to_model to return self.
19
+ # Objects you pass in are expected to return a compliant object from a call
20
+ # to <tt>to_model</tt>. It is perfectly fine for <tt>to_model</tt> to return
21
+ # +self+.
17
22
  module Tests
18
23
 
19
24
  # == Responds to <tt>to_key</tt>
20
25
  #
21
26
  # Returns an Enumerable of all (primary) key attributes
22
- # or nil if model.persisted? is false
27
+ # or nil if <tt>model.persisted?</tt> is false. This is used by
28
+ # <tt>dom_id</tt> to generate unique ids for the object.
23
29
  def test_to_key
24
30
  assert model.respond_to?(:to_key), "The model should respond to to_key"
25
31
  def model.persisted?() false end
@@ -29,13 +35,14 @@ module ActiveModel
29
35
  # == Responds to <tt>to_param</tt>
30
36
  #
31
37
  # Returns a string representing the object's key suitable for use in URLs
32
- # or nil if model.persisted? is false.
38
+ # or +nil+ if <tt>model.persisted?</tt> is +false+.
33
39
  #
34
- # Implementers can decide to either raise an exception or provide a default
35
- # in case the record uses a composite primary key. There are no tests for this
36
- # behavior in lint because it doesn't make sense to force any of the possible
37
- # implementation strategies on the implementer. However, if the resource is
38
- # not persisted?, then to_param should always return nil.
40
+ # Implementers can decide to either raise an exception or provide a
41
+ # default in case the record uses a composite primary key. There are no
42
+ # tests for this behavior in lint because it doesn't make sense to force
43
+ # any of the possible implementation strategies on the implementer.
44
+ # However, if the resource is not persisted?, then <tt>to_param</tt>
45
+ # should always return +nil+.
39
46
  def test_to_param
40
47
  assert model.respond_to?(:to_param), "The model should respond to to_param"
41
48
  def model.to_key() [1] end
@@ -45,70 +52,50 @@ module ActiveModel
45
52
 
46
53
  # == Responds to <tt>to_partial_path</tt>
47
54
  #
48
- # Returns a string giving a relative path. This is used for looking up
55
+ # Returns a string giving a relative path. This is used for looking up
49
56
  # partials. For example, a BlogPost model might return "blog_posts/blog_post"
50
- #
51
57
  def test_to_partial_path
52
58
  assert model.respond_to?(:to_partial_path), "The model should respond to to_partial_path"
53
59
  assert_kind_of String, model.to_partial_path
54
60
  end
55
61
 
56
- # == Responds to <tt>valid?</tt>
57
- #
58
- # Returns a boolean that specifies whether the object is in a valid or invalid
59
- # state.
60
- def test_valid?
61
- assert model.respond_to?(:valid?), "The model should respond to valid?"
62
- assert_boolean model.valid?, "valid?"
63
- end
64
-
65
62
  # == Responds to <tt>persisted?</tt>
66
63
  #
67
- # Returns a boolean that specifies whether the object has been persisted yet.
68
- # This is used when calculating the URL for an object. If the object is
69
- # not persisted, a form for that object, for instance, will be POSTed to the
70
- # collection. If it is persisted, a form for the object will be PUT to the
71
- # URL for the object.
64
+ # Returns a boolean that specifies whether the object has been persisted
65
+ # yet. This is used when calculating the URL for an object. If the object
66
+ # is not persisted, a form for that object, for instance, will route to
67
+ # the create action. If it is persisted, a form for the object will routes
68
+ # to the update action.
72
69
  def test_persisted?
73
70
  assert model.respond_to?(:persisted?), "The model should respond to persisted?"
74
71
  assert_boolean model.persisted?, "persisted?"
75
72
  end
76
73
 
77
- # == Naming
74
+ # == \Naming
78
75
  #
79
76
  # Model.model_name must return a string with some convenience methods:
80
- # :human, :singular, and :plural. Check ActiveModel::Naming for more information.
81
- #
77
+ # <tt>:human</tt>, <tt>:singular</tt> and <tt>:plural</tt>. Check
78
+ # ActiveModel::Naming for more information.
82
79
  def test_model_naming
83
80
  assert model.class.respond_to?(:model_name), "The model should respond to model_name"
84
81
  model_name = model.class.model_name
85
- assert_kind_of String, model_name
86
- assert_kind_of String, model_name.human
87
- assert_kind_of String, model_name.singular
88
- assert_kind_of String, model_name.plural
82
+ assert model_name.respond_to?(:to_str)
83
+ assert model_name.human.respond_to?(:to_str)
84
+ assert model_name.singular.respond_to?(:to_str)
85
+ assert model_name.plural.respond_to?(:to_str)
89
86
  end
90
87
 
91
- # == Errors Testing
92
- #
93
- # Returns an object that has :[] and :full_messages defined on it. See below
94
- # for more details.
88
+ # == \Errors Testing
95
89
  #
96
- # Returns an Array of Strings that are the errors for the attribute in
97
- # question. If localization is used, the Strings should be localized
98
- # for the current locale. If no error is present, this method should
99
- # return an empty Array.
90
+ # Returns an object that implements [](attribute) defined which returns an
91
+ # Array of Strings that are the errors for the attribute in question.
92
+ # If localization is used, the Strings should be localized for the current
93
+ # locale. If no error is present, this method should return an empty Array.
100
94
  def test_errors_aref
101
95
  assert model.respond_to?(:errors), "The model should respond to errors"
102
96
  assert model.errors[:hello].is_a?(Array), "errors#[] should return an Array"
103
97
  end
104
98
 
105
- # Returns an Array of all error messages for the object. Each message
106
- # should contain information about the field, if applicable.
107
- def test_errors_full_messages
108
- assert model.respond_to?(:errors), "The model should respond to errors"
109
- assert model.errors.full_messages.is_a?(Array), "errors#full_messages should return an Array"
110
- end
111
-
112
99
  private
113
100
  def model
114
101
  assert @model.respond_to?(:to_model), "The object should respond_to to_model"
@@ -9,10 +9,11 @@ en:
9
9
  inclusion: "is not included in the list"
10
10
  exclusion: "is reserved"
11
11
  invalid: "is invalid"
12
- confirmation: "doesn't match confirmation"
12
+ confirmation: "doesn't match %{attribute}"
13
13
  accepted: "must be accepted"
14
14
  empty: "can't be empty"
15
15
  blank: "can't be blank"
16
+ present: "must be blank"
16
17
  too_long: "is too long (maximum is %{count} characters)"
17
18
  too_short: "is too short (minimum is %{count} characters)"
18
19
  wrong_length: "is the wrong length (should be %{count} characters)"
@@ -23,5 +24,6 @@ en:
23
24
  equal_to: "must be equal to %{count}"
24
25
  less_than: "must be less than %{count}"
25
26
  less_than_or_equal_to: "must be less than or equal to %{count}"
27
+ other_than: "must be other than %{count}"
26
28
  odd: "must be odd"
27
29
  even: "must be even"
@@ -0,0 +1,97 @@
1
+ module ActiveModel
2
+
3
+ # == Active \Model Basic \Model
4
+ #
5
+ # Includes the required interface for an object to interact with
6
+ # <tt>ActionPack</tt>, using different <tt>ActiveModel</tt> modules.
7
+ # It includes model name introspections, conversions, translations and
8
+ # validations. Besides that, it allows you to initialize the object with a
9
+ # hash of attributes, pretty much like <tt>ActiveRecord</tt> does.
10
+ #
11
+ # A minimal implementation could be:
12
+ #
13
+ # class Person
14
+ # include ActiveModel::Model
15
+ # attr_accessor :name, :age
16
+ # end
17
+ #
18
+ # person = Person.new(name: 'bob', age: '18')
19
+ # person.name # => 'bob'
20
+ # person.age # => 18
21
+ #
22
+ # Note that, by default, <tt>ActiveModel::Model</tt> implements <tt>persisted?</tt>
23
+ # to return +false+, which is the most common case. You may want to override
24
+ # it in your class to simulate a different scenario:
25
+ #
26
+ # class Person
27
+ # include ActiveModel::Model
28
+ # attr_accessor :id, :name
29
+ #
30
+ # def persisted?
31
+ # self.id == 1
32
+ # end
33
+ # end
34
+ #
35
+ # person = Person.new(id: 1, name: 'bob')
36
+ # person.persisted? # => true
37
+ #
38
+ # Also, if for some reason you need to run code on <tt>initialize</tt>, make
39
+ # sure you call +super+ if you want the attributes hash initialization to
40
+ # happen.
41
+ #
42
+ # class Person
43
+ # include ActiveModel::Model
44
+ # attr_accessor :id, :name, :omg
45
+ #
46
+ # def initialize(attributes={})
47
+ # super
48
+ # @omg ||= true
49
+ # end
50
+ # end
51
+ #
52
+ # person = Person.new(id: 1, name: 'bob')
53
+ # person.omg # => true
54
+ #
55
+ # For more detailed information on other functionalities available, please
56
+ # refer to the specific modules included in <tt>ActiveModel::Model</tt>
57
+ # (see below).
58
+ module Model
59
+ def self.included(base) #:nodoc:
60
+ base.class_eval do
61
+ extend ActiveModel::Naming
62
+ extend ActiveModel::Translation
63
+ include ActiveModel::Validations
64
+ include ActiveModel::Conversion
65
+ end
66
+ end
67
+
68
+ # Initializes a new model with the given +params+.
69
+ #
70
+ # class Person
71
+ # include ActiveModel::Model
72
+ # attr_accessor :name, :age
73
+ # end
74
+ #
75
+ # person = Person.new(name: 'bob', age: '18')
76
+ # person.name # => "bob"
77
+ # person.age # => 18
78
+ def initialize(params={})
79
+ params.each do |attr, value|
80
+ self.public_send("#{attr}=", value)
81
+ end if params
82
+ end
83
+
84
+ # Indicates if the model is persisted. Default is +false+.
85
+ #
86
+ # class Person
87
+ # include ActiveModel::Model
88
+ # attr_accessor :id, :name
89
+ # end
90
+ #
91
+ # person = Person.new(id: 1, name: 'bob')
92
+ # person.persisted? # => false
93
+ def persisted?
94
+ false
95
+ end
96
+ end
97
+ end
@@ -1,43 +1,173 @@
1
- require 'active_support/inflector'
2
1
  require 'active_support/core_ext/hash/except'
3
2
  require 'active_support/core_ext/module/introspection'
4
- require 'active_support/deprecation'
5
3
 
6
4
  module ActiveModel
7
- class Name < String
8
- attr_reader :singular, :plural, :element, :collection, :partial_path,
9
- :singular_route_key, :route_key, :param_key, :i18n_key
5
+ class Name
6
+ include Comparable
7
+
8
+ attr_reader :singular, :plural, :element, :collection,
9
+ :singular_route_key, :route_key, :param_key, :i18n_key,
10
+ :name
10
11
 
11
12
  alias_method :cache_key, :collection
12
13
 
13
- deprecate :partial_path => "ActiveModel::Name#partial_path is deprecated. Call #to_partial_path on model instances directly instead."
14
+ ##
15
+ # :method: ==
16
+ #
17
+ # :call-seq:
18
+ # ==(other)
19
+ #
20
+ # Equivalent to <tt>String#==</tt>. Returns +true+ if the class name and
21
+ # +other+ are equal, otherwise +false+.
22
+ #
23
+ # class BlogPost
24
+ # extend ActiveModel::Naming
25
+ # end
26
+ #
27
+ # BlogPost.model_name == 'BlogPost' # => true
28
+ # BlogPost.model_name == 'Blog Post' # => false
14
29
 
15
- def initialize(klass, namespace = nil, name = nil)
16
- name ||= klass.name
30
+ ##
31
+ # :method: ===
32
+ #
33
+ # :call-seq:
34
+ # ===(other)
35
+ #
36
+ # Equivalent to <tt>#==</tt>.
37
+ #
38
+ # class BlogPost
39
+ # extend ActiveModel::Naming
40
+ # end
41
+ #
42
+ # BlogPost.model_name === 'BlogPost' # => true
43
+ # BlogPost.model_name === 'Blog Post' # => false
17
44
 
18
- raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if name.blank?
45
+ ##
46
+ # :method: <=>
47
+ #
48
+ # :call-seq:
49
+ # ==(other)
50
+ #
51
+ # Equivalent to <tt>String#<=></tt>.
52
+ #
53
+ # class BlogPost
54
+ # extend ActiveModel::Naming
55
+ # end
56
+ #
57
+ # BlogPost.model_name <=> 'BlogPost' # => 0
58
+ # BlogPost.model_name <=> 'Blog' # => 1
59
+ # BlogPost.model_name <=> 'BlogPosts' # => -1
60
+
61
+ ##
62
+ # :method: =~
63
+ #
64
+ # :call-seq:
65
+ # =~(regexp)
66
+ #
67
+ # Equivalent to <tt>String#=~</tt>. Match the class name against the given
68
+ # regexp. Returns the position where the match starts or +nil+ if there is
69
+ # no match.
70
+ #
71
+ # class BlogPost
72
+ # extend ActiveModel::Naming
73
+ # end
74
+ #
75
+ # BlogPost.model_name =~ /Post/ # => 4
76
+ # BlogPost.model_name =~ /\d/ # => nil
19
77
 
20
- super(name)
78
+ ##
79
+ # :method: !~
80
+ #
81
+ # :call-seq:
82
+ # !~(regexp)
83
+ #
84
+ # Equivalent to <tt>String#!~</tt>. Match the class name against the given
85
+ # regexp. Returns +true+ if there is no match, otherwise +false+.
86
+ #
87
+ # class BlogPost
88
+ # extend ActiveModel::Naming
89
+ # end
90
+ #
91
+ # BlogPost.model_name !~ /Post/ # => false
92
+ # BlogPost.model_name !~ /\d/ # => true
21
93
 
22
- @unnamespaced = self.sub(/^#{namespace.name}::/, '') if namespace
94
+ ##
95
+ # :method: eql?
96
+ #
97
+ # :call-seq:
98
+ # eql?(other)
99
+ #
100
+ # Equivalent to <tt>String#eql?</tt>. Returns +true+ if the class name and
101
+ # +other+ have the same length and content, otherwise +false+.
102
+ #
103
+ # class BlogPost
104
+ # extend ActiveModel::Naming
105
+ # end
106
+ #
107
+ # BlogPost.model_name.eql?('BlogPost') # => true
108
+ # BlogPost.model_name.eql?('Blog Post') # => false
109
+
110
+ ##
111
+ # :method: to_s
112
+ #
113
+ # :call-seq:
114
+ # to_s()
115
+ #
116
+ # Returns the class name.
117
+ #
118
+ # class BlogPost
119
+ # extend ActiveModel::Naming
120
+ # end
121
+ #
122
+ # BlogPost.model_name.to_s # => "BlogPost"
123
+
124
+ ##
125
+ # :method: to_str
126
+ #
127
+ # :call-seq:
128
+ # to_str()
129
+ #
130
+ # Equivalent to +to_s+.
131
+ delegate :==, :===, :<=>, :=~, :"!~", :eql?, :to_s,
132
+ :to_str, :to => :name
133
+
134
+ # Returns a new ActiveModel::Name instance. By default, the +namespace+
135
+ # and +name+ option will take the namespace and name of the given class
136
+ # respectively.
137
+ #
138
+ # module Foo
139
+ # class Bar
140
+ # end
141
+ # end
142
+ #
143
+ # ActiveModel::Name.new(Foo::Bar).to_s
144
+ # # => "Foo::Bar"
145
+ def initialize(klass, namespace = nil, name = nil)
146
+ @name = name || klass.name
147
+
148
+ raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if @name.blank?
149
+
150
+ @unnamespaced = @name.sub(/^#{namespace.name}::/, '') if namespace
23
151
  @klass = klass
24
- @singular = _singularize(self).freeze
25
- @plural = ActiveSupport::Inflector.pluralize(@singular).freeze
26
- @element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self)).freeze
27
- @human = ActiveSupport::Inflector.humanize(@element).freeze
28
- @collection = ActiveSupport::Inflector.tableize(self).freeze
29
- @partial_path = "#{@collection}/#{@element}".freeze
30
- @param_key = (namespace ? _singularize(@unnamespaced) : @singular).freeze
31
- @i18n_key = self.underscore.to_sym
152
+ @singular = _singularize(@name)
153
+ @plural = ActiveSupport::Inflector.pluralize(@singular)
154
+ @element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(@name))
155
+ @human = ActiveSupport::Inflector.humanize(@element)
156
+ @collection = ActiveSupport::Inflector.tableize(@name)
157
+ @param_key = (namespace ? _singularize(@unnamespaced) : @singular)
158
+ @i18n_key = @name.underscore.to_sym
32
159
 
33
160
  @route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural.dup)
34
- @singular_route_key = ActiveSupport::Inflector.singularize(@route_key).freeze
161
+ @singular_route_key = ActiveSupport::Inflector.singularize(@route_key)
35
162
  @route_key << "_index" if @plural == @singular
36
- @route_key.freeze
37
163
  end
38
164
 
39
165
  # Transform the model name into a more humane format, using I18n. By default,
40
- # it will underscore then humanize the class name
166
+ # it will underscore then humanize the class name.
167
+ #
168
+ # class BlogPost
169
+ # extend ActiveModel::Naming
170
+ # end
41
171
  #
42
172
  # BlogPost.model_name.human # => "Blog post"
43
173
  #
@@ -53,7 +183,7 @@ module ActiveModel
53
183
  defaults << options[:default] if options[:default]
54
184
  defaults << @human
55
185
 
56
- options = {:scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults}.merge(options.except(:default))
186
+ options = { :scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults }.merge!(options.except(:default))
57
187
  I18n.translate(defaults.shift, options)
58
188
  end
59
189
 
@@ -64,7 +194,7 @@ module ActiveModel
64
194
  end
65
195
  end
66
196
 
67
- # == Active Model Naming
197
+ # == Active \Model \Naming
68
198
  #
69
199
  # Creates a +model_name+ method on your object.
70
200
  #
@@ -81,11 +211,20 @@ module ActiveModel
81
211
  # BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover"
82
212
  #
83
213
  # Providing the functionality that ActiveModel::Naming provides in your object
84
- # is required to pass the Active Model Lint test. So either extending the provided
85
- # method below, or rolling your own is required.
214
+ # is required to pass the Active Model Lint test. So either extending the
215
+ # provided method below, or rolling your own is required.
86
216
  module Naming
87
217
  # Returns an ActiveModel::Name object for module. It can be
88
- # used to retrieve all kinds of naming-related information.
218
+ # used to retrieve all kinds of naming-related information
219
+ # (See ActiveModel::Name for more information).
220
+ #
221
+ # class Person < ActiveModel::Model
222
+ # end
223
+ #
224
+ # Person.model_name # => Person
225
+ # Person.model_name.class # => ActiveModel::Name
226
+ # Person.model_name.singular # => "person"
227
+ # Person.model_name.plural # => "people"
89
228
  def model_name
90
229
  @_model_name ||= begin
91
230
  namespace = self.parents.detect do |n|
@@ -95,7 +234,7 @@ module ActiveModel
95
234
  end
96
235
  end
97
236
 
98
- # Returns the plural class name of a record or class. Examples:
237
+ # Returns the plural class name of a record or class.
99
238
  #
100
239
  # ActiveModel::Naming.plural(post) # => "posts"
101
240
  # ActiveModel::Naming.plural(Highrise::Person) # => "highrise_people"
@@ -103,7 +242,7 @@ module ActiveModel
103
242
  model_name_from_record_or_class(record_or_class).plural
104
243
  end
105
244
 
106
- # Returns the singular class name of a record or class. Examples:
245
+ # Returns the singular class name of a record or class.
107
246
  #
108
247
  # ActiveModel::Naming.singular(post) # => "post"
109
248
  # ActiveModel::Naming.singular(Highrise::Person) # => "highrise_person"
@@ -111,10 +250,10 @@ module ActiveModel
111
250
  model_name_from_record_or_class(record_or_class).singular
112
251
  end
113
252
 
114
- # Identifies whether the class name of a record or class is uncountable. Examples:
253
+ # Identifies whether the class name of a record or class is uncountable.
115
254
  #
116
255
  # ActiveModel::Naming.uncountable?(Sheep) # => true
117
- # ActiveModel::Naming.uncountable?(Post) => false
256
+ # ActiveModel::Naming.uncountable?(Post) # => false
118
257
  def self.uncountable?(record_or_class)
119
258
  plural(record_or_class) == singular(record_or_class)
120
259
  end
@@ -122,11 +261,11 @@ module ActiveModel
122
261
  # Returns string to use while generating route names. It differs for
123
262
  # namespaced models regarding whether it's inside isolated engine.
124
263
  #
125
- # For isolated engine:
126
- # ActiveModel::Naming.route_key(Blog::Post) #=> post
264
+ # # For isolated engine:
265
+ # ActiveModel::Naming.singular_route_key(Blog::Post) #=> post
127
266
  #
128
- # For shared engine:
129
- # ActiveModel::Naming.route_key(Blog::Post) #=> blog_post
267
+ # # For shared engine:
268
+ # ActiveModel::Naming.singular_route_key(Blog::Post) #=> blog_post
130
269
  def self.singular_route_key(record_or_class)
131
270
  model_name_from_record_or_class(record_or_class).singular_route_key
132
271
  end
@@ -134,11 +273,11 @@ module ActiveModel
134
273
  # Returns string to use while generating route names. It differs for
135
274
  # namespaced models regarding whether it's inside isolated engine.
136
275
  #
137
- # For isolated engine:
138
- # ActiveModel::Naming.route_key(Blog::Post) #=> posts
276
+ # # For isolated engine:
277
+ # ActiveModel::Naming.route_key(Blog::Post) #=> posts
139
278
  #
140
- # For shared engine:
141
- # ActiveModel::Naming.route_key(Blog::Post) #=> blog_posts
279
+ # # For shared engine:
280
+ # ActiveModel::Naming.route_key(Blog::Post) #=> blog_posts
142
281
  #
143
282
  # The route key also considers if the noun is uncountable and, in
144
283
  # such cases, automatically appends _index.
@@ -149,23 +288,24 @@ module ActiveModel
149
288
  # Returns string to use for params names. It differs for
150
289
  # namespaced models regarding whether it's inside isolated engine.
151
290
  #
152
- # For isolated engine:
153
- # ActiveModel::Naming.param_key(Blog::Post) #=> post
291
+ # # For isolated engine:
292
+ # ActiveModel::Naming.param_key(Blog::Post) #=> post
154
293
  #
155
- # For shared engine:
156
- # ActiveModel::Naming.param_key(Blog::Post) #=> blog_post
294
+ # # For shared engine:
295
+ # ActiveModel::Naming.param_key(Blog::Post) #=> blog_post
157
296
  def self.param_key(record_or_class)
158
297
  model_name_from_record_or_class(record_or_class).param_key
159
298
  end
160
299
 
161
- private
162
- def self.model_name_from_record_or_class(record_or_class)
163
- (record_or_class.is_a?(Class) ? record_or_class : convert_to_model(record_or_class).class).model_name
164
- end
165
-
166
- def self.convert_to_model(object)
167
- object.respond_to?(:to_model) ? object.to_model : object
300
+ def self.model_name_from_record_or_class(record_or_class) #:nodoc:
301
+ if record_or_class.respond_to?(:model_name)
302
+ record_or_class.model_name
303
+ elsif record_or_class.respond_to?(:to_model)
304
+ record_or_class.to_model.class.model_name
305
+ else
306
+ record_or_class.class.model_name
168
307
  end
308
+ end
309
+ private_class_method :model_name_from_record_or_class
169
310
  end
170
-
171
311
  end