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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 88e574f327847973e3b931ee6663de0d395fa6ff26de5830f92ac21d1445fe20
4
- data.tar.gz: bb50aa19e200f15c3b16d6b7fe65dc4e94afc7e44eec3c94ef37ab18fc610b88
3
+ metadata.gz: 6a3d2a1c6e4aac0bc387a6e0d9ba2917dafeacec4d46b2ad617d54e45ff9786f
4
+ data.tar.gz: d71b88c9b069eca62e49d2e5ff25c447a5e70e741d29b76f572c5a21b4994a4b
5
5
  SHA512:
6
- metadata.gz: 77a492aea1e8c5aec20bea84dbbd242c2f94d9d5674a705cacea4ad4b8b3991b54630009fb78ed425a6dee5f10d28dd5f473b7b581a92711701de07472d7a88f
7
- data.tar.gz: a8386a77e58cb7db6c2478137af7bf24e606448987009257b151c695b746be9818de2d963b65672cc60e23b9a8dc7d0b29790087ea3a8271152c0115d2212bdf
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
- activermodel (activerecord) form object pattern.
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
- ### ActiveRecordCompose::Model basic
49
+ ### Basic usage
24
50
 
25
- It wraps AR objects or equivalent models to provide unified operation.
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.)
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
- #### Context-specific callbacks.
59
+ class Profile < ApplicationRecord
60
+ belongs_to :account
61
+ validates :firstname, :lastname, :age, presence: true
62
+ end
63
+ ```
29
64
 
30
- A callback is useful to define some processing before or after a save in a particular model.
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 AccountRegistration < ActiveRecordCompose::Model
38
- def initialize(account = Account.new, attributes = {})
39
- @account = account
40
- super(attributes) # When overrides `#initialize`, be sure to call `super`.
68
+ class UserRegistration < ActiveRecordCompose::Model
69
+ def initialize
70
+ @account = Account.new
71
+ @profile = @account.build_profile
41
72
 
42
- # By including AR instance in models, AR instance itself is saved when this model is saved.
43
- models.push(account)
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
- # By delegating these to the AR instance,
47
- # For example, this model itself can be given directly as an argument to form_with, and it will behave as if it were an instance of the model.
48
- delegate :id, :persisted?, to: :account
82
+ # Attribute declarations using ActiveModel::Attributes are supported.
83
+ attribute :terms_of_service, :boolean
49
84
 
50
- # Defines an attribute of the same name that delegates to account#name and account#email
51
- delegate_attribute :name, :email, to: :account
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
- # You can only define post-processing if you update through this model.
54
- # If this is written directly into the AR model, for example, it would be necessary to consider a callback control for each test data generation.
55
- after_commit :try_send_email_message
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 try_send_email_message
106
+ def send_email_message
62
107
  SendEmailConfirmationJob.perform_later(account)
63
108
  end
64
109
  end
65
110
  ```
66
111
 
67
- #### Validation limited to a specific context.
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
- class AccountRegistration < ActiveRecordCompose::Model
77
- def initialize(account = Account.new, attributes = {})
78
- @account = account
79
- super(attributes)
80
- models.push(account)
81
- end
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
- delegate :id, :persisted?, to: :account
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
- # Only if this model is used, also check the validity of the domain
87
- before_validation :require_valid_domain
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
- private
138
+ ### `delegate_attribute`
90
139
 
91
- attr_reader :account
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
- account = Account.new(name: 'new account', email: 'foo@example.com')
109
- account.valid? #=> true
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
- account_registration = AccountRegistration.new(name: 'new account', email: 'foo@example.com')
112
- account_registration.valid? #=> false
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
- #### updating multiple models at the same time.
170
+ ### Promotion to model from AR-model errors
116
171
 
117
- In an AR model, you can add, for example, `autosave: true` or `accepts_nested_attributes_for` to an association to update the related models at the same time.
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
- class AccountRegistration < ActiveRecordCompose::Model
124
- def initialize(account = Account.new, profile = account.build_profile, attributes = {})
125
- @account = account
126
- @profile = profile
127
- super(attributes)
128
- models << account << profile
129
- end
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
- delegate :id, :persisted?, to: :account
132
- delegate_attribute :name, :email, to: :account
133
- delegate_attribute :firstname, :lastname, :age, to: :profile
193
+ ### I18n
134
194
 
135
- private
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
- attr_reader :account, :profile
138
- end
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
- ```ruby
142
- Account.count #=> 0
143
- Profile.count #=> 0
144
-
145
- account_registration =
146
- AccountRegistration.new(
147
- name: 'foo',
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
- 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.
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 # Suppose that Account has_one 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, attributes = {})
262
+ def initialize(account)
201
263
  @account = account
202
264
  @profile = account.profile || account.build_profile
203
- super(attributes)
265
+ super()
204
266
  models.push(account)
205
- models.push(profile, destroy: :all_blank?) # destroy if all blank, otherwise save.
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 :name, :email, to: :account
209
- delegate_attribute :firstname, :lastname, :age, to: :profile
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 all_blank? = firstname.blank && lastname.blank? && age.blank?
216
- end
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
- ### I18n
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
- ```yaml
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 = destroy_context? ? model.destroy : model.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! = destroy_context? ? model.destroy! : model.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?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordCompose
4
- VERSION = '0.6.2'
4
+ VERSION = '0.6.3'
5
5
  end
@@ -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: (_ARLike | InnerModel, ?destroy: (bool | Symbol | destroy_context_type), ?if: (nil | Symbol | condition_type)) -> InnerModel
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: (_ARLike, ?destroy: (bool | destroy_context_type), ?if: (nil | condition_type)) -> void
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: _ARLike
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: () -> _ARLike
50
+ def __raw_model: () -> ar_like
51
51
 
52
52
  private
53
- def model: () -> _ARLike
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) | (^(_ARLike) -> boolish))
21
- type condition_type = ((^() -> boolish) | (^(_ARLike) -> 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[_ARLike]
38
+ include ::Enumerable[ar_like]
25
39
 
26
- def each: () { (_ARLike) -> void } -> InnerModelCollection | () -> Enumerator[_ARLike, self]
27
- def <<: (_ARLike) -> self
28
- def push: (_ARLike, ?destroy: (bool | Symbol | destroy_context_type), ?if: (nil | Symbol | condition_type)) -> self
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: (_ARLike) -> InnerModelCollection?
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.2
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-04 00:00:00.000000000 Z
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.2
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: