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.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +67 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +266 -0
  5. data/lib/active_model/access.rb +16 -0
  6. data/lib/active_model/api.rb +99 -0
  7. data/lib/active_model/attribute/user_provided_default.rb +55 -0
  8. data/lib/active_model/attribute.rb +277 -0
  9. data/lib/active_model/attribute_assignment.rb +78 -0
  10. data/lib/active_model/attribute_methods.rb +592 -0
  11. data/lib/active_model/attribute_mutation_tracker.rb +189 -0
  12. data/lib/active_model/attribute_registration.rb +117 -0
  13. data/lib/active_model/attribute_set/builder.rb +182 -0
  14. data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
  15. data/lib/active_model/attribute_set.rb +118 -0
  16. data/lib/active_model/attributes.rb +165 -0
  17. data/lib/active_model/callbacks.rb +155 -0
  18. data/lib/active_model/conversion.rb +121 -0
  19. data/lib/active_model/deprecator.rb +7 -0
  20. data/lib/active_model/dirty.rb +416 -0
  21. data/lib/active_model/error.rb +208 -0
  22. data/lib/active_model/errors.rb +547 -0
  23. data/lib/active_model/forbidden_attributes_protection.rb +33 -0
  24. data/lib/active_model/gem_version.rb +17 -0
  25. data/lib/active_model/lint.rb +118 -0
  26. data/lib/active_model/locale/en.yml +38 -0
  27. data/lib/active_model/model.rb +78 -0
  28. data/lib/active_model/naming.rb +359 -0
  29. data/lib/active_model/nested_error.rb +22 -0
  30. data/lib/active_model/railtie.rb +24 -0
  31. data/lib/active_model/secure_password.rb +231 -0
  32. data/lib/active_model/serialization.rb +198 -0
  33. data/lib/active_model/serializers/json.rb +154 -0
  34. data/lib/active_model/translation.rb +78 -0
  35. data/lib/active_model/type/big_integer.rb +36 -0
  36. data/lib/active_model/type/binary.rb +62 -0
  37. data/lib/active_model/type/boolean.rb +48 -0
  38. data/lib/active_model/type/date.rb +78 -0
  39. data/lib/active_model/type/date_time.rb +88 -0
  40. data/lib/active_model/type/decimal.rb +107 -0
  41. data/lib/active_model/type/float.rb +64 -0
  42. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +53 -0
  43. data/lib/active_model/type/helpers/mutable.rb +24 -0
  44. data/lib/active_model/type/helpers/numeric.rb +61 -0
  45. data/lib/active_model/type/helpers/time_value.rb +127 -0
  46. data/lib/active_model/type/helpers/timezone.rb +23 -0
  47. data/lib/active_model/type/helpers.rb +7 -0
  48. data/lib/active_model/type/immutable_string.rb +71 -0
  49. data/lib/active_model/type/integer.rb +113 -0
  50. data/lib/active_model/type/registry.rb +37 -0
  51. data/lib/active_model/type/serialize_cast_value.rb +47 -0
  52. data/lib/active_model/type/string.rb +43 -0
  53. data/lib/active_model/type/time.rb +87 -0
  54. data/lib/active_model/type/value.rb +157 -0
  55. data/lib/active_model/type.rb +55 -0
  56. data/lib/active_model/validations/absence.rb +33 -0
  57. data/lib/active_model/validations/acceptance.rb +113 -0
  58. data/lib/active_model/validations/callbacks.rb +119 -0
  59. data/lib/active_model/validations/clusivity.rb +54 -0
  60. data/lib/active_model/validations/comparability.rb +18 -0
  61. data/lib/active_model/validations/comparison.rb +90 -0
  62. data/lib/active_model/validations/confirmation.rb +80 -0
  63. data/lib/active_model/validations/exclusion.rb +49 -0
  64. data/lib/active_model/validations/format.rb +112 -0
  65. data/lib/active_model/validations/helper_methods.rb +15 -0
  66. data/lib/active_model/validations/inclusion.rb +47 -0
  67. data/lib/active_model/validations/length.rb +130 -0
  68. data/lib/active_model/validations/numericality.rb +222 -0
  69. data/lib/active_model/validations/presence.rb +39 -0
  70. data/lib/active_model/validations/resolve_value.rb +26 -0
  71. data/lib/active_model/validations/validates.rb +175 -0
  72. data/lib/active_model/validations/with.rb +154 -0
  73. data/lib/active_model/validations.rb +489 -0
  74. data/lib/active_model/validator.rb +190 -0
  75. data/lib/active_model/version.rb +10 -0
  76. data/lib/active_model.rb +84 -0
  77. metadata +139 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b9199b4832bbe8dfdddaef904f1830bc5c43aee700c358b48b7ecc205f27d988
