active_record_compose 0.7.0 → 0.8.1

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: ab0bb2d4ff8813ad8fb9a4830c2b1d3887aaa29702f11561c9c8624719a5082f
4
- data.tar.gz: a3fcfb870aaf5a0825a218556423cc1af16644b41ec22f0b4d656cb42cd33790
3
+ metadata.gz: 9b1f795843fd29ef6dcd9cf17e897938ea97fc8f32db35f0b444b36455e619a3
4
+ data.tar.gz: 584e44a2c49c6861ad232f0c52c75b6d73abb550f39165fabe14690a71575c81
5
5
  SHA512:
6
- metadata.gz: d5a1ea42bd83a986437faac38b2543ee48aff7f2003afa6267a7adcd325db1bbb0506f4f1f25e2aaa13b733df7956ebd820627821f19101fc36dd4f046a51c43
7
- data.tar.gz: 4a9cc98fddbcf1068720387207469cb527b96e536a9f109a6174b5a439daa383f54a1625e916c731d01883cf360c6e01281858a238796b5cb64b92665c2405c3
6
+ metadata.gz: 04bc96e1479134c02ad09ae5d014ad5ad93c6d6ae6914a39724d42152f691120ccfaa76c681e3790bb1b436b49cc39d57eb8bfe7f6fc73d064818e22738ce1c9
7
+ data.tar.gz: e757a94164248e188c46413a706ec2418eb99c97f28bc5631305620c064b8fd7b04a6621d4809e405f95ac4d2228c22509700169f0049b581a1998ceba364acd
data/.rubocop.yml CHANGED
@@ -21,6 +21,9 @@ Style/DoubleNegation:
21
21
  Style/NumericPredicate:
22
22
  Enabled: false
23
23
 
24
+ Style/OptionalBooleanParameter:
25
+ Enabled: false
26
+
24
27
  Style/StringLiterals:
25
28
  Enabled: true
26
29
 
@@ -48,6 +51,9 @@ Layout/LeadingCommentSpace:
48
51
  Layout/LineLength:
49
52
  Max: 120
50
53
 
54
+ Lint/UselessMethodDefinition:
55
+ Enabled: false
56
+
51
57
  Metrics/AbcSize:
52
58
  Enabled: false
53
59
 
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.8.1] - 2025-06-21
4
+
5
+ - Add migration guide message.
6
+ - Add upper version.
7
+
8
+ ## [0.8.0] - 2025-02-22
9
+
10
+ - changed `persisted_flag_callback_control` default from `false` to `true`.
11
+ - adjusted to save errors as soon as an invalid is found.
12
+ - drop support rails 6.1.x.
13
+
3
14
  ## [0.7.0] - 2025-02-12
4
15
 
5
16
  - rename ActiveRecordCompose::InnerModel to ActiveRecordCompose::WrappedModel
