active_record_compose 1.1.0 → 1.2.0

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.
@@ -89,315 +89,6 @@ module ActiveRecordCompose
89
89
  include ActiveRecordCompose::TransactionSupport
90
90
  include ActiveRecordCompose::Inspectable
91
91
 
92
- begin
93
- # @group Model Core
94
-
95
- # @!method self.delegate_attribute(*attributes, to:, allow_nil: false)
96
- # Provides a method of attribute access to the encapsulated model.
97
- #
98
- # It provides a way to access the attributes of the model it encompasses,
99
- # allowing transparent access as if it had those attributes itself.
100
- #
101
- # @param [Array<Symbol, String>] attributes
102
- # attributes A variable-length list of attribute names to delegate.
103
- # @param [Symbol, String] to
104
- # The target object to which attributes are delegated (keyword argument).
105
- # @param [Boolean] allow_nil
106
- # allow_nil Whether to allow nil values. Defaults to false.
107
- # @example Basic usage
108
- # delegate_attribute :name, :email, to: :profile
109
- # @example Allowing nil
110
- # delegate_attribute :bio, to: :profile, allow_nil: true
111
- # @see Module#delegate for similar behavior in ActiveSupport
112
-
113
- # @!method self.attribute_names
114
- # Returns a array of attribute name.
115
- # Attributes declared with {.delegate_attribute} are also merged.
116
- #
117
- # @see #attribute_names
118
- # @return [Array<String>] array of attribute name.
119
-
120
- # @!method attribute_names
121
- # Returns a array of attribute name.
122
- # Attributes declared with {.delegate_attribute} are also merged.
123
- #
124
- # class Foo < ActiveRecordCompose::Base
125
- # def initialize(attributes = {})
126
- # @account = Account.new
127
- # super
128
- # end
129
- #
130
- # attribute :confirmation, :boolean, default: false # plain attribute
131
- # delegate_attribute :name, to: :account # delegated attribute
132
- #
133
- # private
134
- #
135
- # attr_reader :account
136
- # end
137
- #
138
- # Foo.attribute_names # Returns the merged state of plain and delegated attributes
139
- # # => ["confirmation" ,"name"]
140
- #
141
- # foo = Foo.new
142
- # foo.attribute_names # Similar behavior for instance method version
143
- # # => ["confirmation", "name"]
144
- #
145
- # @see #attributes
146
- # @return [Array<String>] array of attribute name.
147
-
148
- # @!method attributes
149
- # Returns a hash with the attribute name as key and the attribute value as value.
150
- # Attributes declared with {.delegate_attribute} are also merged.
151
- #
152
- # class Foo < ActiveRecordCompose::Base
153
- # def initialize(attributes = {})
154
- # @account = Account.new
155
- # super
156
- # end
157
- #
158
- # attribute :confirmation, :boolean, default: false # plain attribute
159
- # delegate_attribute :name, to: :account # delegated attribute
160
- #
161
- # private
162
- #
163
- # attr_reader :account
164
- # end
165
- #
166
- # foo = Foo.new
167
- # foo.name = "Alice"
168
- # foo.confirmation = true
169
- #
170
- # foo.attributes # Returns the merged state of plain and delegated attributes
171
- # # => { "confirmation" => true, "name" => "Alice" }
172
- #
173
- # @return [Hash<String, Object>] hash with the attribute name as key and the attribute value as value.
174
-
175
- # @!method persisted?
176
- # Returns true if model is persisted.
177
- #
178
- # By overriding this definition, you can control the callbacks that are triggered when a save is made.
179
- # For example, returning false will trigger before_create, around_create and after_create,
180
- # and returning true will trigger {.before_update}, {.around_update} and {.after_update}.
181
- #
182
- # @return [Boolean] returns true if model is persisted.
183
- # @example
184
- # # A model where persistence is always false
185
- # class Foo < ActiveRecordCompose::Model
186
- # before_save { puts "before_save called" }
187
- # before_create { puts "before_create called" }
188
- # before_update { puts "before_update called" }
189
- # after_update { puts "after_update called" }
190
- # after_create { puts "after_create called" }
191
- # after_save { puts "after_save called" }
192
- #
193
- # def persisted? = false
194
- # end
195
- #
196
- # # A model where persistence is always true
197
- # class Bar < Foo
198
- # def persisted? = true
199
- # end
200
- #
201
- # Foo.new.save!
202
- # # before_save called
203
- # # before_create called
204
- # # after_create called
205
- # # after_save called
206
- #
207
- # Bar.new.save!
208
- # # before_save called
209
- # # before_update called
210
- # # after_update called
211
- # # after_save called
212
-
213
- # @endgroup
214
-
215
- # @group Validations
216
-
217
- # @!method valid?(context = nil)
218
- # Runs all the validations and returns the result as true or false.
219
- # @param context Validation context.
220
- # @return [Boolean] true on success, false on failure.
221
-
222
- # @!method validate(context = nil)
223
- # Alias for {#valid?}
224
- # @see #valid? Validation context.
225
- # @param context
226
- # @return [Boolean] true on success, false on failure.
227
-
228
- # @!method validate!(context = nil)
229
- # @see #valid?
230
- # Runs all the validations within the specified context.
231
- # no errors are found, raises `ActiveRecord::RecordInvalid` otherwise.
232
- # @param context Validation context.
233
- # @raise ActiveRecord::RecordInvalid
234
-
235
- # @!method errors
236
- # Returns the `ActiveModel::Errors` object that holds all information about attribute error messages.
237
- #
238
- # The `ActiveModel::Base` implementation itself,
239
- # but also aggregates error information for objects stored in {#models} when validation is performed.
240
- #
241
- # class Account < ActiveRecord::Base
242
- # validates :name, :email, presence: true
243
- # end
244
- #
245
- # class AccountRegistration < ActiveRecordCompose::Model
246
- # def initialize(attributes = {})
247
- # @account = Account.new
248
- # super(attributes)
249
- # models << account
250
- # end
251
- #
252
- # attribute :confirmation, :boolean, default: false
253
- # validates :confirmation, presence: true
254
- #
255
- # private
256
- #
257
- # attr_reader :account
258
- # end
259
- #
260
- # registration = AccountRegistration
261
- # registration.valid?
262
- # #=> false
263
- #
264
- # # In addition to the model's own validation error information (`confirmation`), also aggregates
265
- # # error information for objects stored in `account` (`name`, `email`) when validation is performed.
266
- #
267
- # registration.errors.map { _1.attribute } #=> [:name, :email, :confirmation]
268
- #
269
- # @return [ActiveModel::Errors]
270
-
271
- # @endgroup
272
-
273
- # @group Persistences
274
-
275
- # @!method save(**options)
276
- # Save the models that exist in models.
277
- # Returns false if any of the targets fail, true if all succeed.
278
- #
279
- # The save is performed within a single transaction.
280
- #
281
- # Only the `:validate` option takes effect as it is required internally.
282
- # However, we do not recommend explicitly specifying `validate: false` to skip validation.
283
- # Additionally, the `:context` option is not accepted.
284
- # The need for such a value indicates that operations from multiple contexts are being processed.
285
- # If the contexts differ, we recommend separating them into different model definitions.
286
- #
287
- # @param options [Hash] parameters.
288
- # @option options [Boolean] :validate Whether to run validations.
289
- # This option is intended for internal use only.
290
- # Users should avoid explicitly passing <tt>validate: false</tt>,
291
- # as skipping validations can lead to unexpected behavior.
292
- # @return [Boolean] returns true on success, false on failure.
293
-
294
- # @!method save!(**options)
295
- # Behavior is same to {#save}, but raises an exception prematurely on failure.
296
- # @see #save
297
- # @raise ActiveRecord::RecordInvalid
298
- # @raise ActiveRecord::RecordNotSaved
299
-
300
- # @!method update(attributes)
301
- # Assign attributes and {#save}.
302
- #
303
- # @param [Hash<String, Object>] attributes
304
- # new attributes.
305
- # @see #save
306
- # @return [Boolean] returns true on success, false on failure.
307
-
308
- # @!method update!(attributes)
309
- # Behavior is same to {#update}, but raises an exception prematurely on failure.
310
- #
311
- # @param [Hash<String, Object>] attributes
312
- # new attributes.
313
- # @see #save
314
- # @see #update
315
- # @raise ActiveRecord::RecordInvalid
316
- # @raise ActiveRecord::RecordNotSaved
317
-
318
- # @endgroup
319
-
320
- # @group Callbacks
321
-
322
- # @!method self.before_save(*args, &block)
323
- # Registers a callback to be called before a model is saved.
324
-
325
- # @!method self.around_save(*args, &block)
326
- # Registers a callback to be called around the save of a model.
327
-
328
- # @!method self.after_save(*args, &block)
329
- # Registers a callback to be called after a model is saved.
330
-
331
- # @!method self.before_create(*args, &block)
332
- # Registers a callback to be called before a model is created.
333
-
334
- # @!method self.around_create(*args, &block)
335
- # Registers a callback to be called around the creation of a model.
336
-
337
- # @!method self.after_create(*args, &block)
338
- # Registers a callback to be called after a model is created.
339
-
340
- # @!method self.before_update(*args, &block)
341
- # Registers a callback to be called before a model is updated.
342
-
343
- # @!method self.around_update(*args, &block)
344
- # Registers a callback to be called around the update of a model.
345
-
346
- # @!method self.after_update(*args, &block)
347
- # Registers a callback to be called after a update is updated.
348
-
349
- # @!method self.after_commit(*args, &block)
350
- # Registers a block to be called after the transaction is fully committed.
351
-
352
- # @!method self.after_rollback(*args, &block)
353
- # Registers a block to be called after the transaction is rolled back.
354
-
355
- # @endgroup
356
-
357
- # @!method inspect
358
- # Returns a formatted string representation of the record's attributes.
359
- # It tries to replicate the inspect format provided by ActiveRecord as closely as possible.
360
- #
361
- # @example
362
- # class Model < ActiveRecordCompose::Model
363
- # def initialize(ar_model)
364
- # @ar_model = ar_model
365
- # super
366
- # end
367
- #
368
- # attribute :foo, :date, default: -> { Date.today }
369
- # delegate_attribute :bar, to: :ar_model
370
- #
371
- # private attr_reader :ar_model
372
- # end
373
- #
374
- # m = Model.new(ar_model)
375
- # m.inspect #=> #<Model:0x00007ff0fe75fe58 foo: "2025-11-14", bar: "bar">
376
- #
377
- # @example use {.filter_attributes}
378
- # class Model < ActiveRecordCompose::Model
379
- # self.filter_attributes += %i[foo]
380
- #
381
- # # ...
382
- # end
383
- #
384
- # m = Model.new(ar_model)
385
- # m.inspect #=> #<Model:0x00007ff0fe75fe58 foo: [FILTERED], bar: "bar">
386
- #
387
- # @return [String] formatted string representation of the record's attributes.
388
- # @see .filter_attributes
389
-
390
- # @!method self.filter_attributes
391
- # Returns columns not to expose when invoking {#inspect}.
392
- # @return [Array<Symbol>]
393
- # @see #inspect
394
-
395
- # @!method self.filter_attributes=(value)
396
- # Specify columns not to expose when invoking {#inspect}.
397
- # @param [Array<Symbol>] value
398
- # @see #inspect
399
- end
400
-
401
92
  def initialize(attributes = {})
