activemodel 6.0.0

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 +172 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +266 -0
  5. data/lib/active_model.rb +77 -0
  6. data/lib/active_model/attribute.rb +247 -0
  7. data/lib/active_model/attribute/user_provided_default.rb +51 -0
  8. data/lib/active_model/attribute_assignment.rb +57 -0
  9. data/lib/active_model/attribute_methods.rb +517 -0
  10. data/lib/active_model/attribute_mutation_tracker.rb +178 -0
  11. data/lib/active_model/attribute_set.rb +106 -0
  12. data/lib/active_model/attribute_set/builder.rb +124 -0
  13. data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
  14. data/lib/active_model/attributes.rb +138 -0
  15. data/lib/active_model/callbacks.rb +156 -0
  16. data/lib/active_model/conversion.rb +111 -0
  17. data/lib/active_model/dirty.rb +280 -0
  18. data/lib/active_model/errors.rb +601 -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 +334 -0
  25. data/lib/active_model/railtie.rb +20 -0
  26. data/lib/active_model/secure_password.rb +128 -0
  27. data/lib/active_model/serialization.rb +192 -0
  28. data/lib/active_model/serializers/json.rb +147 -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 +47 -0
  34. data/lib/active_model/type/date.rb +53 -0
  35. data/lib/active_model/type/date_time.rb +47 -0
  36. data/lib/active_model/type/decimal.rb +70 -0
  37. data/lib/active_model/type/float.rb +34 -0
  38. data/lib/active_model/type/helpers.rb +7 -0
  39. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +45 -0
  40. data/lib/active_model/type/helpers/mutable.rb +20 -0
  41. data/lib/active_model/type/helpers/numeric.rb +44 -0
  42. data/lib/active_model/type/helpers/time_value.rb +81 -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 +58 -0
  46. data/lib/active_model/type/registry.rb +62 -0
  47. data/lib/active_model/type/string.rb +26 -0
  48. data/lib/active_model/type/time.rb +47 -0
  49. data/lib/active_model/type/value.rb +126 -0
  50. data/lib/active_model/validations.rb +437 -0
  51. data/lib/active_model/validations/absence.rb +33 -0
  52. data/lib/active_model/validations/acceptance.rb +102 -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 = 6
