active_record_compose 1.1.0 → 1.1.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: 76bd8c66869f7b24c2938ecb72cf2c3b9f47cdcc34a8e1c87f57329f50880ecd
4
- data.tar.gz: f6efc5ec40f5e870064dec9928e56a3ee8a3ca07ea00bd5dcbe86f0855d440ce
3
+ metadata.gz: 491f46737a3744c5c5d3e0319de3ee9f64ae21a9adfab39f36cedc478bfa0e7c
4
+ data.tar.gz: 406f4c471e0d7c40ec7b3a0d492f8e7e6c96d93fd4b43204a128bf52c1f7ed4c
5
5
  SHA512:
6
- metadata.gz: c0361c3c95394e550e8571ea4880545dd8e566e9a6d4c829eb494db90226c009487361ca7eed8e3691d6dbf59674e7fc2fa782b2db006b79113c95c5b229f95c
7
- data.tar.gz: 2a2ac6e1d86640c8d7df1448fc362e459790561a8223066351cadd7ea297e456bc093a015fb8a765e755deccbd5071e342fe035180a4c222ce81584305388085
6
+ metadata.gz: 9ff0e91cfb0ca22322d1b9ca47c32f09372afa206e9fa6690c1dfe5ca30d221269ccc55fe17556627234ea8a7019118ddec8a46ad24a52c47239e694dbcac323
7
+ data.tar.gz: 925001a566ee81600ce91862c0c95f241dba2cd735f6ecbc3e987e83e6cd6f59d217e67afa4bed0a981f35c16645661ef7eeb00adb18df92999f9127e22116e0
data/.yardopts CHANGED
@@ -1,4 +1,6 @@
1
1
  --private
2
+ --no-private
2
3
  --markup markdown
3
4
  --markup-provider redcarpet
5
+ --plugin activesupport-concern
4
6
  --default-return void
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.1.1] - 2025-12-04
4
+
5
+ * fix: the save method would return nil instead of false.
6
+ * doc: We've simplified the documentation comment yard.
7
+
3
8
  ## [1.1.0] - 2025-11-19
4
9
 
5
10
  * Implemented ActiveRecord-like #inspect
@@ -5,8 +5,6 @@ require_relative "attributes/delegation"
5
5
  require_relative "attributes/querying"
6
6
 
7
7
  module ActiveRecordCompose
8
- # @private
9
- #
10
8
  # Provides attribute-related functionality for use within ActiveRecordCompose::Model.
11
9
  #
12
10
  # This module allows you to define attributes on your composed model, including support
@@ -56,45 +54,32 @@ module ActiveRecordCompose
56
54
  include ActiveModel::Attributes
57
55
 
58
56
  included do
57
+ # @type self: Class
58
+
59
59
  include Querying
60
60
 
61
- # @type self: Class
62
61
  class_attribute :delegated_attributes, instance_writer: false
63
62
  end
64
63
 
65
- module ClassMethods
66
- # Defines the reader and writer for the specified attribute.
67
- #
68
- # @example
69
- # class AccountRegistration < ActiveRecordCompose::Model
70
- # def initialize(account, attributes = {})
71
- # @account = account
72
- # super(attributes)
73
- # models.push(account)
74
- # end
75
- #
76
- # attribute :original_attribute, :string, default: "qux"
77
- # delegate_attribute :name, to: :account
78
- #
79
- # private
80
- #
81
- # attr_reader :account
82
- # end
83
- #
84
- # account = Account.new
85
- # account.name = "foo"
86
- #
87
- # registration = AccountRegistration.new(account)
88
- # registration.name # => "foo" (delegated)
89
- # registration.name? # => true (delegated attribute method + `?`)
90
- #
91
- # registration.name = "bar" # => updates account.name
92
- # account.name # => "bar"
93
- # account.name? # => true
64
+ # steep:ignore:start
65
+
66
+ class_methods do
67
+ # Provides a method of attribute access to the encapsulated model.
94
68
  #
95
- # registration.attributes
96
- # # => { "original_attribute" => "qux", "name" => "bar" }
69
+ # It provides a way to access the attributes of the model it encompasses,
70
+ # allowing transparent access as if it had those attributes itself.
97
71
  #
