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 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: