active_record_compose 0.4.1 → 0.6.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +11 -0
- data/README.md +5 -5
- data/lib/active_record_compose/inner_model.rb +47 -56
- data/lib/active_record_compose/inner_model_collection.rb +24 -34
- data/lib/active_record_compose/model.rb +10 -4
- data/lib/active_record_compose/version.rb +1 -1
- data/sig/active_record_compose.rbs +8 -7
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 701c396babd7d30d3e13779c5237bd1fbaf2fb719a75439154d17a167dc645dc
|
4
|
+
data.tar.gz: 72cf835561b903fc913e9feadff380f8ca986f621320b1fdd1ebd7dc341539cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fddabb884fb62e215cc5a67abe823b563cf5be201f2e05b8057871e2c188025e04d46b5953b84f23e7cdc064840291882cb3f77a93efb221bed3b0c99a6dfe71
|
7
|
+
data.tar.gz: 7b550abe98e5391de53a9b017b8ae214270844f8d6536dadb012587c1d553e4e01cab99f480ec59ae01eaf3d0b742c612ea676546cc7ce82ea945d0e99e0168d
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.6.0] - 2024-11-11
|
4
|
+
|
5
|
+
- refactor: limit the scope of methods needed only for internal library purposes.
|
6
|
+
- support rails 8.0.x
|
7
|
+
- add optional value `if` to exclude from save (or destroy).
|
8
|
+
|
9
|
+
## [0.5.0] - 2024-10-09
|
10
|
+
|
11
|
+
- remove `:context` option. use `:destroy` option instead.
|
12
|
+
- remove `:destroy` option from `InnerModelCollection#destroy`.
|
13
|
+
|
3
14
|
## [0.4.1] - 2024-09-20
|
4
15
|
|
5
16
|
- Omitted optional argument for `InnerModelCollection#destroy`.
|
data/README.md
CHANGED
@@ -31,7 +31,7 @@ A callback is useful to define some processing before or after a save in a parti
|
|
31
31
|
However, if a callback is written directly in the AR model, it is necessary to consider the case where the model is updated in other contexts.
|
32
32
|
In particular, if you frequently create with test data, previously unnecessary processing will be called at every point of creation.
|
33
33
|
In addition to cost, the more complicated the callbacks you write, the more difficult it will be to create even a single test data.
|
34
|
-
If the callbacks are written in a class that inherits from `
|
34
|
+
If the callbacks are written in a class that inherits from `ActiveRecordCompose::Model`, the AR model itself will not be polluted, and the context can be limited.
|
35
35
|
|
36
36
|
```ruby
|
37
37
|
class AccountRegistration < ActiveRecordCompose::Model
|
@@ -70,7 +70,7 @@ Validates are basically fired in all cases where the model is manipulated. To av
|
|
70
70
|
and so on to work only in specific cases. This allows you to create context-sensitive validations for the same model operation.
|
71
71
|
However, this is the first step in making the model more and more complex. You will have to go around with `update(context: :foo)`
|
72
72
|
In some cases, you may have to go around with the context option, such as `update(context: :foo)` everywhere.
|
73
|
-
By writing validates in a class that extends `
|
73
|
+
By writing validates in a class that extends `ActiveRecordCompose::Model`, you can define context-specific validation without polluting the AR model itself.
|
74
74
|
|
75
75
|
```ruby
|
76
76
|
class AccountRegistration < ActiveRecordCompose::Model
|
@@ -116,7 +116,7 @@ account_registration.valid? #=> false
|
|
116
116
|
|
117
117
|
In an AR model, you can add, for example, `autosave: true` or `accepts_nested_attributes_for` to an association to update the related models at the same time.
|
118
118
|
There are ways to update related models at the same time. The operation is safe because it is transactional.
|
119
|
-
`
|
119
|
+
`ActiveRecordCompose::Model` has an internal array called models. By adding an AR object to this models array
|
120
120
|
By adding an AR object to the models, the object stored in the models provides an atomic update operation via #save.
|
121
121
|
|
122
122
|
```ruby
|
@@ -167,12 +167,12 @@ class AccountResignation < ActiveRecordCompose::Model
|
|
167
167
|
models.push(profile, destroy: true)
|
168
168
|
end
|
169
169
|
|
170
|
-
attr_reader :account, :profile
|
171
|
-
|
172
170
|
before_save :set_resigned_at
|
173
171
|
|
174
172
|
private
|
175
173
|
|
174
|
+
attr_reader :account, :profile
|
175
|
+
|
176
176
|
def set_resigned_at
|
177
177
|
account.resigned_at = Time.zone.now
|
178
178
|
end
|
@@ -7,60 +7,32 @@ module ActiveRecordCompose
|
|
7
7
|
# @param model [Object] the model instance.
|
8
8
|
# @param destroy [Boolean] given true, destroy model.
|
9
9
|
# @param destroy [Proc] when proc returning true, destroy model.
|
10
|
-
|
10
|
+
# @param if [Proc] evaluation result is false, it will not be included in the renewal.
|
11
|
+
def initialize(model, destroy: false, if: nil)
|
11
12
|
@model = model
|
12
|
-
@destroy_context_type =
|
13
|
-
|
14
|
-
c = context
|
15
|
-
|
16
|
-
if c.is_a?(Proc)
|
17
|
-
# @type var c: ((^() -> (context)) | (^(_ARLike) -> (context)))
|
18
|
-
if c.arity == 0
|
19
|
-
deprecator.warn(
|
20
|
-
'`:context` will be removed in 0.5.0. Use `:destroy` option instead. ' \
|
21
|
-
'for example, `models.push(model, context: -> { foo? ? :destroy : :save })` ' \
|
22
|
-
'is replaced by `models.push(model, destroy: -> { foo? })`.',
|
23
|
-
)
|
24
|
-
|
25
|
-
# @type var c: ^() -> (context)
|
26
|
-
-> { c.call == :destroy }
|
27
|
-
else
|
28
|
-
deprecator.warn(
|
29
|
-
'`:context` will be removed in 0.5.0. Use `:destroy` option instead. ' \
|
30
|
-
'for example, `models.push(model, context: ->(m) { m.bar? ? :destroy : :save })` ' \
|
31
|
-
'is replaced by `models.push(model, destroy: ->(m) { m.bar? })`.',
|
32
|
-
)
|
33
|
-
|
34
|
-
# @type var c: ^(_ARLike) -> (context)
|
35
|
-
->(model) { c.call(model) == :destroy }
|
36
|
-
end
|
37
|
-
elsif %i[save destroy].include?(c)
|
38
|
-
deprecator.warn(
|
39
|
-
'`:context` will be removed in 0.5.0. Use `:destroy` option instead. ' \
|
40
|
-
"for example, `models.push(model, context: #{c.inspect})` " \
|
41
|
-
"is replaced by `models.push(model, destroy: #{(c == :destroy).inspect})`.",
|
42
|
-
)
|
43
|
-
|
44
|
-
# @type var c: (:save | :destory)
|
45
|
-
c == :destroy
|
46
|
-
else
|
47
|
-
c
|
48
|
-
end
|
49
|
-
else
|
50
|
-
destroy
|
51
|
-
end
|
13
|
+
@destroy_context_type = destroy
|
14
|
+
@if_option = binding.local_variable_get(:if)
|
52
15
|
end
|
53
16
|
|
54
17
|
delegate :errors, to: :model
|
55
18
|
|
19
|
+
# Determines whether to save or delete the target object.
|
20
|
+
# Depends on the `destroy` value of the InnerModel object initialization option.
|
21
|
+
#
|
22
|
+
# On the other hand, there are values `mark_for_destruction` and `marked_for_destruction?` in ActiveRecord.
|
23
|
+
# However, these values are not substituted here.
|
24
|
+
# These values only work if the `autosave` option is enabled for the parent model,
|
25
|
+
# and are not appropriate for other cases.
|
26
|
+
#
|
27
|
+
# @return [Boolean] returns true on destroy, false on save.
|
56
28
|
def destroy_context?
|
57
29
|
d = destroy_context_type
|
58
30
|
if d.is_a?(Proc)
|
59
31
|
if d.arity == 0
|
60
|
-
# @type var d: ^() ->
|
32
|
+
# @type var d: ^() -> bool
|
61
33
|
!!d.call
|
62
34
|
else
|
63
|
-
# @type var d: ^(_ARLike) ->
|
35
|
+
# @type var d: ^(_ARLike) -> bool
|
64
36
|
!!d.call(model)
|
65
37
|
end
|
66
38
|
else
|
@@ -68,6 +40,22 @@ module ActiveRecordCompose
|
|
68
40
|
end
|
69
41
|
end
|
70
42
|
|
43
|
+
# Returns a boolean indicating whether or not to exclude the user from the update.
|
44
|
+
#
|
45
|
+
# @return [Boolean] if true, exclude from update.
|
46
|
+
def ignore?
|
47
|
+
i = if_option
|
48
|
+
if i.nil?
|
49
|
+
false
|
50
|
+
elsif i.arity == 0
|
51
|
+
# @type var i: ^() -> bool
|
52
|
+
!i.call
|
53
|
+
else
|
54
|
+
# @type var i: ^(_ARLike) -> bool
|
55
|
+
!i.call(model)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
71
59
|
# Execute save or destroy. Returns true on success, false on failure.
|
72
60
|
# Whether save or destroy is executed depends on the value of `#destroy_context?`.
|
73
61
|
#
|
@@ -91,28 +79,31 @@ module ActiveRecordCompose
|
|
91
79
|
# @return [Boolean]
|
92
80
|
def ==(other)
|
93
81
|
return false unless self.class == other.class
|
94
|
-
return false unless
|
82
|
+
return false unless model == other.model
|
95
83
|
|
96
84
|
true
|
97
85
|
end
|
98
86
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
#
|
103
|
-
# @return [Object] raw model instance
|
104
|
-
def __raw_model = model
|
87
|
+
protected
|
88
|
+
|
89
|
+
attr_reader :model
|
105
90
|
|
106
91
|
private
|
107
92
|
|
108
|
-
attr_reader :
|
93
|
+
attr_reader :destroy_context_type, :if_option
|
109
94
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
95
|
+
# @private
|
96
|
+
# steep:ignore:start
|
97
|
+
module PackagePrivate
|
98
|
+
refine InnerModel do
|
99
|
+
# @private
|
100
|
+
# Returns a model instance of raw, but it should
|
101
|
+
# be noted that application developers are not expected to use this interface.
|
102
|
+
#
|
103
|
+
# @return [Object] raw model instance
|
104
|
+
def __raw_model = model
|
115
105
|
end
|
116
106
|
end
|
107
|
+
# steep:ignore:end
|
117
108
|
end
|
118
109
|
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
require 'active_record_compose/inner_model'
|
4
4
|
|
5
5
|
module ActiveRecordCompose
|
6
|
+
using InnerModel::PackagePrivate # steep:ignore
|
7
|
+
|
6
8
|
class InnerModelCollection
|
7
9
|
include Enumerable
|
8
10
|
|
@@ -38,9 +40,11 @@ module ActiveRecordCompose
|
|
38
40
|
# @param destroy [Boolean] given true, destroy model.
|
39
41
|
# @param destroy [Proc] when proc returning true, destroy model.
|
40
42
|
# @param destroy [Symbol] applies boolean value of result of sending a message to `owner` to evaluation.
|
43
|
+
# @param if [Proc] evaluation result is false, it will not be included in the renewal.
|
44
|
+
# @param if [Symbol] applies boolean value of result of sending a message to `owner` to evaluation.
|
41
45
|
# @return [self] returns itself.
|
42
|
-
def push(model, destroy: false,
|
43
|
-
models << wrap(model, destroy:,
|
46
|
+
def push(model, destroy: false, if: nil)
|
47
|
+
models << wrap(model, destroy:, if:)
|
44
48
|
self
|
45
49
|
end
|
46
50
|
|
@@ -63,41 +67,18 @@ module ActiveRecordCompose
|
|
63
67
|
# @param model [Object] the model instance
|
64
68
|
# @return [self] Successful deletion
|
65
69
|
# @return [nil] If deletion fails
|
66
|
-
def delete(model
|
67
|
-
if !destroy.nil? || !context.nil?
|
68
|
-
# steep:ignore:start
|
69
|
-
deprecator.warn(
|
70
|
-
'In `InnerModelConnection#destroy`, the option values `destroy` and `context` are ignored. ' \
|
71
|
-
'These options will be removed in 0.5.0.',
|
72
|
-
)
|
73
|
-
# steep:ignore:end
|
74
|
-
end
|
75
|
-
|
70
|
+
def delete(model)
|
76
71
|
wrapped = wrap(model)
|
77
72
|
return nil unless models.delete(wrapped)
|
78
73
|
|
79
74
|
self
|
80
75
|
end
|
81
76
|
|
82
|
-
# @private
|
83
|
-
# Enumerates model objects, but it should be noted that
|
84
|
-
# application developers are not expected to use this interface.
|
85
|
-
#
|
86
|
-
# @yieldparam [InnerModel] rawpped model instance.
|
87
|
-
# @return [Enumerator] when not block given.
|
88
|
-
# @return [self] when block given, returns itself.
|
89
|
-
def __each_by_wrapped
|
90
|
-
return enum_for(:__each_by_wrapped) unless block_given?
|
91
|
-
|
92
|
-
models.each { yield _1 if _1.__raw_model } # steep:ignore
|
93
|
-
self
|
94
|
-
end
|
95
|
-
|
96
77
|
private
|
97
78
|
|
98
79
|
attr_reader :owner, :models
|
99
80
|
|
100
|
-
def wrap(model, destroy: false,
|
81
|
+
def wrap(model, destroy: false, if: nil)
|
101
82
|
if model.is_a?(ActiveRecordCompose::InnerModel) # steep:ignore
|
102
83
|
# @type var model: ActiveRecordCompose::InnerModel
|
103
84
|
model
|
@@ -106,17 +87,26 @@ module ActiveRecordCompose
|
|
106
87
|
method = destroy
|
107
88
|
destroy = -> { owner.__send__(method) }
|
108
89
|
end
|
109
|
-
|
110
|
-
|
90
|
+
if_option = binding.local_variable_get(:if)
|
91
|
+
if if_option.is_a?(Symbol)
|
92
|
+
method = if_option
|
93
|
+
if_option = -> { owner.__send__(method) }
|
94
|
+
end
|
95
|
+
ActiveRecordCompose::InnerModel.new(model, destroy:, if: if_option)
|
111
96
|
end
|
112
97
|
end
|
113
98
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
99
|
+
# @private
|
100
|
+
# steep:ignore:start
|
101
|
+
module PackagePrivate
|
102
|
+
refine InnerModelCollection do
|
103
|
+
# Returns array of wrapped model instance.
|
104
|
+
#
|
105
|
+
# @private
|
106
|
+
# @return [Array[InnerModel] array of wrapped model instance.
|
107
|
+
def __wrapped_models = models.reject { _1.ignore? }.select { _1.__raw_model }
|
119
108
|
end
|
120
109
|
end
|
110
|
+
# steep:ignore:end
|
121
111
|
end
|
122
112
|
end
|
@@ -5,6 +5,8 @@ require 'active_record_compose/inner_model_collection'
|
|
5
5
|
require 'active_record_compose/transaction_support'
|
6
6
|
|
7
7
|
module ActiveRecordCompose
|
8
|
+
using InnerModelCollection::PackagePrivate # steep:ignore
|
9
|
+
|
8
10
|
class Model
|
9
11
|
include ActiveModel::Model
|
10
12
|
include ActiveModel::Validations::Callbacks
|
@@ -150,11 +152,15 @@ module ActiveRecordCompose
|
|
150
152
|
|
151
153
|
def models = @__models ||= ActiveRecordCompose::InnerModelCollection.new(self)
|
152
154
|
|
153
|
-
def
|
154
|
-
|
155
|
-
|
155
|
+
def validate_models
|
156
|
+
wms = models.__wrapped_models # steep:ignore
|
157
|
+
wms.select { _1.invalid? }.each { errors.merge!(_1) }
|
158
|
+
end
|
156
159
|
|
157
|
-
def save_models(bang:)
|
160
|
+
def save_models(bang:)
|
161
|
+
wms = models.__wrapped_models # steep:ignore
|
162
|
+
wms.all? { bang ? _1.save! : _1.save }
|
163
|
+
end
|
158
164
|
|
159
165
|
def raise_validation_error = raise ActiveRecord::RecordInvalid, self
|
160
166
|
|
@@ -12,12 +12,12 @@ module ActiveRecordCompose
|
|
12
12
|
def invalid?: -> bool
|
13
13
|
def valid?: -> bool
|
14
14
|
def errors: -> untyped
|
15
|
+
def ==: (untyped) -> bool
|
15
16
|
end
|
16
17
|
|
17
18
|
type attribute_name = (String | Symbol)
|
18
|
-
type context = (:save | :destroy)
|
19
|
-
type context_proc = ((^() -> context) | (^(_ARLike) -> context))
|
20
19
|
type destroy_context_type = (bool | Symbol | (^() -> boolish) | (^(_ARLike) -> boolish))
|
20
|
+
type condition_type = ((^() -> boolish) | (^(_ARLike) -> boolish))
|
21
21
|
|
22
22
|
module DelegateAttribute
|
23
23
|
extend ActiveSupport::Concern
|
@@ -35,20 +35,21 @@ module ActiveRecordCompose
|
|
35
35
|
def initialize: (Model) -> void
|
36
36
|
def each: () { (_ARLike) -> void } -> InnerModelCollection | () -> Enumerator[_ARLike, self]
|
37
37
|
def <<: (_ARLike) -> self
|
38
|
-
def push: (_ARLike, ?destroy: destroy_context_type, ?
|
38
|
+
def push: (_ARLike, ?destroy: destroy_context_type, ?if: (nil | Symbol | condition_type)) -> self
|
39
39
|
def empty?: -> bool
|
40
40
|
def clear: -> self
|
41
|
-
def delete: (_ARLike | InnerModel
|
41
|
+
def delete: (_ARLike | InnerModel) -> InnerModelCollection?
|
42
42
|
|
43
43
|
private
|
44
44
|
attr_reader owner: Model
|
45
45
|
attr_reader models: Array[InnerModel]
|
46
|
-
def wrap: (_ARLike | InnerModel, ?destroy: destroy_context_type, ?
|
46
|
+
def wrap: (_ARLike | InnerModel, ?destroy: destroy_context_type, ?if: (nil | Symbol | condition_type)) -> InnerModel
|
47
47
|
end
|
48
48
|
|
49
49
|
class InnerModel
|
50
|
-
def initialize: (_ARLike, ?destroy: destroy_context_type, ?
|
50
|
+
def initialize: (_ARLike, ?destroy: destroy_context_type, ?if: (nil | condition_type)) -> void
|
51
51
|
def destroy_context?: -> bool
|
52
|
+
def ignore?: -> bool
|
52
53
|
def save: -> bool
|
53
54
|
def save!: -> untyped
|
54
55
|
def invalid?: -> bool
|
@@ -58,6 +59,7 @@ module ActiveRecordCompose
|
|
58
59
|
private
|
59
60
|
attr_reader model: _ARLike
|
60
61
|
attr_reader destroy_context_type: destroy_context_type
|
62
|
+
attr_reader if_option: (nil | condition_type)
|
61
63
|
end
|
62
64
|
|
63
65
|
class Model
|
@@ -83,7 +85,6 @@ module ActiveRecordCompose
|
|
83
85
|
|
84
86
|
private
|
85
87
|
def models: -> InnerModelCollection
|
86
|
-
def wrapped_models: -> Enumerator[InnerModel, InnerModelCollection]
|
87
88
|
def validate_models: -> void
|
88
89
|
def save_models: (bang: bool) -> bool
|
89
90
|
def raise_validation_error: -> bot
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_record_compose
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- hamajyotan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-11-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -52,7 +52,7 @@ metadata:
|
|
52
52
|
homepage_uri: https://github.com/hamajyotan/active_record_compose
|
53
53
|
source_code_uri: https://github.com/hamajyotan/active_record_compose
|
54
54
|
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.
|
55
|
+
documentation_uri: https://www.rubydoc.info/gems/active_record_compose/0.6.0
|
56
56
|
rubygems_mfa_required: 'true'
|
57
57
|
post_install_message:
|
58
58
|
rdoc_options: []
|
@@ -69,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
69
|
- !ruby/object:Gem::Version
|
70
70
|
version: '0'
|
71
71
|
requirements: []
|
72
|
-
rubygems_version: 3.5.
|
72
|
+
rubygems_version: 3.5.21
|
73
73
|
signing_key:
|
74
74
|
specification_version: 4
|
75
75
|
summary: activemodel form object pattern
|