72
+ # @param [Array<Symbol, String>] attributes
73
+ # attributes A variable-length list of attribute names to delegate.
74
+ # @param [Symbol, String] to
75
+ # The target object to which attributes are delegated (keyword argument).
76
+ # @param [Boolean] allow_nil
77
+ # allow_nil Whether to allow nil values. Defaults to false.
78
+ # @example Basic usage
79
+ # delegate_attribute :name, :email, to: :profile
80
+ # @example Allowing nil
81
+ # delegate_attribute :bio, to: :profile, allow_nil: true
82
+ # @see Module#delegate for similar behavior in ActiveSupport
98
83
  def delegate_attribute(*attributes, to:, allow_nil: false)
99
84
  if to.start_with?("@")
100
85
  raise ArgumentError, "Instance variables cannot be specified in delegate to. (#{to})"
@@ -107,45 +92,68 @@ module ActiveRecordCompose
107
92
  end
108
93
 
109
94
  # Returns a array of attribute name.
110
- # Attributes declared with `delegate_attribute` are also merged.
95
+ # Attributes declared with {.delegate_attribute} are also merged.
111
96
  #
97
+ # @see #attribute_names
112
98
  # @return [Array<String>] array of attribute name.
113
99
  def attribute_names = super + delegated_attributes.to_a.map { _1.attribute_name }
114
100
  end
115
101
 
102
+ # steep:ignore:end
103
+
116
104
  # Returns a array of attribute name.
117
- # Attributes declared with `delegate_attribute` are also merged.
105
+ # Attributes declared with {.delegate_attribute} are also merged.
106
+ #
107
+ # class Foo < ActiveRecordCompose::Base
108
+ # def initialize(attributes = {})
109
+ # @account = Account.new
110
+ # super
111
+ # end
112
+ #
113
+ # attribute :confirmation, :boolean, default: false # plain attribute
114
+ # delegate_attribute :name, to: :account # delegated attribute
115
+ #
116
+ # private
117
+ #
118
+ # attr_reader :account
119
+ # end
118
120
  #
121
+ # Foo.attribute_names # Returns the merged state of plain and delegated attributes
122
+ # # => ["confirmation" ,"name"]
123
+ #
124
+ # foo = Foo.new
125
+ # foo.attribute_names # Similar behavior for instance method version
126
+ # # => ["confirmation", "name"]
127
+ #
128
+ # @see #attributes
119
129
  # @return [Array<String>] array of attribute name.
120
130
  def attribute_names = super + delegated_attributes.to_a.map { _1.attribute_name }
121
131
 
122
132
  # Returns a hash with the attribute name as key and the attribute value as value.
123
- # Attributes declared with `delegate_attribute` are also merged.
133
+ # Attributes declared with {.delegate_attribute} are also merged.
124
134
  #
125
- # @return [Hash] hash with the attribute name as key and the attribute value as value.
126
- # @example
127
- # class AccountRegistration < ActiveRecordCompose::Model
128
- # def initialize(account, attributes = {})
129
- # @account = account
130
- # super(attributes)
131
- # models.push(account)
132
- # end
135
+ # class Foo < ActiveRecordCompose::Base
136
+ # def initialize(attributes = {})
137
+ # @account = Account.new
138
+ # super
139
+ # end
133
140
  #
134
- # attribute :original_attribute, :string, default: "qux"
135
- # delegate_attribute :name, to: :account
141
+ # attribute :confirmation, :boolean, default: false # plain attribute
142
+ # delegate_attribute :name, to: :account # delegated attribute
136
143
  #
137
- # private
144
+ # private
138
145
  #
139
- # attr_reader :account
140
- # end
141
- #
142
- # account = Account.new
143
- # account.name = "foo"
146
+ # attr_reader :account
147
+ # end
144
148
  #
145
- # registration = AccountRegistration.new(account)
149
+ # foo = Foo.new
150
+ # foo.name = "Alice"
151
+ # foo.confirmation = true
146
152
  #
147
- # registration.attributes # => { "original_attribute" => "qux", "name" => "bar" }
153
+ # foo.attributes # Returns the merged state of plain and delegated attributes
154
+ # # => { "confirmation" => true, "name" => "Alice" }
148
155
  #
156
+ # @return [Hash<String, Object>] hash with the attribute name as key and the attribute value as value.
149
157
  def attributes
150
158
  super.merge(*delegated_attributes.to_a.map { _1.attribute_hash(self) })
151
159
  end
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordCompose
4
- # @private
5
- #
6
4
  # Provides hooks into the life cycle of an ActiveRecordCompose model,
7
5
  # allowing you to insert custom logic before or after changes to the object's state.
8
6
  #
@@ -21,18 +19,48 @@ module ActiveRecordCompose
21
19
  include ActiveModel::Validations::Callbacks
22
20
 
23
21
  included do
