active_record_compose 0.8.0 → 0.10.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 +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +16 -0
- data/README.md +47 -36
- data/lib/active_record_compose/callbacks.rb +19 -0
- data/lib/active_record_compose/delegate_attribute.rb +2 -2
- data/lib/active_record_compose/model.rb +28 -227
- data/lib/active_record_compose/transaction_support.rb +1 -1
- data/lib/active_record_compose/validations.rb +48 -0
- data/lib/active_record_compose/version.rb +1 -1
- data/lib/active_record_compose/wrapped_model.rb +6 -6
- data/sig/_internal/package_private.rbs +44 -5
- data/sig/active_record_compose.rbs +14 -21
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c78fcb982e3f8f36a5497483ae835eaba9724d72342efc0866ca596c25fa45a8
|
4
|
+
data.tar.gz: 515f08b1def287d06c1d8436a0c3cb466ef75016094e4778b0e9ba992c95cc30
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1976cef02cc6dbf8d9d543b339254136733c580f116f2ad38c1239fd7a18bdf9a2bde27d7a22be4f041c0eb2576f1bbc97d6e18cfd5859dae52fb2a90624ab69
|
7
|
+
data.tar.gz: 71130efd7792fa18dc33903d12bd6eb7ac17ac59bff95ceeedb2200ce128c3f34fdc7229866f8b5fbeea68f1136bb84dd35a17e2e0167b5990d2b26723022ba3
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.10.0] - 2025-04-07
|
4
|
+
|
5
|
+
- avoid twice validation. As a side effect, save must accept argument `#save(**options)`.
|
6
|
+
In line with this, the model to be put into models must be
|
7
|
+
at least responsive to `model.valid? && model.save(validate: false)`, not `model.save()` (no arguments).
|
8
|
+
- supports context as the first argument of `#valid?`, for example `model.valid(:custom_context)`.
|
9
|
+
At the same time, it accepts `options[:context]` in `#save(**options)`, such as `model.save(context: :custom_context)`.
|
10
|
+
However, this is a convenience support to unify the interface, not a positive one.
|
11
|
+
|
12
|
+
## [0.9.0] - 2025-03-16
|
13
|
+
|
14
|
+
- removed `persisted_flag_callback_control` support.
|
15
|
+
- Omitted `:private` option from `delegate_attribute` because, assuming the
|
16
|
+
behavior and use cases of `ActiveModel::Attributes.attribute`, making it private is unnecessary.
|
17
|
+
- Added the URL for the sample application to the README
|
18
|
+
|
3
19
|
## [0.8.0] - 2025-02-22
|
4
20
|
|
5
21
|
- changed `persisted_flag_callback_control` default from `false` to `true`.
|
data/README.md
CHANGED
@@ -16,6 +16,7 @@ activemodel (activerecord) form object pattern. it embraces multiple AR models a
|
|
16
16
|
- [Advanced Usage](#advanced-usage)
|
17
17
|
- [`destroy` option](#destroy-option)
|
18
18
|
- [Callback ordering by `#persisted?`](#callback-ordering-by-persisted)
|
19
|
+
- [`#save` with custom context option](#save-with-custom-context-option)
|
19
20
|
- [Links](#links)
|
20
21
|
- [Development](#development)
|
21
22
|
- [Contributing](#contributing)
|
@@ -286,18 +287,11 @@ end
|
|
286
287
|
|
287
288
|
### Callback ordering by `#persisted?`
|
288
289
|
|
289
|
-
The behavior of `(before|after|around)_create` and `(before|after|around)_update` hooks
|
290
|
-
the state of the `persisted_flag_callback_control` setting.
|
291
|
-
default value is `true`, behavior when set to `false` will be removed in the next release.
|
292
|
-
|
293
|
-
When `persisted_flag_callback_control` is set to `true`, it behaves almost like callback control in ActiveRecord.
|
294
|
-
Depending on the evaluation result of `#persisted?`,
|
290
|
+
The behavior of `(before|after|around)_create` and `(before|after|around)_update` hooks depending on the evaluation result of `#persisted?`,
|
295
291
|
either the create-related callbacks or the update-related callbacks will be triggered.
|
296
292
|
|
297
293
|
```ruby
|
298
294
|
class ComposedModel < ActiveRecordCompose::Model
|
299
|
-
self.persisted_flag_callback_control = true # In the future, true will be the default and false will no longer be supported.
|
300
|
-
|
301
295
|
# ...
|
302
296
|
|
303
297
|
before_save { puts 'before_save called!' }
|
@@ -345,48 +339,65 @@ model.save # or `model.update` (the same callbacks will be triggered in all case
|
|
345
339
|
# after_save called!
|
346
340
|
```
|
347
341
|
|
348
|
-
|
349
|
-
the execution of `#create`, `#update`, or `#save` determines which callbacks will be triggered.
|
350
|
-
This behavior will no longer be supported in the next release.
|
342
|
+
### `#save` with custom context option
|
351
343
|
|
352
|
-
|
353
|
-
class ComposedModel < ActiveRecordCompose::Model
|
354
|
-
self.persisted_flag_callback_control = false # Currently defaults to false, but will no longer be supported in the future.
|
344
|
+
The interface remains consistent with standard ActiveModel and ActiveRecord models, so the :context option works with #save.
|
355
345
|
|
356
|
-
|
346
|
+
```ruby
|
347
|
+
composed_model.valid?(:custom_context)
|
357
348
|
|
358
|
-
|
359
|
-
before_create { puts 'before_create called!' }
|
360
|
-
before_update { puts 'before_update called!' }
|
361
|
-
after_save { puts 'after_save called!' }
|
362
|
-
after_create { puts 'after_create called!' }
|
363
|
-
after_update { puts 'after_update called!' }
|
364
|
-
end
|
349
|
+
composed_model.save(context: :custom_context)
|
365
350
|
```
|
366
351
|
|
352
|
+
However, this may not be ideal from a design perspective.
|
353
|
+
If your application requires complex context-specific validations, consider separating models by context.
|
354
|
+
|
367
355
|
```ruby
|
368
|
-
|
356
|
+
class Account < ActiveRecord::Base
|
357
|
+
validates :name, presence: true
|
358
|
+
validates :email, presence: true
|
359
|
+
validates :email, format: { with: /\.edu\z/ }, on: :education
|
360
|
+
end
|
369
361
|
|
370
|
-
|
371
|
-
|
372
|
-
|
362
|
+
class Registration < ActiveRecordCompose::Model
|
363
|
+
def initialize(attributes = {})
|
364
|
+
models.push(@account = Account.new)
|
365
|
+
super(attributes)
|
366
|
+
end
|
373
367
|
|
374
|
-
|
375
|
-
|
376
|
-
# before_create called!
|
377
|
-
# after_create called!
|
378
|
-
# after_save called!
|
368
|
+
attribute :accept, :boolean
|
369
|
+
validates :accept, presence: true, on: :education
|
379
370
|
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
371
|
+
delegate_attribute :name, :email, to: :account
|
372
|
+
|
373
|
+
private
|
374
|
+
|
375
|
+
attr_reader :account
|
376
|
+
end
|
377
|
+
```
|
378
|
+
```ruby
|
379
|
+
r = Registration.new(name: 'foo', email: 'example@example.com', accept: false)
|
380
|
+
r.valid?
|
381
|
+
=> true
|
382
|
+
|
383
|
+
r.valid?(:education)
|
384
|
+
=> false
|
385
|
+
r.errors.map { [_1.attribute, _1.type] }
|
386
|
+
=> [[:email, :invalid], [:accept, :blank]]
|
387
|
+
|
388
|
+
r.email = 'example@example.edu'
|
389
|
+
r.accept = true
|
390
|
+
|
391
|
+
r.valid?(:education)
|
392
|
+
=> true
|
393
|
+
r.save(context: :education)
|
394
|
+
=> true
|
385
395
|
```
|
386
396
|
|
387
397
|
## Links
|
388
398
|
|
389
399
|
- [Smart way to update multiple models simultaneously in Rails](https://dev.to/hamajyotan/smart-way-to-update-multiple-models-simultaneously-in-rails-51b6)
|
400
|
+
- [Sample application as an example](https://github.com/hamajyotan/active_record_compose-example)
|
390
401
|
|
391
402
|
## Development
|
392
403
|
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordCompose
|
4
|
+
module Callbacks
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
define_model_callbacks :save
|
9
|
+
define_model_callbacks :create
|
10
|
+
define_model_callbacks :update
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def with_callbacks(&block) = run_callbacks(:save) { run_callbacks(callback_context, &block) }
|
16
|
+
|
17
|
+
def callback_context = persisted? ? :update : :create
|
18
|
+
end
|
19
|
+
end
|
@@ -45,7 +45,7 @@ module ActiveRecordCompose
|
|
45
45
|
module ClassMethods
|
46
46
|
# Defines the reader and writer for the specified attribute.
|
47
47
|
#
|
48
|
-
def delegate_attribute(*attributes, to:, allow_nil: nil
|
48
|
+
def delegate_attribute(*attributes, to:, allow_nil: nil)
|
49
49
|
delegates = attributes.flat_map do |attribute|
|
50
50
|
reader = attribute.to_s
|
51
51
|
writer = "#{attribute}="
|
@@ -53,7 +53,7 @@ module ActiveRecordCompose
|
|
53
53
|
[reader, writer]
|
54
54
|
end
|
55
55
|
|
56
|
-
delegate(*delegates, to:, allow_nil
|
56
|
+
delegate(*delegates, to:, allow_nil:)
|
57
57
|
self.delegated_attributes = delegated_attributes.to_a + attributes.map { _1.to_s }
|
58
58
|
end
|
59
59
|
end
|
@@ -1,8 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'active_record_compose/callbacks'
|
3
4
|
require 'active_record_compose/composed_collection'
|
4
5
|
require 'active_record_compose/delegate_attribute'
|
5
6
|
require 'active_record_compose/transaction_support'
|
7
|
+
require 'active_record_compose/validations'
|
6
8
|
|
7
9
|
module ActiveRecordCompose
|
8
10
|
using ComposedCollection::PackagePrivate
|
@@ -12,52 +14,10 @@ module ActiveRecordCompose
|
|
12
14
|
include ActiveModel::Validations::Callbacks
|
13
15
|
include ActiveModel::Attributes
|
14
16
|
|
17
|
+
include ActiveRecordCompose::Callbacks
|
15
18
|
include ActiveRecordCompose::DelegateAttribute
|
16
19
|
include ActiveRecordCompose::TransactionSupport
|
17
|
-
|
18
|
-
# This flag controls the callback sequence for models.
|
19
|
-
# The current default value is `true`, behavior when set to `false` will be removed in the next release.
|
20
|
-
#
|
21
|
-
# When `persisted_flag_callback_control` is set to `true`,
|
22
|
-
# the occurrence of callbacks depends on the evaluation result of `#persisted?`.
|
23
|
-
# Additionally, the definition of `#persisted?` itself can be appropriately overridden in subclasses.
|
24
|
-
#
|
25
|
-
# if `#persisted?` returns `false`:
|
26
|
-
# * before_save
|
27
|
-
# * before_create
|
28
|
-
# * after_create
|
29
|
-
# * after_save
|
30
|
-
#
|
31
|
-
# if `#persisted?` returns `true`:
|
32
|
-
# * before_save
|
33
|
-
# * before_update
|
34
|
-
# * after_update
|
35
|
-
# * after_save
|
36
|
-
#
|
37
|
-
# On the other hand, when `persisted_flag_callback_control` is set to `false`,
|
38
|
-
# the invoked methods during saving operations vary depending on the method used.
|
39
|
-
#
|
40
|
-
# when performing `#save` or `#save!`:
|
41
|
-
# * before_save
|
42
|
-
# * after_save
|
43
|
-
#
|
44
|
-
# when performing `#update` or `#update!`:
|
45
|
-
# * before_save
|
46
|
-
# * before_update
|
47
|
-
# * after_update
|
48
|
-
# * after_save
|
49
|
-
#
|
50
|
-
# when performing `#create` or `#create!`:
|
51
|
-
# * before_save
|
52
|
-
# * before_create
|
53
|
-
# * after_create
|
54
|
-
# * after_save
|
55
|
-
#
|
56
|
-
class_attribute :persisted_flag_callback_control, instance_accessor: false, default: true
|
57
|
-
|
58
|
-
define_model_callbacks :save
|
59
|
-
define_model_callbacks :create
|
60
|
-
define_model_callbacks :update
|
20
|
+
prepend ActiveRecordCompose::Validations
|
61
21
|
|
62
22
|
validate :validate_models
|
63
23
|
|
@@ -70,23 +30,16 @@ module ActiveRecordCompose
|
|
70
30
|
#
|
71
31
|
# The save is performed within a single transaction.
|
72
32
|
#
|
33
|
+
# Only the `:validate` option takes effect as it is required internally.
|
34
|
+
# However, we do not recommend explicitly specifying `validate: false` to skip validation.
|
35
|
+
# Additionally, the `:context` option is not accepted.
|
36
|
+
# The need for such a value indicates that operations from multiple contexts are being processed.
|
37
|
+
# If the contexts differ, we recommend separating them into different model definitions.
|
38
|
+
#
|
73
39
|
# @return [Boolean] returns true on success, false on failure.
|
74
|
-
def save
|
75
|
-
return false if invalid?
|
76
|
-
|
40
|
+
def save(**options)
|
77
41
|
with_transaction_returning_status do
|
78
|
-
|
79
|
-
with_callbacks { save_models(bang: false) }
|
80
|
-
else
|
81
|
-
# steep:ignore:start
|
82
|
-
deprecator.warn(
|
83
|
-
'The behavior with `persisted_flag_callback_control` set to `false` will be removed in 0.9.0. ' \
|
84
|
-
'Use `self.persisted_flag_callback_control = true` set to `true`. ' \
|
85
|
-
'(Alternatively, exclude statements that set `false`)',
|
86
|
-
)
|
87
|
-
# steep:ignore:end
|
88
|
-
run_callbacks(:save) { save_models(bang: false) }
|
89
|
-
end
|
42
|
+
with_callbacks { save_models(**options, bang: false) }
|
90
43
|
rescue ActiveRecord::RecordInvalid
|
91
44
|
false
|
92
45
|
end
|
@@ -97,165 +50,35 @@ module ActiveRecordCompose
|
|
97
50
|
#
|
98
51
|
# Saving, like `#save`, is performed within a single transaction.
|
99
52
|
#
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
with_callbacks { save_models(bang: true) }
|
106
|
-
else
|
107
|
-
# steep:ignore:start
|
108
|
-
deprecator.warn(
|
109
|
-
'The behavior with `persisted_flag_callback_control` set to `false` will be removed in 0.9.0. ' \
|
110
|
-
'Use `self.persisted_flag_callback_control = true` set to `true`. ' \
|
111
|
-
'(Alternatively, exclude statements that set `false`)',
|
112
|
-
)
|
113
|
-
# steep:ignore:end
|
114
|
-
run_callbacks(:save) { save_models(bang: true) }
|
115
|
-
end
|
116
|
-
end || raise_on_save_error
|
117
|
-
end
|
118
|
-
|
119
|
-
# Behavior is same to `#save`, but `before_create` and `after_create` hooks fires.
|
120
|
-
#
|
121
|
-
# class ComposedModel < ActiveRecordCompose::Model
|
122
|
-
# # ...
|
123
|
-
#
|
124
|
-
# before_save { puts 'before_save called!' }
|
125
|
-
# before_create { puts 'before_create called!' }
|
126
|
-
# before_update { puts 'before_update called!' }
|
127
|
-
# after_save { puts 'after_save called!' }
|
128
|
-
# after_create { puts 'after_create called!' }
|
129
|
-
# after_update { puts 'after_update called!' }
|
130
|
-
# end
|
53
|
+
# Only the `:validate` option takes effect as it is required internally.
|
54
|
+
# However, we do not recommend explicitly specifying `validate: false` to skip validation.
|
55
|
+
# Additionally, the `:context` option is not accepted.
|
56
|
+
# The need for such a value indicates that operations from multiple contexts are being processed.
|
57
|
+
# If the contexts differ, we recommend separating them into different model definitions.
|
131
58
|
#
|
132
|
-
|
133
|
-
#
|
134
|
-
# model.save
|
135
|
-
# # before_save called!
|
136
|
-
# # after_save called!
|
137
|
-
#
|
138
|
-
# model.create
|
139
|
-
# # before_save called!
|
140
|
-
# # before_create called!
|
141
|
-
# # after_create called!
|
142
|
-
# # after_save called!
|
143
|
-
#
|
144
|
-
# @deprecated
|
145
|
-
def create(attributes = {})
|
146
|
-
if self.class.persisted_flag_callback_control
|
147
|
-
raise '`#create` cannot be called. The context for creation or update is determined by the `#persisted` flag.'
|
148
|
-
end
|
149
|
-
|
150
|
-
# steep:ignore:start
|
151
|
-
deprecator.warn(
|
152
|
-
'The behavior with `persisted_flag_callback_control` set to `false` will be removed in 0.9.0. ' \
|
153
|
-
'Use `self.persisted_flag_callback_control = true` set to `true`. ' \
|
154
|
-
'(Alternatively, exclude statements that set `false`)',
|
155
|
-
)
|
156
|
-
# steep:ignore:end
|
157
|
-
|
158
|
-
assign_attributes(attributes)
|
159
|
-
return false if invalid?
|
160
|
-
|
59
|
+
def save!(**options)
|
161
60
|
with_transaction_returning_status do
|
162
|
-
with_callbacks
|
163
|
-
rescue ActiveRecord::RecordInvalid
|
164
|
-
false
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
# Behavior is same to `#create`, but raises an exception prematurely on failure.
|
169
|
-
#
|
170
|
-
# @deprecated
|
171
|
-
def create!(attributes = {})
|
172
|
-
if self.class.persisted_flag_callback_control
|
173
|
-
raise '`#create` cannot be called. The context for creation or update is determined by the `#persisted` flag.'
|
174
|
-
end
|
175
|
-
|
176
|
-
# steep:ignore:start
|
177
|
-
deprecator.warn(
|
178
|
-
'The behavior with `persisted_flag_callback_control` set to `false` will be removed in 0.9.0. ' \
|
179
|
-
'Use `self.persisted_flag_callback_control = true` set to `true`. ' \
|
180
|
-
'(Alternatively, exclude statements that set `false`)',
|
181
|
-
)
|
182
|
-
# steep:ignore:end
|
183
|
-
|
184
|
-
assign_attributes(attributes)
|
185
|
-
valid? || raise_validation_error
|
186
|
-
|
187
|
-
with_transaction_returning_status do
|
188
|
-
with_callbacks(context: :create) { save_models(bang: true) }
|
61
|
+
with_callbacks { save_models(**options, bang: true) }
|
189
62
|
end || raise_on_save_error
|
190
63
|
end
|
191
64
|
|
192
|
-
#
|
193
|
-
#
|
194
|
-
# class ComposedModel < ActiveRecordCompose::Model
|
195
|
-
# # ...
|
196
|
-
#
|
197
|
-
# before_save { puts 'before_save called!' }
|
198
|
-
# before_create { puts 'before_create called!' }
|
199
|
-
# before_update { puts 'before_update called!' }
|
200
|
-
# after_save { puts 'after_save called!' }
|
201
|
-
# after_create { puts 'after_create called!' }
|
202
|
-
# after_update { puts 'after_update called!' }
|
203
|
-
# end
|
204
|
-
#
|
205
|
-
# model = ComposedModel.new
|
206
|
-
#
|
207
|
-
# model.save
|
208
|
-
# # before_save called!
|
209
|
-
# # after_save called!
|
210
|
-
#
|
211
|
-
# model.update
|
212
|
-
# # before_save called!
|
213
|
-
# # before_update called!
|
214
|
-
# # after_update called!
|
215
|
-
# # after_save called!
|
65
|
+
# Assign attributes and save.
|
216
66
|
#
|
67
|
+
# @return [Boolean] returns true on success, false on failure.
|
217
68
|
def update(attributes = {})
|
218
|
-
assign_attributes(attributes)
|
219
|
-
return false if invalid?
|
220
|
-
|
221
69
|
with_transaction_returning_status do
|
222
|
-
|
223
|
-
|
224
|
-
else
|
225
|
-
# steep:ignore:start
|
226
|
-
deprecator.warn(
|
227
|
-
'The behavior with `persisted_flag_callback_control` set to `false` will be removed in 0.9.0. ' \
|
228
|
-
'Use `self.persisted_flag_callback_control = true` set to `true`. ' \
|
229
|
-
'(Alternatively, exclude statements that set `false`)',
|
230
|
-
)
|
231
|
-
# steep:ignore:end
|
232
|
-
with_callbacks(context: :update) { save_models(bang: false) }
|
233
|
-
end
|
234
|
-
rescue ActiveRecord::RecordInvalid
|
235
|
-
false
|
70
|
+
assign_attributes(attributes)
|
71
|
+
save
|
236
72
|
end
|
237
73
|
end
|
238
74
|
|
239
75
|
# Behavior is same to `#update`, but raises an exception prematurely on failure.
|
240
76
|
#
|
241
77
|
def update!(attributes = {})
|
242
|
-
assign_attributes(attributes)
|
243
|
-
valid? || raise_validation_error
|
244
|
-
|
245
78
|
with_transaction_returning_status do
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
# steep:ignore:start
|
250
|
-
deprecator.warn(
|
251
|
-
'The behavior with `persisted_flag_callback_control` set to `false` will be removed in 0.9.0. ' \
|
252
|
-
'Use `self.persisted_flag_callback_control = true` set to `true`. ' \
|
253
|
-
'(Alternatively, exclude statements that set `false`)',
|
254
|
-
)
|
255
|
-
# steep:ignore:end
|
256
|
-
with_callbacks(context: :update) { save_models(bang: true) }
|
257
|
-
end
|
258
|
-
end || raise_on_save_error
|
79
|
+
assign_attributes(attributes)
|
80
|
+
save!
|
81
|
+
end
|
259
82
|
end
|
260
83
|
|
261
84
|
# Returns true if model is persisted.
|
@@ -271,34 +94,12 @@ module ActiveRecordCompose
|
|
271
94
|
|
272
95
|
def models = @__models ||= ActiveRecordCompose::ComposedCollection.new(self)
|
273
96
|
|
274
|
-
def
|
275
|
-
models.__wrapped_models.
|
276
|
-
end
|
277
|
-
|
278
|
-
def with_callbacks(context: nil, &block)
|
279
|
-
run_callbacks(:save) { run_callbacks(callback_context(context:), &block) }
|
280
|
-
end
|
281
|
-
|
282
|
-
def callback_context(context: nil)
|
283
|
-
context || (persisted? ? :update : :create)
|
284
|
-
end
|
285
|
-
|
286
|
-
def save_models(bang:)
|
287
|
-
models.__wrapped_models.all? { bang ? _1.save! : _1.save }
|
97
|
+
def save_models(bang:, **options)
|
98
|
+
models.__wrapped_models.all? { bang ? _1.save!(**options, validate: false) : _1.save(**options, validate: false) }
|
288
99
|
end
|
289
100
|
|
290
|
-
def raise_validation_error = raise ActiveRecord::RecordInvalid, self
|
291
|
-
|
292
101
|
def raise_on_save_error = raise ActiveRecord::RecordNotSaved.new(raise_on_save_error_message, self)
|
293
102
|
|
294
103
|
def raise_on_save_error_message = 'Failed to save the model.'
|
295
|
-
|
296
|
-
def deprecator
|
297
|
-
if ActiveRecord.respond_to?(:deprecator)
|
298
|
-
ActiveRecord.deprecator # steep:ignore
|
299
|
-
else # for rails 7.0.x or lower
|
300
|
-
ActiveSupport::Deprecation
|
301
|
-
end
|
302
|
-
end
|
303
104
|
end
|
304
105
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordCompose
|
4
|
+
using ComposedCollection::PackagePrivate
|
5
|
+
|
6
|
+
module Validations
|
7
|
+
def save(**options)
|
8
|
+
perform_validations(options) ? super : false
|
9
|
+
end
|
10
|
+
|
11
|
+
def save!(**options)
|
12
|
+
perform_validations(options) ? super : raise_validation_error
|
13
|
+
end
|
14
|
+
|
15
|
+
def valid?(context = nil) = context_for_override_validation.with_override(context) { super }
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def validate_models
|
20
|
+
context = override_validation_context
|
21
|
+
models.__wrapped_models.lazy.select { _1.invalid?(context) }.each { errors.merge!(_1) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def perform_validations(options)
|
25
|
+
options[:validate] == false || valid?(options[:context])
|
26
|
+
end
|
27
|
+
|
28
|
+
def raise_validation_error = raise ActiveRecord::RecordInvalid, self
|
29
|
+
|
30
|
+
def context_for_override_validation
|
31
|
+
@context_for_override_validation ||= OverrideValidationContext.new
|
32
|
+
end
|
33
|
+
|
34
|
+
def override_validation_context = context_for_override_validation.context
|
35
|
+
|
36
|
+
class OverrideValidationContext
|
37
|
+
attr_reader :context
|
38
|
+
|
39
|
+
def with_override(context)
|
40
|
+
@context, original = context, @context
|
41
|
+
yield
|
42
|
+
ensure
|
43
|
+
@context = original # steep:ignore
|
44
|
+
end
|
45
|
+
end
|
46
|
+
private_constant :OverrideValidationContext
|
47
|
+
end
|
48
|
+
end
|
@@ -60,7 +60,7 @@ module ActiveRecordCompose
|
|
60
60
|
# Whether save or destroy is executed depends on the value of `#destroy_context?`.
|
61
61
|
#
|
62
62
|
# @return [Boolean] returns true on success, false on failure.
|
63
|
-
def save
|
63
|
+
def save(**options)
|
64
64
|
# While errors caused by the type check are avoided,
|
65
65
|
# it is important to note that an error can still occur
|
66
66
|
# if `#destroy_context?` returns true but ar_like does not implement `#destroy`.
|
@@ -70,14 +70,14 @@ module ActiveRecordCompose
|
|
70
70
|
m.destroy
|
71
71
|
else
|
72
72
|
# @type var m: ActiveRecordCompose::_ARLike
|
73
|
-
m.save
|
73
|
+
m.save(**options)
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
77
77
|
# Execute save or destroy. Unlike #save, an exception is raises on failure.
|
78
78
|
# Whether save or destroy is executed depends on the value of `#destroy_context?`.
|
79
79
|
#
|
80
|
-
def save!
|
80
|
+
def save!(**options)
|
81
81
|
# While errors caused by the type check are avoided,
|
82
82
|
# it is important to note that an error can still occur
|
83
83
|
# if `#destroy_context?` returns true but ar_like does not implement `#destroy`.
|
@@ -87,15 +87,15 @@ module ActiveRecordCompose
|
|
87
87
|
m.destroy!
|
88
88
|
else
|
89
89
|
# @type var model: ActiveRecordCompose::_ARLike
|
90
|
-
m.save!
|
90
|
+
m.save!(**options)
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
94
94
|
# @return [Boolean]
|
95
|
-
def invalid? =
|
95
|
+
def invalid?(context = nil) = !valid?(context)
|
96
96
|
|
97
97
|
# @return [Boolean]
|
98
|
-
def valid? =
|
98
|
+
def valid?(context = nil) = destroy_context? || model.valid?(context)
|
99
99
|
|
100
100
|
# Returns true if equivalent.
|
101
101
|
#
|
@@ -1,4 +1,15 @@
|
|
1
1
|
module ActiveRecordCompose
|
2
|
+
module Callbacks
|
3
|
+
include ActiveModel::Model
|
4
|
+
include ActiveModel::Validations::Callbacks
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
extend ActiveModel::Callbacks
|
7
|
+
|
8
|
+
private
|
9
|
+
def with_callbacks: { () -> bool } -> bool
|
10
|
+
def callback_context: -> (:create | :update)
|
11
|
+
end
|
12
|
+
|
2
13
|
class ComposedCollection
|
3
14
|
def initialize: (Model) -> void
|
4
15
|
|
@@ -24,7 +35,7 @@ module ActiveRecordCompose
|
|
24
35
|
def delegated_attributes: () -> Array[String]
|
25
36
|
|
26
37
|
module ClassMethods : Module
|
27
|
-
def delegate_attribute: (*untyped methods, to: untyped, ?allow_nil: untyped
|
38
|
+
def delegate_attribute: (*untyped methods, to: untyped, ?allow_nil: untyped?) -> untyped
|
28
39
|
def delegated_attributes: () -> Array[String]
|
29
40
|
def delegated_attributes=: (Array[String]) -> untyped
|
30
41
|
end
|
@@ -35,8 +46,13 @@ module ActiveRecordCompose
|
|
35
46
|
extend DelegateAttribute::ClassMethods
|
36
47
|
include TransactionSupport
|
37
48
|
extend TransactionSupport::ClassMethods
|
49
|
+
include ActiveRecordCompose::Callbacks
|
38
50
|
|
39
51
|
@__models: ComposedCollection
|
52
|
+
|
53
|
+
private
|
54
|
+
def validate_models: -> void
|
55
|
+
def override_validation_context: -> validation_context
|
40
56
|
end
|
41
57
|
|
42
58
|
module TransactionSupport
|
@@ -54,14 +70,37 @@ module ActiveRecordCompose
|
|
54
70
|
end
|
55
71
|
end
|
56
72
|
|
73
|
+
module Validations : Model
|
74
|
+
def save: (**untyped options) -> bool
|
75
|
+
def save!: (**untyped options) -> untyped
|
76
|
+
def valid?: (?validation_context context) -> bool
|
77
|
+
|
78
|
+
@context_for_override_validation: OverrideValidationContext
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def perform_validations: (::Hash[untyped, untyped]) -> bool
|
83
|
+
def raise_validation_error: -> bot
|
84
|
+
def context_for_override_validation: -> OverrideValidationContext
|
85
|
+
def override_validation_context: -> validation_context
|
86
|
+
|
87
|
+
class OverrideValidationContext
|
88
|
+
@context: validation_context
|
89
|
+
|
90
|
+
attr_reader context: validation_context
|
91
|
+
|
92
|
+
def with_override: [T] (validation_context) { () -> T } -> T
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
57
96
|
class WrappedModel
|
58
97
|
def initialize: (ar_like, ?destroy: (bool | destroy_context_type), ?if: (nil | condition_type)) -> void
|
59
98
|
def destroy_context?: -> bool
|
60
99
|
def ignore?: -> bool
|
61
|
-
def save: -> bool
|
62
|
-
def save!: -> untyped
|
63
|
-
def invalid?: -> bool
|
64
|
-
def valid?: -> bool
|
100
|
+
def save: (**untyped options) -> bool
|
101
|
+
def save!: (**untyped options) -> untyped
|
102
|
+
def invalid?: (?validation_context context) -> bool
|
103
|
+
def valid?: (?validation_context context) -> bool
|
65
104
|
def is_a?: (untyped) -> bool
|
66
105
|
def ==: (untyped) -> bool
|
67
106
|
|
@@ -5,27 +5,29 @@ module ActiveRecordCompose
|
|
5
5
|
VERSION: String
|
6
6
|
|
7
7
|
interface _ARLike
|
8
|
-
def save: -> bool
|
9
|
-
def save!: -> untyped
|
10
|
-
def invalid?: -> bool
|
11
|
-
def valid?: -> bool
|
8
|
+
def save: (**untyped options) -> bool
|
9
|
+
def save!: (**untyped options) -> untyped
|
10
|
+
def invalid?: (?validation_context context) -> bool
|
11
|
+
def valid?: (?validation_context context) -> bool
|
12
12
|
def errors: -> untyped
|
13
13
|
def is_a?: (untyped) -> bool
|
14
14
|
def ==: (untyped) -> bool
|
15
15
|
end
|
16
16
|
interface _ARLikeWithDestroy
|
17
|
-
def save: -> bool
|
18
|
-
def save!: -> untyped
|
17
|
+
def save: (**untyped options) -> bool
|
18
|
+
def save!: (**untyped options) -> untyped
|
19
19
|
def destroy: -> bool
|
20
20
|
def destroy!: -> untyped
|
21
|
-
def invalid?: -> bool
|
22
|
-
def valid?: -> bool
|
21
|
+
def invalid?: (?validation_context context) -> bool
|
22
|
+
def valid?: (?validation_context context) -> bool
|
23
23
|
def errors: -> untyped
|
24
24
|
def is_a?: (untyped) -> bool
|
25
25
|
def ==: (untyped) -> bool
|
26
26
|
end
|
27
27
|
type ar_like = (_ARLike | _ARLikeWithDestroy)
|
28
28
|
|
29
|
+
type validation_context = nil | Symbol | Array[Symbol]
|
30
|
+
|
29
31
|
type condition[T] = Symbol | ^(T) [self: T] -> boolish
|
30
32
|
type callback[T] = Symbol | ^(T) [self: T] -> void
|
31
33
|
type around_callback[T] = Symbol | ^(T, Proc) [self: T] -> void
|
@@ -69,30 +71,21 @@ module ActiveRecordCompose
|
|
69
71
|
def self.after_commit: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> void } -> void
|
70
72
|
def self.after_rollback: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> void } -> void
|
71
73
|
|
72
|
-
def self.
|
73
|
-
def self.persisted_flag_callback_control=: (boolish) -> untyped
|
74
|
-
|
75
|
-
def self.delegate_attribute: (*untyped methods, to: untyped, ?allow_nil: untyped?, ?private: untyped?) -> untyped
|
74
|
+
def self.delegate_attribute: (*untyped methods, to: untyped, ?allow_nil: untyped?) -> untyped
|
76
75
|
def self.connection: -> ActiveRecord::ConnectionAdapters::AbstractAdapter
|
77
76
|
def self.lease_connection: -> ActiveRecord::ConnectionAdapters::AbstractAdapter
|
78
77
|
def self.with_connection: [T] () { () -> T } -> T
|
79
78
|
|
80
79
|
def initialize: (?Hash[attribute_name, untyped]) -> void
|
81
|
-
def save: -> bool
|
82
|
-
def save!: -> untyped
|
83
|
-
def create: (?Hash[attribute_name, untyped]) -> bool
|
84
|
-
def create!: (?Hash[attribute_name, untyped]) -> untyped
|
80
|
+
def save: (**untyped options) -> bool
|
81
|
+
def save!: (**untyped options) -> untyped
|
85
82
|
def update: (?Hash[attribute_name, untyped]) -> bool
|
86
83
|
def update!: (?Hash[attribute_name, untyped]) -> untyped
|
87
84
|
def id: -> untyped
|
88
85
|
|
89
86
|
private
|
90
87
|
def models: -> ComposedCollection
|
91
|
-
def
|
92
|
-
def callback_context: (?context: (nil | :create | :update)) -> (:create | :update)
|
93
|
-
def validate_models: -> void
|
94
|
-
def save_models: (bang: bool) -> bool
|
95
|
-
def raise_validation_error: -> bot
|
88
|
+
def save_models: (bang: bool, **untyped options) -> bool
|
96
89
|
def raise_on_save_error: -> bot
|
97
90
|
def raise_on_save_error_message: -> String
|
98
91
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_record_compose
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- hamajyotan
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-04-07 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: activerecord
|
@@ -37,10 +37,12 @@ files:
|
|
37
37
|
- LICENSE.txt
|
38
38
|
- README.md
|
39
39
|
- lib/active_record_compose.rb
|
40
|
+
- lib/active_record_compose/callbacks.rb
|
40
41
|
- lib/active_record_compose/composed_collection.rb
|
41
42
|
- lib/active_record_compose/delegate_attribute.rb
|
42
43
|
- lib/active_record_compose/model.rb
|
43
44
|
- lib/active_record_compose/transaction_support.rb
|
45
|
+
- lib/active_record_compose/validations.rb
|
44
46
|
- lib/active_record_compose/version.rb
|
45
47
|
- lib/active_record_compose/wrapped_model.rb
|
46
48
|
- sig/_internal/package_private.rbs
|
@@ -52,7 +54,7 @@ metadata:
|
|
52
54
|
homepage_uri: https://github.com/hamajyotan/active_record_compose
|
53
55
|
source_code_uri: https://github.com/hamajyotan/active_record_compose
|
54
56
|
changelog_uri: https://github.com/hamajyotan/active_record_compose/blob/main/CHANGELOG.md
|
55
|
-
documentation_uri: https://www.rubydoc.info/gems/active_record_compose/0.
|
57
|
+
documentation_uri: https://www.rubydoc.info/gems/active_record_compose/0.10.0
|
56
58
|
rubygems_mfa_required: 'true'
|
57
59
|
rdoc_options: []
|
58
60
|
require_paths:
|