active_record_compose 0.3.3 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f9038b3106a9206304243a5723488fe400e6c05eba4edb69fef0e2a7a154f15f
4
- data.tar.gz: 7432c93447a3627cf6c1e6706d6d0d3d1ea1e6d80079b9038e73fe1fd1bddaeb
3
+ metadata.gz: 8d9e12c3cb3d2e8571d418957576a8cf5b5d3dbedb9e83e2bcd25f172def43f3
4
+ data.tar.gz: a991715192e6cb62e8b794860422667811b1fb1f985089e415b444f0b7bc0d4a
5
5
  SHA512:
6
- metadata.gz: 7ce874d30acf694f7be00c7177a7dcc82a49f8119709fa07b0d9cac98fc4f44ca03211bb104c8d9e2173d42ceebb0de00567c8666e1b1b6adaadd587e60d35ba
7
- data.tar.gz: 0f3100317539f2e383ac07fed57f027c90f4030ccd2717fae2e54f288a3f17f92488fa23dc86d6cc80a05746542ef68b780c56b63dd9609ab2f27b39c21a8045
6
+ metadata.gz: 837b3d35c903eb0ff4400e5c83c438255d9ee02515407792fd19d24ab9a5a63cea1f9faa1d12a5fd5184d52fee2d662625e7c0d0b2a79e27ebb0866054a6b2a4
7
+ data.tar.gz: e245d8380a2d20f0c9bbbc1f24712f1d7982697bf90b172886dcd2c9bd666ace8ddd3b29a20b9bcc709d8a236a1395a41c0f53ccdb3869f66a527d5ad6be08dc
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,24 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.0] - 2024-09-15
4
+
5
+ - support `destrpy` option. and deprecated `context` option.
6
+ `:context` will be removed in 0.5.0. Use `:destroy` option instead.
7
+ for example,
8
+ - `models.push(model, context: :destroy)` is replaced by `models.push(model, destroy: true)`
9
+ - `models.push(model, context: -> { foo? ? :destroy : :save })` is replaced by `models.push(model, destroy: -> { foo? })`
10
+ - `models.push(model, context: ->(m) { m.bar? ? :destroy : :save })` is replaced by `models.push(model, destroy: ->(m) { m.bar? })`
11
+ - `destroy` option can now be specified with a `Symbol` representing the method name.
12
+
13
+ ## [0.3.4] - 2024-09-01
14
+
15
+ - ci: removed sqlite3 version specifing for new AR.
16
+ - `delegate_attribute` options are now specific and do not accept `prefix`
17
+
18
+ ## [0.3.3] - 2024-06-24
19
+
20
+ - use steep:ignore
21
+
3
22
  ## [0.3.2] - 2024-04-10
4
23
 
5
24
  - support `ActiveRecord::Base#with_connection`
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.
@@ -44,7 +44,7 @@ module ActiveRecordCompose
44
44
  module ClassMethods
45
45
  # Defines the reader and writer for the specified attribute.
46
46
  #
47
- def delegate_attribute(*attributes, to:, **options)
47
+ def delegate_attribute(*attributes, to:, allow_nil: nil, private: nil)
48
48
  delegates = attributes.flat_map do |attribute|
49
49
  reader = attribute.to_s
50
50
  writer = "#{attribute}="
@@ -52,7 +52,7 @@ module ActiveRecordCompose
52
52
  [reader, writer]
53
53
  end
54
54
 
55
- delegate(*delegates, to:, **options) # steep:ignore
55
+ delegate(*delegates, to:, allow_nil:, private:) # steep:ignore
56
56
  delegated_attributes = (self.delegated_attributes ||= []) # steep:ignore
57
57
  attributes.each { delegated_attributes.push(_1.to_s) }
58
58
  end
@@ -5,67 +5,85 @@ 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(owner, model, destroy: false, context: nil)
11
+ @owner = owner
11
12
  @model = model
12
- @context = context
13
- end
13
+ @destroy =
14
+ if context
15
+ c = context
14
16
 
