active_record_compose 0.3.4 → 0.4.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: 1f089bd84bf403659ef6e4835080c952a07d7fb393aa7820c8b030294956f525
4
- data.tar.gz: 9c9d6137b7c1d4715e89bc4c0f7c9e9965fdd3b9a2c265517ccac8568c3365fd
3
+ metadata.gz: 5fe788a613275af9e9784ba51b84af314792e0a5761e0c8d5e985602dfe7d586
4
+ data.tar.gz: 0621ff28f43377240efc94a97b12ffde861e71142f6ba2ac349d4d9a165d6401
5
5
  SHA512:
6
- metadata.gz: e6a6bc59528725186b32d4447de1b7883e831cbdbd0579bfd5dadc100290a385ecd8410da67d760bf8bccc8c5548f73401319cb18b2289d649ad06a70184d42d
7
- data.tar.gz: 1b25e47a0c9a4c49d1b8141499357b7f32bd6d724e05958531e98d164db3d738c1cf19c1045df96eae4d441e1144af610a6472c2372f018c32021aff12d0fa3b
6
+ metadata.gz: d67bf25365ccad6f049a17cbc311cbf19c48ee4719e51afefc149fb0227989ec8f1c55684ab26328839ae7c79897fed27ac6b5c7db90c42bf0b81e3bc4437c97
7
+ data.tar.gz: ab71a4b2bd61619136f8685b0b1346d4ad111fec0717a1527ce714985f5eaee91f0c91dd9713ba835a2633acbdc2a197f2ae2c77767d2028b13eb4bcb5f21887
data/.rubocop.yml CHANGED
@@ -12,6 +12,9 @@ Style/CommentedKeyword:
12
12
  Style/Documentation:
13
13
  Enabled: false
14
14
 
15
+ Style/DoubleNegation:
16
+ Enabled: false
17
+
15
18
  Style/NumericPredicate:
16
19
  Enabled: false
17
20
 
@@ -39,6 +42,9 @@ Layout/LeadingCommentSpace:
39
42
  Layout/LineLength:
40
43
  Max: 120
41
44
 
45
+ Metrics/AbcSize:
46
+ Enabled: false
47
+
42
48
  Metrics/BlockLength:
43
49
  Enabled: false
44
50
 
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.1] - 2024-09-20
4
+
5
+ - Omitted optional argument for `InnerModelCollection#destroy`.
6
+ `InnerModel` equivalence is always performed based on the instance of the inner `model`.
7
+ Since there are no use cases that depend on the original behavior.
8
+
9
+ ## [0.4.0] - 2024-09-15
10
+
11
+ - support `destrpy` option. and deprecated `context` option.
12
+ `:context` will be removed in 0.5.0. Use `:destroy` option instead.
13
+ for example,
14
+ - `models.push(model, context: :destroy)` is replaced by `models.push(model, destroy: true)`
15
+ - `models.push(model, context: -> { foo? ? :destroy : :save })` is replaced by `models.push(model, destroy: -> { foo? })`
16
+ - `models.push(model, context: ->(m) { m.bar? ? :destroy : :save })` is replaced by `models.push(model, destroy: ->(m) { m.bar? })`
17
+ - `destroy` option can now be specified with a `Symbol` representing the method name.
18
+
3
19
  ## [0.3.4] - 2024-09-01
4
20
 
5
21
  - ci: removed sqlite3 version specifing for new AR.
data/README.md CHANGED
@@ -156,7 +156,7 @@ Account.count #=> 1
156
156
  Profile.count #=> 1
157
157
  ```
158
158
 
159
- By adding to the `models` array while specifying `context: :destroy`, you can perform a delete instead of a save on the model at `#save` time.
159
+ By adding to the `models` array while specifying `destroy: true`, you can perform a delete instead of a save on the model at `#save` time.
160
160
 
161
161
  ```ruby
162
162
  class AccountResignation < ActiveRecordCompose::Model
@@ -164,7 +164,7 @@ class AccountResignation < ActiveRecordCompose::Model
164
164
  @account = account
165
165
  @profile = account.profile # Suppose that Account has_one Profile.
166
166
  models.push(account)
167
- models.push(profile, context: :destroy)
167
+ models.push(profile, destroy: true)
168
168
  end
169
169
 
170
170
  attr_reader :account, :profile
@@ -193,6 +193,29 @@ account.resigned_at.present? #=> Tue, 02 Jan 2024 22:58:01.991008870 JST +09:00
193
193
  account.profile.blank? #=> true
194
194
  ```
195
195
 
196
+ Conditional destroy (or save) can be written like this.
197
+
198
+ ```ruby
199
+ class AccountRegistration < ActiveRecordCompose::Model
200
+ def initialize(account, attributes = {})
201
+ @account = account
202
+ @profile = account.profile || account.build_profile
203
+ super(attributes)
204
+ models.push(account)
205
+ models.push(profile, destroy: :all_blank?) # destroy if all blank, otherwise save.
206
+ end
207
+
208
+ delegate_attribute :name, :email, to: :account
209
+ delegate_attribute :firstname, :lastname, :age, to: :profile
210
+
211
+ private
212
+
213
+ attr_reader :account, :profile
214
+
215
+ def all_blank? = firstname.blank && lastname.blank? && age.blank?
216
+ end
217
+ ```
218
+
196
219
  ### `delegate_attribute`
197
220
 
198
221
  It provides a macro description that expresses access to the attributes of the AR model through delegation.
@@ -5,67 +5,82 @@ require 'active_support/core_ext/object'
5
5
  module ActiveRecordCompose
6
6
  class InnerModel
7
7
  # @param model [Object] the model instance.
8
- # @param context [Symbol] :save or :destroy
9
- # @param context [Proc] proc returning either :save or :destroy
10
- def initialize(model, context: :save)
8
+ # @param destroy [Boolean] given true, destroy model.
9
+ # @param destroy [Proc] when proc returning true, destroy model.
10
+ def initialize(model, destroy: false, context: nil)
11
11
  @model = model
12
- @context = context
13
- end
12
+ @destroy_context_type =
13
+ if context
14
+ c = context
14
15
 
15
- delegate :errors, to: :model
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
+ )
16
33
 
17
- # @return [Symbol] :save or :destroy
18
- def context #: ActiveRecordCompose::context
19
- c = @context
20
- ret =
21
- if c.is_a?(Proc)
22
- if c.arity == 0
23
- # @type var c: ^() -> context
24
- c.call
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
25
46
  else
26
- # @type var c: ^(_ARLike) -> context
27
- c.call(model)
47
+ c
28
48
  end
29
49
  else
30
- c
50
+ destroy
31
51
  end
32
- ret.presence_in(%i[save destroy]) || :save
33
52
  end
34
53
 
35
- # Execute save or destroy. Returns true on success, false on failure.
36
- # Whether save or destroy is executed depends on the value of context.
37
- #
38
- # @return [Boolean] returns true on success, false on failure.
39
- def save
40
- case context
41
- when :destroy
42
- model.destroy
54
+ delegate :errors, to: :model
55
+
56
+ def destroy_context?
57
+ d = destroy_context_type
58
+ if d.is_a?(Proc)
59
+ if d.arity == 0
60
+ # @type var d: ^() -> (bool | context)
61
+ !!d.call
62
+ else
63
+ # @type var d: ^(_ARLike) -> (bool | context)
64
+ !!d.call(model)
65
+ end
43
66
  else
44
- model.save
67
+ !!d
45
68
  end
46
69
  end
47
70
 
71
+ # Execute save or destroy. Returns true on success, false on failure.
72
+ # Whether save or destroy is executed depends on the value of `#destroy_context?`.
73
+ #
74
+ # @return [Boolean] returns true on success, false on failure.
75
+ def save = destroy_context? ? model.destroy : model.save
76
+
48
77
  # Execute save or destroy. Unlike #save, an exception is raises on failure.
49
- # Whether save or destroy is executed depends on the value of context.
78
+ # Whether save or destroy is executed depends on the value of `#destroy_context?`.
50
79
  #
51
- def save!
52
- case context
53
- when :destroy
54
- model.destroy!
55
- else
56
- model.save!
57
- end
58
- end
80
+ def save! = destroy_context? ? model.destroy! : model.save!
59
81
 
