omg-activemodel 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
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