22
+ # @!method self.before_save(*args, &block)
23
+ # Registers a callback to be called before a model is saved.
24
+
25
+ # @!method self.around_save(*args, &block)
26
+ # Registers a callback to be called around the save of a model.
27
+
28
+ # @!method self.after_save(*args, &block)
29
+ # Registers a callback to be called after a model is saved.
30
+
24
31
  define_model_callbacks :save
32
+
33
+ # @!method self.before_create(*args, &block)
34
+ # Registers a callback to be called before a model is created.
35
+
36
+ # @!method self.around_create(*args, &block)
37
+ # Registers a callback to be called around the creation of a model.
38
+
39
+ # @!method self.after_create(*args, &block)
40
+ # Registers a callback to be called after a model is created.
41
+
25
42
  define_model_callbacks :create
43
+
44
+ # @!method self.before_update(*args, &block)
45
+ # Registers a callback to be called before a model is updated.
46
+
47
+ # @!method self.around_update(*args, &block)
48
+ # Registers a callback to be called around the update of a model.
49
+
50
+ # @!method self.after_update(*args, &block)
51
+ # Registers a callback to be called after a update is updated.
26
52
  define_model_callbacks :update
27
53
  end
28
54
 
29
55
  private
30
56
 
57
+ # @private
31
58
  # Evaluate while firing callbacks such as `before_save` `after_save`
32
59
  # before and after block evaluation.
33
60
  #
34
61
  def with_callbacks(&block) = run_callbacks(:save) { run_callbacks(callback_context, &block) }
35
62
 
63
+ # @private
36
64
  # Returns the symbol representing the callback context, which is `:create` if the record
37
65
  # is new, or `:update` if it has been persisted.
38
66
  #
@@ -4,8 +4,6 @@ require "active_support/parameter_filter"
4
4
  require_relative "attributes"
5
5
 
6
6
  module ActiveRecordCompose
7
- # @private
8
- #
9
7
  # It provides #inspect behavior.
10
8
  # It tries to replicate the inspect format provided by ActiveRecord as closely as possible.
11
9
  #
@@ -39,26 +37,48 @@ module ActiveRecordCompose
39
37
  extend ActiveSupport::Concern
40
38
  include ActiveRecordCompose::Attributes
41
39
 
40
+ # steep:ignore:start
41
+
42
+ # @private
43
+ FILTERED_MASK =
44
+ Class.new(DelegateClass(::String)) do
45
+ def pretty_print(pp)
46
+ pp.text __getobj__
47
+ end
48
+ end.new(ActiveSupport::ParameterFilter::FILTERED).freeze
49
+ private_constant :FILTERED_MASK
50
+
51
+ # steep:ignore:end
52
+
42
53
  included do
43
54
  self.filter_attributes = []
44
55
  end
45
56
 
46
- module ClassMethods
57
+ # steep:ignore:start
58
+
59
+ class_methods do
60
+ # Returns columns not to expose when invoking {#inspect}.
61
+ #
62
+ # @return [Array<Symbol>]
63
+ # @see #inspect
47
64
  def filter_attributes
48
65
  if @filter_attributes.nil?
49
- superclass.filter_attributes # steep:ignore
66
+ superclass.filter_attributes
50
67
  else
51
68
  @filter_attributes
52
69
  end
53
70
  end
54
71
 
72
+ # Specify columns not to expose when invoking {#inspect}.
73
+ #
74
+ # @param [Array<Symbol>] value
75
+ # @see #inspect
55
76
  def filter_attributes=(value)
56
77
  @inspection_filter = nil
57
78
  @filter_attributes = value
58
79
  end
59
80
 
60
- # steep:ignore:start
61
-
81
+ # @private
62
82
  def inspection_filter
63
83
  if @filter_attributes.nil?
64
84
  superclass.inspection_filter
@@ -77,20 +97,41 @@ module ActiveRecordCompose
77
97
  @filter_attributes ||= nil
78
98
  end
79
99
  end
80
-
81
- FILTERED_MASK =
82
- Class.new(DelegateClass(::String)) do
83
- def pretty_print(pp)
84
- pp.text __getobj__
85
- end
86
- end.new(ActiveSupport::ParameterFilter::FILTERED).freeze
87
- private_constant :FILTERED_MASK
88
-
89
- # steep:ignore:end
90
100
  end
91
101
 
102
+ # steep:ignore:end
103
+
92
104
  # Returns a formatted string representation of the record's attributes.