60
82
  # @return [Boolean]
61
- def invalid?
62
- case context
63
- when :destroy
64
- false
65
- else
66
- model.invalid?
67
- end
68
- end
83
+ def invalid? = destroy_context? ? false : model.invalid?
69
84
 
70
85
  # @return [Boolean]
71
86
  def valid? = !invalid?
@@ -77,11 +92,11 @@ module ActiveRecordCompose
77
92
  def ==(other)
78
93
  return false unless self.class == other.class
79
94
  return false unless __raw_model == other.__raw_model # steep:ignore
80
- return false unless context == other.context
81
95
 
82
96
  true
83
97
  end
84
98
 
99
+ # @private
85
100
  # Returns a model instance of raw, but it should
86
101
  # be noted that application developers are not expected to use this interface.
87
102
  #
@@ -90,6 +105,14 @@ module ActiveRecordCompose
90
105
 
91
106
  private
92
107
 
93
- attr_reader :model
108
+ attr_reader :model, :destroy_context_type
109
+
110
+ def deprecator
111
+ if ActiveRecord.respond_to?(:deprecator)
112
+ ActiveRecord.deprecator
113
+ else # for rails 7.0.x or lower
114
+ ActiveSupport::Deprecation
115
+ end
116
+ end
94
117
  end
95
118
  end
@@ -6,6 +6,11 @@ module ActiveRecordCompose
6
6
  class InnerModelCollection
7
7
  include Enumerable
8
8
 
9
+ def initialize(owner)
10
+ @owner = owner
11
+ @models = []
12
+ end
13
+
9
14
  # Enumerates model objects.
10
15
  #
11
16
  # @yieldparam [Object] the model instance
@@ -23,17 +28,19 @@ module ActiveRecordCompose
23
28
  # @param model [Object] the model instance
24
29
  # @return [self] returns itself.
25
30
  def <<(model)
26
- models << wrap(model, context: :save)
31
+ models << wrap(model, destroy: false)
27
32
  self
28
33
  end
29
34
 
30
35
  # Appends model to collection.
31
36
  #
32
37
  # @param model [Object] the model instance
33
- # @param context [Symbol] :save or :destroy
38
+ # @param destroy [Boolean] given true, destroy model.
39
+ # @param destroy [Proc] when proc returning true, destroy model.
40
+ # @param destroy [Symbol] applies boolean value of result of sending a message to `owner` to evaluation.
34
41
  # @return [self] returns itself.
35
- def push(model, context: :save)
36
- models << wrap(model, context:)
42
+ def push(model, destroy: false, context: nil)
43
+ models << wrap(model, destroy:, context:)
37
44
  self
38
45
  end
39
46
 
@@ -54,16 +61,25 @@ module ActiveRecordCompose
54
61
  # Returns nil if the deletion fails, self if it succeeds.
55
62
  #
56
63
  # @param model [Object] the model instance
57
- # @param context [Symbol] :save or :destroy
58
64
  # @return [self] Successful deletion
59
65
  # @return [nil] If deletion fails
60
- def delete(model, context: :save)
61
- wrapped = wrap(model, context:)
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
+
76
+ wrapped = wrap(model)
62
77
  return nil unless models.delete(wrapped)
63
78
 
64
79
  self
65
80
  end
66
81
 
82
+ # @private
67
83
  # Enumerates model objects, but it should be noted that
68
84
  # application developers are not expected to use this interface.
69
85
  #
@@ -79,15 +95,27 @@ module ActiveRecordCompose
79
95
 
80
96
  private
81
97
 
82
- def models = @models ||= []
98
+ attr_reader :owner, :models
83
99
 
84
- def wrap(model, context:)
100
+ def wrap(model, destroy: false, context: nil)
85
101
  if model.is_a?(ActiveRecordCompose::InnerModel) # steep:ignore
86
102
  # @type var model: ActiveRecordCompose::InnerModel
87
103
  model
88
104
  else
105
+ if destroy.is_a?(Symbol)
106
+ method = destroy
107
+ destroy = -> { owner.__send__(method) }
108
+ end
89
109
  # @type var model: ActiveRecordCompose::_ARLike