15
- delegate :errors, to: :model
17
+ if c.is_a?(Proc)
18
+ # @type var c: ((^() -> (context)) | (^(_ARLike) -> (context)))
19
+ if c.arity == 0
20
+ deprecator.warn(
21
+ '`:context` will be removed in 0.5.0. Use `:destroy` option instead. ' \
22
+ 'for example, `models.push(model, context: -> { foo? ? :destroy : :save })` ' \
23
+ 'is replaced by `models.push(model, destroy: -> { foo? })`.',
24
+ )
25
+
26
+ # @type var c: ^() -> (context)
27
+ -> { c.call == :destroy }
28
+ else
29
+ deprecator.warn(
30
+ '`:context` will be removed in 0.5.0. Use `:destroy` option instead. ' \
31
+ 'for example, `models.push(model, context: ->(m) { m.bar? ? :destroy : :save })` ' \
32
+ 'is replaced by `models.push(model, destroy: ->(m) { m.bar? })`.',
33
+ )
34
+
35
+ # @type var c: ^(_ARLike) -> (context)
36
+ ->(model) { c.call(model) == :destroy }
37
+ end
38
+ elsif %i[save destroy].include?(c)
39
+ deprecator.warn(
40
+ '`:context` will be removed in 0.5.0. Use `:destroy` option instead. ' \
41
+ "for example, `models.push(model, context: #{c.inspect})` " \
42
+ "is replaced by `models.push(model, destroy: #{(c == :destroy).inspect})`.",
43
+ )
16
44
 
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
45
+ # @type var c: (:save | :destory)
46
+ c == :destroy
25
47
  else
26
- # @type var c: ^(_ARLike) -> context
27
- c.call(model)
48
+ c
28
49
  end
29
50
  else
30
- c
51
+ destroy
31
52
  end
32
- ret.presence_in(%i[save destroy]) || :save
33
53
  end
34
54
 
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
55
+ delegate :errors, to: :model
56
+
57
+ def destroy_context?
58
+ d = destroy
59
+ if d.is_a?(Proc)
60
+ if d.arity == 0
61
+ # @type var d: ^() -> (bool | context)
62
+ d.call
63
+ else
64
+ # @type var d: ^(_ARLike) -> (bool | context)
65
+ d.call(model)
66
+ end
67
+ elsif d.is_a?(Symbol)
68
+ owner.send(d)
43
69
  else
44
- model.save
70
+ !!d
45
71
  end
46
72
  end
47
73
 
74
+ # Execute save or destroy. Returns true on success, false on failure.
75
+ # Whether save or destroy is executed depends on the value of `#destroy_context?`.
76
+ #
77
+ # @return [Boolean] returns true on success, false on failure.
78
+ def save = destroy_context? ? model.destroy : model.save
79
+
48
80
  # 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.
81
+ # Whether save or destroy is executed depends on the value of `#destroy_context?`.
50
82
  #
51
- def save!
52
- case context
53
- when :destroy
54
- model.destroy!
55
- else
56
- model.save!
57
- end
58
- end
83
+ def save! = destroy_context? ? model.destroy! : model.save!
59
84
 
60
85
  # @return [Boolean]
61
- def invalid?
62
- case context
63
- when :destroy
64
- false
65
- else
66
- model.invalid?
67
- end
68
- end
86
+ def invalid? = destroy_context? ? false : model.invalid?
69
87
 
70
88
  # @return [Boolean]
71
89
  def valid? = !invalid?
@@ -76,8 +94,8 @@ module ActiveRecordCompose
76
94
  # @return [Boolean]
77
95
  def ==(other)
78
96
  return false unless self.class == other.class
79
- return false unless __raw_model == other.__raw_model # steep:ignore
80
- return false unless context == other.context
97
+ return false unless __raw_model == other.__raw_model
98
+ return false unless __destroy == other.__destroy
81
99
 
82
100
  true
83
101
  end
@@ -88,8 +106,23 @@ module ActiveRecordCompose
88
106
  # @return [Object] raw model instance
89
107
  def __raw_model = model
90
108
 
109
+ # Returns a model instance of raw, but it should
110
+ # be noted that application developers are not expected to use this interface.
111
+ #
112
+ # @return [Boolean] raw destroy instance
113
+ # @return [Proc] raw destroy instance
114
+ def __destroy = destroy
115
+
91
116
  private
92
117
 
93
- attr_reader :model
118
+ attr_reader :owner, :model, :destroy
119
+
120
+ def deprecator
121
+ if ActiveRecord.respond_to?(:deprecator)
122
+ ActiveRecord.deprecator
123
+ else # for rails 7.0.x or lower
124
+ ActiveSupport::Deprecation
125
+ end
126
+ end
94
127
  end
95
128
  end
@@ -6,6 +6,10 @@ module ActiveRecordCompose
6
6
  class InnerModelCollection
7
7
  include Enumerable
8
8
 
9
+ def initialize(owner)
10
+ @owner = owner
11
+ end
12
+
9
13
  # Enumerates model objects.
10
14
  #
11
15
  # @yieldparam [Object] the model instance
@@ -23,17 +27,18 @@ module ActiveRecordCompose
23
27
  # @param model [Object] the model instance
24
28
  # @return [self] returns itself.
25
29
  def <<(model)
26
- models << wrap(model, context: :save)
30
+ models << wrap(model, destroy: false)
27
31
  self
28
32
  end
29
33
 