105
+ # It tries to replicate the inspect format provided by ActiveRecord as closely as possible.
106
+ #
107
+ # @example
108
+ # class Model < ActiveRecordCompose::Model
109
+ # def initialize(ar_model)
110
+ # @ar_model = ar_model
111
+ # super
112
+ # end
113
+ #
114
+ # attribute :foo, :date, default: -> { Date.today }
115
+ # delegate_attribute :bar, to: :ar_model
116
+ #
117
+ # private attr_reader :ar_model
118
+ # end
119
+ #
120
+ # m = Model.new(ar_model)
121
+ # m.inspect #=> #<Model:0x00007ff0fe75fe58 foo: "2025-11-14", bar: "bar">
122
+ #
123
+ # @example use {.filter_attributes}
124
+ # class Model < ActiveRecordCompose::Model
125
+ # self.filter_attributes += %i[foo]
126
+ #
127
+ # # ...
128
+ # end
129
+ #
130
+ # m = Model.new(ar_model)
131
+ # m.inspect #=> #<Model:0x00007ff0fe75fe58 foo: [FILTERED], bar: "bar">
93
132
  #
133
+ # @return [String] formatted string representation of the record's attributes.
134
+ # @see .filter_attributes
94
135
  def inspect
95
136
  inspection =
96
137
  if @attributes
@@ -126,6 +167,7 @@ module ActiveRecordCompose
126
167
 
127
168
  private
128
169
 
170
+ # @private
129
171
  def format_for_inspect(name, value)
130
172
  return value.inspect if value.nil?
131
173
 
@@ -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
@@ -5,7 +5,6 @@ require_relative "composed_collection"
5
5
  module ActiveRecordCompose
6
6
  using ComposedCollection::PackagePrivate
7
7
 
8
- # @private
9
8
  module Validations
10
9
  extend ActiveSupport::Concern
11
10
  include ActiveModel::Validations::Callbacks
@@ -22,27 +21,86 @@ module ActiveRecordCompose
22
21
  perform_validations(options) ? super : raise_validation_error
23
22
  end
24
23
 
24
+ # Runs all the validations and returns the result as true or false.
25
+ #
26
+ # @param context Validation context.
27
+ # @return [Boolean] true on success, false on failure.
25
28
  def valid?(context = nil) = context_for_override_validation.with_override(context) { super }
26
29
 
30
+ # @!method validate(context = nil)
31
+ # Alias for {#valid?}
32
+ # @see #valid? Validation context.
33
+ # @param context
34
+ # @return [Boolean] true on success, false on failure.
35
+
36
+ # @!method validate!(context = nil)
37
+ # @see #valid?
38
+ # Runs all the validations within the specified context.
39
+ # no errors are found, raises `ActiveRecord::RecordInvalid` otherwise.
40
+ # @param context Validation context.
41
+ # @raise ActiveRecord::RecordInvalid
42
+
43
+ # @!method errors
44
+ # Returns the `ActiveModel::Errors` object that holds all information about attribute error messages.
45
+ #
46
+ # The `ActiveModel::Base` implementation itself,
47
+ # but also aggregates error information for objects stored in {#models} when validation is performed.
48
+ #
49
+ # class Account < ActiveRecord::Base
50
+ # validates :name, :email, presence: true
51
+ # end
52
+ #
53
+ # class AccountRegistration < ActiveRecordCompose::Model
54
+ # def initialize(attributes = {})
55
+ # @account = Account.new
56
+ # super(attributes)
57
+ # models << account
58
+ # end
59
+ #
60
+ # attribute :confirmation, :boolean, default: false
61
+ # validates :confirmation, presence: true
62
+ #
63
+ # private
64
+ #
65
+ # attr_reader :account
66
+ # end
67
+ #
68
+ # registration = AccountRegistration
69
+ # registration.valid?
70
+ # #=> false
71
+ #
72
+ # # In addition to the model's own validation error information (`confirmation`), also aggregates
73
+ # # error information for objects stored in `account` (`name`, `email`) when validation is performed.
74
+ #
75
+ # registration.errors.map { _1.attribute } #=> [:name, :email, :confirmation]
76
+ #
77
+ # @return [ActiveModel::Errors]
78
+
27
79
  private
28
80
 
81
+ # @private
29
82
  def validate_models
30
83
  context = override_validation_context
31
84
  models.__wrapped_models.lazy.select { _1.invalid?(context) }.each { errors.merge!(_1) }
32
85
  end
33
86
 
87
+ # @private
34
88
  def perform_validations(options)
35
89
  options[:validate] == false || valid?(options[:context])
36
90
  end
37
91
 