11
+ MINOR = 0
12
+ TINY = 0
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,334 @@
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: match?
115
+ #
116
+ # :call-seq:
117
+ # match?(regexp)
118
+ #
119
+ # Equivalent to <tt>String#match?</tt>. Match the class name against the
120
+ # given regexp. Returns +true+ if there is a match, otherwise +false+.
121
+ #
122
+ # class BlogPost
123
+ # extend ActiveModel::Naming
124
+ # end
125
+ #
126
+ # BlogPost.model_name.match?(/Post/) # => true
127
+ # BlogPost.model_name.match?(/\d/) # => false
128
+
129
+ ##
130
+ # :method: to_s
131
+ #
132
+ # :call-seq:
133
+ # to_s()
134
+ #
135
+ # Returns the class name.
136
+ #
137
+ # class BlogPost
138
+ # extend ActiveModel::Naming
139
+ # end
140
+ #
141
+ # BlogPost.model_name.to_s # => "BlogPost"
142
+
143
+ ##
144
+ # :method: to_str
145
+ #
146
+ # :call-seq:
147
+ # to_str()
148
+ #
149
+ # Equivalent to +to_s+.
150
+ delegate :==, :===, :<=>, :=~, :"!~", :eql?, :match?, :to_s,
151
+ :to_str, :as_json, to: :name
152
+
153
+ # Returns a new ActiveModel::Name instance. By default, the +namespace+
154
+ # and +name+ option will take the namespace and name of the given class
155
+ # respectively.
156
+ #
157
+ # module Foo
158
+ # class Bar
159
+ # end
160
+ # end
161
+ #
162
+ # ActiveModel::Name.new(Foo::Bar).to_s
163
+ # # => "Foo::Bar"
164
+ def initialize(klass, namespace = nil, name = nil)
165
+ @name = name || klass.name
166
+
167
+ raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if @name.blank?
168
+
169
+ @unnamespaced = @name.sub(/^#{namespace.name}::/, "") if namespace
170
+ @klass = klass
171
+ @singular = _singularize(@name)
172
+ @plural = ActiveSupport::Inflector.pluralize(@singular)
173
+ @element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(@name))
174
+ @human = ActiveSupport::Inflector.humanize(@element)
175
+ @collection = ActiveSupport::Inflector.tableize(@name)
176
+ @param_key = (namespace ? _singularize(@unnamespaced) : @singular)
177
+ @i18n_key = @name.underscore.to_sym
178
+
179
+ @route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural.dup)
180
+ @singular_route_key = ActiveSupport::Inflector.singularize(@route_key)
181
+ @route_key << "_index" if @plural == @singular
182
+ end
183
+
184
+ # Transform the model name into a more human format, using I18n. By default,
185
+ # it will underscore then humanize the class name.
186
+ #
187
+ # class BlogPost
188
+ # extend ActiveModel::Naming
189
+ # end
190
+ #
191
+ # BlogPost.model_name.human # => "Blog post"
192
+ #
193
+ # Specify +options+ with additional translating options.
194
+ def human(options = {})
195
+ return @human unless @klass.respond_to?(:lookup_ancestors) &&
196
+ @klass.respond_to?(:i18n_scope)
197
+
198
+ defaults = @klass.lookup_ancestors.map do |klass|
199
+ klass.model_name.i18n_key
200
+ end
201
+
202
+ defaults << options[:default] if options[:default]
203
+ defaults << @human
204
+
205
+ options = { scope: [@klass.i18n_scope, :models], count: 1, default: defaults }.merge!(options.except(:default))
206
+ I18n.translate(defaults.shift, options)
207
+ end
208
+
209
+ private
210
+
211
+ def _singularize(string)
212
+ ActiveSupport::Inflector.underscore(string).tr("/", "_")
213
+ end
214
+ end
215
+
216
+ # == Active \Model \Naming
217
+ #
218
+ # Creates a +model_name+ method on your object.
219
+ #
220
+ # To implement, just extend ActiveModel::Naming in your object:
221
+ #
222
+ # class BookCover
223
+ # extend ActiveModel::Naming
224
+ # end
225
+ #
226
+ # BookCover.model_name.name # => "BookCover"
227
+ # BookCover.model_name.human # => "Book cover"
228
+ #
229
+ # BookCover.model_name.i18n_key # => :book_cover
230
+ # BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover"
231
+ #
232
+ # Providing the functionality that ActiveModel::Naming provides in your object
233
+ # is required to pass the \Active \Model Lint test. So either extending the
234
+ # provided method below, or rolling your own is required.
235
+ module Naming
236
+ def self.extended(base) #:nodoc:
237
+ base.silence_redefinition_of_method :model_name
238
+ base.delegate :model_name, to: :class
239
+ end
240
+
241
+ # Returns an ActiveModel::Name object for module. It can be
242
+ # used to retrieve all kinds of naming-related information
243
+ # (See ActiveModel::Name for more information).
244
+ #
245
+ # class Person
246
+ # extend ActiveModel::Naming
247
+ # end
248
+ #
249
+ # Person.model_name.name # => "Person"
250
+ # Person.model_name.class # => ActiveModel::Name
251
+ # Person.model_name.singular # => "person"
252
+ # Person.model_name.plural # => "people"
253
+ def model_name
254
+ @_model_name ||= begin
255
+ namespace = module_parents.detect do |n|
256
+ n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
257
+ end
258
+ ActiveModel::Name.new(self, namespace)
259
+ end
260
+ end
261
+
262
+ # Returns the plural class name of a record or class.
263
+ #
264
+ # ActiveModel::Naming.plural(post) # => "posts"
265
+ # ActiveModel::Naming.plural(Highrise::Person) # => "highrise_people"
266
+ def self.plural(record_or_class)
267
+ model_name_from_record_or_class(record_or_class).plural
268
+ end
269
+
270
+ # Returns the singular class name of a record or class.
271
+ #
272
+ # ActiveModel::Naming.singular(post) # => "post"
273
+ # ActiveModel::Naming.singular(Highrise::Person) # => "highrise_person"
274
+ def self.singular(record_or_class)
275
+ model_name_from_record_or_class(record_or_class).singular
276
+ end
277
+
278
+ # Identifies whether the class name of a record or class is uncountable.
279
+ #
280
+ # ActiveModel::Naming.uncountable?(Sheep) # => true
281
+ # ActiveModel::Naming.uncountable?(Post) # => false
282
+ def self.uncountable?(record_or_class)
283
+ plural(record_or_class) == singular(record_or_class)
284
+ end
285
+
286
+ # Returns string to use while generating route names. It differs for
287
+ # namespaced models regarding whether it's inside isolated engine.
288
+ #
289
+ # # For isolated engine:
290
+ # ActiveModel::Naming.singular_route_key(Blog::Post) # => "post"
291
+ #
292
+ # # For shared engine:
293
+ # ActiveModel::Naming.singular_route_key(Blog::Post) # => "blog_post"
294
+ def self.singular_route_key(record_or_class)
295
+ model_name_from_record_or_class(record_or_class).singular_route_key
296
+ end
297
+
298
+ # Returns string to use while generating route names. It differs for
299
+ # namespaced models regarding whether it's inside isolated engine.
300
+ #
301
+ # # For isolated engine:
302
+ # ActiveModel::Naming.route_key(Blog::Post) # => "posts"
303
+ #
304
+ # # For shared engine:
305
+ # ActiveModel::Naming.route_key(Blog::Post) # => "blog_posts"
306
+ #
307
+ # The route key also considers if the noun is uncountable and, in
308
+ # such cases, automatically appends _index.
309
+ def self.route_key(record_or_class)
310
+ model_name_from_record_or_class(record_or_class).route_key
311
+ end
312
+
313
+ # Returns string to use for params names. It differs for
314
+ # namespaced models regarding whether it's inside isolated engine.
315
+ #
316
+ # # For isolated engine:
317
+ # ActiveModel::Naming.param_key(Blog::Post) # => "post"
318
+ #
319
+ # # For shared engine:
320
+ # ActiveModel::Naming.param_key(Blog::Post) # => "blog_post"
321
+ def self.param_key(record_or_class)
322
+ model_name_from_record_or_class(record_or_class).param_key
323
+ end
324
+
325
+ def self.model_name_from_record_or_class(record_or_class) #:nodoc:
326
+ if record_or_class.respond_to?(:to_model)
327
+ record_or_class.to_model.model_name
328
+ else
329
+ record_or_class.model_name
330
+ end
331
+ end
332
+ private_class_method :model_name_from_record_or_class
333
+ end
334
+ end