active_record_compose 0.6.2 → 0.6.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
![CI](https://github.com/hamajyotan/active_record_compose/workflows/CI/badge.svg)
|
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:
|