activemodel 4.2.0 → 6.1.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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +49 -37
- data/MIT-LICENSE +1 -1
- data/README.rdoc +16 -22
- data/lib/active_model/attribute/user_provided_default.rb +51 -0
- data/lib/active_model/attribute.rb +248 -0
- data/lib/active_model/attribute_assignment.rb +55 -0
- data/lib/active_model/attribute_methods.rb +150 -73
- data/lib/active_model/attribute_mutation_tracker.rb +181 -0
- data/lib/active_model/attribute_set/builder.rb +191 -0
- data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
- data/lib/active_model/attribute_set.rb +106 -0
- data/lib/active_model/attributes.rb +132 -0
- data/lib/active_model/callbacks.rb +31 -25
- data/lib/active_model/conversion.rb +20 -9
- data/lib/active_model/dirty.rb +142 -116
- data/lib/active_model/error.rb +207 -0
- data/lib/active_model/errors.rb +436 -202
- data/lib/active_model/forbidden_attributes_protection.rb +6 -3
- data/lib/active_model/gem_version.rb +5 -3
- data/lib/active_model/lint.rb +47 -42
- data/lib/active_model/locale/en.yml +2 -1
- data/lib/active_model/model.rb +7 -7
- data/lib/active_model/naming.rb +36 -18
- data/lib/active_model/nested_error.rb +22 -0
- data/lib/active_model/railtie.rb +8 -0
- data/lib/active_model/secure_password.rb +61 -67
- data/lib/active_model/serialization.rb +48 -17
- data/lib/active_model/serializers/json.rb +22 -13
- data/lib/active_model/translation.rb +5 -4
- data/lib/active_model/type/big_integer.rb +14 -0
- data/lib/active_model/type/binary.rb +52 -0
- data/lib/active_model/type/boolean.rb +46 -0
- data/lib/active_model/type/date.rb +52 -0
- data/lib/active_model/type/date_time.rb +46 -0
- data/lib/active_model/type/decimal.rb +69 -0
- data/lib/active_model/type/float.rb +35 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +49 -0
- data/lib/active_model/type/helpers/mutable.rb +20 -0
- data/lib/active_model/type/helpers/numeric.rb +48 -0
- data/lib/active_model/type/helpers/time_value.rb +90 -0
- data/lib/active_model/type/helpers/timezone.rb +19 -0
- data/lib/active_model/type/helpers.rb +7 -0
- data/lib/active_model/type/immutable_string.rb +35 -0
- data/lib/active_model/type/integer.rb +67 -0
- data/lib/active_model/type/registry.rb +70 -0
- data/lib/active_model/type/string.rb +35 -0
- data/lib/active_model/type/time.rb +46 -0
- data/lib/active_model/type/value.rb +133 -0
- data/lib/active_model/type.rb +53 -0
- data/lib/active_model/validations/absence.rb +6 -4
- data/lib/active_model/validations/acceptance.rb +72 -14
- data/lib/active_model/validations/callbacks.rb +23 -19
- data/lib/active_model/validations/clusivity.rb +18 -12
- data/lib/active_model/validations/confirmation.rb +27 -14
- data/lib/active_model/validations/exclusion.rb +7 -4
- data/lib/active_model/validations/format.rb +27 -27
- data/lib/active_model/validations/helper_methods.rb +15 -0
- data/lib/active_model/validations/inclusion.rb +8 -7
- data/lib/active_model/validations/length.rb +35 -32
- data/lib/active_model/validations/numericality.rb +72 -34
- data/lib/active_model/validations/presence.rb +3 -3
- data/lib/active_model/validations/validates.rb +17 -15
- data/lib/active_model/validations/with.rb +6 -12
- data/lib/active_model/validations.rb +58 -23
- data/lib/active_model/validator.rb +23 -17
- data/lib/active_model/version.rb +4 -2
- data/lib/active_model.rb +18 -11
- metadata +44 -25
- data/lib/active_model/serializers/xml.rb +0 -238
- data/lib/active_model/test_case.rb +0 -4
@@ -0,0 +1,191 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/attribute"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
class AttributeSet # :nodoc:
|
7
|
+
class Builder # :nodoc:
|
8
|
+
attr_reader :types, :default_attributes
|
9
|
+
|
10
|
+
def initialize(types, default_attributes = {})
|
11
|
+
@types = types
|
12
|
+
@default_attributes = default_attributes
|
13
|
+
end
|
14
|
+
|
15
|
+
def build_from_database(values = {}, additional_types = {})
|
16
|
+
LazyAttributeSet.new(values, types, additional_types, default_attributes)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class LazyAttributeSet < AttributeSet # :nodoc:
|
22
|
+
def initialize(values, types, additional_types, default_attributes, attributes = {})
|
23
|
+
super(attributes)
|
24
|
+
@values = values
|
25
|
+
@types = types
|
26
|
+
@additional_types = additional_types
|
27
|
+
@default_attributes = default_attributes
|
28
|
+
@casted_values = {}
|
29
|
+
@materialized = false
|
30
|
+
end
|
31
|
+
|
32
|
+
def key?(name)
|
33
|
+
(values.key?(name) || types.key?(name) || @attributes.key?(name)) && self[name].initialized?
|
34
|
+
end
|
35
|
+
|
36
|
+
def keys
|
37
|
+
keys = values.keys | types.keys | @attributes.keys
|
38
|
+
keys.keep_if { |name| self[name].initialized? }
|
39
|
+
end
|
40
|
+
|
41
|
+
def fetch_value(name, &block)
|
42
|
+
if attr = @attributes[name]
|
43
|
+
return attr.value(&block)
|
44
|
+
end
|
45
|
+
|
46
|
+
@casted_values.fetch(name) do
|
47
|
+
value_present = true
|
48
|
+
value = values.fetch(name) { value_present = false }
|
49
|
+
|
50
|
+
if value_present
|
51
|
+
type = additional_types.fetch(name, types[name])
|
52
|
+
@casted_values[name] = type.deserialize(value)
|
53
|
+
else
|
54
|
+
attr = default_attribute(name, value_present, value)
|
55
|
+
attr.value(&block)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
def attributes
|
62
|
+
unless @materialized
|
63
|
+
values.each_key { |key| self[key] }
|
64
|
+
types.each_key { |key| self[key] }
|
65
|
+
@materialized = true
|
66
|
+
end
|
67
|
+
@attributes
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
attr_reader :values, :types, :additional_types, :default_attributes
|
72
|
+
|
73
|
+
def default_attribute(
|
74
|
+
name,
|
75
|
+
value_present = true,
|
76
|
+
value = values.fetch(name) { value_present = false }
|
77
|
+
)
|
78
|
+
type = additional_types.fetch(name, types[name])
|
79
|
+
|
80
|
+
if value_present
|
81
|
+
@attributes[name] = Attribute.from_database(name, value, type, @casted_values[name])
|
82
|
+
elsif types.key?(name)
|
83
|
+
if attr = default_attributes[name]
|
84
|
+
@attributes[name] = attr.dup
|
85
|
+
else
|
86
|
+
@attributes[name] = Attribute.uninitialized(name, type)
|
87
|
+
end
|
88
|
+
else
|
89
|
+
Attribute.null(name)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class LazyAttributeHash # :nodoc:
|
95
|
+
delegate :transform_values, :each_value, :fetch, :except, to: :materialize
|
96
|
+
|
97
|
+
def initialize(types, values, additional_types, default_attributes, delegate_hash = {})
|
98
|
+
@types = types
|
99
|
+
@values = values
|
100
|
+
@additional_types = additional_types
|
101
|
+
@materialized = false
|
102
|
+
@delegate_hash = delegate_hash
|
103
|
+
@default_attributes = default_attributes
|
104
|
+
end
|
105
|
+
|
106
|
+
def key?(key)
|
107
|
+
delegate_hash.key?(key) || values.key?(key) || types.key?(key)
|
108
|
+
end
|
109
|
+
|
110
|
+
def [](key)
|
111
|
+
delegate_hash[key] || assign_default_value(key)
|
112
|
+
end
|
113
|
+
|
114
|
+
def []=(key, value)
|
115
|
+
delegate_hash[key] = value
|
116
|
+
end
|
117
|
+
|
118
|
+
def deep_dup
|
119
|
+
dup.tap do |copy|
|
120
|
+
copy.instance_variable_set(:@delegate_hash, delegate_hash.transform_values(&:dup))
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def initialize_dup(_)
|
125
|
+
@delegate_hash = Hash[delegate_hash]
|
126
|
+
super
|
127
|
+
end
|
128
|
+
|
129
|
+
def each_key(&block)
|
130
|
+
keys = types.keys | values.keys | delegate_hash.keys
|
131
|
+
keys.each(&block)
|
132
|
+
end
|
133
|
+
|
134
|
+
def ==(other)
|
135
|
+
if other.is_a?(LazyAttributeHash)
|
136
|
+
materialize == other.materialize
|
137
|
+
else
|
138
|
+
materialize == other
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def marshal_dump
|
143
|
+
[@types, @values, @additional_types, @default_attributes, @delegate_hash]
|
144
|
+
end
|
145
|
+
|
146
|
+
def marshal_load(values)
|
147
|
+
if values.is_a?(Hash)
|
148
|
+
ActiveSupport::Deprecation.warn(<<~MSG)
|
149
|
+
Marshalling load from legacy attributes format is deprecated and will be removed in Rails 6.2.
|
150
|
+
MSG
|
151
|
+
empty_hash = {}.freeze
|
152
|
+
initialize(empty_hash, empty_hash, empty_hash, empty_hash, values)
|
153
|
+
@materialized = true
|
154
|
+
else
|
155
|
+
initialize(*values)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
protected
|
160
|
+
def materialize
|
161
|
+
unless @materialized
|
162
|
+
values.each_key { |key| self[key] }
|
163
|
+
types.each_key { |key| self[key] }
|
164
|
+
unless frozen?
|
165
|
+
@materialized = true
|
166
|
+
end
|
167
|
+
end
|
168
|
+
delegate_hash
|
169
|
+
end
|
170
|
+
|
171
|
+
private
|
172
|
+
attr_reader :types, :values, :additional_types, :delegate_hash, :default_attributes
|
173
|
+
|
174
|
+
def assign_default_value(name)
|
175
|
+
type = additional_types.fetch(name, types[name])
|
176
|
+
value_present = true
|
177
|
+
value = values.fetch(name) { value_present = false }
|
178
|
+
|
179
|
+
if value_present
|
180
|
+
delegate_hash[name] = Attribute.from_database(name, value, type)
|
181
|
+
elsif types.key?(name)
|
182
|
+
attr = default_attributes[name]
|
183
|
+
if attr
|
184
|
+
delegate_hash[name] = attr.dup
|
185
|
+
else
|
186
|
+
delegate_hash[name] = Attribute.uninitialized(name, type)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
class AttributeSet
|
5
|
+
# Attempts to do more intelligent YAML dumping of an
|
6
|
+
# ActiveModel::AttributeSet to reduce the size of the resulting string
|
7
|
+
class YAMLEncoder # :nodoc:
|
8
|
+
def initialize(default_types)
|
9
|
+
@default_types = default_types
|
10
|
+
end
|
11
|
+
|
12
|
+
def encode(attribute_set, coder)
|
13
|
+
coder["concise_attributes"] = attribute_set.each_value.map do |attr|
|
14
|
+
if attr.type.equal?(default_types[attr.name])
|
15
|
+
attr.with_type(nil)
|
16
|
+
else
|
17
|
+
attr
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def decode(coder)
|
23
|
+
if coder["attributes"]
|
24
|
+
coder["attributes"]
|
25
|
+
else
|
26
|
+
attributes_hash = Hash[coder["concise_attributes"].map do |attr|
|
27
|
+
if attr.type.nil?
|
28
|
+
attr = attr.with_type(default_types[attr.name])
|
29
|
+
end
|
30
|
+
[attr.name, attr]
|
31
|
+
end]
|
32
|
+
AttributeSet.new(attributes_hash)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
attr_reader :default_types
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/enumerable"
|
4
|
+
require "active_support/core_ext/object/deep_dup"
|
5
|
+
require "active_model/attribute_set/builder"
|
6
|
+
require "active_model/attribute_set/yaml_encoder"
|
7
|
+
|
8
|
+
module ActiveModel
|
9
|
+
class AttributeSet # :nodoc:
|
10
|
+
delegate :each_value, :fetch, :except, to: :attributes
|
11
|
+
|
12
|
+
def initialize(attributes)
|
13
|
+
@attributes = attributes
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](name)
|
17
|
+
@attributes[name] || default_attribute(name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def []=(name, value)
|
21
|
+
@attributes[name] = value
|
22
|
+
end
|
23
|
+
|
24
|
+
def values_before_type_cast
|
25
|
+
attributes.transform_values(&:value_before_type_cast)
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_hash
|
29
|
+
keys.index_with { |name| self[name].value }
|
30
|
+
end
|
31
|
+
alias :to_h :to_hash
|
32
|
+
|
33
|
+
def key?(name)
|
34
|
+
attributes.key?(name) && self[name].initialized?
|
35
|
+
end
|
36
|
+
|
37
|
+
def keys
|
38
|
+
attributes.each_key.select { |name| self[name].initialized? }
|
39
|
+
end
|
40
|
+
|
41
|
+
def fetch_value(name, &block)
|
42
|
+
self[name].value(&block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def write_from_database(name, value)
|
46
|
+
@attributes[name] = self[name].with_value_from_database(value)
|
47
|
+
end
|
48
|
+
|
49
|
+
def write_from_user(name, value)
|
50
|
+
raise FrozenError, "can't modify frozen attributes" if frozen?
|
51
|
+
@attributes[name] = self[name].with_value_from_user(value)
|
52
|
+
value
|
53
|
+
end
|
54
|
+
|
55
|
+
def write_cast_value(name, value)
|
56
|
+
@attributes[name] = self[name].with_cast_value(value)
|
57
|
+
value
|
58
|
+
end
|
59
|
+
|
60
|
+
def freeze
|
61
|
+
attributes.freeze
|
62
|
+
super
|
63
|
+
end
|
64
|
+
|
65
|
+
def deep_dup
|
66
|
+
AttributeSet.new(attributes.deep_dup)
|
67
|
+
end
|
68
|
+
|
69
|
+
def initialize_dup(_)
|
70
|
+
@attributes = @attributes.dup
|
71
|
+
super
|
72
|
+
end
|
73
|
+
|
74
|
+
def initialize_clone(_)
|
75
|
+
@attributes = @attributes.clone
|
76
|
+
super
|
77
|
+
end
|
78
|
+
|
79
|
+
def reset(key)
|
80
|
+
if key?(key)
|
81
|
+
write_from_database(key, nil)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def accessed
|
86
|
+
attributes.each_key.select { |name| self[name].has_been_read? }
|
87
|
+
end
|
88
|
+
|
89
|
+
def map(&block)
|
90
|
+
new_attributes = attributes.transform_values(&block)
|
91
|
+
AttributeSet.new(new_attributes)
|
92
|
+
end
|
93
|
+
|
94
|
+
def ==(other)
|
95
|
+
attributes == other.attributes
|
96
|
+
end
|
97
|
+
|
98
|
+
protected
|
99
|
+
attr_reader :attributes
|
100
|
+
|
101
|
+
private
|
102
|
+
def default_attribute(name)
|
103
|
+
Attribute.null(name)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/attribute_set"
|
4
|
+
require "active_model/attribute/user_provided_default"
|
5
|
+
|
6
|
+
module ActiveModel
|
7
|
+
module Attributes #:nodoc:
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
include ActiveModel::AttributeMethods
|
10
|
+
|
11
|
+
included do
|
12
|
+
attribute_method_suffix "="
|
13
|
+
class_attribute :attribute_types, :_default_attributes, instance_accessor: false
|
14
|
+
self.attribute_types = Hash.new(Type.default_value)
|
15
|
+
self._default_attributes = AttributeSet.new({})
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def attribute(name, type = Type::Value.new, **options)
|
20
|
+
name = name.to_s
|
21
|
+
if type.is_a?(Symbol)
|
22
|
+
type = ActiveModel::Type.lookup(type, **options.except(:default))
|
23
|
+
end
|
24
|
+
self.attribute_types = attribute_types.merge(name => type)
|
25
|
+
define_default_attribute(name, options.fetch(:default, NO_DEFAULT_PROVIDED), type)
|
26
|
+
define_attribute_method(name)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns an array of attribute names as strings
|
30
|
+
#
|
31
|
+
# class Person
|
32
|
+
# include ActiveModel::Attributes
|
33
|
+
#
|
34
|
+
# attribute :name, :string
|
35
|
+
# attribute :age, :integer
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# Person.attribute_names
|
39
|
+
# # => ["name", "age"]
|
40
|
+
def attribute_names
|
41
|
+
attribute_types.keys
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def define_method_attribute=(name, owner:)
|
46
|
+
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
|
47
|
+
owner, name, writer: true,
|
48
|
+
) do |temp_method_name, attr_name_expr|
|
49
|
+
owner <<
|
50
|
+
"def #{temp_method_name}(value)" <<
|
51
|
+
" _write_attribute(#{attr_name_expr}, value)" <<
|
52
|
+
"end"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
NO_DEFAULT_PROVIDED = Object.new # :nodoc:
|
57
|
+
private_constant :NO_DEFAULT_PROVIDED
|
58
|
+
|
59
|
+
def define_default_attribute(name, value, type)
|
60
|
+
self._default_attributes = _default_attributes.deep_dup
|
61
|
+
if value == NO_DEFAULT_PROVIDED
|
62
|
+
default_attribute = _default_attributes[name].with_type(type)
|
63
|
+
else
|
64
|
+
default_attribute = Attribute::UserProvidedDefault.new(
|
65
|
+
name,
|
66
|
+
value,
|
67
|
+
type,
|
68
|
+
_default_attributes.fetch(name.to_s) { nil },
|
69
|
+
)
|
70
|
+
end
|
71
|
+
_default_attributes[name] = default_attribute
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def initialize(*)
|
76
|
+
@attributes = self.class._default_attributes.deep_dup
|
77
|
+
super
|
78
|
+
end
|
79
|
+
|
80
|
+
def initialize_dup(other) # :nodoc:
|
81
|
+
@attributes = @attributes.deep_dup
|
82
|
+
super
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
|
86
|
+
#
|
87
|
+
# class Person
|
88
|
+
# include ActiveModel::Attributes
|
89
|
+
#
|
90
|
+
# attribute :name, :string
|
91
|
+
# attribute :age, :integer
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# person = Person.new(name: 'Francesco', age: 22)
|
95
|
+
# person.attributes
|
96
|
+
# # => {"name"=>"Francesco", "age"=>22}
|
97
|
+
def attributes
|
98
|
+
@attributes.to_hash
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns an array of attribute names as strings
|
102
|
+
#
|
103
|
+
# class Person
|
104
|
+
# include ActiveModel::Attributes
|
105
|
+
#
|
106
|
+
# attribute :name, :string
|
107
|
+
# attribute :age, :integer
|
108
|
+
# end
|
109
|
+
#
|
110
|
+
# person = Person.new
|
111
|
+
# person.attribute_names
|
112
|
+
# # => ["name", "age"]
|
113
|
+
def attribute_names
|
114
|
+
@attributes.keys
|
115
|
+
end
|
116
|
+
|
117
|
+
def freeze
|
118
|
+
@attributes = @attributes.clone.freeze unless frozen?
|
119
|
+
super
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
def _write_attribute(attr_name, value)
|
124
|
+
@attributes.write_from_user(attr_name, value)
|
125
|
+
end
|
126
|
+
alias :attribute= :_write_attribute
|
127
|
+
|
128
|
+
def attribute(attr_name)
|
129
|
+
@attributes.fetch_value(attr_name)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -1,4 +1,7 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/array/extract_options"
|
4
|
+
require "active_support/core_ext/hash/keys"
|
2
5
|
|
3
6
|
module ActiveModel
|
4
7
|
# == Active \Model \Callbacks
|
@@ -6,7 +9,7 @@ module ActiveModel
|
|
6
9
|
# Provides an interface for any class to have Active Record like callbacks.
|
7
10
|
#
|
8
11
|
# Like the Active Record methods, the callback chain is aborted as soon as
|
9
|
-
# one of the methods
|
12
|
+
# one of the methods throws +:abort+.
|
10
13
|
#
|
11
14
|
# First, extend ActiveModel::Callbacks from the class you are creating:
|
12
15
|
#
|
@@ -49,13 +52,16 @@ module ActiveModel
|
|
49
52
|
# puts 'block successfully called.'
|
50
53
|
# end
|
51
54
|
#
|
52
|
-
# You can choose
|
55
|
+
# You can choose to have only specific callbacks by passing a hash to the
|
53
56
|
# +define_model_callbacks+ method.
|
54
57
|
#
|
55
58
|
# define_model_callbacks :create, only: [:after, :before]
|
56
59
|
#
|
57
60
|
# Would only create the +after_create+ and +before_create+ callback methods in
|
58
61
|
# your class.
|
62
|
+
#
|
63
|
+
# NOTE: Calling the same callback multiple times will overwrite previous callback definitions.
|
64
|
+
#
|
59
65
|
module Callbacks
|
60
66
|
def self.extended(base) #:nodoc:
|
61
67
|
base.class_eval do
|
@@ -98,12 +104,11 @@ module ActiveModel
|
|
98
104
|
# end
|
99
105
|
# end
|
100
106
|
#
|
101
|
-
# NOTE: +method_name+ passed to
|
102
|
-
#
|
107
|
+
# NOTE: +method_name+ passed to define_model_callbacks must not end with
|
108
|
+
# <tt>!</tt>, <tt>?</tt> or <tt>=</tt>.
|
103
109
|
def define_model_callbacks(*callbacks)
|
104
110
|
options = callbacks.extract_options!
|
105
111
|
options = {
|
106
|
-
terminator: ->(_,result) { result == false },
|
107
112
|
skip_after_callbacks_if_terminated: true,
|
108
113
|
scope: [:kind, :name],
|
109
114
|
only: [:before, :around, :after]
|
@@ -121,29 +126,30 @@ module ActiveModel
|
|
121
126
|
end
|
122
127
|
|
123
128
|
private
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
129
|
+
def _define_before_model_callback(klass, callback)
|
130
|
+
klass.define_singleton_method("before_#{callback}") do |*args, **options, &block|
|
131
|
+
options.assert_valid_keys(:if, :unless, :prepend)
|
132
|
+
set_callback(:"#{callback}", :before, *args, options, &block)
|
133
|
+
end
|
128
134
|
end
|
129
|
-
end
|
130
135
|
|
131
|
-
|
132
|
-
|
133
|
-
|
136
|
+
def _define_around_model_callback(klass, callback)
|
137
|
+
klass.define_singleton_method("around_#{callback}") do |*args, **options, &block|
|
138
|
+
options.assert_valid_keys(:if, :unless, :prepend)
|
139
|
+
set_callback(:"#{callback}", :around, *args, options, &block)
|
140
|
+
end
|
134
141
|
end
|
135
|
-
end
|
136
142
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
143
|
+
def _define_after_model_callback(klass, callback)
|
144
|
+
klass.define_singleton_method("after_#{callback}") do |*args, **options, &block|
|
145
|
+
options.assert_valid_keys(:if, :unless, :prepend)
|
146
|
+
options[:prepend] = true
|
147
|
+
conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v|
|
148
|
+
v != false
|
149
|
+
}
|
150
|
+
options[:if] = Array(options[:if]) << conditional
|
151
|
+
set_callback(:"#{callback}", :after, *args, options, &block)
|
152
|
+
end
|
146
153
|
end
|
147
|
-
end
|
148
154
|
end
|
149
155
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveModel
|
2
4
|
# == Active \Model \Conversion
|
3
5
|
#
|
@@ -22,7 +24,7 @@ module ActiveModel
|
|
22
24
|
module Conversion
|
23
25
|
extend ActiveSupport::Concern
|
24
26
|
|
25
|
-
# If your object is already designed to implement all of the Active Model
|
27
|
+
# If your object is already designed to implement all of the \Active \Model
|
26
28
|
# you can use the default <tt>:to_model</tt> implementation, which simply
|
27
29
|
# returns +self+.
|
28
30
|
#
|
@@ -33,22 +35,26 @@ module ActiveModel
|
|
33
35
|
# person = Person.new
|
34
36
|
# person.to_model == person # => true
|
35
37
|
#
|
36
|
-
# If your model does not act like an Active Model object, then you should
|
38
|
+
# If your model does not act like an \Active \Model object, then you should
|
37
39
|
# define <tt>:to_model</tt> yourself returning a proxy object that wraps
|
38
|
-
# your object with Active Model compliant methods.
|
40
|
+
# your object with \Active \Model compliant methods.
|
39
41
|
def to_model
|
40
42
|
self
|
41
43
|
end
|
42
44
|
|
43
|
-
# Returns an Array of all key attributes if any is set,
|
44
|
-
# the object is persisted
|
45
|
+
# Returns an Array of all key attributes if any of the attributes is set, whether or not
|
46
|
+
# the object is persisted. Returns +nil+ if there are no key attributes.
|
45
47
|
#
|
46
48
|
# class Person
|
47
49
|
# include ActiveModel::Conversion
|
48
50
|
# attr_accessor :id
|
51
|
+
#
|
52
|
+
# def initialize(id)
|
53
|
+
# @id = id
|
54
|
+
# end
|
49
55
|
# end
|
50
56
|
#
|
51
|
-
# person = Person.
|
57
|
+
# person = Person.new(1)
|
52
58
|
# person.to_key # => [1]
|
53
59
|
def to_key
|
54
60
|
key = respond_to?(:id) && id
|
@@ -61,15 +67,20 @@ module ActiveModel
|
|
61
67
|
# class Person
|
62
68
|
# include ActiveModel::Conversion
|
63
69
|
# attr_accessor :id
|
70
|
+
#
|
71
|
+
# def initialize(id)
|
72
|
+
# @id = id
|
73
|
+
# end
|
74
|
+
#
|
64
75
|
# def persisted?
|
65
76
|
# true
|
66
77
|
# end
|
67
78
|
# end
|
68
79
|
#
|
69
|
-
# person = Person.
|
80
|
+
# person = Person.new(1)
|
70
81
|
# person.to_param # => "1"
|
71
82
|
def to_param
|
72
|
-
(persisted? && key = to_key) ? key.join(
|
83
|
+
(persisted? && key = to_key) ? key.join("-") : nil
|
73
84
|
end
|
74
85
|
|
75
86
|
# Returns a +string+ identifying the path associated with the object.
|
@@ -92,7 +103,7 @@ module ActiveModel
|
|
92
103
|
@_to_partial_path ||= begin
|
93
104
|
element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name))
|
94
105
|
collection = ActiveSupport::Inflector.tableize(name)
|
95
|
-
"#{collection}/#{element}"
|
106
|
+
"#{collection}/#{element}"
|
96
107
|
end
|
97
108
|
end
|
98
109
|
end
|