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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +114 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +264 -0
- data/lib/active_model.rb +77 -0
- data/lib/active_model/attribute.rb +248 -0
- data/lib/active_model/attribute/user_provided_default.rb +52 -0
- data/lib/active_model/attribute_assignment.rb +57 -0
- data/lib/active_model/attribute_methods.rb +478 -0
- data/lib/active_model/attribute_mutation_tracker.rb +124 -0
- data/lib/active_model/attribute_set.rb +114 -0
- data/lib/active_model/attribute_set/builder.rb +126 -0
- data/lib/active_model/attribute_set/yaml_encoder.rb +41 -0
- data/lib/active_model/attributes.rb +111 -0
- data/lib/active_model/callbacks.rb +153 -0
- data/lib/active_model/conversion.rb +111 -0
- data/lib/active_model/dirty.rb +343 -0
- data/lib/active_model/errors.rb +517 -0
- data/lib/active_model/forbidden_attributes_protection.rb +31 -0
- data/lib/active_model/gem_version.rb +17 -0
- data/lib/active_model/lint.rb +118 -0
- data/lib/active_model/locale/en.yml +36 -0
- data/lib/active_model/model.rb +99 -0
- data/lib/active_model/naming.rb +318 -0
- data/lib/active_model/railtie.rb +14 -0
- data/lib/active_model/secure_password.rb +129 -0
- data/lib/active_model/serialization.rb +192 -0
- data/lib/active_model/serializers/json.rb +146 -0
- data/lib/active_model/translation.rb +70 -0
- data/lib/active_model/type.rb +53 -0
- data/lib/active_model/type/big_integer.rb +15 -0
- data/lib/active_model/type/binary.rb +52 -0
- data/lib/active_model/type/boolean.rb +38 -0
- data/lib/active_model/type/date.rb +57 -0
- data/lib/active_model/type/date_time.rb +51 -0
- data/lib/active_model/type/decimal.rb +70 -0
- data/lib/active_model/type/float.rb +36 -0
- data/lib/active_model/type/helpers.rb +7 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +41 -0
- data/lib/active_model/type/helpers/mutable.rb +20 -0
- data/lib/active_model/type/helpers/numeric.rb +37 -0
- data/lib/active_model/type/helpers/time_value.rb +68 -0
- data/lib/active_model/type/helpers/timezone.rb +19 -0
- data/lib/active_model/type/immutable_string.rb +32 -0
- data/lib/active_model/type/integer.rb +70 -0
- data/lib/active_model/type/registry.rb +70 -0
- data/lib/active_model/type/string.rb +26 -0
- data/lib/active_model/type/time.rb +51 -0
- data/lib/active_model/type/value.rb +126 -0
- data/lib/active_model/validations.rb +439 -0
- data/lib/active_model/validations/absence.rb +33 -0
- data/lib/active_model/validations/acceptance.rb +106 -0
- data/lib/active_model/validations/callbacks.rb +122 -0
- data/lib/active_model/validations/clusivity.rb +54 -0
- data/lib/active_model/validations/confirmation.rb +80 -0
- data/lib/active_model/validations/exclusion.rb +49 -0
- data/lib/active_model/validations/format.rb +114 -0
- data/lib/active_model/validations/helper_methods.rb +15 -0
- data/lib/active_model/validations/inclusion.rb +47 -0
- data/lib/active_model/validations/length.rb +129 -0
- data/lib/active_model/validations/numericality.rb +189 -0
- data/lib/active_model/validations/presence.rb +39 -0
- data/lib/active_model/validations/validates.rb +174 -0
- data/lib/active_model/validations/with.rb +147 -0
- data/lib/active_model/validator.rb +183 -0
- data/lib/active_model/version.rb +10 -0
- 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
|