4
+ data.tar.gz: 14f2bbdd71b68ccbaa9613688c58197de86cd6e281260a3f062171ee46084af0
5
+ SHA512:
6
+ metadata.gz: 4aaa1d510d524f7133108ec430fdad64f50ee8cb05e113a260c2ff203ed477ea574c7523c6c31cb1955fd4ed704b8e7b3df42c02ca7a918ab9694288f5c8ae12
7
+ data.tar.gz: 6dba06dff924fcf9bbccf45bf0eb3efd968daee301d619c0ad509cbfc6d3b602180735823d0cc731451c80451f792effd4140cb12a95ef6ae2b4e862a58365c8
data/CHANGELOG.md ADDED
@@ -0,0 +1,67 @@
1
+ * Add a default token generator for password reset tokens when using `has_secure_password`.
2
+
3
+ ```ruby
4
+ class User < ApplicationRecord
5
+ has_secure_password
6
+ end
7
+
8
+ user = User.create!(name: "david", password: "123", password_confirmation: "123")
9
+ token = user.password_reset_token
10
+ User.find_by_password_reset_token(token) # returns user
11
+
12
+ # 16 minutes later...
13
+ User.find_by_password_reset_token(token) # returns nil
14
+
15
+ # raises ActiveSupport::MessageVerifier::InvalidSignature since the token is expired
16
+ User.find_by_password_reset_token!(token)
17
+ ```
18
+
19
+ *DHH*
20
+
21
+ * Add a load hook `active_model_translation` for `ActiveModel::Translation`.
22
+
23
+ *Shouichi Kamiya*
24
+
25
+ * Add `raise_on_missing_translations` option to `ActiveModel::Translation`.
26
+ When the option is set, `human_attribute_name` raises an error if a translation of the given attribute is missing.
27
+
28
+ ```ruby
29
+ # ActiveModel::Translation.raise_on_missing_translations = false
30
+ Post.human_attribute_name("title")
31
+ => "Title"
32
+
33
+ # ActiveModel::Translation.raise_on_missing_translations = true
34
+ Post.human_attribute_name("title")
35
+ => Translation missing. Options considered were: (I18n::MissingTranslationData)
36
+ - en.activerecord.attributes.post.title
37
+ - en.attributes.title
38
+
39
+ raise exception.respond_to?(:to_exception) ? exception.to_exception : exception
40
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
41
+ ```
42
+
43
+ *Shouichi Kamiya*
44
+
45
+ * Introduce `ActiveModel::AttributeAssignment#attribute_writer_missing`
46
+
47
+ Provide instances with an opportunity to gracefully handle assigning to an
48
+ unknown attribute:
49
+
50
+ ```ruby
51
+ class Rectangle
52
+ include ActiveModel::AttributeAssignment
53
+
54
+ attr_accessor :length, :width
55
+
56
+ def attribute_writer_missing(name, value)
57
+ Rails.logger.warn "Tried to assign to unknown attribute #{name}"
58
+ end
59
+ end
60
+
61
+ rectangle = Rectangle.new
62
+ rectangle.assign_attributes(height: 10) # => Logs "Tried to assign to unknown attribute 'height'"
63
+ ```
64
+
65
+ *Sean Doyle*
66
+
67
+ Please check [7-2-stable](https://github.com/rails/rails/blob/7-2-stable/activemodel/CHANGELOG.md) for previous changes.
data/MIT-LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) David Heinemeier Hansson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
data/README.rdoc ADDED
@@ -0,0 +1,266 @@
1
+ = Active Model -- model interfaces for \Rails
2
+
3
+ Active Model provides a known set of interfaces for usage in model classes.
4
+ They allow for Action Pack helpers to interact with non-Active Record models,
5
+ for example. Active Model also helps with building custom ORMs for use outside of
6
+ the \Rails framework.
7
+
8
+ You can read more about Active Model in the {Active Model Basics}[https://guides.rubyonrails.org/active_model_basics.html] guide.
9
+
10
+ Prior to \Rails 3.0, if a plugin or gem developer wanted to have an object
11
+ interact with Action Pack helpers, it was required to either copy chunks of
12
+ code from \Rails, or monkey patch entire helpers to make them handle objects
13
+ that did not exactly conform to the Active Record interface. This would result
14
+ in code duplication and fragile applications that broke on upgrades. Active
15
+ Model solves this by defining an explicit API. You can read more about the
16
+ API in +ActiveModel::Lint::Tests+.
17
+
18
+ Active Model provides a default module that implements the basic API required
19
+ to integrate with Action Pack out of the box: ActiveModel::API.
20
+
21
+ class Person
22
+ include ActiveModel::API
23
+
24
+ attr_accessor :name, :age
25
+ validates_presence_of :name
26
+ end
27
+
28
+ person = Person.new(name: 'bob', age: '18')
29
+ person.name # => 'bob'
30
+ person.age # => '18'
31
+ person.valid? # => true
32
+
33
+ It includes model name introspections, conversions, translations and
34
+ validations, resulting in a class suitable to be used with Action Pack.
35
+ See ActiveModel::API for more examples.
36
+
37
+ Active Model also provides the following functionality to have ORM-like
38
+ behavior out of the box:
39
+
40
+ * Add attribute magic to objects
41
+
42
+ class Person
43
+ include ActiveModel::AttributeMethods
44
+
45
+ attribute_method_prefix 'clear_'
46
+ define_attribute_methods :name, :age
47
+
48
+ attr_accessor :name, :age
49
+
50
+ def clear_attribute(attr)
51
+ send("#{attr}=", nil)
52
+ end
53
+ end
54
+
55
+ person = Person.new
56
+ person.clear_name
57
+ person.clear_age
58
+
59
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/AttributeMethods.html]
60
+
61
+ * Callbacks for certain operations
62
+
63
+ class Person
64
+ extend ActiveModel::Callbacks
65
+ define_model_callbacks :create
66
+
67
+ def create
68
+ run_callbacks :create do
69
+ # Your create action methods here
70
+ end
71
+ end
72
+ end
73
+
74
+ This generates +before_create+, +around_create+ and +after_create+
75
+ class methods that wrap your create method.
76
+
77
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Callbacks.html]
78
+
79
+ * Tracking value changes
80
+
81
+ class Person
82
+ include ActiveModel::Dirty
83
+
84
+ define_attribute_methods :name
85
+
86
+ def name
87
+ @name
88
+ end
89
+
90
+ def name=(val)
91
+ name_will_change! unless val == @name
92
+ @name = val
93
+ end
94
+
95
+ def save
96
+ # do persistence work
97
+ changes_applied
98
+ end
99
+ end
100
+
101
+ person = Person.new
102
+ person.name # => nil
103
+ person.changed? # => false
104
+ person.name = 'bob'
105
+ person.changed? # => true
106
+ person.changed # => ['name']
107
+ person.changes # => { 'name' => [nil, 'bob'] }
108
+ person.save
109
+ person.name = 'robert'
110
+ person.save
111
+ person.previous_changes # => {'name' => ['bob, 'robert']}
112
+
113
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Dirty.html]
114
+
115
+ * Adding +errors+ interface to objects
116
+
117
+ Exposing error messages allows objects to interact with Action Pack
118
+ helpers seamlessly.
119
+
120
+ class Person
121
+
122
+ def initialize
123
+ @errors = ActiveModel::Errors.new(self)
124
+ end
125
+
126
+ attr_accessor :name
127
+ attr_reader :errors
128
+
129
+ def validate!
130
+ errors.add(:name, "cannot be nil") if name.nil?
131
+ end
132
+
133
+ def self.human_attribute_name(attr, options = {})
134
+ "Name"
135
+ end
136
+ end
137
+
138
+ person = Person.new
139
+ person.name = nil
140
+ person.validate!
141
+ person.errors.full_messages
142
+ # => ["Name cannot be nil"]
143
+
144
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Errors.html]
145
+
146
+ * Model name introspection
147
+
148
+ class NamedPerson
149
+ extend ActiveModel::Naming
150
+ end
151
+
152
+ NamedPerson.model_name.name # => "NamedPerson"
153
+ NamedPerson.model_name.human # => "Named person"
154
+
155
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Naming.html]
156
+
157
+ * Making objects serializable
158
+
159
+ ActiveModel::Serialization provides a standard interface for your object
160
+ to provide +to_json+ serialization.
161
+
162
+ class SerialPerson
163
+ include ActiveModel::Serialization
164
+
165
+ attr_accessor :name
166
+
167
+ def attributes
168
+ {'name' => name}
169
+ end
170
+ end
171
+
172
+ s = SerialPerson.new
173
+ s.serializable_hash # => {"name"=>nil}
174
+
175
+ class SerialPerson
176
+ include ActiveModel::Serializers::JSON
177
+ end
178
+
179
+ s = SerialPerson.new
180
+ s.to_json # => "{\"name\":null}"
181
+
182
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Serialization.html]
183
+
184
+ * Internationalization (i18n) support
185
+
186
+ class Person
187
+ extend ActiveModel::Translation
188
+ end
189
+
190
+ Person.human_attribute_name('my_attribute')
191
+ # => "My attribute"
192
+
193
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Translation.html]
194
+
195
+ * Validation support
196
+
197
+ class Person
198
+ include ActiveModel::Validations
199
+
200
+ attr_accessor :first_name, :last_name
201
+
202
+ validates_each :first_name, :last_name do |record, attr, value|
203
+ record.errors.add attr, "starts with z." if value.start_with?("z")
204
+ end
205
+ end
206
+
207
+ person = Person.new
208
+ person.first_name = 'zoolander'
209
+ person.valid? # => false
210
+
211
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Validations.html]
212
+
213
+ * Custom validators
214
+
215
+ class HasNameValidator < ActiveModel::Validator
216
+ def validate(record)
217
+ record.errors.add(:name, "must exist") if record.name.blank?
218
+ end
219
+ end
220
+
221
+ class ValidatorPerson
222
+ include ActiveModel::Validations
223
+ validates_with HasNameValidator
224
+ attr_accessor :name
225
+ end
226
+
227
+ p = ValidatorPerson.new
228
+ p.valid? # => false
229
+ p.errors.full_messages # => ["Name must exist"]
230
+ p.name = "Bob"
231
+ p.valid? # => true
232
+
233
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Validator.html]
234
+
235
+
236
+ == Download and installation
237
+
238
+ The latest version of Active Model can be installed with RubyGems:
239
+
240
+ $ gem install activemodel
241
+
242
+ Source code can be downloaded as part of the \Rails project on GitHub
243
+
244
+ * https://github.com/rails/rails/tree/main/activemodel
245
+
246
+
247
+ == License
248
+
249
+ Active Model is released under the MIT license:
250
+
251
+ * https://opensource.org/licenses/MIT
252
+
253
+
254
+ == Support
255
+
256
+ API documentation is at:
257
+
258
+ * https://api.rubyonrails.org
259
+
260
+ Bug reports for the Ruby on \Rails project can be filed here:
261
+
262
+ * https://github.com/rails/rails/issues
263
+
264
+ Feature requests should be discussed on the rails-core mailing list here:
265
+
266
+ * https://discuss.rubyonrails.org/c/rubyonrails-core
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/enumerable"
4
+ require "active_support/core_ext/hash/indifferent_access"
5
+
6
+ module ActiveModel
7
+ module Access # :nodoc:
8
+ def slice(*methods)
9
+ methods.flatten.index_with { |method| public_send(method) }.with_indifferent_access
10
+ end
11
+
12
+ def values_at(*methods)
13
+ methods.flatten.map! { |method| public_send(method) }
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ # = Active \Model \API
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::API
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, +ActiveModel::API+ implements #persisted?
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::API
29
+ # attr_accessor :id, :name
30
+ #
31
+ # def persisted?
32
+ # self.id.present?
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 initialize ( ::new ), make
40
+ # sure you call +super+ if you want the attributes hash initialization to
41
+ # happen.
42
+ #
43
+ # class Person
44
+ # include ActiveModel::API
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 +ActiveModel::API+
58
+ # (see below).
59
+ module API
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::API
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::API
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,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/attribute"
4
+
5
+ module ActiveModel
6
+ class Attribute # :nodoc:
7
+ def with_user_default(value)
8
+ UserProvidedDefault.new(name, value, type, self.is_a?(FromDatabase) ? self : original_attribute)
9
+ end
10
+
11
+ class UserProvidedDefault < FromUser # :nodoc:
12
+ def initialize(name, value, type, database_default)
13
+ @user_provided_value = value
14
+ super(name, value, type, database_default)
15
+ end
16
+
17
+ def value_before_type_cast
18
+ if user_provided_value.is_a?(Proc)
19
+ @memoized_value_before_type_cast ||= user_provided_value.call
20
+ else
21
+ @user_provided_value
22
+ end
23
+ end
24
+
25
+ def with_type(type)
26
+ self.class.new(name, user_provided_value, type, original_attribute)
27
+ end
28
+
29
+ def marshal_dump
30
+ result = [
31
+ name,
32
+ value_before_type_cast,
33
+ type,
34
+ original_attribute,
35
+ ]
36
+ result << value if defined?(@value)
37
+ result
38
+ end
39
+
40
+ def marshal_load(values)
41
+ name, user_provided_value, type, original_attribute, value = values
42
+ @name = name
43
+ @user_provided_value = user_provided_value
44
+ @type = type
45
+ @original_attribute = original_attribute
46
+ if values.length == 5
47
+ @value = value
48
+ end
49
+ end
50
+
51
+ private
52
+ attr_reader :user_provided_value
53
+ end
54
+ end
55
+ end