402
93
  super
403
94
  end
@@ -425,8 +116,6 @@ module ActiveRecordCompose
425
116
  #
426
117
  def id = nil
427
118
 
428
- # @group Model Core
429
-
430
119
  private
431
120
 
432
121
  # Returns a collection of model elements to encapsulate.
@@ -439,7 +128,5 @@ module ActiveRecordCompose
439
128
  # @return [ActiveRecordCompose::ComposedCollection]
440
129
  #
441
130
  def models = @__models ||= ActiveRecordCompose::ComposedCollection.new(self)
442
-
443
- # @endgroup
444
131
  end
445
132
  end
@@ -6,7 +6,6 @@ require_relative "composed_collection"
6
6
  module ActiveRecordCompose
7
7
  using ComposedCollection::PackagePrivate
8
8
 
9
- # @private
10
9
  module Persistence
11
10
  extend ActiveSupport::Concern
12
11
  include ActiveRecordCompose::Callbacks
@@ -22,6 +21,11 @@ module ActiveRecordCompose
22
21
  # The need for such a value indicates that operations from multiple contexts are being processed.
23
22
  # If the contexts differ, we recommend separating them into different model definitions.
24
23
  #
24
+ # @param options [Hash] parameters.
25
+ # @option options [Boolean] :validate Whether to run validations.
26
+ # This option is intended for internal use only.
27
+ # Users should avoid explicitly passing <tt>validate: false</tt>,
28
+ # as skipping validations can lead to unexpected behavior.
25
29
  # @return [Boolean] returns true on success, false on failure.
