active_record_compose 0.6.2 → 0.6.3
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 +7 -0
- data/README.md +176 -165
- data/lib/active_record_compose/inner_model.rb +26 -2
- data/lib/active_record_compose/version.rb +1 -1
- data/sig/_internal/package_private.rbs +5 -5
- data/sig/active_record_compose.rbs +40 -9
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6a3d2a1c6e4aac0bc387a6e0d9ba2917dafeacec4d46b2ad617d54e45ff9786f
|
4
|
+
data.tar.gz: d71b88c9b069eca62e49d2e5ff25c447a5e70e741d29b76f572c5a21b4994a4b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3579fdc36270eaaa2f9f253b247aabc5bc4aa6547df5511e81158ebbf3277b37dbbaa3e4d4376fff5c9ac0066cebbf0207db993cd20b4c8b159a6f3069ae24f1
|
7
|
+
data.tar.gz: 4f2e98a4158a5c19c13f5bc5d83276c7071ece19b2c969d73c24cce47231c9f272fb32620d5a31748e2bd060d72733bcd28be3d4d3eb6c1e9c8ded1bb9db0340
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.6.2] - 2025-01-31
|
4
|
+
|
5
|
+
- fix: type error in `ActiveRecordCompose::Model` subclass definitions.
|
6
|
+
- fixed type errors in subclass callback definitions, etc.
|
7
|
+
- doc: more detailed gem desciption.
|
8
|
+
- rewrite readme.
|
9
|
+
|
3
10
|
## [0.6.2] - 2025-01-04
|
4
11
|
|
5
12
|
- 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
|
-
|
51
|
+
(Below, it is assumed that there are two AR model definitions, `Account` and `Profile`, for the sake of explanation.)
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
class Account < ApplicationRecord
|
55
|
+
has_one :profile # can work without `autosave:true`
|
56
|
+
validates :name, :email, presence: true
|
57
|
+
end
|
27
58
|
|
28
|
-
|
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,63 +259,31 @@ 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
289
|
Sometimes, multiple AR objects are passed to the models in the arguments.
|
@@ -294,29 +324,9 @@ model.update
|
|
294
324
|
# after_save called!
|
295
325
|
```
|
296
326
|
|
297
|
-
|
298
|
-
|
299
|
-
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.
|
300
|
-
The specific keys required are `activemodel.errors.messages.record_invalid` or `errors.messages.record_invalid`.
|
301
|
-
|
302
|
-
(Replace `en` as appropriate in the context.)
|
303
|
-
|
304
|
-
```yaml
|
305
|
-
en:
|
306
|
-
activemodel:
|
307
|
-
errors:
|
308
|
-
messages:
|
309
|
-
record_invalid: 'Validation failed: %{errors}'
|
310
|
-
```
|
311
|
-
|
312
|
-
Alternatively, the following definition is also acceptable:
|
327
|
+
## Links
|
313
328
|
|
314
|
-
|
315
|
-
en:
|
316
|
-
errors:
|
317
|
-
messages:
|
318
|
-
record_invalid: 'Validation failed: %{errors}'
|
319
|
-
```
|
329
|
+
- [Smart way to update multiple models simultaneously in Rails](https://dev.to/hamajyotan/smart-way-to-update-multiple-models-simultaneously-in-rails-51b6)
|
320
330
|
|
321
331
|
## Development
|
322
332
|
|
@@ -335,3 +345,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
335
345
|
## Code of Conduct
|
336
346
|
|
337
347
|
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).
|
348
|
+
|
@@ -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?
|
@@ -18,7 +18,7 @@ module ActiveRecordCompose
|
|
18
18
|
private
|
19
19
|
attr_reader owner: Model
|
20
20
|
attr_reader models: Array[InnerModel]
|
21
|
-
def wrap: (
|
21
|
+
def wrap: (ar_like | InnerModel, ?destroy: (bool | Symbol | destroy_context_type), ?if: (nil | Symbol | condition_type)) -> InnerModel
|
22
22
|
|
23
23
|
module PackagePrivate
|
24
24
|
def __wrapped_models: () -> Array[InnerModel]
|
@@ -31,7 +31,7 @@ module ActiveRecordCompose
|
|
31
31
|
end
|
32
32
|
|
33
33
|
class InnerModel
|
34
|
-
def initialize: (
|
34
|
+
def initialize: (ar_like, ?destroy: (bool | destroy_context_type), ?if: (nil | condition_type)) -> void
|
35
35
|
def destroy_context?: -> bool
|
36
36
|
def ignore?: -> bool
|
37
37
|
def save: -> bool
|
@@ -42,15 +42,15 @@ module ActiveRecordCompose
|
|
42
42
|
def ==: (untyped) -> bool
|
43
43
|
|
44
44
|
private
|
45
|
-
attr_reader model:
|
45
|
+
attr_reader model: ar_like
|
46
46
|
attr_reader destroy_context_type: (bool | destroy_context_type)
|
47
47
|
attr_reader if_option: (nil | condition_type)
|
48
48
|
|
49
49
|
module PackagePrivate
|
50
|
-
def __raw_model: () ->
|
50
|
+
def __raw_model: () -> ar_like
|
51
51
|
|
52
52
|
private
|
53
|
-
def model: () ->
|
53
|
+
def model: () -> ar_like
|
54
54
|
end
|
55
55
|
|
56
56
|
include PackagePrivate
|
@@ -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,50 @@ 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
37
|
class InnerModelCollection
|
24
|
-
include ::Enumerable[
|
38
|
+
include ::Enumerable[ar_like]
|
25
39
|
|
26
|
-
def each: () { (
|
27
|
-
def <<: (
|
28
|
-
def push: (
|
40
|
+
def each: () { (ar_like) -> void } -> InnerModelCollection | () -> 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) -> InnerModelCollection?
|
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
|
40
71
|
|
41
72
|
def self.delegate_attribute: (*untyped methods, to: untyped, ?allow_nil: untyped?, ?private: untyped?) -> untyped
|
42
73
|
def self.connection: -> ActiveRecord::ConnectionAdapters::AbstractAdapter
|
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.6.
|
4
|
+
version: 0.6.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- hamajyotan
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-01-
|
10
|
+
date: 2025-01-31 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: []
|
@@ -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.6.
|
55
|
+
documentation_uri: https://www.rubydoc.info/gems/active_record_compose/0.6.3
|
55
56
|
rubygems_mfa_required: 'true'
|
56
57
|
rdoc_options: []
|
57
58
|
require_paths:
|