omg-activemodel 8.0.0.alpha1
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 +67 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +266 -0
- data/lib/active_model/access.rb +16 -0
- data/lib/active_model/api.rb +99 -0
- data/lib/active_model/attribute/user_provided_default.rb +55 -0
- data/lib/active_model/attribute.rb +277 -0
- data/lib/active_model/attribute_assignment.rb +78 -0
- data/lib/active_model/attribute_methods.rb +592 -0
- data/lib/active_model/attribute_mutation_tracker.rb +189 -0
- data/lib/active_model/attribute_registration.rb +117 -0
- data/lib/active_model/attribute_set/builder.rb +182 -0
- data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
- data/lib/active_model/attribute_set.rb +118 -0
- data/lib/active_model/attributes.rb +165 -0
- data/lib/active_model/callbacks.rb +155 -0
- data/lib/active_model/conversion.rb +121 -0
- data/lib/active_model/deprecator.rb +7 -0
- data/lib/active_model/dirty.rb +416 -0
- data/lib/active_model/error.rb +208 -0
- data/lib/active_model/errors.rb +547 -0
- data/lib/active_model/forbidden_attributes_protection.rb +33 -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 +38 -0
- data/lib/active_model/model.rb +78 -0
- data/lib/active_model/naming.rb +359 -0
- data/lib/active_model/nested_error.rb +22 -0
- data/lib/active_model/railtie.rb +24 -0
- data/lib/active_model/secure_password.rb +231 -0
- data/lib/active_model/serialization.rb +198 -0
- data/lib/active_model/serializers/json.rb +154 -0
- data/lib/active_model/translation.rb +78 -0
- data/lib/active_model/type/big_integer.rb +36 -0
- data/lib/active_model/type/binary.rb +62 -0
- data/lib/active_model/type/boolean.rb +48 -0
- data/lib/active_model/type/date.rb +78 -0
- data/lib/active_model/type/date_time.rb +88 -0
- data/lib/active_model/type/decimal.rb +107 -0
- data/lib/active_model/type/float.rb +64 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +53 -0
- data/lib/active_model/type/helpers/mutable.rb +24 -0
- data/lib/active_model/type/helpers/numeric.rb +61 -0
- data/lib/active_model/type/helpers/time_value.rb +127 -0
- data/lib/active_model/type/helpers/timezone.rb +23 -0
- data/lib/active_model/type/helpers.rb +7 -0
- data/lib/active_model/type/immutable_string.rb +71 -0
- data/lib/active_model/type/integer.rb +113 -0
- data/lib/active_model/type/registry.rb +37 -0
- data/lib/active_model/type/serialize_cast_value.rb +47 -0
- data/lib/active_model/type/string.rb +43 -0
- data/lib/active_model/type/time.rb +87 -0
- data/lib/active_model/type/value.rb +157 -0
- data/lib/active_model/type.rb +55 -0
- data/lib/active_model/validations/absence.rb +33 -0
- data/lib/active_model/validations/acceptance.rb +113 -0
- data/lib/active_model/validations/callbacks.rb +119 -0
- data/lib/active_model/validations/clusivity.rb +54 -0
- data/lib/active_model/validations/comparability.rb +18 -0
- data/lib/active_model/validations/comparison.rb +90 -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 +112 -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 +130 -0
- data/lib/active_model/validations/numericality.rb +222 -0
- data/lib/active_model/validations/presence.rb +39 -0
- data/lib/active_model/validations/resolve_value.rb +26 -0
- data/lib/active_model/validations/validates.rb +175 -0
- data/lib/active_model/validations/with.rb +154 -0
- data/lib/active_model/validations.rb +489 -0
- data/lib/active_model/validator.rb +190 -0
- data/lib/active_model/version.rb +10 -0
- data/lib/active_model.rb +84 -0
- metadata +139 -0
@@ -0,0 +1,165 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
# = Active \Model \Attributes
|
5
|
+
#
|
6
|
+
# The Attributes module allows models to define attributes beyond simple Ruby
|
7
|
+
# readers and writers. Similar to Active Record attributes, which are
|
8
|
+
# typically inferred from the database schema, Active Model Attributes are
|
9
|
+
# aware of data types, can have default values, and can handle casting and
|
10
|
+
# serialization.
|
11
|
+
#
|
12
|
+
# To use Attributes, include the module in your model class and define your
|
13
|
+
# attributes using the +attribute+ macro. It accepts a name, a type, a default
|
14
|
+
# value, and any other options supported by the attribute type.
|
15
|
+
#
|
16
|
+
# ==== Examples
|
17
|
+
#
|
18
|
+
# class Person
|
19
|
+
# include ActiveModel::Attributes
|
20
|
+
#
|
21
|
+
# attribute :name, :string
|
22
|
+
# attribute :active, :boolean, default: true
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# person = Person.new
|
26
|
+
# person.name = "Volmer"
|
27
|
+
#
|
28
|
+
# person.name # => "Volmer"
|
29
|
+
# person.active # => true
|
30
|
+
module Attributes
|
31
|
+
extend ActiveSupport::Concern
|
32
|
+
include ActiveModel::AttributeRegistration
|
33
|
+
include ActiveModel::AttributeMethods
|
34
|
+
|
35
|
+
included do
|
36
|
+
attribute_method_suffix "=", parameters: "value"
|
37
|
+
end
|
38
|
+
|
39
|
+
module ClassMethods
|
40
|
+
##
|
41
|
+
# :call-seq: attribute(name, cast_type = nil, default: nil, **options)
|
42
|
+
#
|
43
|
+
# Defines a model attribute. In addition to the attribute name, a cast
|
44
|
+
# type and default value may be specified, as well as any options
|
45
|
+
# supported by the given cast type.
|
46
|
+
#
|
47
|
+
# class Person
|
48
|
+
# include ActiveModel::Attributes
|
49
|
+
#
|
50
|
+
# attribute :name, :string
|
51
|
+
# attribute :active, :boolean, default: true
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# person = Person.new
|
55
|
+
# person.name = "Volmer"
|
56
|
+
#
|
57
|
+
# person.name # => "Volmer"
|
58
|
+
# person.active # => true
|
59
|
+
def attribute(name, ...)
|
60
|
+
super
|
61
|
+
define_attribute_method(name)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns an array of attribute names as strings.
|
65
|
+
#
|
66
|
+
# class Person
|
67
|
+
# include ActiveModel::Attributes
|
68
|
+
#
|
69
|
+
# attribute :name, :string
|
70
|
+
# attribute :age, :integer
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# Person.attribute_names # => ["name", "age"]
|
74
|
+
def attribute_names
|
75
|
+
attribute_types.keys
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# :method: type_for_attribute
|
80
|
+
# :call-seq: type_for_attribute(attribute_name, &block)
|
81
|
+
#
|
82
|
+
# Returns the type of the specified attribute after applying any
|
83
|
+
# modifiers. This method is the only valid source of information for
|
84
|
+
# anything related to the types of a model's attributes. The return value
|
85
|
+
# of this method will implement the interface described by
|
86
|
+
# ActiveModel::Type::Value (though the object itself may not subclass it).
|
87
|
+
#--
|
88
|
+
# Implemented by ActiveModel::AttributeRegistration::ClassMethods#type_for_attribute.
|
89
|
+
|
90
|
+
##
|
91
|
+
private
|
92
|
+
def define_method_attribute=(canonical_name, owner:, as: canonical_name)
|
93
|
+
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
|
94
|
+
owner, canonical_name, writer: true,
|
95
|
+
) do |temp_method_name, attr_name_expr|
|
96
|
+
owner.define_cached_method(temp_method_name, as: "#{as}=", namespace: :active_model) do |batch|
|
97
|
+
batch <<
|
98
|
+
"def #{temp_method_name}(value)" <<
|
99
|
+
" _write_attribute(#{attr_name_expr}, value)" <<
|
100
|
+
"end"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def initialize(*) # :nodoc:
|
107
|
+
@attributes = self.class._default_attributes.deep_dup
|
108
|
+
super
|
109
|
+
end
|
110
|
+
|
111
|
+
def initialize_dup(other) # :nodoc:
|
112
|
+
@attributes = @attributes.deep_dup
|
113
|
+
super
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns a hash of all the attributes with their names as keys and the
|
117
|
+
# values of the attributes as values.
|
118
|
+
#
|
119
|
+
# class Person
|
120
|
+
# include ActiveModel::Attributes
|
121
|
+
#
|
122
|
+
# attribute :name, :string
|
123
|
+
# attribute :age, :integer
|
124
|
+
# end
|
125
|
+
#
|
126
|
+
# person = Person.new
|
127
|
+
# person.name = "Francesco"
|
128
|
+
# person.age = 22
|
129
|
+
#
|
130
|
+
# person.attributes # => { "name" => "Francesco", "age" => 22}
|
131
|
+
def attributes
|
132
|
+
@attributes.to_hash
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns an array of attribute names as strings.
|
136
|
+
#
|
137
|
+
# class Person
|
138
|
+
# include ActiveModel::Attributes
|
139
|
+
#
|
140
|
+
# attribute :name, :string
|
141
|
+
# attribute :age, :integer
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
# person = Person.new
|
145
|
+
# person.attribute_names # => ["name", "age"]
|
146
|
+
def attribute_names
|
147
|
+
@attributes.keys
|
148
|
+
end
|
149
|
+
|
150
|
+
def freeze # :nodoc:
|
151
|
+
@attributes = @attributes.clone.freeze unless frozen?
|
152
|
+
super
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
def _write_attribute(attr_name, value)
|
157
|
+
@attributes.write_from_user(attr_name, value)
|
158
|
+
end
|
159
|
+
alias :attribute= :_write_attribute
|
160
|
+
|
161
|
+
def attribute(attr_name)
|
162
|
+
@attributes.fetch_value(attr_name)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/array/extract_options"
|
4
|
+
require "active_support/core_ext/hash/keys"
|
5
|
+
|
6
|
+
module ActiveModel
|
7
|
+
# = Active \Model \Callbacks
|
8
|
+
#
|
9
|
+
# Provides an interface for any class to have Active Record like callbacks.
|
10
|
+
#
|
11
|
+
# Like the Active Record methods, the callback chain is aborted as soon as
|
12
|
+
# one of the methods throws +:abort+.
|
13
|
+
#
|
14
|
+
# First, extend +ActiveModel::Callbacks+ from the class you are creating:
|
15
|
+
#
|
16
|
+
# class MyModel
|
17
|
+
# extend ActiveModel::Callbacks
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# Then define a list of methods that you want callbacks attached to:
|
21
|
+
#
|
22
|
+
# define_model_callbacks :create, :update
|
23
|
+
#
|
24
|
+
# This will provide all three standard callbacks (before, around and after)
|
25
|
+
# for both the <tt>:create</tt> and <tt>:update</tt> methods. To implement,
|
26
|
+
# you need to wrap the methods you want callbacks on in a block so that the
|
27
|
+
# callbacks get a chance to fire:
|
28
|
+
#
|
29
|
+
# def create
|
30
|
+
# run_callbacks :create do
|
31
|
+
# # Your create action methods here
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# Then in your class, you can use the +before_create+, +after_create+, and
|
36
|
+
# +around_create+ methods, just as you would in an Active Record model.
|
37
|
+
#
|
38
|
+
# before_create :action_before_create
|
39
|
+
#
|
40
|
+
# def action_before_create
|
41
|
+
# # Your code here
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# When defining an around callback remember to yield to the block, otherwise
|
45
|
+
# it won't be executed:
|
46
|
+
#
|
47
|
+
# around_create :log_status
|
48
|
+
#
|
49
|
+
# def log_status
|
50
|
+
# puts 'going to call the block...'
|
51
|
+
# yield
|
52
|
+
# puts 'block successfully called.'
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# You can choose to have only specific callbacks by passing a hash to the
|
56
|
+
# +define_model_callbacks+ method.
|
57
|
+
#
|
58
|
+
# define_model_callbacks :create, only: [:after, :before]
|
59
|
+
#
|
60
|
+
# Would only create the +after_create+ and +before_create+ callback methods in
|
61
|
+
# your class.
|
62
|
+
#
|
63
|
+
# NOTE: Defining the same callback multiple times will overwrite previous callback definitions.
|
64
|
+
#
|
65
|
+
module Callbacks
|
66
|
+
def self.extended(base) # :nodoc:
|
67
|
+
base.class_eval do
|
68
|
+
include ActiveSupport::Callbacks
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# +define_model_callbacks+ accepts the same options +define_callbacks+ does,
|
73
|
+
# in case you want to overwrite a default. Besides that, it also accepts an
|
74
|
+
# <tt>:only</tt> option, where you can choose if you want all types (before,
|
75
|
+
# around or after) or just some.
|
76
|
+
#
|
77
|
+
# define_model_callbacks :initialize, only: :after
|
78
|
+
#
|
79
|
+
# Note, the <tt>only: <type></tt> hash will apply to all callbacks defined
|
80
|
+
# on that method call. To get around this you can call the +define_model_callbacks+
|
81
|
+
# method as many times as you need.
|
82
|
+
#
|
83
|
+
# define_model_callbacks :create, only: :after
|
84
|
+
# define_model_callbacks :update, only: :before
|
85
|
+
# define_model_callbacks :destroy, only: :around
|
86
|
+
#
|
87
|
+
# Would create +after_create+, +before_update+, and +around_destroy+ methods
|
88
|
+
# only.
|
89
|
+
#
|
90
|
+
# You can pass in a class to before_<type>, after_<type> and around_<type>,
|
91
|
+
# in which case the callback will call that class's <action>_<type> method
|
92
|
+
# passing the object that the callback is being called on.
|
93
|
+
#
|
94
|
+
# class MyModel
|
95
|
+
# extend ActiveModel::Callbacks
|
96
|
+
# define_model_callbacks :create
|
97
|
+
#
|
98
|
+
# before_create AnotherClass
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# class AnotherClass
|
102
|
+
# def self.before_create( obj )
|
103
|
+
# # obj is the MyModel instance that the callback is being called on
|
104
|
+
# end
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# NOTE: +method_name+ passed to +define_model_callbacks+ must not end with
|
108
|
+
# <tt>!</tt>, <tt>?</tt> or <tt>=</tt>.
|
109
|
+
def define_model_callbacks(*callbacks)
|
110
|
+
options = callbacks.extract_options!
|
111
|
+
options = {
|
112
|
+
skip_after_callbacks_if_terminated: true,
|
113
|
+
scope: [:kind, :name],
|
114
|
+
only: [:before, :around, :after]
|
115
|
+
}.merge!(options)
|
116
|
+
|
117
|
+
types = Array(options.delete(:only))
|
118
|
+
|
119
|
+
callbacks.each do |callback|
|
120
|
+
define_callbacks(callback, options)
|
121
|
+
|
122
|
+
types.each do |type|
|
123
|
+
send("_define_#{type}_model_callback", self, callback)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
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
|
134
|
+
end
|
135
|
+
|
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
|
141
|
+
end
|
142
|
+
|
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
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
# = Active \Model \Conversion
|
5
|
+
#
|
6
|
+
# Handles default conversions: to_model, to_key, to_param, and to_partial_path.
|
7
|
+
#
|
8
|
+
# Let's take for example this non-persisted object.
|
9
|
+
#
|
10
|
+
# class ContactMessage
|
11
|
+
# include ActiveModel::Conversion
|
12
|
+
#
|
13
|
+
# # ContactMessage are never persisted in the DB
|
14
|
+
# def persisted?
|
15
|
+
# false
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# cm = ContactMessage.new
|
20
|
+
# cm.to_model == cm # => true
|
21
|
+
# cm.to_key # => nil
|
22
|
+
# cm.to_param # => nil
|
23
|
+
# cm.to_partial_path # => "contact_messages/contact_message"
|
24
|
+
module Conversion
|
25
|
+
extend ActiveSupport::Concern
|
26
|
+
|
27
|
+
included do
|
28
|
+
##
|
29
|
+
# :singleton-method:
|
30
|
+
#
|
31
|
+
# Accepts a string that will be used as a delimiter of object's key values in the `to_param` method.
|
32
|
+
class_attribute :param_delimiter, instance_reader: false, default: "-"
|
33
|
+
end
|
34
|
+
|
35
|
+
# If your object is already designed to implement all of the \Active \Model
|
36
|
+
# you can use the default <tt>:to_model</tt> implementation, which simply
|
37
|
+
# returns +self+.
|
38
|
+
#
|
39
|
+
# class Person
|
40
|
+
# include ActiveModel::Conversion
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# person = Person.new
|
44
|
+
# person.to_model == person # => true
|
45
|
+
#
|
46
|
+
# If your model does not act like an \Active \Model object, then you should
|
47
|
+
# define <tt>:to_model</tt> yourself returning a proxy object that wraps
|
48
|
+
# your object with \Active \Model compliant methods.
|
49
|
+
def to_model
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns an Array of all key attributes if any of the attributes is set, whether or not
|
54
|
+
# the object is persisted. Returns +nil+ if there are no key attributes.
|
55
|
+
#
|
56
|
+
# class Person
|
57
|
+
# include ActiveModel::Conversion
|
58
|
+
# attr_accessor :id
|
59
|
+
#
|
60
|
+
# def initialize(id)
|
61
|
+
# @id = id
|
62
|
+
# end
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# person = Person.new(1)
|
66
|
+
# person.to_key # => [1]
|
67
|
+
def to_key
|
68
|
+
key = respond_to?(:id) && id
|
69
|
+
key ? Array(key) : nil
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns a +string+ representing the object's key suitable for use in URLs,
|
73
|
+
# or +nil+ if <tt>persisted?</tt> is +false+.
|
74
|
+
#
|
75
|
+
# class Person
|
76
|
+
# include ActiveModel::Conversion
|
77
|
+
# attr_accessor :id
|
78
|
+
#
|
79
|
+
# def initialize(id)
|
80
|
+
# @id = id
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# def persisted?
|
84
|
+
# true
|
85
|
+
# end
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# person = Person.new(1)
|
89
|
+
# person.to_param # => "1"
|
90
|
+
def to_param
|
91
|
+
(persisted? && (key = to_key) && key.all?) ? key.join(self.class.param_delimiter) : nil
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns a +string+ identifying the path associated with the object.
|
95
|
+
# ActionPack uses this to find a suitable partial to represent the object.
|
96
|
+
#
|
97
|
+
# class Person
|
98
|
+
# include ActiveModel::Conversion
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# person = Person.new
|
102
|
+
# person.to_partial_path # => "people/person"
|
103
|
+
def to_partial_path
|
104
|
+
self.class._to_partial_path
|
105
|
+
end
|
106
|
+
|
107
|
+
module ClassMethods # :nodoc:
|
108
|
+
# Provide a class level cache for #to_partial_path. This is an
|
109
|
+
# internal method and should not be accessed directly.
|
110
|
+
def _to_partial_path # :nodoc:
|
111
|
+
@_to_partial_path ||= if respond_to?(:model_name)
|
112
|
+
"#{model_name.collection}/#{model_name.element}"
|
113
|
+
else
|
114
|
+
element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name))
|
115
|
+
collection = ActiveSupport::Inflector.tableize(name)
|
116
|
+
"#{collection}/#{element}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|