26
30
  def save(**options)
27
31
  with_callbacks { save_models(**options, bang: false) }
@@ -29,38 +33,80 @@ module ActiveRecordCompose
29
33
  false
30
34
  end
31
35
 
32
- # Save the models that exist in models.
33
- # Unlike #save, an exception is raises on failure.
34
- #
35
- # Saving, like `#save`, is performed within a single transaction.
36
- #
37
- # Only the `:validate` option takes effect as it is required internally.
38
- # However, we do not recommend explicitly specifying `validate: false` to skip validation.
39
- # Additionally, the `:context` option is not accepted.
40
- # The need for such a value indicates that operations from multiple contexts are being processed.
41
- # If the contexts differ, we recommend separating them into different model definitions.
36
+ # Behavior is same to {#save}, but raises an exception prematurely on failure.
42
37
  #
38
+ # @see #save
39
+ # @raise ActiveRecord::RecordInvalid
40
+ # @raise ActiveRecord::RecordNotSaved
43
41
  def save!(**options)
44
42
  with_callbacks { save_models(**options, bang: true) } || raise_on_save_error
45
43
  end
46
44
 
47
- # Assign attributes and save.
45
+ # Assign attributes and {#save}.
48
46
  #
47
+ # @param [Hash<String, Object>] attributes
48
+ # new attributes.
49
+ # @see #save
49
50
  # @return [Boolean] returns true on success, false on failure.
