active_record_compose 0.4.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5fe788a613275af9e9784ba51b84af314792e0a5761e0c8d5e985602dfe7d586
4
- data.tar.gz: 0621ff28f43377240efc94a97b12ffde861e71142f6ba2ac349d4d9a165d6401
3
+ metadata.gz: 701c396babd7d30d3e13779c5237bd1fbaf2fb719a75439154d17a167dc645dc
4
+ data.tar.gz: 72cf835561b903fc913e9feadff380f8ca986f621320b1fdd1ebd7dc341539cb
5
5
  SHA512:
6
- metadata.gz: d67bf25365ccad6f049a17cbc311cbf19c48ee4719e51afefc149fb0227989ec8f1c55684ab26328839ae7c79897fed27ac6b5c7db90c42bf0b81e3bc4437c97
7
- data.tar.gz: ab71a4b2bd61619136f8685b0b1346d4ad111fec0717a1527ce714985f5eaee91f0c91dd9713ba835a2633acbdc2a197f2ae2c77767d2028b13eb4bcb5f21887
6
+ metadata.gz: fddabb884fb62e215cc5a67abe823b563cf5be201f2e05b8057871e2c188025e04d46b5953b84f23e7cdc064840291882cb3f77a93efb221bed3b0c99a6dfe71
7
+ data.tar.gz: 7b550abe98e5391de53a9b017b8ae214270844f8d6536dadb012587c1d553e4e01cab99f480ec59ae01eaf3d0b742c612ea676546cc7ce82ea945d0e99e0168d
data/.rubocop.yml CHANGED
@@ -24,6 +24,9 @@ Style/StringLiterals:
24
24
  Style/StringLiteralsInInterpolation:
25
25
  Enabled: true
26
26
 
27
+ Style/SuperArguments:
28
+ Enabled: false
29
+
27
30
  Style/SymbolProc:
28
31
  Enabled: false
29
32
 
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 `ApplicationRecordCompose::Model`, the AR model itself will not be polluted, and the context can be limited.
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 `ApplicationRecordCompose::Model`, you can define context-specific validation without polluting the AR model itself.
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
- `ApplicationRecordCompose::Model` has an internal array called models. By adding an AR object to this models array
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
- def initialize(model, destroy: false, context: nil)
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
- if context
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: ^() -> (bool | context)
32
+ # @type var d: ^() -> bool
61
33
  !!d.call
62
34
  else
63
- # @type var d: ^(_ARLike) -> (bool | context)
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 __raw_model == other.__raw_model # steep:ignore
82
+ return false unless model == other.model
95
83
 
96
84
  true
97
85
  end
98
86
 
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
87
+ protected
88
+
89
+ attr_reader :model
105
90
 
106
91
  private
107
92
 
108
- attr_reader :model, :destroy_context_type
93
+ attr_reader :destroy_context_type, :if_option
109
94
 
110
- def deprecator
111
- if ActiveRecord.respond_to?(:deprecator)
112
- ActiveRecord.deprecator
113
- else # for rails 7.0.x or lower
114
- ActiveSupport::Deprecation
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, context: nil)
43
- models << wrap(model, destroy:, context:)
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, destroy: nil, context: nil)
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, context: nil)
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
- # @type var model: ActiveRecordCompose::_ARLike
110
- ActiveRecordCompose::InnerModel.new(model, destroy:, context:)
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
- def deprecator
115
- if ActiveRecord.respond_to?(:deprecator)
116
- ActiveRecord.deprecator # steep:ignore
117
- else # for rails 7.0.x or lower
118
- ActiveSupport::Deprecation
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 wrapped_models = models.__each_by_wrapped # steep:ignore
154
-
155
- def validate_models = wrapped_models.select { _1.invalid? }.each { errors.merge!(_1) }
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:) = wrapped_models.all? { bang ? _1.save! : _1.save }
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordCompose
4
- VERSION = '0.4.1'
4
+ VERSION = '0.6.0'
5
5
  end
@@ -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, ?context: (nil | context | context_proc)) -> self
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, ?destroy: (nil | destroy_context_type), ?context: (nil | context | context_proc)) -> InnerModelCollection?
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, ?context: (nil | context | context_proc)) -> InnerModel
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, ?context: (nil | context | context_proc)) -> void
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.1
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-09-19 00:00:00.000000000 Z
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.4.1
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.18
72
+ rubygems_version: 3.5.21
73
73
  signing_key:
74
74
  specification_version: 4
75
75
  summary: activemodel form object pattern