active_record_compose 0.6.2 → 0.7.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/CHANGELOG.md +16 -0
- data/README.md +238 -163
- data/lib/active_record_compose/{inner_model_collection.rb → composed_collection.rb} +14 -19
- data/lib/active_record_compose/model.rb +80 -13
- data/lib/active_record_compose/version.rb +1 -1
- data/lib/active_record_compose/{inner_model.rb → wrapped_model.rb} +29 -5
- data/sig/_internal/package_private.rbs +40 -40
- data/sig/active_record_compose.rbs +46 -11
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ab0bb2d4ff8813ad8fb9a4830c2b1d3887aaa29702f11561c9c8624719a5082f
|
4
|
+
data.tar.gz: a3fcfb870aaf5a0825a218556423cc1af16644b41ec22f0b4d656cb42cd33790
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5a1ea42bd83a986437faac38b2543ee48aff7f2003afa6267a7adcd325db1bbb0506f4f1f25e2aaa13b733df7956ebd820627821f19101fc36dd4f046a51c43
|
7
|
+
data.tar.gz: 4a9cc98fddbcf1068720387207469cb527b96e536a9f109a6174b5a439daa383f54a1625e916c731d01883cf360c6e01281858a238796b5cb64b92665c2405c3
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.7.0] - 2025-02-12
|
4
|
+
|
5
|
+
- rename ActiveRecordCompose::InnerModel to ActiveRecordCompose::WrappedModel
|
6
|
+
- rename ActiveRecordCompose::InnerModelCollection to ActiveRecordCompose::ComposedCollection
|
7
|
+
- A new callback control flag, `persisted_flag_callback_control`, has been defined.
|
8
|
+
Currently, the default value is false, which does not change the existing behavior, but it will be deprecated in the future.
|
9
|
+
When the flag is set to true, the behavior will be almost the same as the callback sequence in ActiveRecord.
|
10
|
+
(https://github.com/hamajyotan/active_record_compose/issues/11)
|
11
|
+
|
12
|
+
## [0.6.3] - 2025-01-31
|
13
|
+
|
14
|
+
- fix: type error in `ActiveRecordCompose::Model` subclass definitions.
|
15
|
+
- fixed type errors in subclass callback definitions, etc.
|
16
|
+
- doc: more detailed gem desciption.
|
17
|
+
- rewrite readme.
|
18
|
+
|
3
19
|
## [0.6.2] - 2025-01-04
|
4
20
|
|
5
21
|
- fix: `delegate_attribute` defined in a subclass had an unintended side effect on the superclass.
|
data/README.md
CHANGED
@@ -1,9 +1,35 @@
|
|
1
1
|
# ActiveRecordCompose
|
2
2
|
|
3
|
-
|
3
|
+
activemodel (activerecord) form object pattern. it embraces multiple AR models and provides a transparent interface as if they were a single model.
|
4
4
|
|
5
5
|

|
6
6
|
|
7
|
+
## Table of Contents
|
8
|
+
|
9
|
+
- [Motivation](#motivation)
|
10
|
+
- [Installation](#installation)
|
11
|
+
- [Usage](#usage)
|
12
|
+
- [Basic usage](#basic-usage)
|
13
|
+
- [`delegate_attribute`](#delegate_attribute)
|
14
|
+
- [Promotion to model from AR-model errors](#promotion-to-model-from-ar-model-errors)
|
15
|
+
- [I18n](#i18n)
|
16
|
+
- [Advanced Usage](#advanced-usage)
|
17
|
+
- [`destroy` option](#destroy-option)
|
18
|
+
- [Callback ordering by `#save`, `#create` and `#update`](#callback-ordering-by-save-create-and-update)
|
19
|
+
- [Links](#links)
|
20
|
+
- [Development](#development)
|
21
|
+
- [Contributing](#contributing)
|
22
|
+
- [License](#license)
|
23
|
+
- [Code of Conduct](#code-of-conduct)
|
24
|
+
|
25
|
+
## Motivation
|
26
|
+
|
27
|
+
`ActiveRecord::Base` is responsible for persisting data to the database, and by defining validations and callbacks, it allows you to structure your use cases effectively. This is a crucial component of Rails. However, when a specific model starts being updated by multiple different use cases, validations and callbacks may require conditions such as `on: :context` or `save(validate: false)`. As a result, the model needs to account for multiple dependent use cases, leading to increased complexity.
|
28
|
+
|
29
|
+
In such cases, `ActiveModel::Model` becomes useful. It provides the same interfaces as `ActiveRecord::Base`, such as `attribute` and `errors`, allowing it to be used similarly to an ActiveRecord model. Additionally, it enables you to define validations and callbacks within a limited context, preventing conditions related to multiple contexts from being embedded in `ActiveRecord::Base` validations and callbacks. This results in simpler, more maintainable code.
|
30
|
+
|
31
|
+
This gem is built on `ActiveModel::Model` and acts as a first-class model within the Rails context. It provides methods for performing batch and safe updates on 0..N encapsulated models, enables transparent attribute access, and facilitates access to error information.
|
32
|
+
|
7
33
|
## Installation
|
8
34
|
|
9
35
|
To install `active_record_compose`, just put this line in your Gemfile:
|
@@ -20,149 +46,186 @@ $ bundle
|
|
20
46
|
|
21
47
|
## Usage
|
22
48
|
|
23
|
-
###
|
49
|
+
### Basic usage
|
24
50
|
|
25
|
-
|
26
|
-
For example, the following cases are supported
|
51
|
+
(Below, it is assumed that there are two AR model definitions, `Account` and `Profile`, for the sake of explanation.)
|
27
52
|
|
28
|
-
|
53
|
+
```ruby
|
54
|
+
class Account < ApplicationRecord
|
55
|
+
has_one :profile # can work without `autosave:true`
|
56
|
+
validates :name, :email, presence: true
|
57
|
+
end
|
58
|
+
|
59
|
+
class Profile < ApplicationRecord
|
60
|
+
belongs_to :account
|
61
|
+
validates :firstname, :lastname, :age, presence: true
|
62
|
+
end
|
63
|
+
```
|
29
64
|
|
30
|
-
|
31
|
-
However, if a callback is written directly in the AR model, it is necessary to consider the case where the model is updated in other contexts.
|
32
|
-
In particular, if you frequently create with test data, previously unnecessary processing will be called at every point of creation.
|
33
|
-
In addition to cost, the more complicated the callbacks you write, the more difficult it will be to create even a single test data.
|
34
|
-
If the callbacks are written in a class that inherits from `ActiveRecordCompose::Model`, the AR model itself will not be polluted, and the context can be limited.
|
65
|
+
Here is an example of designing a model that updates both Account and Profile at the same time, using `ActiveRecordCompose::Model`.
|
35
66
|
|
36
67
|
```ruby
|
37
|
-
class
|
38
|
-
def initialize
|
39
|
-
@account =
|
40
|
-
|
68
|
+
class UserRegistration < ActiveRecordCompose::Model
|
69
|
+
def initialize
|
70
|
+
@account = Account.new
|
71
|
+
@profile = @account.build_profile
|
41
72
|
|
42
|
-
#
|
43
|
-
|
73
|
+
super() # Don't forget to call `super()`
|
74
|
+
# RuboCop's Lint/MissingSuper cop assists in addressing this.
|
75
|
+
|
76
|
+
models << account << profile
|
77
|
+
# Alternatively, it can also be written as follows:
|
78
|
+
# models.push(account)
|
79
|
+
# models.push(profile)
|
44
80
|
end
|
45
81
|
|
46
|
-
#
|
47
|
-
|
48
|
-
delegate :id, :persisted?, to: :account
|
82
|
+
# Attribute declarations using ActiveModel::Attributes are supported.
|
83
|
+
attribute :terms_of_service, :boolean
|
49
84
|
|
50
|
-
#
|
51
|
-
|
85
|
+
# You can provide validation definitions limited to UserRegistration.
|
86
|
+
# Instead of directly defining validations for Account or Profile, such
|
87
|
+
# as `on: :create` in the context, the model itself explains the context.
|
88
|
+
validates :terms_of_service, presence: true
|
89
|
+
validates :email, confirmation: true
|
90
|
+
|
91
|
+
# You can provide callback definitions limited to UserRegistration.
|
92
|
+
# For example, if this is written directly in the AR model, you need to consider
|
93
|
+
# callback control for data generation during tests and other situations.
|
94
|
+
after_commit :send_email_message
|
52
95
|
|
53
|
-
#
|
54
|
-
#
|
55
|
-
|
96
|
+
# UserRegistration behaves as if it has attributes like email, name, and age
|
97
|
+
# For example, `email` is delegated to `account.email`,
|
98
|
+
# and `email=` is delegated to `account.email=`.
|
99
|
+
delegate_attribute :name, :email, to: :account
|
100
|
+
delegate_attribute :firstname, :lastname, :age, to: :profile
|
56
101
|
|
57
102
|
private
|
58
103
|
|
59
|
-
attr_reader :account
|
104
|
+
attr_reader :account, :profile
|
60
105
|
|
61
|
-
def
|
106
|
+
def send_email_message
|
62
107
|
SendEmailConfirmationJob.perform_later(account)
|
63
108
|
end
|
64
109
|
end
|
65
110
|
```
|
66
111
|
|
67
|
-
|
68
|
-
|
69
|
-
Validates are basically fired in all cases where the model is manipulated. To avoid this, use `on: :create`, etc. to make it work only in specific cases.
|
70
|
-
and so on to work only in specific cases. This allows you to create context-sensitive validations for the same model operation.
|
71
|
-
However, this is the first step in making the model more and more complex. You will have to go around with `update(context: :foo)`
|
72
|
-
In some cases, you may have to go around with the context option, such as `update(context: :foo)` everywhere.
|
73
|
-
By writing validates in a class that extends `ActiveRecordCompose::Model`, you can define context-specific validation without polluting the AR model itself.
|
112
|
+
The above model is used as follows.
|
74
113
|
|
75
114
|
```ruby
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
115
|
+
registration = UserRegistration.new
|
116
|
+
|
117
|
+
# Atomically update Account and Profile.
|
118
|
+
registration.update!(
|
119
|
+
name: "foo",
|
120
|
+
email: "bar@example.com",
|
121
|
+
firstname: "taro",
|
122
|
+
lastname: "yamada",
|
123
|
+
age: 18,
|
124
|
+
email_confirmation: "bar@example.com",
|
125
|
+
terms_of_service: true,
|
126
|
+
)
|
127
|
+
```
|
82
128
|
|
83
|
-
|
84
|
-
delegate_attribute :name, :email, to: :account
|
129
|
+
By executing `save`, you can simultaneously update multiple models added to `models`. Furthermore, the save operation is performed within a database transaction, ensuring atomic processing.
|
85
130
|
|
86
|
-
|
87
|
-
|
131
|
+
```ruby
|
132
|
+
user_registration.save # Atomically update Account and Profile.
|
133
|
+
# In case of failure, a false value is returned.
|
134
|
+
user_registration.save! # With the bang method,
|
135
|
+
# an exception is raised in case of failure.
|
136
|
+
```
|
88
137
|
|
89
|
-
|
138
|
+
### `delegate_attribute`
|
90
139
|
|
91
|
-
|
92
|
-
|
93
|
-
# Validity of the domain part of the e-mail address is also checked only when registering an account.
|
94
|
-
def require_valid_domain
|
95
|
-
e = ValidEmail2::Address.new(email.to_s)
|
96
|
-
unless e.valid?
|
97
|
-
errors.add(:email, :invalid_format)
|
98
|
-
return
|
99
|
-
end
|
100
|
-
unless e.valid_mx?
|
101
|
-
errors.add(:email, :invalid_domain)
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
```
|
140
|
+
In many cases, the composed models have attributes that need to be assigned before saving. `ActiveRecordCompose::Model` provides `delegate_attribute`, allowing transparent access to those attributes."
|
106
141
|
|
107
142
|
```ruby
|
108
|
-
|
109
|
-
account.
|
143
|
+
# UserRegistration behaves as if it has attributes like email, name, and age
|
144
|
+
# For example, `email` is delegated to `account.email`,
|
145
|
+
# and `email=` is delegated to `account.email=`.
|
146
|
+
delegate_attribute :name, :email, to: :account
|
147
|
+
delegate_attribute :firstname, :lastname, :age, to: :profile
|
148
|
+
```
|
149
|
+
|
150
|
+
Attributes defined with `.delegate_attribute` can be accessed through `#attributes` in the same way as the original attributes defined with `.attribute`.
|
110
151
|
|
111
|
-
|
112
|
-
|
152
|
+
```ruby
|
153
|
+
registration = UserRegistration.new
|
154
|
+
registration.name = "foo"
|
155
|
+
registration.terms_of_service = true
|
156
|
+
|
157
|
+
# Not only the email_confirmation defined with attribute,
|
158
|
+
# but also the attributes defined with delegate_attribute are included.
|
159
|
+
registration.attributes
|
160
|
+
# => {
|
161
|
+
# "terms_of_service" => true,
|
162
|
+
# "email" => nil,
|
163
|
+
# "name" => "foo",
|
164
|
+
# "age" => nil,
|
165
|
+
# "firstname" => nil,
|
166
|
+
# "lastname" => nil
|
167
|
+
# }
|
113
168
|
```
|
114
169
|
|
115
|
-
|
170
|
+
### Promotion to model from AR-model errors
|
116
171
|
|
117
|
-
|
118
|
-
There are ways to update related models at the same time. The operation is safe because it is transactional.
|
119
|
-
`ActiveRecordCompose::Model` has an internal array called models. By adding an AR object to this models array
|
120
|
-
By adding an AR object to the models, the object stored in the models provides an atomic update operation via #save.
|
172
|
+
When saving a composed model with `#save`, models that are not valid with `#valid?` will obviously not be saved. As a result, the #errors information can be accessed from `ActiveRecordCompose::Model`.
|
121
173
|
|
122
174
|
```ruby
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
175
|
+
user_registration = UserRegistration.new
|
176
|
+
user_registration.email = "foo@example.com"
|
177
|
+
user_registration.email_confirmation = "BAZ@example.com"
|
178
|
+
user_registration.age = 18
|
179
|
+
user_registration.terms_of_service = true
|
180
|
+
|
181
|
+
user_registration.save
|
182
|
+
#=> false
|
183
|
+
|
184
|
+
user_registration.errors.to_a
|
185
|
+
# => [
|
186
|
+
# "Name can't be blank",
|
187
|
+
# "Firstname can't be blank",
|
188
|
+
# "Lastname can't be blank",
|
189
|
+
# "Email confirmation doesn't match Email"
|
190
|
+
# ]
|
191
|
+
```
|
130
192
|
|
131
|
-
|
132
|
-
delegate_attribute :name, :email, to: :account
|
133
|
-
delegate_attribute :firstname, :lastname, :age, to: :profile
|
193
|
+
### I18n
|
134
194
|
|
135
|
-
|
195
|
+
When the `#save!` operation raises an `ActiveRecord::RecordInvalid` exception, it is necessary to have pre-existing locale definitions in order to construct i18n information correctly.
|
196
|
+
The specific keys required are `activemodel.errors.messages.record_invalid` or `errors.messages.record_invalid`.
|
136
197
|
|
137
|
-
|
138
|
-
|
198
|
+
(Replace `en` as appropriate in the context.)
|
199
|
+
|
200
|
+
```yaml
|
201
|
+
en:
|
202
|
+
activemodel:
|
203
|
+
errors:
|
204
|
+
messages:
|
205
|
+
record_invalid: 'Validation failed: %{errors}'
|
139
206
|
```
|
140
207
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
email: 'foo@example.com',
|
149
|
-
firstname: 'bar',
|
150
|
-
lastname: 'baz',
|
151
|
-
age: 36,
|
152
|
-
)
|
153
|
-
account_registration.save!
|
154
|
-
|
155
|
-
Account.count #=> 1
|
156
|
-
Profile.count #=> 1
|
208
|
+
Alternatively, the following definition is also acceptable:
|
209
|
+
|
210
|
+
```yaml
|
211
|
+
en:
|
212
|
+
errors:
|
213
|
+
messages:
|
214
|
+
record_invalid: 'Validation failed: %{errors}'
|
157
215
|
```
|
158
216
|
|
159
|
-
|
217
|
+
## Advanced Usage
|
218
|
+
|
219
|
+
### `destroy` option
|
220
|
+
|
221
|
+
By adding to the models array while specifying destroy: true, you can perform a delete instead of a save on the model at #save time.
|
160
222
|
|
161
223
|
```ruby
|
162
224
|
class AccountResignation < ActiveRecordCompose::Model
|
163
225
|
def initialize(account)
|
164
226
|
@account = account
|
165
|
-
@profile = account.profile
|
227
|
+
@profile = account.profile || account.build_profile
|
228
|
+
super()
|
166
229
|
models.push(account)
|
167
230
|
models.push(profile, destroy: true)
|
168
231
|
end
|
@@ -178,7 +241,6 @@ class AccountResignation < ActiveRecordCompose::Model
|
|
178
241
|
end
|
179
242
|
end
|
180
243
|
```
|
181
|
-
|
182
244
|
```ruby
|
183
245
|
account = Account.last
|
184
246
|
|
@@ -197,72 +259,44 @@ Conditional destroy (or save) can be written like this.
|
|
197
259
|
|
198
260
|
```ruby
|
199
261
|
class AccountRegistration < ActiveRecordCompose::Model
|
200
|
-
def initialize(account
|
262
|
+
def initialize(account)
|
201
263
|
@account = account
|
202
264
|
@profile = account.profile || account.build_profile
|
203
|
-
super(
|
265
|
+
super()
|
204
266
|
models.push(account)
|
205
|
-
|
267
|
+
|
268
|
+
# destroy if all blank, otherwise save.
|
269
|
+
models.push(profile, destroy: :profile_field_is_blank?)
|
270
|
+
# Alternatively, it can also be written as follows:
|
271
|
+
# models.push(profile, destroy: -> { profile_field_is_blank? })
|
206
272
|
end
|
207
273
|
|
208
|
-
delegate_attribute :
|
209
|
-
delegate_attribute :
|
274
|
+
delegate_attribute :email, to: :account
|
275
|
+
delegate_attribute :name, :age, to: :profile
|
210
276
|
|
211
277
|
private
|
212
278
|
|
213
279
|
attr_reader :account, :profile
|
214
280
|
|
215
|
-
def
|
216
|
-
|
217
|
-
```
|
218
|
-
|
219
|
-
### `delegate_attribute`
|
220
|
-
|
221
|
-
It provides a macro description that expresses access to the attributes of the AR model through delegation.
|
222
|
-
|
223
|
-
```ruby
|
224
|
-
class AccountRegistration < ActiveRecordCompose::Model
|
225
|
-
def initialize(account, attributes = {})
|
226
|
-
@account = account
|
227
|
-
super(attributes)
|
228
|
-
models.push(account)
|
281
|
+
def profile_field_is_blank?
|
282
|
+
firstname.blank? && lastname.blank? && age.blank?
|
229
283
|
end
|
230
|
-
|
231
|
-
attribute :original_attribute, :string, default: 'qux'
|
232
|
-
delegate_attribute :name, to: :account
|
233
|
-
|
234
|
-
private
|
235
|
-
|
236
|
-
attr_reader :account
|
237
284
|
end
|
238
285
|
```
|
239
286
|
|
240
|
-
```ruby
|
241
|
-
account = Account.new
|
242
|
-
account.name = 'foo'
|
243
|
-
|
244
|
-
registration = AccountRegistration.new(account)
|
245
|
-
registration.name #=> 'foo'
|
246
|
-
|
247
|
-
registration.name = 'bar'
|
248
|
-
account.name #=> 'bar'
|
249
|
-
```
|
250
|
-
|
251
|
-
Overrides `#attributes`, merging attributes defined with `delegate_attribute` in addition to the original attributes.
|
252
|
-
|
253
|
-
```
|
254
|
-
account.attributes #=> {'original_attribute' => 'qux', 'name' => 'bar'}
|
255
|
-
```
|
256
|
-
|
257
287
|
### Callback ordering by `#save`, `#create` and `#update`.
|
258
288
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
289
|
+
The behavior of `(before|after|around)_create` and `(before|after|around)_update` hooks depends on
|
290
|
+
the state of the `persisted_flag_callback_control` setting.
|
291
|
+
|
292
|
+
When `persisted_flag_callback_control` is set to false,
|
293
|
+
the execution of `#create`, `#update`, or `#save` determines which callbacks will be triggered.
|
294
|
+
Currently, the default value is `false`, but it will no longer be supported in the future.
|
263
295
|
|
264
296
|
```ruby
|
265
297
|
class ComposedModel < ActiveRecordCompose::Model
|
298
|
+
self.persisted_flag_callback_control = false # Currently defaults to false, but will no longer be supported in the future.
|
299
|
+
|
266
300
|
# ...
|
267
301
|
|
268
302
|
before_save { puts 'before_save called!' }
|
@@ -294,30 +328,70 @@ model.update
|
|
294
328
|
# after_save called!
|
295
329
|
```
|
296
330
|
|
297
|
-
|
331
|
+
When `persisted_flag_callback_control` is set to `true`, it behaves almost like callback control in ActiveRecord.
|
332
|
+
This behavior will be the default in the future.
|
298
333
|
|
299
|
-
|
300
|
-
|
334
|
+
```ruby
|
335
|
+
class ComposedModel < ActiveRecordCompose::Model
|
336
|
+
self.persisted_flag_callback_control = true # In the future, true will be the default and false will no longer be supported.
|
301
337
|
|
302
|
-
|
338
|
+
# ...
|
303
339
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
340
|
+
before_save { puts 'before_save called!' }
|
341
|
+
before_create { puts 'before_create called!' }
|
342
|
+
before_update { puts 'before_update called!' }
|
343
|
+
after_save { puts 'after_save called!' }
|
344
|
+
after_create { puts 'after_create called!' }
|
345
|
+
after_update { puts 'after_update called!' }
|
346
|
+
|
347
|
+
def persisted?
|
348
|
+
# Override and return a boolish value depending on the state of the inner model.
|
349
|
+
# For example, it could be transferred to the primary model to be manipulated.
|
350
|
+
#
|
351
|
+
# # ex.)
|
352
|
+
# def persisted? = the_model.persisted?
|
353
|
+
#
|
354
|
+
true
|
355
|
+
end
|
356
|
+
end
|
310
357
|
```
|
311
358
|
|
312
|
-
|
359
|
+
```ruby
|
360
|
+
# when `model.persisted?` returns `true`
|
313
361
|
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
362
|
+
model = ComposedModel.new
|
363
|
+
|
364
|
+
model.save # or `model.update` (the same callbacks will be triggered in all cases).
|
365
|
+
|
366
|
+
# before_save called!
|
367
|
+
# before_update called! # when persisted? is false, before_create hook is fired here instead.
|
368
|
+
# after_update called! # when persisted? is false, after_create hook is fired here instead.
|
369
|
+
# after_save called!
|
319
370
|
```
|
320
371
|
|
372
|
+
```ruby
|
373
|
+
# when `model.persisted?` returns `false`
|
374
|
+
|
375
|
+
model = ComposedModel.new
|
376
|
+
|
377
|
+
model.save # or `model.update` (the same callbacks will be triggered in all cases).
|
378
|
+
|
379
|
+
# before_save called!
|
380
|
+
# before_create called!
|
381
|
+
# after_create called!
|
382
|
+
# after_save called!
|
383
|
+
```
|
384
|
+
|
385
|
+
When `persisted_flag_callback_control` is `true`, `#create` is not supported.
|
386
|
+
|
387
|
+
```ruby
|
388
|
+
model.create # => raises RuntimeError
|
389
|
+
```
|
390
|
+
|
391
|
+
## Links
|
392
|
+
|
393
|
+
- [Smart way to update multiple models simultaneously in Rails](https://dev.to/hamajyotan/smart-way-to-update-multiple-models-simultaneously-in-rails-51b6)
|
394
|
+
|
321
395
|
## Development
|
322
396
|
|
323
397
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -335,3 +409,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
335
409
|
## Code of Conduct
|
336
410
|
|
337
411
|
Everyone interacting in the ActiveRecord::Compose project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/hamajyotan/active_record_compose/blob/main/CODE_OF_CONDUCT.md).
|
412
|
+
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'active_record_compose/
|
3
|
+
require 'active_record_compose/wrapped_model'
|
4
4
|
|
5
5
|
module ActiveRecordCompose
|
6
|
-
using
|
6
|
+
using WrappedModel::PackagePrivate
|
7
7
|
|
8
|
-
class
|
8
|
+
class ComposedCollection
|
9
9
|
include Enumerable
|
10
10
|
|
11
11
|
def initialize(owner)
|
@@ -79,30 +79,25 @@ module ActiveRecordCompose
|
|
79
79
|
attr_reader :owner, :models
|
80
80
|
|
81
81
|
def wrap(model, destroy: false, if: nil)
|
82
|
-
if
|
83
|
-
|
84
|
-
|
85
|
-
else
|
86
|
-
if destroy.is_a?(Symbol)
|
87
|
-
method = destroy
|
88
|
-
destroy = -> { owner.__send__(method) }
|
89
|
-
end
|
90
|
-
if_option = binding.local_variable_get(:if)
|
91
|
-
if if_option.is_a?(Symbol)
|
92
|
-
method = if_option
|
93
|
-
if_option = -> { owner.__send__(method) }
|
94
|
-
end
|
95
|
-
ActiveRecordCompose::InnerModel.new(model, destroy:, if: if_option)
|
82
|
+
if destroy.is_a?(Symbol)
|
83
|
+
method = destroy
|
84
|
+
destroy = -> { owner.__send__(method) }
|
96
85
|
end
|
86
|
+
if_option = binding.local_variable_get(:if)
|
87
|
+
if if_option.is_a?(Symbol)
|
88
|
+
method = if_option
|
89
|
+
if_option = -> { owner.__send__(method) }
|
90
|
+
end
|
91
|
+
ActiveRecordCompose::WrappedModel.new(model, destroy:, if: if_option)
|
97
92
|
end
|
98
93
|
|
99
94
|
# @private
|
100
95
|
module PackagePrivate
|
101
|
-
refine
|
96
|
+
refine ComposedCollection do
|
102
97
|
# Returns array of wrapped model instance.
|
103
98
|
#
|
104
99
|
# @private
|
105
|
-
# @return [Array[
|
100
|
+
# @return [Array[WrappedModel]] array of wrapped model instance.
|
106
101
|
def __wrapped_models = models.reject { _1.ignore? }.select { _1.__raw_model }
|
107
102
|
end
|
108
103
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'active_record_compose/composed_collection'
|
3
4
|
require 'active_record_compose/delegate_attribute'
|
4
|
-
require 'active_record_compose/inner_model_collection'
|
5
5
|
require 'active_record_compose/transaction_support'
|
6
6
|
|
7
7
|
module ActiveRecordCompose
|
8
|
-
using
|
8
|
+
using ComposedCollection::PackagePrivate
|
9
9
|
|
10
10
|
class Model
|
11
11
|
include ActiveModel::Model
|
@@ -15,6 +15,46 @@ module ActiveRecordCompose
|
|
15
15
|
include ActiveRecordCompose::DelegateAttribute
|
16
16
|
include ActiveRecordCompose::TransactionSupport
|
17
17
|
|
18
|
+
# This flag controls the callback sequence for models.
|
19
|
+
# The current default value is `false`, but support for `false` is planned to be discontinued in the future.
|
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: false
|
57
|
+
|
18
58
|
define_model_callbacks :save
|
19
59
|
define_model_callbacks :create
|
20
60
|
define_model_callbacks :update
|
@@ -35,7 +75,11 @@ module ActiveRecordCompose
|
|
35
75
|
return false if invalid?
|
36
76
|
|
37
77
|
with_transaction_returning_status do
|
38
|
-
|
78
|
+
if self.class.persisted_flag_callback_control
|
79
|
+
with_callbacks { save_models(bang: false) }
|
80
|
+
else
|
81
|
+
run_callbacks(:save) { save_models(bang: false) }
|
82
|
+
end
|
39
83
|
rescue ActiveRecord::RecordInvalid
|
40
84
|
false
|
41
85
|
end
|
@@ -50,7 +94,11 @@ module ActiveRecordCompose
|
|
50
94
|
valid? || raise_validation_error
|
51
95
|
|
52
96
|
with_transaction_returning_status do
|
53
|
-
|
97
|
+
if self.class.persisted_flag_callback_control
|
98
|
+
with_callbacks { save_models(bang: true) }
|
99
|
+
else
|
100
|
+
run_callbacks(:save) { save_models(bang: true) }
|
101
|
+
end
|
54
102
|
end || raise_on_save_error
|
55
103
|
end
|
56
104
|
|
@@ -80,11 +128,15 @@ module ActiveRecordCompose
|
|
80
128
|
# # after_save called!
|
81
129
|
#
|
82
130
|
def create(attributes = {})
|
131
|
+
if self.class.persisted_flag_callback_control
|
132
|
+
raise '`#create` cannot be called. The context for creation or update is determined by the `#persisted` flag.'
|
133
|
+
end
|
134
|
+
|
83
135
|
assign_attributes(attributes)
|
84
136
|
return false if invalid?
|
85
137
|
|
86
138
|
with_transaction_returning_status do
|
87
|
-
|
139
|
+
with_callbacks(context: :create) { save_models(bang: false) }
|
88
140
|
rescue ActiveRecord::RecordInvalid
|
89
141
|
false
|
90
142
|
end
|
@@ -93,11 +145,15 @@ module ActiveRecordCompose
|
|
93
145
|
# Behavior is same to `#create`, but raises an exception prematurely on failure.
|
94
146
|
#
|
95
147
|
def create!(attributes = {})
|
148
|
+
if self.class.persisted_flag_callback_control
|
149
|
+
raise '`#create` cannot be called. The context for creation or update is determined by the `#persisted` flag.'
|
150
|
+
end
|
151
|
+
|
96
152
|
assign_attributes(attributes)
|
97
153
|
valid? || raise_validation_error
|
98
154
|
|
99
155
|
with_transaction_returning_status do
|
100
|
-
|
156
|
+
with_callbacks(context: :create) { save_models(bang: true) }
|
101
157
|
end || raise_on_save_error
|
102
158
|
end
|
103
159
|
|
@@ -131,7 +187,11 @@ module ActiveRecordCompose
|
|
131
187
|
return false if invalid?
|
132
188
|
|
133
189
|
with_transaction_returning_status do
|
134
|
-
|
190
|
+
if self.class.persisted_flag_callback_control
|
191
|
+
with_callbacks { save_models(bang: false) }
|
192
|
+
else
|
193
|
+
with_callbacks(context: :update) { save_models(bang: false) }
|
194
|
+
end
|
135
195
|
rescue ActiveRecord::RecordInvalid
|
136
196
|
false
|
137
197
|
end
|
@@ -144,22 +204,29 @@ module ActiveRecordCompose
|
|
144
204
|
valid? || raise_validation_error
|
145
205
|
|
146
206
|
with_transaction_returning_status do
|
147
|
-
|
207
|
+
if self.class.persisted_flag_callback_control
|
208
|
+
with_callbacks { save_models(bang: true) }
|
209
|
+
else
|
210
|
+
with_callbacks(context: :update) { save_models(bang: true) }
|
211
|
+
end
|
148
212
|
end || raise_on_save_error
|
149
213
|
end
|
150
214
|
|
151
215
|
private
|
152
216
|
|
153
|
-
def models = @__models ||= ActiveRecordCompose::
|
217
|
+
def models = @__models ||= ActiveRecordCompose::ComposedCollection.new(self)
|
154
218
|
|
155
219
|
def validate_models
|
156
|
-
|
157
|
-
|
220
|
+
models.__wrapped_models.select { _1.invalid? }.each { errors.merge!(_1) }
|
221
|
+
end
|
222
|
+
|
223
|
+
def with_callbacks(context: nil, &block)
|
224
|
+
context ||= persisted? ? :update : :create
|
225
|
+
run_callbacks(:save) { run_callbacks(context, &block) }
|
158
226
|
end
|
159
227
|
|
160
228
|
def save_models(bang:)
|
161
|
-
|
162
|
-
wms.all? { bang ? _1.save! : _1.save }
|
229
|
+
models.__wrapped_models.all? { bang ? _1.save! : _1.save }
|
163
230
|
end
|
164
231
|
|
165
232
|
def raise_validation_error = raise ActiveRecord::RecordInvalid, self
|
@@ -3,7 +3,7 @@
|
|
3
3
|
require 'active_support/core_ext/object'
|
4
4
|
|
5
5
|
module ActiveRecordCompose
|
6
|
-
class
|
6
|
+
class WrappedModel
|
7
7
|
# @param model [Object] the model instance.
|
8
8
|
# @param destroy [Boolean] given true, destroy model.
|
9
9
|
# @param destroy [Proc] when proc returning true, destroy model.
|
@@ -17,7 +17,7 @@ module ActiveRecordCompose
|
|
17
17
|
delegate :errors, to: :model
|
18
18
|
|
19
19
|
# Determines whether to save or delete the target object.
|
20
|
-
# Depends on the `destroy` value of the
|
20
|
+
# Depends on the `destroy` value of the WrappedModel object initialization option.
|
21
21
|
#
|
22
22
|
# On the other hand, there are values `mark_for_destruction` and `marked_for_destruction?` in ActiveRecord.
|
23
23
|
# However, these values are not substituted here.
|
@@ -60,12 +60,36 @@ 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
|
64
|
+
# While errors caused by the type check are avoided,
|
65
|
+
# it is important to note that an error can still occur
|
66
|
+
# if `#destroy_context?` returns true but ar_like does not implement `#destroy`.
|
67
|
+
m = model
|
68
|
+
if destroy_context?
|
69
|
+
# @type var m: ActiveRecordCompose::_ARLikeWithDestroy
|
70
|
+
m.destroy
|
71
|
+
else
|
72
|
+
# @type var m: ActiveRecordCompose::_ARLike
|
73
|
+
m.save
|
74
|
+
end
|
75
|
+
end
|
64
76
|
|
65
77
|
# Execute save or destroy. Unlike #save, an exception is raises on failure.
|
66
78
|
# Whether save or destroy is executed depends on the value of `#destroy_context?`.
|
67
79
|
#
|
68
|
-
def save!
|
80
|
+
def save!
|
81
|
+
# While errors caused by the type check are avoided,
|
82
|
+
# it is important to note that an error can still occur
|
83
|
+
# if `#destroy_context?` returns true but ar_like does not implement `#destroy`.
|
84
|
+
m = model
|
85
|
+
if destroy_context?
|
86
|
+
# @type var m: ActiveRecordCompose::_ARLikeWithDestroy
|
87
|
+
m.destroy!
|
88
|
+
else
|
89
|
+
# @type var model: ActiveRecordCompose::_ARLike
|
90
|
+
m.save!
|
91
|
+
end
|
92
|
+
end
|
69
93
|
|
70
94
|
# @return [Boolean]
|
71
95
|
def invalid? = destroy_context? ? false : model.invalid?
|
@@ -94,7 +118,7 @@ module ActiveRecordCompose
|
|
94
118
|
|
95
119
|
# @private
|
96
120
|
module PackagePrivate
|
97
|
-
refine
|
121
|
+
refine WrappedModel do
|
98
122
|
# @private
|
99
123
|
# Returns a model instance of raw, but it should
|
100
124
|
# be noted that application developers are not expected to use this interface.
|
@@ -1,59 +1,33 @@
|
|
1
1
|
module ActiveRecordCompose
|
2
|
-
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
def attributes: -> Hash[String, untyped]
|
6
|
-
def delegated_attributes: () -> Array[String]
|
7
|
-
|
8
|
-
module ClassMethods : Module
|
9
|
-
def delegate_attribute: (*untyped methods, to: untyped, ?allow_nil: untyped?, ?private: untyped?) -> untyped
|
10
|
-
def delegated_attributes: () -> Array[String]
|
11
|
-
def delegated_attributes=: (Array[String]) -> untyped
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
class InnerModelCollection
|
2
|
+
class ComposedCollection
|
16
3
|
def initialize: (Model) -> void
|
17
4
|
|
18
5
|
private
|
19
6
|
attr_reader owner: Model
|
20
|
-
attr_reader models: Array[
|
21
|
-
def wrap: (
|
7
|
+
attr_reader models: Array[WrappedModel]
|
8
|
+
def wrap: (ar_like, ?destroy: (bool | Symbol | destroy_context_type), ?if: (nil | Symbol | condition_type)) -> WrappedModel
|
22
9
|
|
23
10
|
module PackagePrivate
|
24
|
-
def __wrapped_models: () -> Array[
|
11
|
+
def __wrapped_models: () -> Array[WrappedModel]
|
25
12
|
|
26
13
|
private
|
27
|
-
def models: () -> Array[
|
14
|
+
def models: () -> Array[WrappedModel]
|
28
15
|
end
|
29
16
|
|
30
17
|
include PackagePrivate
|
31
18
|
end
|
32
19
|
|
33
|
-
|
34
|
-
|
35
|
-
def destroy_context?: -> bool
|
36
|
-
def ignore?: -> bool
|
37
|
-
def save: -> bool
|
38
|
-
def save!: -> untyped
|
39
|
-
def invalid?: -> bool
|
40
|
-
def valid?: -> bool
|
41
|
-
def is_a?: (untyped) -> bool
|
42
|
-
def ==: (untyped) -> bool
|
43
|
-
|
44
|
-
private
|
45
|
-
attr_reader model: _ARLike
|
46
|
-
attr_reader destroy_context_type: (bool | destroy_context_type)
|
47
|
-
attr_reader if_option: (nil | condition_type)
|
20
|
+
module DelegateAttribute : ActiveModel::Attributes
|
21
|
+
extend ActiveSupport::Concern
|
48
22
|
|
49
|
-
|
50
|
-
|
23
|
+
def attributes: -> Hash[String, untyped]
|
24
|
+
def delegated_attributes: () -> Array[String]
|
51
25
|
|
52
|
-
|
53
|
-
def
|
26
|
+
module ClassMethods : Module
|
27
|
+
def delegate_attribute: (*untyped methods, to: untyped, ?allow_nil: untyped?, ?private: untyped?) -> untyped
|
28
|
+
def delegated_attributes: () -> Array[String]
|
29
|
+
def delegated_attributes=: (Array[String]) -> untyped
|
54
30
|
end
|
55
|
-
|
56
|
-
include PackagePrivate
|
57
31
|
end
|
58
32
|
|
59
33
|
class Model
|
@@ -62,7 +36,7 @@ module ActiveRecordCompose
|
|
62
36
|
include TransactionSupport
|
63
37
|
extend TransactionSupport::ClassMethods
|
64
38
|
|
65
|
-
@__models:
|
39
|
+
@__models: ComposedCollection
|
66
40
|
end
|
67
41
|
|
68
42
|
module TransactionSupport
|
@@ -79,4 +53,30 @@ module ActiveRecordCompose
|
|
79
53
|
def ar_class: -> singleton(ActiveRecord::Base)
|
80
54
|
end
|
81
55
|
end
|
56
|
+
|
57
|
+
class WrappedModel
|
58
|
+
def initialize: (ar_like, ?destroy: (bool | destroy_context_type), ?if: (nil | condition_type)) -> void
|
59
|
+
def destroy_context?: -> bool
|
60
|
+
def ignore?: -> bool
|
61
|
+
def save: -> bool
|
62
|
+
def save!: -> untyped
|
63
|
+
def invalid?: -> bool
|
64
|
+
def valid?: -> bool
|
65
|
+
def is_a?: (untyped) -> bool
|
66
|
+
def ==: (untyped) -> bool
|
67
|
+
|
68
|
+
private
|
69
|
+
attr_reader model: ar_like
|
70
|
+
attr_reader destroy_context_type: (bool | destroy_context_type)
|
71
|
+
attr_reader if_option: (nil | condition_type)
|
72
|
+
|
73
|
+
module PackagePrivate
|
74
|
+
def __raw_model: () -> ar_like
|
75
|
+
|
76
|
+
private
|
77
|
+
def model: () -> ar_like
|
78
|
+
end
|
79
|
+
|
80
|
+
include PackagePrivate
|
81
|
+
end
|
82
82
|
end
|
@@ -5,6 +5,15 @@ 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
|
12
|
+
def errors: -> untyped
|
13
|
+
def is_a?: (untyped) -> bool
|
14
|
+
def ==: (untyped) -> bool
|
15
|
+
end
|
16
|
+
interface _ARLikeWithDestroy
|
8
17
|
def save: -> bool
|
9
18
|
def save!: -> untyped
|
10
19
|
def destroy: -> bool
|
@@ -15,28 +24,53 @@ module ActiveRecordCompose
|
|
15
24
|
def is_a?: (untyped) -> bool
|
16
25
|
def ==: (untyped) -> bool
|
17
26
|
end
|
27
|
+
type ar_like = (_ARLike | _ARLikeWithDestroy)
|
28
|
+
|
29
|
+
type condition[T] = Symbol | ^(T) [self: T] -> boolish
|
30
|
+
type callback[T] = Symbol | ^(T) [self: T] -> void
|
31
|
+
type around_callback[T] = Symbol | ^(T, Proc) [self: T] -> void
|
18
32
|
|
19
33
|
type attribute_name = (String | Symbol)
|
20
|
-
type destroy_context_type = ((^() -> boolish) | (^(
|
21
|
-
type condition_type = ((^() -> boolish) | (^(
|
34
|
+
type destroy_context_type = ((^() -> boolish) | (^(ar_like) -> boolish))
|
35
|
+
type condition_type = ((^() -> boolish) | (^(ar_like) -> boolish))
|
22
36
|
|
23
|
-
class
|
24
|
-
include ::Enumerable[
|
37
|
+
class ComposedCollection
|
38
|
+
include ::Enumerable[ar_like]
|
25
39
|
|
26
|
-
def each: () { (
|
27
|
-
def <<: (
|
28
|
-
def push: (
|
40
|
+
def each: () { (ar_like) -> void } -> ComposedCollection | () -> Enumerator[ar_like, self]
|
41
|
+
def <<: (ar_like) -> self
|
42
|
+
def push: (ar_like, ?destroy: (bool | Symbol | destroy_context_type), ?if: (nil | Symbol | condition_type)) -> self
|
29
43
|
def empty?: -> bool
|
30
44
|
def clear: -> self
|
31
|
-
def delete: (
|
45
|
+
def delete: (ar_like) -> ComposedCollection?
|
32
46
|
end
|
33
47
|
|
34
48
|
class Model
|
35
|
-
extend ActiveModel::Callbacks
|
36
49
|
include ActiveModel::Model
|
37
50
|
include ActiveModel::Validations::Callbacks
|
38
|
-
extend ActiveModel::Validations::ClassMethods
|
39
51
|
include ActiveModel::Attributes
|
52
|
+
extend ActiveModel::Callbacks
|
53
|
+
extend ActiveModel::Validations::ClassMethods
|
54
|
+
extend ActiveModel::Validations::Callbacks::ClassMethods
|
55
|
+
extend ActiveModel::Attributes::ClassMethods
|
56
|
+
|
57
|
+
def self.before_save: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> void } -> void
|
58
|
+
def self.around_save: (*around_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> void } -> void
|
59
|
+
def self.after_save: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> void } -> void
|
60
|
+
|
61
|
+
def self.before_create: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> void } -> void
|
62
|
+
def self.around_create: (*around_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> void } -> void
|
63
|
+
def self.after_create: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> void } -> void
|
64
|
+
|
65
|
+
def self.before_update: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> void } -> void
|
66
|
+
def self.around_update: (*around_callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> void } -> void
|
67
|
+
def self.after_update: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> void } -> void
|
68
|
+
|
69
|
+
def self.after_commit: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> void } -> void
|
70
|
+
def self.after_rollback: (*callback[instance], ?if: condition[instance], ?unless: condition[instance], **untyped) ?{ () [self: instance] -> void } -> void
|
71
|
+
|
72
|
+
def self.persisted_flag_callback_control: () -> boolish
|
73
|
+
def self.persisted_flag_callback_control=: (boolish) -> untyped
|
40
74
|
|
41
75
|
def self.delegate_attribute: (*untyped methods, to: untyped, ?allow_nil: untyped?, ?private: untyped?) -> untyped
|
42
76
|
def self.connection: -> ActiveRecord::ConnectionAdapters::AbstractAdapter
|
@@ -53,7 +87,8 @@ module ActiveRecordCompose
|
|
53
87
|
def id: -> untyped
|
54
88
|
|
55
89
|
private
|
56
|
-
def models: ->
|
90
|
+
def models: -> ComposedCollection
|
91
|
+
def with_callbacks: (?context: (nil | :create | :update)) { () -> bool } -> bool
|
57
92
|
def validate_models: -> void
|
58
93
|
def save_models: (bang: bool) -> bool
|
59
94
|
def raise_validation_error: -> bot
|
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.7.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-02-12 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: activerecord
|
@@ -23,7 +23,8 @@ dependencies:
|
|
23
23
|
- - ">="
|
24
24
|
- !ruby/object:Gem::Version
|
25
25
|
version: '6.1'
|
26
|
-
description: activemodel form object pattern
|
26
|
+
description: activemodel form object pattern. it embraces multiple AR models and provides
|
27
|
+
a transparent interface as if they were a single model.
|
27
28
|
email:
|
28
29
|
- hamajyotan@gmail.com
|
29
30
|
executables: []
|
@@ -36,12 +37,12 @@ files:
|
|
36
37
|
- LICENSE.txt
|
37
38
|
- README.md
|
38
39
|
- lib/active_record_compose.rb
|
40
|
+
- lib/active_record_compose/composed_collection.rb
|
39
41
|
- lib/active_record_compose/delegate_attribute.rb
|
40
|
-
- lib/active_record_compose/inner_model.rb
|
41
|
-
- lib/active_record_compose/inner_model_collection.rb
|
42
42
|
- lib/active_record_compose/model.rb
|
43
43
|
- lib/active_record_compose/transaction_support.rb
|
44
44
|
- lib/active_record_compose/version.rb
|
45
|
+
- lib/active_record_compose/wrapped_model.rb
|
45
46
|
- sig/_internal/package_private.rbs
|
46
47
|
- sig/active_record_compose.rbs
|
47
48
|
homepage: https://github.com/hamajyotan/active_record_compose
|
@@ -51,7 +52,7 @@ metadata:
|
|
51
52
|
homepage_uri: https://github.com/hamajyotan/active_record_compose
|
52
53
|
source_code_uri: https://github.com/hamajyotan/active_record_compose
|
53
54
|
changelog_uri: https://github.com/hamajyotan/active_record_compose/blob/main/CHANGELOG.md
|
54
|
-
documentation_uri: https://www.rubydoc.info/gems/active_record_compose/0.
|
55
|
+
documentation_uri: https://www.rubydoc.info/gems/active_record_compose/0.7.0
|
55
56
|
rubygems_mfa_required: 'true'
|
56
57
|
rdoc_options: []
|
57
58
|
require_paths:
|