50
51
  def update(attributes)
51
52
  assign_attributes(attributes)
52
53
  save
53
54
  end
54
55
 
55
- # Behavior is same to `#update`, but raises an exception prematurely on failure.
56
+ # Behavior is same to {#update}, but raises an exception prematurely on failure.
56
57
  #
58
+ # @param [Hash<String, Object>] attributes
59
+ # new attributes.
60
+ # @see #save
61
+ # @see #update
62
+ # @raise ActiveRecord::RecordInvalid
63
+ # @raise ActiveRecord::RecordNotSaved
57
64
  def update!(attributes)
58
65
  assign_attributes(attributes)
59
66
  save!
60
67
  end
61
68
 
69
+ # @!method persisted?
70
+ # Returns true if model is persisted.
71
+ #
72
+ # By overriding this definition, you can control the callbacks that are triggered when a save is made.
73
+ # For example, returning false will trigger before_create, around_create and after_create,
74
+ # and returning true will trigger {.before_update}, {.around_update} and {.after_update}.
75
+ #
76
+ # @return [Boolean] returns true if model is persisted.
77
+ # @example
78
+ # # A model where persistence is always false
79
+ # class Foo < ActiveRecordCompose::Model
80
+ # before_save { puts "before_save called" }
81
+ # before_create { puts "before_create called" }
82
+ # before_update { puts "before_update called" }
83
+ # after_update { puts "after_update called" }
84
+ # after_create { puts "after_create called" }
85
+ # after_save { puts "after_save called" }
86
+ #
87
+ # def persisted? = false
88
+ # end
89
+ #
90
+ # # A model where persistence is always true
91
+ # class Bar < Foo
92
+ # def persisted? = true
93
+ # end
94
+ #
95
+ # Foo.new.save!
96
+ # # before_save called
97
+ # # before_create called
98
+ # # after_create called
99
+ # # after_save called
100
+ #
101
+ # Bar.new.save!
102
+ # # before_save called
103
+ # # before_update called
104
+ # # after_update called
105
+ # # after_save called
106
+
62
107
  private
