activemodel 5.2.3

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 (67) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +114 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +264 -0
  5. data/lib/active_model.rb +77 -0
  6. data/lib/active_model/attribute.rb +248 -0
  7. data/lib/active_model/attribute/user_provided_default.rb +52 -0
  8. data/lib/active_model/attribute_assignment.rb +57 -0
  9. data/lib/active_model/attribute_methods.rb +478 -0
  10. data/lib/active_model/attribute_mutation_tracker.rb +124 -0
  11. data/lib/active_model/attribute_set.rb +114 -0
  12. data/lib/active_model/attribute_set/builder.rb +126 -0
  13. data/lib/active_model/attribute_set/yaml_encoder.rb +41 -0
  14. data/lib/active_model/attributes.rb +111 -0
  15. data/lib/active_model/callbacks.rb +153 -0
  16. data/lib/active_model/conversion.rb +111 -0
  17. data/lib/active_model/dirty.rb +343 -0
  18. data/lib/active_model/errors.rb +517 -0
  19. data/lib/active_model/forbidden_attributes_protection.rb +31 -0
  20. data/lib/active_model/gem_version.rb +17 -0
  21. data/lib/active_model/lint.rb +118 -0
  22. data/lib/active_model/locale/en.yml +36 -0
  23. data/lib/active_model/model.rb +99 -0
  24. data/lib/active_model/naming.rb +318 -0
  25. data/lib/active_model/railtie.rb +14 -0
  26. data/lib/active_model/secure_password.rb +129 -0
  27. data/lib/active_model/serialization.rb +192 -0
  28. data/lib/active_model/serializers/json.rb +146 -0
  29. data/lib/active_model/translation.rb +70 -0
  30. data/lib/active_model/type.rb +53 -0
  31. data/lib/active_model/type/big_integer.rb +15 -0
  32. data/lib/active_model/type/binary.rb +52 -0
  33. data/lib/active_model/type/boolean.rb +38 -0
  34. data/lib/active_model/type/date.rb +57 -0
  35. data/lib/active_model/type/date_time.rb +51 -0
  36. data/lib/active_model/type/decimal.rb +70 -0
  37. data/lib/active_model/type/float.rb +36 -0
  38. data/lib/active_model/type/helpers.rb +7 -0
  39. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +41 -0
  40. data/lib/active_model/type/helpers/mutable.rb +20 -0
  41. data/lib/active_model/type/helpers/numeric.rb +37 -0
  42. data/lib/active_model/type/helpers/time_value.rb +68 -0
  43. data/lib/active_model/type/helpers/timezone.rb +19 -0
  44. data/lib/active_model/type/immutable_string.rb +32 -0
  45. data/lib/active_model/type/integer.rb +70 -0
  46. data/lib/active_model/type/registry.rb +70 -0
  47. data/lib/active_model/type/string.rb +26 -0
  48. data/lib/active_model/type/time.rb +51 -0
  49. data/lib/active_model/type/value.rb +126 -0
  50. data/lib/active_model/validations.rb +439 -0
  51. data/lib/active_model/validations/absence.rb +33 -0
  52. data/lib/active_model/validations/acceptance.rb +106 -0
  53. data/lib/active_model/validations/callbacks.rb +122 -0
  54. data/lib/active_model/validations/clusivity.rb +54 -0
  55. data/lib/active_model/validations/confirmation.rb +80 -0
  56. data/lib/active_model/validations/exclusion.rb +49 -0
  57. data/lib/active_model/validations/format.rb +114 -0
  58. data/lib/active_model/validations/helper_methods.rb +15 -0
  59. data/lib/active_model/validations/inclusion.rb +47 -0
  60. data/lib/active_model/validations/length.rb +129 -0
  61. data/lib/active_model/validations/numericality.rb +189 -0
  62. data/lib/active_model/validations/presence.rb +39 -0
  63. data/lib/active_model/validations/validates.rb +174 -0
  64. data/lib/active_model/validations/with.rb +147 -0
  65. data/lib/active_model/validator.rb +183 -0
  66. data/lib/active_model/version.rb +10 -0
  67. metadata +125 -0
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ # Raised when forbidden attributes are used for mass assignment.
5
+ #
6
+ # class Person < ActiveRecord::Base
7
+ # end
8
+ #
9
+ # params = ActionController::Parameters.new(name: 'Bob')
10
+ # Person.new(params)
11
+ # # => ActiveModel::ForbiddenAttributesError
12
+ #
13
+ # params.permit!
14
+ # Person.new(params)
15
+ # # => #<Person id: nil, name: "Bob">
16
+ class ForbiddenAttributesError < StandardError
17
+ end
18
+
19
+ module ForbiddenAttributesProtection # :nodoc:
20
+ private
21
+ def sanitize_for_mass_assignment(attributes)
22
+ if attributes.respond_to?(:permitted?)
23
+ raise ActiveModel::ForbiddenAttributesError if !attributes.permitted?
24
+ attributes.to_h
25
+ else
26
+ attributes
27
+ end
28
+ end
29
+ alias :sanitize_forbidden_attributes :sanitize_for_mass_assignment
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ # Returns the version of the currently loaded \Active \Model as a <tt>Gem::Version</tt>
5
+ def self.gem_version
6
+ Gem::Version.new VERSION::STRING
7
+ end
8
+
9
+ module VERSION
10
+ MAJOR = 5
11
+ MINOR = 2
12
+ TINY = 3
13
+ PRE = nil
14
+
15
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
+ end
17
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Lint
5
+ # == Active \Model \Lint \Tests
6
+ #
7
+ # You can test whether an object is compliant with the Active \Model API by
8
+ # including <tt>ActiveModel::Lint::Tests</tt> in your TestCase. It will
9
+ # include tests that tell you whether your object is fully compliant,
10
+ # or if not, which aspects of the API are not implemented.
11
+ #
12
+ # Note an object is not required to implement all APIs in order to work
13
+ # with Action Pack. This module only intends to provide guidance in case
14
+ # you want all features out of the box.
15
+ #
16
+ # These tests do not attempt to determine the semantic correctness of the
17
+ # returned values. For instance, you could implement <tt>valid?</tt> to
18
+ # always return +true+, and the tests would pass. It is up to you to ensure
19
+ # that the values are semantically meaningful.
20
+ #
21
+ # Objects you pass in are expected to return a compliant object from a call
22
+ # to <tt>to_model</tt>. It is perfectly fine for <tt>to_model</tt> to return
23
+ # +self+.
24
+ module Tests
25
+ # Passes if the object's model responds to <tt>to_key</tt> and if calling
26
+ # this method returns +nil+ when the object is not persisted.
27
+ # Fails otherwise.
28
+ #
29
+ # <tt>to_key</tt> returns an Enumerable of all (primary) key attributes
30
+ # of the model, and is used to a generate unique DOM id for the object.
31
+ def test_to_key
32
+ assert_respond_to model, :to_key
33
+ def model.persisted?() false end
34
+ assert model.to_key.nil?, "to_key should return nil when `persisted?` returns false"
35
+ end
36
+
37
+ # Passes if the object's model responds to <tt>to_param</tt> and if
38
+ # calling this method returns +nil+ when the object is not persisted.
39
+ # Fails otherwise.
40
+ #
41
+ # <tt>to_param</tt> is used to represent the object's key in URLs.
42
+ # Implementers can decide to either raise an exception or provide a
43
+ # default in case the record uses a composite primary key. There are no
44
+ # tests for this behavior in lint because it doesn't make sense to force
45
+ # any of the possible implementation strategies on the implementer.
46
+ def test_to_param
47
+ assert_respond_to model, :to_param
48
+ def model.to_key() [1] end
49
+ def model.persisted?() false end
50
+ assert model.to_param.nil?, "to_param should return nil when `persisted?` returns false"
51
+ end
52
+
53
+ # Passes if the object's model responds to <tt>to_partial_path</tt> and if
54
+ # calling this method returns a string. Fails otherwise.
55
+ #
56
+ # <tt>to_partial_path</tt> is used for looking up partials. For example,
57
+ # a BlogPost model might return "blog_posts/blog_post".
58
+ def test_to_partial_path
59
+ assert_respond_to model, :to_partial_path
60
+ assert_kind_of String, model.to_partial_path
61
+ end
62
+
63
+ # Passes if the object's model responds to <tt>persisted?</tt> and if
64
+ # calling this method returns either +true+ or +false+. Fails otherwise.
65
+ #
66
+ # <tt>persisted?</tt> is used when calculating the URL for an object.
67
+ # If the object is not persisted, a form for that object, for instance,
68
+ # will route to the create action. If it is persisted, a form for the
69
+ # object will route to the update action.
70
+ def test_persisted?
71
+ assert_respond_to model, :persisted?
72
+ assert_boolean model.persisted?, "persisted?"
73
+ end
74
+
75
+ # Passes if the object's model responds to <tt>model_name</tt> both as
76
+ # an instance method and as a class method, and if calling this method
77
+ # returns a string with some convenience methods: <tt>:human</tt>,
78
+ # <tt>:singular</tt> and <tt>:plural</tt>.
79
+ #
80
+ # Check ActiveModel::Naming for more information.
81
+ def test_model_naming
82
+ assert_respond_to model.class, :model_name
83
+ model_name = model.class.model_name
84
+ assert_respond_to model_name, :to_str
85
+ assert_respond_to model_name.human, :to_str
86
+ assert_respond_to model_name.singular, :to_str
87
+ assert_respond_to model_name.plural, :to_str
88
+
89
+ assert_respond_to model, :model_name
90
+ assert_equal model.model_name, model.class.model_name
91
+ end
92
+
93
+ # Passes if the object's model responds to <tt>errors</tt> and if calling
94
+ # <tt>[](attribute)</tt> on the result of this method returns an array.
95
+ # Fails otherwise.
96
+ #
97
+ # <tt>errors[attribute]</tt> is used to retrieve the errors of a model
98
+ # for a given attribute. If errors are present, the method should return
99
+ # an array of strings that are the errors for the attribute in question.
100
+ # If localization is used, the strings should be localized for the current
101
+ # locale. If no error is present, the method should return an empty array.
102
+ def test_errors_aref
103
+ assert_respond_to model, :errors
104
+ assert model.errors[:hello].is_a?(Array), "errors#[] should return an Array"
105
+ end
106
+
107
+ private
108
+ def model
109
+ assert_respond_to @model, :to_model
110
+ @model.to_model
111
+ end
112
+
113
+ def assert_boolean(result, name)
114
+ assert result == true || result == false, "#{name} should be a boolean"
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,36 @@
1
+ en:
2
+ errors:
3
+ # The default format to use in full error messages.
4
+ format: "%{attribute} %{message}"
5
+
6
+ # The values :model, :attribute and :value are always available for interpolation
7
+ # The value :count is available when applicable. Can be used for pluralization.
8
+ messages:
9
+ model_invalid: "Validation failed: %{errors}"
10
+ inclusion: "is not included in the list"
11
+ exclusion: "is reserved"
12
+ invalid: "is invalid"
13
+ confirmation: "doesn't match %{attribute}"
14
+ accepted: "must be accepted"
15
+ empty: "can't be empty"
16
+ blank: "can't be blank"
17
+ present: "must be blank"
18
+ too_long:
19
+ one: "is too long (maximum is 1 character)"
20
+ other: "is too long (maximum is %{count} characters)"
21
+ too_short:
22
+ one: "is too short (minimum is 1 character)"
23
+ other: "is too short (minimum is %{count} characters)"
24
+ wrong_length:
25
+ one: "is the wrong length (should be 1 character)"
26
+ other: "is the wrong length (should be %{count} characters)"
27
+ not_a_number: "is not a number"
28
+ not_an_integer: "must be an integer"
29
+ greater_than: "must be greater than %{count}"
30
+ greater_than_or_equal_to: "must be greater than or equal to %{count}"
31
+ equal_to: "must be equal to %{count}"
32
+ less_than: "must be less than %{count}"
33
+ less_than_or_equal_to: "must be less than or equal to %{count}"
34
+ other_than: "must be other than %{count}"
35
+ odd: "must be odd"
36
+ even: "must be even"
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ # == Active \Model \Basic \Model
5
+ #
6
+ # Includes the required interface for an object to interact with
7
+ # Action Pack and Action View, using different Active Model modules.
8
+ # It includes model name introspections, conversions, translations and
9
+ # validations. Besides that, it allows you to initialize the object with a
10
+ # hash of attributes, pretty much like Active Record does.
11
+ #
12
+ # A minimal implementation could be:
13
+ #
14
+ # class Person
15
+ # include ActiveModel::Model
16
+ # attr_accessor :name, :age
17
+ # end
18
+ #
19
+ # person = Person.new(name: 'bob', age: '18')
20
+ # person.name # => "bob"
21
+ # person.age # => "18"
22
+ #
23
+ # Note that, by default, <tt>ActiveModel::Model</tt> implements <tt>persisted?</tt>
24
+ # to return +false+, which is the most common case. You may want to override
25
+ # it in your class to simulate a different scenario:
26
+ #
27
+ # class Person
28
+ # include ActiveModel::Model
29
+ # attr_accessor :id, :name
30
+ #
31
+ # def persisted?
32
+ # self.id == 1
33
+ # end
34
+ # end
35
+ #
36
+ # person = Person.new(id: 1, name: 'bob')
37
+ # person.persisted? # => true
38
+ #
39
+ # Also, if for some reason you need to run code on <tt>initialize</tt>, make
40
+ # sure you call +super+ if you want the attributes hash initialization to
41
+ # happen.
42
+ #
43
+ # class Person
44
+ # include ActiveModel::Model
45
+ # attr_accessor :id, :name, :omg
46
+ #
47
+ # def initialize(attributes={})
48
+ # super
49
+ # @omg ||= true
50
+ # end
51
+ # end
52
+ #
53
+ # person = Person.new(id: 1, name: 'bob')
54
+ # person.omg # => true
55
+ #
56
+ # For more detailed information on other functionalities available, please
57
+ # refer to the specific modules included in <tt>ActiveModel::Model</tt>
58
+ # (see below).
59
+ module Model
60
+ extend ActiveSupport::Concern
61
+ include ActiveModel::AttributeAssignment
62
+ include ActiveModel::Validations
63
+ include ActiveModel::Conversion
64
+
65
+ included do
66
+ extend ActiveModel::Naming
67
+ extend ActiveModel::Translation
68
+ end
69
+
70
+ # Initializes a new model with the given +params+.
71
+ #
72
+ # class Person
73
+ # include ActiveModel::Model
74
+ # attr_accessor :name, :age
75
+ # end
76
+ #
77
+ # person = Person.new(name: 'bob', age: '18')
78
+ # person.name # => "bob"
79
+ # person.age # => "18"
80
+ def initialize(attributes = {})
81
+ assign_attributes(attributes) if attributes
82
+
83
+ super()
84
+ end
85
+
86
+ # Indicates if the model is persisted. Default is +false+.
87
+ #
88
+ # class Person
89
+ # include ActiveModel::Model
90
+ # attr_accessor :id, :name
91
+ # end
92
+ #
93
+ # person = Person.new(id: 1, name: 'bob')
94
+ # person.persisted? # => false
95
+ def persisted?
96
+ false
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,318 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/except"
4
+ require "active_support/core_ext/module/introspection"
5
+ require "active_support/core_ext/module/redefine_method"
6
+
7
+ module ActiveModel
8
+ class Name
9
+ include Comparable
10
+
11
+ attr_reader :singular, :plural, :element, :collection,
12
+ :singular_route_key, :route_key, :param_key, :i18n_key,
13
+ :name
14
+
15
+ alias_method :cache_key, :collection
16
+
17
+ ##
18
+ # :method: ==
19
+ #
20
+ # :call-seq:
21
+ # ==(other)
22
+ #
23
+ # Equivalent to <tt>String#==</tt>. Returns +true+ if the class name and
24
+ # +other+ are equal, otherwise +false+.
25
+ #
26
+ # class BlogPost
27
+ # extend ActiveModel::Naming
28
+ # end
29
+ #
30
+ # BlogPost.model_name == 'BlogPost' # => true
31
+ # BlogPost.model_name == 'Blog Post' # => false
32
+
33
+ ##
34
+ # :method: ===
35
+ #
36
+ # :call-seq:
37
+ # ===(other)
38
+ #
39
+ # Equivalent to <tt>#==</tt>.
40
+ #
41
+ # class BlogPost
42
+ # extend ActiveModel::Naming
43
+ # end
44
+ #
45
+ # BlogPost.model_name === 'BlogPost' # => true
46
+ # BlogPost.model_name === 'Blog Post' # => false
47
+
48
+ ##
49
+ # :method: <=>
50
+ #
51
+ # :call-seq:
52
+ # <=>(other)
53
+ #
54
+ # Equivalent to <tt>String#<=></tt>.
55
+ #
56
+ # class BlogPost
57
+ # extend ActiveModel::Naming
58
+ # end
59
+ #
60
+ # BlogPost.model_name <=> 'BlogPost' # => 0
61
+ # BlogPost.model_name <=> 'Blog' # => 1
62
+ # BlogPost.model_name <=> 'BlogPosts' # => -1
63
+
64
+ ##
65
+ # :method: =~
66
+ #
67
+ # :call-seq:
68
+ # =~(regexp)
69
+ #
70
+ # Equivalent to <tt>String#=~</tt>. Match the class name against the given
71
+ # regexp. Returns the position where the match starts or +nil+ if there is
72
+ # no match.
73
+ #
74
+ # class BlogPost
75
+ # extend ActiveModel::Naming
76
+ # end
77
+ #
78
+ # BlogPost.model_name =~ /Post/ # => 4
79
+ # BlogPost.model_name =~ /\d/ # => nil
80
+
81
+ ##
82
+ # :method: !~
83
+ #
84
+ # :call-seq:
85
+ # !~(regexp)
86
+ #
87
+ # Equivalent to <tt>String#!~</tt>. Match the class name against the given
88
+ # regexp. Returns +true+ if there is no match, otherwise +false+.
89
+ #
90
+ # class BlogPost
91
+ # extend ActiveModel::Naming
92
+ # end
93
+ #
94
+ # BlogPost.model_name !~ /Post/ # => false
95
+ # BlogPost.model_name !~ /\d/ # => true
96
+
97
+ ##
98
+ # :method: eql?
99
+ #
100
+ # :call-seq:
101
+ # eql?(other)
102
+ #
103
+ # Equivalent to <tt>String#eql?</tt>. Returns +true+ if the class name and
104
+ # +other+ have the same length and content, otherwise +false+.
105
+ #
106
+ # class BlogPost
107
+ # extend ActiveModel::Naming
108
+ # end
109
+ #
110
+ # BlogPost.model_name.eql?('BlogPost') # => true
111
+ # BlogPost.model_name.eql?('Blog Post') # => false
112
+
113
+ ##
114
+ # :method: to_s
115
+ #
116
+ # :call-seq:
117
+ # to_s()
118
+ #
119
+ # Returns the class name.
120
+ #
121
+ # class BlogPost
122
+ # extend ActiveModel::Naming
123
+ # end
124
+ #
125
+ # BlogPost.model_name.to_s # => "BlogPost"
126
+
127
+ ##
128
+ # :method: to_str
129
+ #
130
+ # :call-seq:
131
+ # to_str()
132
+ #
133
+ # Equivalent to +to_s+.
134
+ delegate :==, :===, :<=>, :=~, :"!~", :eql?, :to_s,
135
+ :to_str, :as_json, to: :name
136
+
137
+ # Returns a new ActiveModel::Name instance. By default, the +namespace+
138
+ # and +name+ option will take the namespace and name of the given class
139
+ # respectively.
140
+ #
141
+ # module Foo
142
+ # class Bar
143
+ # end
144
+ # end
145
+ #
146
+ # ActiveModel::Name.new(Foo::Bar).to_s
147
+ # # => "Foo::Bar"
148
+ def initialize(klass, namespace = nil, name = nil)
149
+ @name = name || klass.name
150
+
151
+ raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if @name.blank?
152
+
153
+ @unnamespaced = @name.sub(/^#{namespace.name}::/, "") if namespace
154
+ @klass = klass
155
+ @singular = _singularize(@name)
156
+ @plural = ActiveSupport::Inflector.pluralize(@singular)
157
+ @element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(@name))
158
+ @human = ActiveSupport::Inflector.humanize(@element)
159
+ @collection = ActiveSupport::Inflector.tableize(@name)
160
+ @param_key = (namespace ? _singularize(@unnamespaced) : @singular)
161
+ @i18n_key = @name.underscore.to_sym
162
+
163
+ @route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural.dup)
164
+ @singular_route_key = ActiveSupport::Inflector.singularize(@route_key)
165
+ @route_key << "_index" if @plural == @singular
166
+ end
167
+
168
+ # Transform the model name into a more human format, using I18n. By default,
169
+ # it will underscore then humanize the class name.
170
+ #
171
+ # class BlogPost
172
+ # extend ActiveModel::Naming
173
+ # end
174
+ #
175
+ # BlogPost.model_name.human # => "Blog post"
176
+ #
177
+ # Specify +options+ with additional translating options.
178
+ def human(options = {})
179
+ return @human unless @klass.respond_to?(:lookup_ancestors) &&
180
+ @klass.respond_to?(:i18n_scope)
181
+
182
+ defaults = @klass.lookup_ancestors.map do |klass|
183
+ klass.model_name.i18n_key
184
+ end
185
+
186
+ defaults << options[:default] if options[:default]
187
+ defaults << @human
188
+
189
+ options = { scope: [@klass.i18n_scope, :models], count: 1, default: defaults }.merge!(options.except(:default))
190
+ I18n.translate(defaults.shift, options)
191
+ end
192
+
193
+ private
194
+
195
+ def _singularize(string)
196
+ ActiveSupport::Inflector.underscore(string).tr("/".freeze, "_".freeze)
197
+ end
198
+ end
199
+
200
+ # == Active \Model \Naming
201
+ #
202
+ # Creates a +model_name+ method on your object.
203
+ #
204
+ # To implement, just extend ActiveModel::Naming in your object:
205
+ #
206
+ # class BookCover
207
+ # extend ActiveModel::Naming
208
+ # end
209
+ #
210
+ # BookCover.model_name.name # => "BookCover"
211
+ # BookCover.model_name.human # => "Book cover"
212
+ #
213
+ # BookCover.model_name.i18n_key # => :book_cover
214
+ # BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover"
215
+ #
216
+ # Providing the functionality that ActiveModel::Naming provides in your object
217
+ # is required to pass the \Active \Model Lint test. So either extending the
218
+ # provided method below, or rolling your own is required.
219
+ module Naming
220
+ def self.extended(base) #:nodoc:
221
+ base.silence_redefinition_of_method :model_name
222
+ base.delegate :model_name, to: :class
223
+ end
224
+
225
+ # Returns an ActiveModel::Name object for module. It can be
226
+ # used to retrieve all kinds of naming-related information
227
+ # (See ActiveModel::Name for more information).
228
+ #
229
+ # class Person
230
+ # extend ActiveModel::Naming
231
+ # end
232
+ #
233
+ # Person.model_name.name # => "Person"
234
+ # Person.model_name.class # => ActiveModel::Name
235
+ # Person.model_name.singular # => "person"
236
+ # Person.model_name.plural # => "people"
237
+ def model_name
238
+ @_model_name ||= begin
239
+ namespace = parents.detect do |n|
240
+ n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
241
+ end
242
+ ActiveModel::Name.new(self, namespace)
243
+ end
244
+ end
245
+
246
+ # Returns the plural class name of a record or class.
247
+ #
248
+ # ActiveModel::Naming.plural(post) # => "posts"
249
+ # ActiveModel::Naming.plural(Highrise::Person) # => "highrise_people"
250
+ def self.plural(record_or_class)
251
+ model_name_from_record_or_class(record_or_class).plural
252
+ end
253
+
254
+ # Returns the singular class name of a record or class.
255
+ #
256
+ # ActiveModel::Naming.singular(post) # => "post"
257
+ # ActiveModel::Naming.singular(Highrise::Person) # => "highrise_person"
258
+ def self.singular(record_or_class)
259
+ model_name_from_record_or_class(record_or_class).singular
260
+ end
261
+
262
+ # Identifies whether the class name of a record or class is uncountable.
263
+ #
264
+ # ActiveModel::Naming.uncountable?(Sheep) # => true
265
+ # ActiveModel::Naming.uncountable?(Post) # => false
266
+ def self.uncountable?(record_or_class)
267
+ plural(record_or_class) == singular(record_or_class)
268
+ end
269
+
270
+ # Returns string to use while generating route names. It differs for
271
+ # namespaced models regarding whether it's inside isolated engine.
272
+ #
273
+ # # For isolated engine:
274
+ # ActiveModel::Naming.singular_route_key(Blog::Post) # => "post"
275
+ #
276
+ # # For shared engine:
277
+ # ActiveModel::Naming.singular_route_key(Blog::Post) # => "blog_post"
278
+ def self.singular_route_key(record_or_class)
279
+ model_name_from_record_or_class(record_or_class).singular_route_key
280
+ end
281
+
282
+ # Returns string to use while generating route names. It differs for
283
+ # namespaced models regarding whether it's inside isolated engine.
284
+ #
285
+ # # For isolated engine:
286
+ # ActiveModel::Naming.route_key(Blog::Post) # => "posts"
287
+ #
288
+ # # For shared engine:
289
+ # ActiveModel::Naming.route_key(Blog::Post) # => "blog_posts"
290
+ #
291
+ # The route key also considers if the noun is uncountable and, in
292
+ # such cases, automatically appends _index.
293
+ def self.route_key(record_or_class)
294
+ model_name_from_record_or_class(record_or_class).route_key
295
+ end
296
+
297
+ # Returns string to use for params names. It differs for
298
+ # namespaced models regarding whether it's inside isolated engine.
299
+ #
300
+ # # For isolated engine:
301
+ # ActiveModel::Naming.param_key(Blog::Post) # => "post"
302
+ #
303
+ # # For shared engine:
304
+ # ActiveModel::Naming.param_key(Blog::Post) # => "blog_post"
305
+ def self.param_key(record_or_class)
306
+ model_name_from_record_or_class(record_or_class).param_key
307
+ end
308
+
309
+ def self.model_name_from_record_or_class(record_or_class) #:nodoc:
310
+ if record_or_class.respond_to?(:to_model)
311
+ record_or_class.to_model.model_name
312
+ else
313
+ record_or_class.model_name
314
+ end
315
+ end
316
+ private_class_method :model_name_from_record_or_class
317
+ end
318
+ end