data/README.md CHANGED
@@ -15,7 +15,7 @@ activemodel (activerecord) form object pattern. it embraces multiple AR models a
15
15
  - [I18n](#i18n)
16
16
  - [Advanced Usage](#advanced-usage)
17
17
  - [`destroy` option](#destroy-option)
18
- - [Callback ordering by `#save`, `#create` and `#update`](#callback-ordering-by-save-create-and-update)
18
+ - [Callback ordering by `#persisted?`](#callback-ordering-by-persisted)
19
19
  - [Links](#links)
20
20
  - [Development](#development)
21
21
  - [Contributing](#contributing)
@@ -284,52 +284,15 @@ class AccountRegistration < ActiveRecordCompose::Model
284
284
  end
285
285
  ```
286
286
 
287
- ### Callback ordering by `#save`, `#create` and `#update`.
287
+ ### Callback ordering by `#persisted?`
288
288
 
289
289
  The behavior of `(before|after|around)_create` and `(before|after|around)_update` hooks depends on
290
290
  the state of the `persisted_flag_callback_control` setting.
291
-
292
- When `persisted_flag_callback_control` is set to false,
293
- the execution of `#create`, `#update`, or `#save` determines which callbacks will be triggered.
294
- Currently, the default value is `false`, but it will no longer be supported in the future.
295
-
296
- ```ruby
297
- class ComposedModel < ActiveRecordCompose::Model
298
- self.persisted_flag_callback_control = false # Currently defaults to false, but will no longer be supported in the future.
299
-
300
- # ...
301
-
302
- before_save { puts 'before_save called!' }
303
- before_create { puts 'before_create called!' }
304
- before_update { puts 'before_update called!' }
305
- after_save { puts 'after_save called!' }
306
- after_create { puts 'after_create called!' }
307
- after_update { puts 'after_update called!' }
308
- end
309
- ```
310
-
311
- ```ruby
312
- model = ComposedModel.new
313
-
314
- model.save
315
- # before_save called!
316
- # after_save called!
317
-
318
- model.create
319
- # before_save called!
320
- # before_create called!
321
- # after_create called!
322
- # after_save called!
323
-
324
- model.update
325
- # before_save called!
326
- # before_update called!
327
- # after_update called!
328
- # after_save called!
329
- ```
291
+ default value is `true`, behavior when set to `false` will be removed in the next release.
330
292
 
331
293
  When `persisted_flag_callback_control` is set to `true`, it behaves almost like callback control in ActiveRecord.
332
- This behavior will be the default in the future.
294
+ Depending on the evaluation result of `#persisted?`,
295
+ either the create-related callbacks or the update-related callbacks will be triggered.
333
296
 
334
297
  ```ruby
335
298
  class ComposedModel < ActiveRecordCompose::Model
@@ -382,10 +345,43 @@ model.save # or `model.update` (the same callbacks will be triggered in all case
382
345
  # after_save called!
383
346
  ```
384
347
 
385
- When `persisted_flag_callback_control` is `true`, `#create` is not supported.
348
+ When `persisted_flag_callback_control` is set to false,
349
+ the execution of `#create`, `#update`, or `#save` determines which callbacks will be triggered.
350
+ This behavior will no longer be supported in the next release.
351
+
352
+ ```ruby
353
+ class ComposedModel < ActiveRecordCompose::Model
354
+ self.persisted_flag_callback_control = false # Currently defaults to false, but will no longer be supported in the future.
355
+
356
+ # ...
357
+
358
+ before_save { puts 'before_save called!' }
359
+ before_create { puts 'before_create called!' }
360
+ before_update { puts 'before_update called!' }
361
+ after_save { puts 'after_save called!' }
362
+ after_create { puts 'after_create called!' }
363
+ after_update { puts 'after_update called!' }
364
+ end
365
+ ```
386
366
 
387
367
  ```ruby
388
- model.create # => raises RuntimeError
368
+ model = ComposedModel.new
369
+
370
+ model.save
371
+ # before_save called!
372
+ # after_save called!
373
+
374
+ model.create
375
+ # before_save called!
376
+ # before_create called!
377
+ # after_create called!
378
+ # after_save called!
379
+
380
+ model.update
381
+ # before_save called!
382
+ # before_update called!
383
+ # after_update called!
384
+ # after_save called!
389
385
  ```
390
386
 
391
387
  ## Links
data/UPGRADE.md ADDED
@@ -0,0 +1,113 @@
1
+ # Migration Guide: `active_record_compose` 0.8.x to 0.9.0+
2
+
3
+ ⚠️ **Breaking Change**
4
+
5
+ This guide explains how to migrate from version **0.8.x** to **0.9.0 or later**, assuming that your codebase includes:
6
+
7
+ ```ruby
8
+ self.persisted_flag_callback_control = false
9
+ ```
10
+
11
+ This setting was officially **deprecated** in 0.9.0 and is **no longer supported**. Migration is required to adopt the current default behavior.
12
+
13
+ ## Background
14
+
15
+ In version **0.7.x**, the default value of `persisted_flag_callback_control` was `false`.
16
+
17
+ In **0.8.x**, the default was changed to `true`.
18
+ If you are still using `false` in your models, it means you have **explicitly overridden the default**, likely to preserve backward-compatible behavior.
19
+
20
+ This guide helps you safely migrate to the new behavior while preserving intended callback semantics.
21
+
22
+ ## Goal of the Migration
23
+
24
+ - Remove any use of `persisted_flag_callback_control = false`
25
+ - Adjust callback definitions to align with the semantics of `#persisted?`
26
+
27
+ ## Step 1 – Remove Deprecated Flag
28
+
29
+ Find and remove all instances of:
30
+
31
+ ```diff
32
+ -self.persisted_flag_callback_control = false
33
+ ```
34
+
35
+ ## Step 2 – Understand Callback Behavior Changes
36
+
37
+ With `persisted_flag_callback_control = true`, whether a callback is fired depends on the return value of `#persisted?`, not the method used (`create` or `update`).
38
+
39
+ ### When saving with `#update`:
40
+
41
+ If `persisted?` returns `false`, then:
42
+
43
+ - `before_update`
44
+ - `after_update`
45
+ - `around_update`
46
+
47
+ will **not** be triggered.
48
+
49
+ ✅ Recommended fix:
50
+
51
+ If you don’t differentiate between creation and update phases, switch to `*_save` callbacks:
52
+
53
+ ```diff
54
+ - before_update :track_change
55
+ + before_save :track_change
56
+ ```
57
+
58
+ ### When saving with `#create`:
59
+
60
+ If `persisted?` returns `true`, then:
61
+
62
+ - `before_create`
63
+ - `after_create`
64
+ - `around_create`
65
+
66
+ will **not** be triggered.
67
+
68
+ ✅ Recommended fix:
69
+
70
+ Again, prefer `*_save` if you're using shared logic across creation and update:
71
+
72
+ ```diff
73
+ - after_create :send_notification
74
+ + after_save :send_notification
75
+ ```
76
+
77
+ ## Step 3 – Override `#persisted?` if Needed
78
+
79
+ If your composed model wraps an ActiveRecord instance and delegates its persistence logic, be sure to override `#persisted?` to reflect the correct state:
80
+
81
+ ```ruby
82
+ class Foo < ActiveRecordCompose::Model
83
+ def initialize(bar = Bar.new)
84
+ super()
85
+ @bar = bar
86
+ end
87
+
88
+ def persisted? = bar.persisted?
89
+
90
+ private
91
+
92
+ attr_reader :bar
93
+ end
94
+ ```
95
+
96
+ ## Migration Checklist ✅
97
+
98
+ - [ ] All models now `persisted_flag_callback_control` omit it entirely
99
+ - [ ] All callback definitions have been reviewed and updated
100
+ - [ ] Any `*_create` or `*_update` callbacks have been replaced with `*_save` where applicable
101
+ - [ ] `#persisted?` is correctly overridden where needed
102
+ - [ ] All tests pass and expected callbacks are fired
103
+
104
+ ## Why This Change?
105
+
106
+ By aligning callback behavior with `persisted?`, you gain:
107
+
108
+ - Clearer intent and semantics
109
+ - More accurate behavior when composing or wrapping persisted models
110
+ - Improved compatibility with Rails conventions
111
+ - Less surprising callback triggering
112
+
113
+ If you have questions or run into edge cases, feel free to [open an issue](https://github.com/hamajyotan/active_record_compose/issues) or start a discussion.
@@ -16,7 +16,7 @@ module ActiveRecordCompose
16
16
  include ActiveRecordCompose::TransactionSupport
17
17
 
18
18
  # This flag controls the callback sequence for models.
19
- # The current default value is `false`, but support for `false` is planned to be discontinued in the future.
19
+ # The current default value is `true`, behavior when set to `false` will be removed in the next release.
20
20
  #
21
21
  # When `persisted_flag_callback_control` is set to `true`,
22
22
  # the occurrence of callbacks depends on the evaluation result of `#persisted?`.
@@ -53,7 +53,7 @@ module ActiveRecordCompose
53
53
  # * after_create
54
54
  # * after_save
55
55
  #
56
- class_attribute :persisted_flag_callback_control, instance_accessor: false, default: false
56
+ class_attribute :persisted_flag_callback_control, instance_accessor: false, default: true
57
57
 
58
58
  define_model_callbacks :save
59
59
  define_model_callbacks :create
@@ -78,6 +78,14 @@ module ActiveRecordCompose
78
78
  if self.class.persisted_flag_callback_control
79
79
  with_callbacks { save_models(bang: false) }
80
80
  else
81
+ # steep:ignore:start
82
+ deprecator.warn(
83
+ 'The behavior with `persisted_flag_callback_control` set to `false` will be removed in 0.9.0. ' \
84
+ 'Use `self.persisted_flag_callback_control = true` set to `true`. ' \
85
+ '(Alternatively, exclude statements that set `false`) ' \
86
+ 'cf. https://github.com/hamajyotan/active_record_compose/blob/v0.8.1/UPGRADE.md ',
87
+ )
88
+ # steep:ignore:end
81
89
  run_callbacks(:save) { save_models(bang: false) }
82
90
  end
83
91
  rescue ActiveRecord::RecordInvalid
@@ -97,6 +105,14 @@ module ActiveRecordCompose
97
105
  if self.class.persisted_flag_callback_control
98
106
  with_callbacks { save_models(bang: true) }
99
107
  else
108
+ # steep:ignore:start
109
+ deprecator.warn(
110
+ 'The behavior with `persisted_flag_callback_control` set to `false` will be removed in 0.9.0. ' \
111
+ 'Use `self.persisted_flag_callback_control = true` set to `true`. ' \
112
+ '(Alternatively, exclude statements that set `false`) ' \
113
+ 'cf. https://github.com/hamajyotan/active_record_compose/blob/v0.8.1/UPGRADE.md ',
114
+ )
115
+ # steep:ignore:end
100
116
  run_callbacks(:save) { save_models(bang: true) }
101
117
  end
102
118
  end || raise_on_save_error
@@ -127,11 +143,21 @@ module ActiveRecordCompose
127
143
  # # after_create called!
128
144
  # # after_save called!
129
145
  #
146
+ # @deprecated
130
147
  def create(attributes = {})
131
148
  if self.class.persisted_flag_callback_control
132
149
  raise '`#create` cannot be called. The context for creation or update is determined by the `#persisted` flag.'
133
150
  end
134
151
 
152
+ # steep:ignore:start
153
+ deprecator.warn(
154
+ 'The behavior with `persisted_flag_callback_control` set to `false` will be removed in 0.9.0. ' \
155
+ 'Use `self.persisted_flag_callback_control = true` set to `true`. ' \
156
+ '(Alternatively, exclude statements that set `false`) ' \
157
+ 'cf. https://github.com/hamajyotan/active_record_compose/blob/v0.8.1/UPGRADE.md ',
158
+ )
159
+ # steep:ignore:end
160
+
135
161
  assign_attributes(attributes)
136
162
  return false if invalid?
137
163
 
@@ -144,11 +170,21 @@ module ActiveRecordCompose
144
170
 
145
171
  # Behavior is same to `#create`, but raises an exception prematurely on failure.
146
172
  #
173
+ # @deprecated
147
174
  def create!(attributes = {})
148
175
  if self.class.persisted_flag_callback_control
149
176
  raise '`#create` cannot be called. The context for creation or update is determined by the `#persisted` flag.'
150
177
  end
151
178
 
179
+ # steep:ignore:start
180
+ deprecator.warn(
181
+ 'The behavior with `persisted_flag_callback_control` set to `false` will be removed in 0.9.0. ' \
182
+ 'Use `self.persisted_flag_callback_control = true` set to `true`. ' \
183
+ '(Alternatively, exclude statements that set `false`) ' \
184
+ 'cf. https://github.com/hamajyotan/active_record_compose/blob/v0.8.1/UPGRADE.md ',
185
+ )
186
+ # steep:ignore:end
187
+
152
188
  assign_attributes(attributes)
153
189
  valid? || raise_validation_error
154
190
 
@@ -190,6 +226,14 @@ module ActiveRecordCompose
190
226
  if self.class.persisted_flag_callback_control
191
227
  with_callbacks { save_models(bang: false) }
192
228
  else
229
+ # steep:ignore:start
230
+ deprecator.warn(
231
+ 'The behavior with `persisted_flag_callback_control` set to `false` will be removed in 0.9.0. ' \
232
+ 'Use `self.persisted_flag_callback_control = true` set to `true`. ' \
233
+ '(Alternatively, exclude statements that set `false`) ' \
234
+ 'cf. https://github.com/hamajyotan/active_record_compose/blob/v0.8.1/UPGRADE.md ',
235
+ )
236
+ # steep:ignore:end
193
237
  with_callbacks(context: :update) { save_models(bang: false) }
194
238
  end
195
239
  rescue ActiveRecord::RecordInvalid
@@ -207,22 +251,42 @@ module ActiveRecordCompose
207
251
  if self.class.persisted_flag_callback_control
208
252
  with_callbacks { save_models(bang: true) }
209
253
  else
254
+ # steep:ignore:start
255
+ deprecator.warn(
256
+ 'The behavior with `persisted_flag_callback_control` set to `false` will be removed in 0.9.0. ' \
257
+ 'Use `self.persisted_flag_callback_control = true` set to `true`. ' \
258
+ '(Alternatively, exclude statements that set `false`) ' \
259
+ 'cf. https://github.com/hamajyotan/active_record_compose/blob/v0.8.1/UPGRADE.md ',
260
+ )
261
+ # steep:ignore:end
210
262
  with_callbacks(context: :update) { save_models(bang: true) }
211
263
  end
212
264
  end || raise_on_save_error
213
265
  end
214
266
 
267
+ # Returns true if model is persisted.
268
+ #
269
+ # By overriding this definition, you can control the callbacks that are triggered when a save is made.
270
+ # For example, returning false will trigger before_create, around_create and after_create,
271
+ # and returning true will trigger before_update, around_update and after_update.
272
+ #
273
+ # @return [Boolean] returns true if model is persisted.
274
+ def persisted? = super
275
+
215
276
  private
216
277
 
217
278
  def models = @__models ||= ActiveRecordCompose::ComposedCollection.new(self)
218
279
 
219
280
  def validate_models
220
- models.__wrapped_models.select { _1.invalid? }.each { errors.merge!(_1) }
281
+ models.__wrapped_models.lazy.select { _1.invalid? }.each { errors.merge!(_1) }
221
282
  end
222
283
 
223
284
  def with_callbacks(context: nil, &block)
224
- context ||= persisted? ? :update : :create
225
- run_callbacks(:save) { run_callbacks(context, &block) }
285
+ run_callbacks(:save) { run_callbacks(callback_context(context:), &block) }
286
+ end
287
+
288
+ def callback_context(context: nil)
289
+ context || (persisted? ? :update : :create)
226
290
  end
227
291
 
228
292
  def save_models(bang:)
@@ -234,5 +298,15 @@ module ActiveRecordCompose
234
298
  def raise_on_save_error = raise ActiveRecord::RecordNotSaved.new(raise_on_save_error_message, self)
235
299
 
236
300
  def raise_on_save_error_message = 'Failed to save the model.'
301
+
302
+ # steep:ignore:start
303
+ def deprecator
304
+ if ActiveRecord.respond_to?(:deprecator)
305
+ ActiveRecord.deprecator
306
+ else # for rails 7.0.x or lower
307
+ ActiveSupport::Deprecation
308
+ end
309
+ end
310
+ # steep:ignore:end
237
311
  end
238
312
  end
@@ -18,7 +18,7 @@ module ActiveRecordCompose
18
18
 
19
19
  def with_connection(&) = ar_class.with_connection(&) # steep:ignore
20
20
 
21
- def composite_primary_key? = false
21
+ def composite_primary_key? = false # steep:ignore
22
22
 
23
23
  private
24
24
 
@@ -28,6 +28,6 @@ module ActiveRecordCompose
28
28
  def id = nil
29
29
 
30
30
  def trigger_transactional_callbacks? = true
31
- def restore_transaction_record_state(_force_restore_state = false) = nil # rubocop:disable Style/OptionalBooleanParameter
31
+ def restore_transaction_record_state(_force_restore_state = false) = nil
32
32
  end
33
33
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordCompose
4
- VERSION = '0.7.0'
4
+ VERSION = '0.8.1'
5
5
  end
@@ -7,3 +7,15 @@ require_relative 'active_record_compose/model'
7
7
 
8
8
  module ActiveRecordCompose
9
9
  end
10
+
11
+ if ActiveRecordCompose::VERSION == '0.8.1'
12
+ unless ENV['ACTIVE_RECORD_COMPOSE_SILENCE_DEPRECATION'] # rubocop:disable Style/SoleNestedConditional
13
+ warn <<~WARN
14
+
15
+ [DEPRECATION] You are using active_record_compose version 0.8.1, which is deprecated.
16
+ Please upgrade to the latest version.
17
+ See: https://github.com/hamajyotan/active_record_compose/blob/v0.8.1/UPGRADE.md
18
+
19
+ WARN
20
+ end
21
+ end
@@ -89,6 +89,7 @@ module ActiveRecordCompose
89
89
  private
90
90
  def models: -> ComposedCollection
91
91
  def with_callbacks: (?context: (nil | :create | :update)) { () -> bool } -> bool
92
+ def callback_context: (?context: (nil | :create | :update)) -> (:create | :update)
92
93
  def validate_models: -> void
93
94
  def save_models: (bang: bool) -> bool
94
95
  def raise_validation_error: -> bot
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_compose
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - hamajyotan
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-02-12 00:00:00.000000000 Z
10
+ date: 2025-06-21 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activerecord
@@ -15,14 +15,20 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '6.1'
18
+ version: '7.0'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '8.1'
19
22
  type: :runtime
20
23
  prerelease: false
21
24
  version_requirements: !ruby/object:Gem::Requirement
22
25
  requirements:
23
26
  - - ">="
24
27
  - !ruby/object:Gem::Version
25
- version: '6.1'
28
+ version: '7.0'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '8.1'
26
32
  description: activemodel form object pattern. it embraces multiple AR models and provides
27
33
  a transparent interface as if they were a single model.
28
34
  email:
@@ -36,6 +42,7 @@ files:
36
42
  - CODE_OF_CONDUCT.md
37
43
  - LICENSE.txt
38
44
  - README.md
45
+ - UPGRADE.md
39
46
  - lib/active_record_compose.rb
40
47
  - lib/active_record_compose/composed_collection.rb
41
48
  - lib/active_record_compose/delegate_attribute.rb
@@ -52,8 +59,17 @@ metadata:
52
59
  homepage_uri: https://github.com/hamajyotan/active_record_compose
53
60
  source_code_uri: https://github.com/hamajyotan/active_record_compose
54
61
  changelog_uri: https://github.com/hamajyotan/active_record_compose/blob/main/CHANGELOG.md
55
- documentation_uri: https://www.rubydoc.info/gems/active_record_compose/0.7.0
62
+ documentation_uri: https://www.rubydoc.info/gems/active_record_compose/0.8.1
56
63
  rubygems_mfa_required: 'true'
64
+ post_install_message: |2+
65
+
66
+ Notice: This version (0.8.1) is deprecated.
67
+
68
+ Please upgrade to the latest version of `active_record_compose` as soon as possible.
69
+ Future releases will not support this version.
70
+
71
+ See https://github.com/hamajyotan/active_record_compose/blob/v0.8.1/UPGRADE.md for migration steps.
72
+
57
73
  rdoc_options: []
58
74
  require_paths:
59
75
  - lib
@@ -72,3 +88,4 @@ rubygems_version: 3.6.2
72
88
  specification_version: 4
73
89
  summary: activemodel form object pattern
74
90
  test_files: []
91
+ ...