63
108
 
109
+ # @private
64
110
  def save_models(bang:, **options)
65
111
  models.__wrapped_models.all? do |model|
66
112
  if bang
@@ -71,8 +117,10 @@ module ActiveRecordCompose
71
117
  end
72
118
  end
73
119
 
120
+ # @private
74
121
  def raise_on_save_error = raise ActiveRecord::RecordNotSaved.new(raise_on_save_error_message, self)
75
122
 
123
+ # @private
76
124
  def raise_on_save_error_message = "Failed to save the model."
77
125
  end
78
126
  end
@@ -3,7 +3,6 @@
3
3
  require "active_support/core_ext/module"
4
4
 
5
5
  module ActiveRecordCompose
6
- # @private
7
6
  module TransactionSupport
8
7
  extend ActiveSupport::Concern
9
8
 
@@ -11,82 +10,103 @@ module ActiveRecordCompose
11
10
  define_callbacks :commit, :rollback, :before_commit, scope: [ :kind, :name ]
12
11
  end
13
12
 
14
- module ClassMethods
15
- # steep:ignore:start
13
+ # steep:ignore:start
16
14
 
15
+ class_methods do
16
+ # @private
17
17
  # @deprecated
18
18
  def with_connection(...)
19
19
  ActiveRecord.deprecator.warn("`with_connection` is deprecated. Use `ActiveRecord::Base.with_connection` instead.")
20
20
  ActiveRecord::Base.with_connection(...)
21
21
  end
22
22
 
23
+ # @private
23
24
  # @deprecated
24
25
  def lease_connection(...)
25
26
  ActiveRecord.deprecator.warn("`lease_connection` is deprecated. Use `ActiveRecord::Base.lease_connection` instead.")
26
27
  ActiveRecord::Base.lease_connection(...)
27
28
  end
28
29
 
30
+ # @private
29
31
  # @deprecated
30
32
  def connection(...)
31
33
  ActiveRecord.deprecator.warn("`connection` is deprecated. Use `ActiveRecord::Base.connection` instead.")
32
34
  ActiveRecord::Base.connection(...)
33
35
  end
36
+ end
37
+
38
+ # steep:ignore:end
34
39
 
35
- # steep:ignore:end
40
+ # steep:ignore:start
36
41
 
42
+ class_methods do
43
+ # @private
37
44
  def before_commit(*args, &block)
38
45
  set_options_for_callbacks!(args)
39
- set_callback(:before_commit, :before, *args, &block) # steep:ignore
46
+ set_callback(:before_commit, :before, *args, &block)
40
47
  end
41
48
 
49
+ # Registers a block to be called after the transaction is fully committed.
50
+ #
42
51
  def after_commit(*args, &block)
43
52
  set_options_for_callbacks!(args, prepend_option)
