active_record_compose 0.11.1 → 0.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -76
- data/.yardopts +4 -0
- data/CHANGELOG.md +25 -0
- data/README.md +7 -8
- data/lib/active_record_compose/attributes/delegation.rb +40 -0
- data/lib/active_record_compose/attributes/querying.rb +69 -0
- data/lib/active_record_compose/attributes.rb +179 -0
- data/lib/active_record_compose/callbacks.rb +27 -0
- data/lib/active_record_compose/composed_collection.rb +17 -10
- data/lib/active_record_compose/model.rb +352 -84
- data/lib/active_record_compose/persistence.rb +86 -0
- data/lib/active_record_compose/transaction_support.rb +1 -0
- data/lib/active_record_compose/validations.rb +9 -0
- data/lib/active_record_compose/version.rb +1 -1
- data/lib/active_record_compose/wrapped_model.rb +5 -3
- data/lib/active_record_compose.rb +7 -3
- data/sig/_internal/package_private.rbs +63 -22
- data/sig/active_record_compose.rbs +1 -1
- metadata +15 -6
- data/lib/active_record_compose/attribute_querying.rb +0 -66
- data/lib/active_record_compose/delegate_attribute.rb +0 -76
@@ -1,107 +1,375 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
require 'active_record_compose/transaction_support'
|
8
|
-
require 'active_record_compose/validations'
|
3
|
+
require_relative "attributes"
|
4
|
+
require_relative "callbacks"
|
5
|
+
require_relative "composed_collection"
|
6
|
+
require_relative "persistence"
|
9
7
|
|
10
8
|
module ActiveRecordCompose
|
11
|
-
|
12
|
-
|
9
|
+
# This is the core class of {ActiveRecordCompose}.
|
10
|
+
#
|
11
|
+
# By defining subclasses of this model, you can use ActiveRecordCompose functionality in your application.
|
12
|
+
# It has the basic functionality of `ActiveModel::Model` and `ActiveModel::Attributes`,
|
13
|
+
# and also provides aggregation of multiple models and atomic updates through transaction control.
|
14
|
+
# @example Example of model registration.
|
15
|
+
# class AccountRegistration < ActiveRecordCompose::Model
|
16
|
+
# def initialize(account = Account.new, attributes = {})
|
17
|
+
# @account = account
|
18
|
+
# @profile = @account.build_profile
|
19
|
+
# models << account << profile
|
20
|
+
# super(attributes)
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# attribute :register_confirmation, :boolean, default: false
|
24
|
+
# delegate_attribute :name, :email, to: :account
|
25
|
+
# delegate_attribute :firstname, :lastname, :age, to: :profile
|
26
|
+
#
|
27
|
+
# validates :register_confirmation, presence: true
|
28
|
+
#
|
29
|
+
# private
|
30
|
+
#
|
31
|
+
# attr_reader :account, :profile
|
32
|
+
# end
|
33
|
+
# @example Multiple model update once.
|
34
|
+
# registration = AccountRegistration.new
|
35
|
+
# registration.assign_attributes(
|
36
|
+
# name: "alice-in-wonderland",
|
37
|
+
# email: "alice@example.com",
|
38
|
+
# firstname: "Alice",
|
39
|
+
# lastname: "Smith",
|
40
|
+
# age: 24,
|
41
|
+
# register_confirmation: true
|
42
|
+
# )
|
43
|
+
#
|
44
|
+
# registration.save! # Register Account and Profile models at the same time.
|
45
|
+
# Account.count # => (0 ->) 1
|
46
|
+
# Profile.count # => (0 ->) 1
|
47
|
+
# @example Attribute delegation.
|
48
|
+
# account = Account.new
|
49
|
+
# account.name = "foo"
|
50
|
+
#
|
51
|
+
# registration = AccountRegistration.new(account)
|
52
|
+
# registration.name # => "foo" (delegated)
|
53
|
+
# registration.name? # => true (delegated attribute method + `?`)
|
54
|
+
#
|
55
|
+
# registration.name = "bar" # => updates account.name
|
56
|
+
# account.name # => "bar"
|
57
|
+
# account.name? # => true
|
58
|
+
#
|
59
|
+
# registration.attributes # => { "original_attribute" => "qux", "name" => "bar" }
|
60
|
+
# @example Aggregate errors on invalid.
|
61
|
+
# registration = AccountRegistration.new
|
62
|
+
#
|
63
|
+
# registration.name = "alice-in-wonderland"
|
64
|
+
# registration.firstname = "Alice"
|
65
|
+
# registration.age = 18
|
66
|
+
#
|
67
|
+
# registration.valid?
|
68
|
+
# #=> false
|
69
|
+
#
|
70
|
+
# # The error contents of the objects stored in models are aggregated.
|
71
|
+
# # For example, direct access to errors in Account#email.
|
72
|
+
# registration.errors[:email].to_a # Account#email
|
73
|
+
# #=> ["can't be blank"]
|
74
|
+
#
|
75
|
+
# # Of course, the validation defined for itself is also working.
|
76
|
+
# registration.errors[:register_confirmation].to_a
|
77
|
+
# #=> ["can't be blank"]
|
78
|
+
#
|
79
|
+
# registration.errors.to_a
|
80
|
+
# #=> ["Email can't be blank", "Lastname can't be blank", "Register confirmation can't be blank"]
|
13
81
|
class Model
|
14
82
|
include ActiveModel::Model
|
15
|
-
include ActiveModel::Validations::Callbacks
|
16
|
-
include ActiveModel::Attributes
|
17
83
|
|
18
|
-
include ActiveRecordCompose::
|
84
|
+
include ActiveRecordCompose::Attributes
|
85
|
+
include ActiveRecordCompose::Persistence
|
19
86
|
include ActiveRecordCompose::Callbacks
|
20
|
-
include ActiveRecordCompose::DelegateAttribute
|
21
|
-
include ActiveRecordCompose::TransactionSupport
|
22
|
-
prepend ActiveRecordCompose::Validations
|
23
87
|
|
24
|
-
|
88
|
+
begin
|
89
|
+
# @group Model Core
|
25
90
|
|
26
|
-
|
27
|
-
|
28
|
-
|
91
|
+
# @!method self.delegate_attribute(*attributes, to:, allow_nil: false)
|
92
|
+
# Provides a method of attribute access to the encapsulated model.
|
93
|
+
#
|
94
|
+
# It provides a way to access the attributes of the model it encompasses,
|
95
|
+
# allowing transparent access as if it had those attributes itself.
|
96
|
+
#
|
97
|
+
# @param [Array<Symbol, String>] attributes
|
98
|
+
# attributes A variable-length list of attribute names to delegate.
|
99
|
+
# @param [Symbol, String] to
|
100
|
+
# The target object to which attributes are delegated (keyword argument).
|
101
|
+
# @param [Boolean] allow_nil
|
102
|
+
# allow_nil Whether to allow nil values. Defaults to false.
|
103
|
+
# @example Basic usage
|
104
|
+
# delegate_attribute :name, :email, to: :profile
|
105
|
+
# @example Allowing nil
|
106
|
+
# delegate_attribute :bio, to: :profile, allow_nil: true
|
107
|
+
# @see Module#delegate for similar behavior in ActiveSupport
|
29
108
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
# However, we do not recommend explicitly specifying `validate: false` to skip validation.
|
37
|
-
# Additionally, the `:context` option is not accepted.
|
38
|
-
# The need for such a value indicates that operations from multiple contexts are being processed.
|
39
|
-
# If the contexts differ, we recommend separating them into different model definitions.
|
40
|
-
#
|
41
|
-
# @return [Boolean] returns true on success, false on failure.
|
42
|
-
def save(**options)
|
43
|
-
with_transaction_returning_status do
|
44
|
-
with_callbacks { save_models(**options, bang: false) }
|
45
|
-
rescue ActiveRecord::RecordInvalid
|
46
|
-
false
|
47
|
-
end
|
48
|
-
end
|
109
|
+
# @!method self.attribute_names
|
110
|
+
# Returns a array of attribute name.
|
111
|
+
# Attributes declared with {.delegate_attribute} are also merged.
|
112
|
+
#
|
113
|
+
# @see #attribute_names
|
114
|
+
# @return [Array<String>] array of attribute name.
|
49
115
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
116
|
+
# @!method attribute_names
|
117
|
+
# Returns a array of attribute name.
|
118
|
+
# Attributes declared with {.delegate_attribute} are also merged.
|
119
|
+
#
|
120
|
+
# class Foo < ActiveRecordCompose::Base
|
121
|
+
# def initialize(attributes = {})
|
122
|
+
# @account = Account.new
|
123
|
+
# super
|
124
|
+
# end
|
125
|
+
#
|
126
|
+
# attribute :confirmation, :boolean, default: false # plain attribute
|
127
|
+
# delegate_attribute :name, to: :account # delegated attribute
|
128
|
+
#
|
129
|
+
# private
|
130
|
+
#
|
131
|
+
# attr_reader :account
|
132
|
+
# end
|
133
|
+
#
|
134
|
+
# Foo.attribute_names # Returns the merged state of plain and delegated attributes
|
135
|
+
# # => ["confirmation" ,"name"]
|
136
|
+
#
|
137
|
+
# foo = Foo.new
|
138
|
+
# foo.attribute_names # Similar behavior for instance method version
|
139
|
+
# # => ["confirmation", "name"]
|
140
|
+
#
|
141
|
+
# @see #attributes
|
142
|
+
# @return [Array<String>] array of attribute name.
|
66
143
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
144
|
+
# @!method attributes
|
145
|
+
# Returns a hash with the attribute name as key and the attribute value as value.
|
146
|
+
# Attributes declared with {.delegate_attribute} are also merged.
|
147
|
+
#
|
148
|
+
# class Foo < ActiveRecordCompose::Base
|
149
|
+
# def initialize(attributes = {})
|
150
|
+
# @account = Account.new
|
151
|
+
# super
|
152
|
+
# end
|
153
|
+
#
|
154
|
+
# attribute :confirmation, :boolean, default: false # plain attribute
|
155
|
+
# delegate_attribute :name, to: :account # delegated attribute
|
156
|
+
#
|
157
|
+
# private
|
158
|
+
#
|
159
|
+
# attr_reader :account
|
160
|
+
# end
|
161
|
+
#
|
162
|
+
# foo = Foo.new
|
163
|
+
# foo.name = "Alice"
|
164
|
+
# foo.confirmation = true
|
165
|
+
#
|
166
|
+
# foo.attributes # Returns the merged state of plain and delegated attributes
|
167
|
+
# # => { "confirmation" => true, "name" => "Alice" }
|
168
|
+
#
|
169
|
+
# @return [Hash<String, Object>] hash with the attribute name as key and the attribute value as value.
|
76
170
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
171
|
+
# @!method persisted?
|
172
|
+
# Returns true if model is persisted.
|
173
|
+
#
|
174
|
+
# By overriding this definition, you can control the callbacks that are triggered when a save is made.
|
175
|
+
# For example, returning false will trigger before_create, around_create and after_create,
|
176
|
+
# and returning true will trigger {.before_update}, {.around_update} and {.after_update}.
|
177
|
+
#
|
178
|
+
# @return [Boolean] returns true if model is persisted.
|
179
|
+
# @example
|
180
|
+
# # A model where persistence is always false
|
181
|
+
# class Foo < ActiveRecordCompose::Model
|
182
|
+
# before_save { puts "before_save called" }
|
183
|
+
# before_create { puts "before_create called" }
|
184
|
+
# before_update { puts "before_update called" }
|
185
|
+
# after_update { puts "after_update called" }
|
186
|
+
# after_create { puts "after_create called" }
|
187
|
+
# after_save { puts "after_save called" }
|
188
|
+
#
|
189
|
+
# def persisted? = false
|
190
|
+
# end
|
191
|
+
#
|
192
|
+
# # A model where persistence is always true
|
193
|
+
# class Bar < Foo
|
194
|
+
# def persisted? = true
|
195
|
+
# end
|
196
|
+
#
|
197
|
+
# Foo.new.save!
|
198
|
+
# # before_save called
|
199
|
+
# # before_create called
|
200
|
+
# # after_create called
|
201
|
+
# # after_save called
|
202
|
+
#
|
203
|
+
# Bar.new.save!
|
204
|
+
# # before_save called
|
205
|
+
# # before_update called
|
206
|
+
# # after_update called
|
207
|
+
# # after_save called
|
85
208
|
|
86
|
-
|
87
|
-
#
|
88
|
-
# By overriding this definition, you can control the callbacks that are triggered when a save is made.
|
89
|
-
# For example, returning false will trigger before_create, around_create and after_create,
|
90
|
-
# and returning true will trigger before_update, around_update and after_update.
|
91
|
-
#
|
92
|
-
# @return [Boolean] returns true if model is persisted.
|
93
|
-
def persisted? = super
|
209
|
+
# @endgroup
|
94
210
|
|
95
|
-
|
211
|
+
# @group Validations
|
96
212
|
|
97
|
-
|
213
|
+
# @!method valid?(context = nil)
|
214
|
+
# Runs all the validations and returns the result as true or false.
|
215
|
+
# @param context Validation context.
|
216
|
+
# @return [Boolean] true on success, false on failure.
|
217
|
+
|
218
|
+
# @!method validate(context = nil)
|
219
|
+
# Alias for {#valid?}
|
220
|
+
# @see #valid? Validation context.
|
221
|
+
# @param context
|
222
|
+
# @return [Boolean] true on success, false on failure.
|
223
|
+
|
224
|
+
# @!method validate!(context = nil)
|
225
|
+
# @see #valid?
|
226
|
+
# Runs all the validations within the specified context.
|
227
|
+
# no errors are found, raises `ActiveRecord::RecordInvalid` otherwise.
|
228
|
+
# @param context Validation context.
|
229
|
+
# @raise ActiveRecord::RecordInvalid
|
230
|
+
|
231
|
+
# @!method errors
|
232
|
+
# Returns the `ActiveModel::Errors` object that holds all information about attribute error messages.
|
233
|
+
#
|
234
|
+
# The `ActiveModel::Base` implementation itself,
|
235
|
+
# but also aggregates error information for objects stored in {#models} when validation is performed.
|
236
|
+
#
|
237
|
+
# class Account < ActiveRecord::Base
|
238
|
+
# validates :name, :email, presence: true
|
239
|
+
# end
|
240
|
+
#
|
241
|
+
# class AccountRegistration < ActiveRecordCompose::Model
|
242
|
+
# def initialize(attributes = {})
|
243
|
+
# @account = Account.new
|
244
|
+
# super(attributes)
|
245
|
+
# models << account
|
246
|
+
# end
|
247
|
+
#
|
248
|
+
# attribute :confirmation, :boolean, default: false
|
249
|
+
# validates :confirmation, presence: true
|
250
|
+
#
|
251
|
+
# private
|
252
|
+
#
|
253
|
+
# attr_reader :account
|
254
|
+
# end
|
255
|
+
#
|
256
|
+
# registration = AccountRegistration
|
257
|
+
# registration.valid?
|
258
|
+
# #=> false
|
259
|
+
#
|
260
|
+
# # In addition to the model's own validation error information (`confirmation`), also aggregates
|
261
|
+
# # error information for objects stored in `account` (`name`, `email`) when validation is performed.
|
262
|
+
#
|
263
|
+
# registration.errors.map { _1.attribute } #=> [:name, :email, :confirmation]
|
264
|
+
#
|
265
|
+
# @return [ActiveModel::Errors]
|
266
|
+
|
267
|
+
# @endgroup
|
268
|
+
|
269
|
+
# @group Persistences
|
270
|
+
|
271
|
+
# @!method save(**options)
|
272
|
+
# Save the models that exist in models.
|
273
|
+
# Returns false if any of the targets fail, true if all succeed.
|
274
|
+
#
|
275
|
+
# The save is performed within a single transaction.
|
276
|
+
#
|
277
|
+
# Only the `:validate` option takes effect as it is required internally.
|
278
|
+
# However, we do not recommend explicitly specifying `validate: false` to skip validation.
|
279
|
+
# Additionally, the `:context` option is not accepted.
|
280
|
+
# The need for such a value indicates that operations from multiple contexts are being processed.
|
281
|
+
# If the contexts differ, we recommend separating them into different model definitions.
|
282
|
+
#
|
283
|
+
# @params [Hash] Optional parameters.
|
284
|
+
# @option options [Boolean] :validate Whether to run validations.
|
285
|
+
# This option is intended for internal use only.
|
286
|
+
# Users should avoid explicitly passing <tt>validate: false</tt>,
|
287
|
+
# as skipping validations can lead to unexpected behavior.
|
288
|
+
# @return [Boolean] returns true on success, false on failure.
|
98
289
|
|
99
|
-
|
100
|
-
|
290
|
+
# @!method save!(**options)
|
291
|
+
# Behavior is same to {#save}, but raises an exception prematurely on failure.
|
292
|
+
# @see #save
|
293
|
+
# @raise ActiveRecord::RecordInvalid
|
294
|
+
# @raise ActiveRecord::RecordNotSaved
|
295
|
+
|
296
|
+
# @!method update(attributes)
|
297
|
+
# Assign attributes and {#save}.
|
298
|
+
#
|
299
|
+
# @param [Hash<String, Object>] attributes
|
300
|
+
# new attributes.
|
301
|
+
# @see #save
|
302
|
+
# @return [Boolean] returns true on success, false on failure.
|
303
|
+
|
304
|
+
# @!method update!(attributes)
|
305
|
+
# Behavior is same to {#update}, but raises an exception prematurely on failure.
|
306
|
+
#
|
307
|
+
# @param [Hash<String, Object>] attributes
|
308
|
+
# new attributes.
|
309
|
+
# @see #save
|
310
|
+
# @see #update
|
311
|
+
# @raise ActiveRecord::RecordInvalid
|
312
|
+
# @raise ActiveRecord::RecordNotSaved
|
313
|
+
|
314
|
+
# @endgroup
|
315
|
+
|
316
|
+
# @group Callbacks
|
317
|
+
|
318
|
+
# @!method self.before_save(*args, &block)
|
319
|
+
# Registers a callback to be called before a model is saved.
|
320
|
+
|
321
|
+
# @!method self.around_save(*args, &block)
|
322
|
+
# Registers a callback to be called around the save of a model.
|
323
|
+
|
324
|
+
# @!method self.after_save(*args, &block)
|
325
|
+
# Registers a callback to be called after a model is saved.
|
326
|
+
|
327
|
+
# @!method self.before_create(*args, &block)
|
328
|
+
# Registers a callback to be called before a model is created.
|
329
|
+
|
330
|
+
# @!method self.around_create(*args, &block)
|
331
|
+
# Registers a callback to be called around the creation of a model.
|
332
|
+
|
333
|
+
# @!method self.after_create(*args, &block)
|
334
|
+
# Registers a callback to be called after a model is created.
|
335
|
+
|
336
|
+
# @!method self.before_update(*args, &block)
|
337
|
+
# Registers a callback to be called before a model is updated.
|
338
|
+
|
339
|
+
# @!method self.around_update(*args, &block)
|
340
|
+
# Registers a callback to be called around the update of a model.
|
341
|
+
|
342
|
+
# @!method self.after_update(*args, &block)
|
343
|
+
# Registers a callback to be called after a update is updated.
|
344
|
+
|
345
|
+
# @!method self.after_commit(*args, &block)
|
346
|
+
# Registers a block to be called after the transaction is fully committed.
|
347
|
+
|
348
|
+
# @!method self.after_rollback(*args, &block)
|
349
|
+
# Registers a block to be called after the transaction is rolled back.
|
350
|
+
|
351
|
+
# @endgroup
|
352
|
+
end
|
353
|
+
|
354
|
+
# @group Model Core
|
355
|
+
|
356
|
+
def initialize(attributes = {})
|
357
|
+
super
|
101
358
|
end
|
102
359
|
|
103
|
-
|
360
|
+
private
|
361
|
+
|
362
|
+
# Returns a collection of model elements to encapsulate.
|
363
|
+
# @example Adding models
|
364
|
+
# models << inner_model_a << inner_model_b
|
365
|
+
# models.push(inner_model_c)
|
366
|
+
# @example `#push` can have `:destroy` `:if` options
|
367
|
+
# models.push(profile, destroy: :blank_profile?)
|
368
|
+
# models.push(profile, destroy: -> { blank_profile? })
|
369
|
+
# @return [ActiveRecordCompose::ComposedCollection]
|
370
|
+
#
|
371
|
+
def models = @__models ||= ActiveRecordCompose::ComposedCollection.new(self)
|
104
372
|
|
105
|
-
|
373
|
+
# @endgroup
|
106
374
|
end
|
107
375
|
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "composed_collection"
|
4
|
+
require_relative "transaction_support"
|
5
|
+
|
6
|
+
module ActiveRecordCompose
|
7
|
+
using ComposedCollection::PackagePrivate
|
8
|
+
|
9
|
+
# @private
|
10
|
+
module Persistence
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
include ActiveRecordCompose::TransactionSupport
|
13
|
+
|
14
|
+
# Save the models that exist in models.
|
15
|
+
# Returns false if any of the targets fail, true if all succeed.
|
16
|
+
#
|
17
|
+
# The save is performed within a single transaction.
|
18
|
+
#
|
19
|
+
# Only the `:validate` option takes effect as it is required internally.
|
20
|
+
# However, we do not recommend explicitly specifying `validate: false` to skip validation.
|
21
|
+
# Additionally, the `:context` option is not accepted.
|
22
|
+
# The need for such a value indicates that operations from multiple contexts are being processed.
|
23
|
+
# If the contexts differ, we recommend separating them into different model definitions.
|
24
|
+
#
|
25
|
+
# @return [Boolean] returns true on success, false on failure.
|
26
|
+
def save(**options)
|
27
|
+
with_transaction_returning_status do
|
28
|
+
with_callbacks { save_models(**options, bang: false) }
|
29
|
+
rescue ActiveRecord::RecordInvalid
|
30
|
+
false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Save the models that exist in models.
|
35
|
+
# Unlike #save, an exception is raises on failure.
|
36
|
+
#
|
37
|
+
# Saving, like `#save`, is performed within a single transaction.
|
38
|
+
#
|
39
|
+
# Only the `:validate` option takes effect as it is required internally.
|
40
|
+
# However, we do not recommend explicitly specifying `validate: false` to skip validation.
|
41
|
+
# Additionally, the `:context` option is not accepted.
|
42
|
+
# The need for such a value indicates that operations from multiple contexts are being processed.
|
43
|
+
# If the contexts differ, we recommend separating them into different model definitions.
|
44
|
+
#
|
45
|
+
def save!(**options)
|
46
|
+
with_transaction_returning_status do
|
47
|
+
with_callbacks { save_models(**options, bang: true) }
|
48
|
+
end || raise_on_save_error
|
49
|
+
end
|
50
|
+
|
51
|
+
# Assign attributes and save.
|
52
|
+
#
|
53
|
+
# @return [Boolean] returns true on success, false on failure.
|
54
|
+
def update(attributes = {})
|
55
|
+
with_transaction_returning_status do
|
56
|
+
assign_attributes(attributes)
|
57
|
+
save
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Behavior is same to `#update`, but raises an exception prematurely on failure.
|
62
|
+
#
|
63
|
+
def update!(attributes = {})
|
64
|
+
with_transaction_returning_status do
|
65
|
+
assign_attributes(attributes)
|
66
|
+
save!
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def save_models(bang:, **options)
|
73
|
+
models.__wrapped_models.all? do |model|
|
74
|
+
if bang
|
75
|
+
model.save!(**options, validate: false)
|
76
|
+
else
|
77
|
+
model.save(**options, validate: false)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def raise_on_save_error = raise ActiveRecord::RecordNotSaved.new(raise_on_save_error_message, self)
|
83
|
+
|
84
|
+
def raise_on_save_error_message = "Failed to save the model."
|
85
|
+
end
|
86
|
+
end
|
@@ -1,9 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "composed_collection"
|
4
|
+
|
3
5
|
module ActiveRecordCompose
|
4
6
|
using ComposedCollection::PackagePrivate
|
5
7
|
|
8
|
+
# @private
|
6
9
|
module Validations
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
included do
|
13
|
+
validate :validate_models
|
14
|
+
end
|
15
|
+
|
7
16
|
def save(**options)
|
8
17
|
perform_validations(options) ? super : false
|
9
18
|
end
|
@@ -1,12 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "active_support/core_ext/object"
|
4
4
|
|
5
5
|
module ActiveRecordCompose
|
6
|
+
# @private
|
6
7
|
class WrappedModel
|
7
8
|
# @param model [Object] the model instance.
|
8
|
-
# @param destroy [Boolean]
|
9
|
-
#
|
9
|
+
# @param destroy [Boolean, Proc, Symbol] Controls whether the model should be destroyed.
|
10
|
+
# - Boolean: if `true`, the model will be destroyed.
|
11
|
+
# - Proc: the model will be destroyed if the proc returns `true`.
|
10
12
|
# @param if [Proc] evaluation result is false, it will not be included in the renewal.
|
11
13
|
def initialize(model, destroy: false, if: nil)
|
12
14
|
@model = model
|
@@ -1,9 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "active_record"
|
4
4
|
|
5
|
-
require_relative
|
6
|
-
require_relative
|
5
|
+
require_relative "active_record_compose/version"
|
6
|
+
require_relative "active_record_compose/model"
|
7
7
|
|
8
|
+
# namespaces in gem `active_record_compose`.
|
9
|
+
#
|
10
|
+
# Most of the functionality resides in {ActiveRecordCompose::Model}.
|
11
|
+
#
|
8
12
|
module ActiveRecordCompose
|
9
13
|
end
|