92
+ # @private
38
93
  def raise_validation_error = raise ActiveRecord::RecordInvalid, self
39
94
 
95
+ # @private
40
96
  def context_for_override_validation
41
97
  @context_for_override_validation ||= OverrideValidationContext.new
42
98
  end
43
99
 
100
+ # @private
44
101
  def override_validation_context = context_for_override_validation.context
45
102
 
103
+ # @private
46
104
  class OverrideValidationContext
47
105
  attr_reader :context
48
106
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordCompose
4
- VERSION = "1.1.0"
4
+ VERSION = "1.1.1"
5
5
  end
@@ -4,19 +4,13 @@ module ActiveRecordCompose
4
4
  include ActiveModel::Attributes
5
5
  include Querying
6
6
 
7
+ def self.delegate_attribute: (*untyped methods, to: untyped, ?allow_nil: bool) -> untyped
8
+ def self.delegated_attributes: () -> Array[Delegation]
9
+ def self.delegated_attributes=: (Array[Delegation]) -> untyped
7
10
  def delegated_attributes: () -> Array[Delegation]
8
11
 
9
12
  @attributes: untyped
10
13
 
11
- module ClassMethods : Module
12
- include ActiveModel::Attributes::ClassMethods
13
- include ActiveModel::AttributeMethods::ClassMethods
14
-
15
- def delegate_attribute: (*untyped methods, to: untyped, ?allow_nil: bool) -> untyped
16
- def delegated_attributes: () -> Array[Delegation]
17
- def delegated_attributes=: (Array[Delegation]) -> untyped
18
- end
19
-
20
14
  class AttributePredicate
21
15
  def initialize: (untyped value) -> void
22
16
  def call: -> bool
@@ -88,31 +82,20 @@ module ActiveRecordCompose
88
82
  module Inspectable
89
83
  extend ActiveSupport::Concern
90
84
  include Attributes
91
- extend Inspectable::ClassMethods
92
85
 
86
+ def self.inspection_filter: () -> ActiveSupport::ParameterFilter
87
+ def self.filter_attributes: () -> Array[untyped]
88
+ def self.filter_attributes=: (Array[untyped]) -> void
93
89
  def inspect: () -> String
94
90
  def pretty_print: (untyped q) -> void
95
91
 
96
92
  private
97
93
  def format_for_inspect: (String name, untyped value) -> String
98
-
99
- module ClassMethods
100
- FILTERED_MASK: String
101
-
102
- def inspection_filter: () -> ActiveSupport::ParameterFilter
103
- def filter_attributes: () -> Array[untyped]
104
- def filter_attributes=: (Array[untyped]) -> void
105
-
106
- @inspection_filter: ActiveSupport::ParameterFilter?
107
- @filter_attributes: untyped
108
- end
109
94
  end
110
95
 
111
96
  class Model
112
97
  include Attributes
113
- extend Attributes::ClassMethods
114
98
  include TransactionSupport
115
- extend TransactionSupport::ClassMethods
116
99
  include Callbacks
117
100
 
118
101
  @__models: ComposedCollection
@@ -125,19 +108,12 @@ module ActiveRecordCompose
125
108
  module TransactionSupport
126
109
  extend ActiveSupport::Concern
127
110
  include ActiveRecord::Transactions
128
- extend TransactionSupport::ClassMethods
129
-
130
- module ClassMethods
131
- include ActiveSupport::Callbacks::ClassMethods
132
-
133
- def before_commit: (*untyped) -> untyped
134
- def after_commit: (*untyped) -> untyped
135
- def after_rollback: (*untyped) -> untyped
111
+ include ActiveSupport::Callbacks
112
+ extend ActiveSupport::Callbacks::ClassMethods
136
113
 
137
- private
138
- def prepend_option: -> ({ prepend: true } | {})
139
- def set_options_for_callbacks!: (untyped, ?({ prepend: true } | {})) -> untyped
140
- end
114
+ def self.before_commit: (*untyped) -> untyped
115
+ def self.after_commit: (*untyped) -> untyped
116
+ def self.after_rollback: (*untyped) -> untyped
141
117
 
142
118
  def save: (**untyped options) -> bool
143
119
  def save!: (**untyped options) -> untyped
@@ -154,7 +130,6 @@ module ActiveRecordCompose
154
130
  module Persistence
155
131
  include Callbacks
156
132
  include TransactionSupport
157
- extend TransactionSupport::ClassMethods
158
133
 
159
134
  def save: (**untyped options) -> bool
160
135
  def save!: (**untyped options) -> untyped
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_compose
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - hamajyotan