90
- ActiveRecordCompose::InnerModel.new(model, context:)
110
+ ActiveRecordCompose::InnerModel.new(model, destroy:, context:)
111
+ end
112
+ end
113
+
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
91
119
  end
92
120
  end
93
121
  end
@@ -148,7 +148,7 @@ module ActiveRecordCompose
148
148
 
149
149
  private
150
150
 
151
- def models = @__models ||= ActiveRecordCompose::InnerModelCollection.new
151
+ def models = @__models ||= ActiveRecordCompose::InnerModelCollection.new(self)
152
152
 
153
153
  def wrapped_models = models.__each_by_wrapped # steep:ignore
154
154
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordCompose
4
- VERSION = '0.3.4'
4
+ VERSION = '0.4.1'
5
5
  end
@@ -3,8 +3,6 @@
3
3
  require 'active_record'
4
4
 
5
5
  require_relative 'active_record_compose/version'
6
- require_relative 'active_record_compose/inner_model'
7
- require_relative 'active_record_compose/inner_model_collection'
8
6
  require_relative 'active_record_compose/model'
9
7
 
10
8
  module ActiveRecordCompose
@@ -17,6 +17,7 @@ module ActiveRecordCompose
17
17
  type attribute_name = (String | Symbol)
18
18
  type context = (:save | :destroy)
19
19
  type context_proc = ((^() -> context) | (^(_ARLike) -> context))
20
+ type destroy_context_type = (bool | Symbol | (^() -> boolish) | (^(_ARLike) -> boolish))
20
21
 
21
22
  module DelegateAttribute
22
23
  extend ActiveSupport::Concern
@@ -30,25 +31,24 @@ module ActiveRecordCompose
30
31
 
31
32
  class InnerModelCollection
32
33
  include ::Enumerable[_ARLike]
33
- @models: Array[InnerModel]
34
34
 
35
+ def initialize: (Model) -> void
35
36
  def each: () { (_ARLike) -> void } -> InnerModelCollection | () -> Enumerator[_ARLike, self]
36
37
  def <<: (_ARLike) -> self
37
- def push: (_ARLike, ?context: (context | context_proc)) -> self
38
+ def push: (_ARLike, ?destroy: destroy_context_type, ?context: (nil | context | context_proc)) -> self
38
39
  def empty?: -> bool
39
40
  def clear: -> self
40
- def delete: (_ARLike | InnerModel, ?context: (context | context_proc)) -> InnerModelCollection?
41
+ def delete: (_ARLike | InnerModel, ?destroy: (nil | destroy_context_type), ?context: (nil | context | context_proc)) -> InnerModelCollection?
41
42
 
42
43
  private
43
- def models: -> Array[InnerModel]
44
- def wrap: (_ARLike | InnerModel, context: (context | context_proc)) -> InnerModel
44
+ attr_reader owner: Model
45
+ attr_reader models: Array[InnerModel]
46
+ def wrap: (_ARLike | InnerModel, ?destroy: destroy_context_type, ?context: (nil | context | context_proc)) -> InnerModel
45
47
  end
46
48
 
47
49
  class InnerModel
48
- @context: (context | context_proc)
49
-
50
- def initialize: (_ARLike, ?context: (context | context_proc)) -> void
51
- def context: -> context
50
+ def initialize: (_ARLike, ?destroy: destroy_context_type, ?context: (nil | context | context_proc)) -> void
51
+ def destroy_context?: -> bool
52
52
  def save: -> bool
53
53
  def save!: -> untyped
54
54
  def invalid?: -> bool
@@ -57,6 +57,7 @@ module ActiveRecordCompose
57
57
 
58
58
  private
59
59
  attr_reader model: _ARLike
60
+ attr_reader destroy_context_type: destroy_context_type
60
61
  end
61
62
 
62
63
  class Model
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.3.4
4
+ version: 0.4.1
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-01 00:00:00.000000000 Z
11
+ date: 2024-09-19 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.3.4
55
+ documentation_uri: https://www.rubydoc.info/gems/active_record_compose/0.4.1
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.11
72
+ rubygems_version: 3.5.18
73
73
  signing_key:
74
74
  specification_version: 4
75
75
  summary: activemodel form object pattern