30
34
  # Appends model to collection.
31
35
  #
32
36
  # @param model [Object] the model instance
37
+ # @param destroy [Boolean] given true, destroy model.
33
38
  # @param context [Symbol] :save or :destroy
34
39
  # @return [self] returns itself.
35
- def push(model, context: :save)
36
- models << wrap(model, context:)
40
+ def push(model, destroy: false, context: nil)
41
+ models << wrap(model, destroy:, context:)
37
42
  self
38
43
  end
39
44
 
@@ -54,11 +59,12 @@ module ActiveRecordCompose
54
59
  # Returns nil if the deletion fails, self if it succeeds.
55
60
  #
56
61
  # @param model [Object] the model instance
62
+ # @param destroy [Boolean] given true, destroy model.
57
63
  # @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: false, context: nil)
67
+ wrapped = wrap(model, destroy:, context:)
62
68
  return nil unless models.delete(wrapped)
63
69
 
64
70
  self
@@ -79,15 +85,17 @@ module ActiveRecordCompose
79
85
 
80
86
  private
81
87
 
88
+ attr_reader :owner
89
+
82
90
  def models = @models ||= []
83
91
 
84
- def wrap(model, context:)
92
+ def wrap(model, destroy:, context: nil)
85
93
  if model.is_a?(ActiveRecordCompose::InnerModel) # steep:ignore
86
94
  # @type var model: ActiveRecordCompose::InnerModel
87
95
  model
88
96
  else
89
97
  # @type var model: ActiveRecordCompose::_ARLike
90
- ActiveRecordCompose::InnerModel.new(model, context:)
98
+ ActiveRecordCompose::InnerModel.new(owner, model, destroy:, context:)
91
99
  end
92
100
  end
93
101
  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.3'
4
+ VERSION = '0.4.0'
5
5
  end
@@ -24,31 +24,35 @@ module ActiveRecordCompose
24
24
  def attributes: -> Hash[String, untyped]
25
25
 
26
26
  module ClassMethods
27
- def delegate_attribute: (*untyped methods, to: untyped?, **untyped) -> untyped
27
+ def delegate_attribute: (*untyped methods, to: untyped?, ?allow_nil: untyped?, ?private: untyped?) -> untyped
28
28
  end
29
29
  end
30
30
 
31
31
  class InnerModelCollection
32
32
  include ::Enumerable[_ARLike]
33
+ @owner: Model
33
34
  @models: Array[InnerModel]
34
35
 
36
+ def initialize: (Model) -> void
35
37
  def each: () { (_ARLike) -> void } -> InnerModelCollection | () -> Enumerator[_ARLike, self]
36
38
  def <<: (_ARLike) -> self
37
- def push: (_ARLike, ?context: (context | context_proc)) -> self
39
+ def push: (_ARLike, ?destroy: bool, ?context: (nil | context | context_proc)) -> self
38
40
  def empty?: -> bool
39
41
  def clear: -> self
40
- def delete: (_ARLike | InnerModel, ?context: (context | context_proc)) -> InnerModelCollection?
42
+ def delete: (_ARLike | InnerModel, ?destroy: bool, ?context: (nil | context | context_proc)) -> InnerModelCollection?
41
43
 
42
44
  private
45
+ attr_reader owner: Model
43
46
  def models: -> Array[InnerModel]
44
- def wrap: (_ARLike | InnerModel, context: (context | context_proc)) -> InnerModel
47
+ def wrap: (_ARLike | InnerModel, destroy: bool, ?context: (nil | context | context_proc)) -> InnerModel
45
48
  end
46
49
 
47
50
  class InnerModel
51
+ @owner: Model
48
52
  @context: (context | context_proc)
49
53
 
50
- def initialize: (_ARLike, ?context: (context | context_proc)) -> void
51
- def context: -> context
54
+ def initialize: (Model, _ARLike, ?destroy: bool, ?context: (nil | context | context_proc)) -> void
55
+ def destroy_context?: -> bool
52
56
  def save: -> bool
53
57
  def save!: -> untyped
54
58
  def invalid?: -> bool
@@ -56,6 +60,7 @@ module ActiveRecordCompose
56
60
  def ==: (untyped) -> bool
57
61
 
58
62
  private
63
+ attr_reader owner: Model
59
64
  attr_reader model: _ARLike
60
65
  end
61
66
 
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.3
4
+ version: 0.4.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-06-23 00:00:00.000000000 Z
11
+ date: 2024-09-14 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.3
55
+ documentation_uri: https://www.rubydoc.info/gems/active_record_compose/0.4.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.11
72
+ rubygems_version: 3.5.18
73
73
  signing_key:
74
74
  specification_version: 4
75
75
  summary: activemodel form object pattern