44
- set_callback(:commit, :after, *args, &block) # steep:ignore
53
+ set_callback(:commit, :after, *args, &block)
45
54
  end
46
55
 
56
+ # Registers a block to be called after the transaction is rolled back.
57
+ #
47
58
  def after_rollback(*args, &block)
48
59
  set_options_for_callbacks!(args, prepend_option)
49
- set_callback(:rollback, :after, *args, &block) # steep:ignore
60
+ set_callback(:rollback, :after, *args, &block)
50
61
  end
51
62
 
52
63
  private
53
64
 
65
+ # @private
54
66
  def prepend_option
55
- if ActiveRecord.run_after_transaction_callbacks_in_order_defined # steep:ignore
67
+ if ActiveRecord.run_after_transaction_callbacks_in_order_defined
56
68
  { prepend: true }
57
69
  else
58
70
  {}
59
71
  end
60
72
  end
61
73
 
74
+ # @private
62
75
  def set_options_for_callbacks!(args, enforced_options = {})
63
76
  options = args.extract_options!.merge!(enforced_options)
64
77
  args << options
65
78
  end
66
79
  end
67
80
 
68
- def save(**options) = with_transaction_returning_status { super }
69
-
70
- def save!(**options) = with_transaction_returning_status { super }
81
+ # steep:ignore:end
71
82
 
72
83
  concerning :SupportForActiveRecordConnectionAdaptersTransaction do
84
+ # @private
73
85
  def trigger_transactional_callbacks? = true
74
86
 
87
+ # @private
75
88
  def before_committed!
76
89
  _run_before_commit_callbacks
77
90
  end
78
91
 
92
+ # @private
79
93
  def committed!(should_run_callbacks: true)
80
94
  _run_commit_callbacks if should_run_callbacks
81
95
  end
82
96
 
97
+ # @private
83
98
  def rolledback!(force_restore_state: false, should_run_callbacks: true)
84
99
  _run_rollback_callbacks if should_run_callbacks
85
100
  end
86
101
  end
87
102
 
103
+ def save(**options) = with_transaction_returning_status { super }
104
+
105
+ def save!(**options) = with_transaction_returning_status { super }
106
+
88
107
  private
89
108
 
109
+ # @private
90
110
  def with_transaction_returning_status
91
111
  connection_pool.with_connection do |connection|
92
112
  with_pool_transaction_isolation_level(connection) do
@@ -96,13 +116,15 @@ module ActiveRecordCompose
96
116
  connection.add_transaction_record(self, ensure_finalize || has_transactional_callbacks?) # steep:ignore
97
117
 
98
118
  yield.tap { raise ActiveRecord::Rollback unless _1 }
99
- end
119
+ end || false
100
120
  end
101
121
  end
102
122
  end
103
123
 
124
+ # @private
104
125
  def default_ar_class = ActiveRecord::Base
105
126
 
127
+ # @private
106
128
  def connection_pool(ar_class: default_ar_class)
107
129
  connection_specification_name = ar_class.connection_specification_name
108
130
  role = ar_class.current_role
@@ -114,6 +136,7 @@ module ActiveRecordCompose
114
136
  connection_handler.retrieve_connection_pool(connection_specification_name, **retrieve_options)
115
137
  end
116
138
 
139
+ # @private
117
140
  def with_pool_transaction_isolation_level(connection, &block)
118
141
  if ActiveRecord.gem_version.release >= Gem::Version.new("8.1.0")
119
142
  isolation_level = ActiveRecord.default_transaction_isolation_level # steep:ignore
@@ -123,6 +146,7 @@ module ActiveRecordCompose
123
146
  end
124
147
  end
125
148
 
149
+ # @private
126
150
  def has_transactional_callbacks?
127
151
  _rollback_callbacks.present? || _commit_callbacks.present? || _before_commit_callbacks.present? # steep:ignore
128
152
  end