shale-builder 0.8.4 → 0.9.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: 9599ec92d021b9ed5b62272efaabaca3a77944c4a8c9611f8ad6fbee730d6e9d
4
- data.tar.gz: e8c0dfab2765c8e328fe29916f80e751748c27eb293ff8d3db7c927deb89f901
3
+ metadata.gz: 293665a7fae7d93488f9c8f65e0c4d1fed9cc01c629549f4d06f4163ef712b6b
4
+ data.tar.gz: 10bf23ea86288b0d2dc67e8730fa339a63424b1d9f003d6def9d9274007d032b
5
5
  SHA512:
6
- metadata.gz: 4fee3d4b8016d6e3d334101f4fd0a4c1f7b712271603ab0ff586e5d0697eabf7ec905387efde342345f44eff5e3ef6c43e7f2e467404a6ba10b21728c9c66ff2
7
- data.tar.gz: 62c613bfa8e6f0803e96995b628633a4a085b78d061c5573bd44a41ef648aae6eaf3330fe8d27b5c99098ad816260b5a3afa4dbc554c32dc2ed975714948c2cf
6
+ metadata.gz: 3ad60377af23996a3a6fca6b186f783c9a693261b3df56ce7a4ecf3ac32048b38b072fa0a3ece2584137767e0e1ca1d9bc5a474a026b9e00873e1d8f9fe4eca1
7
+ data.tar.gz: e5b06e6b2cd4d0ec52b9cfb94063052ce30902855b5b3931850dfe669869d9fc9b5734f3078b6de1dfd793af7c9e2f225c9f9bff926183cc649961ffbac35d97
data/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.9.0] - 2026-04-24
9
+
10
+ [Diff](https://github.com/Verseth/ruby-shale-builder/compare/v0.8.5...v0.9.0)
11
+
12
+ ### Changes
13
+ - Add `memoize:` optional keyword arg to complex getters to preserve the already existing object instead of overriding it
14
+
15
+ ## [0.8.5] - 2025-10-27
16
+
17
+ [Diff](https://github.com/Verseth/ruby-shale-builder/compare/v0.8.3...v0.8.5)
18
+
19
+ ### Changes
20
+ - Fix the tapioca compiler behaviour with `T.untyped`
21
+
8
22
  ## [0.8.3] - 2025-10-24
9
23
 
10
24
  [Diff](https://github.com/Verseth/ruby-shale-builder/compare/v0.8.2...v0.8.3)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shale-builder (0.8.4)
4
+ shale-builder (0.9.0)
5
5
  booleans (>= 0.1)
6
6
  shale (< 2.0)
7
7
  sorbet-runtime (> 0.5)
data/README.md CHANGED
@@ -145,6 +145,47 @@ non-primitive types have been overridden to accept blocks.
145
145
  When a block is given to such a getter, it instantiates an empty object
146
146
  of its type and yields it to the block.
147
147
 
148
+ #### Overriding
149
+
150
+ By default if you call the builder method with a block a second time
151
+ it will override the previous object with a new one
152
+
153
+ ```rb
154
+ transaction = Transaction.build do |t|
155
+ t.amount do |a|
156
+ a.value = 2.3
157
+ a.currency = 'PLN'
158
+ end
159
+ t.amount do |a|
160
+ a.value = 10
161
+ end
162
+ end
163
+
164
+ transaction.amount.value #=> 10
165
+ transaction.amount.currency #=> nil
166
+ ```
167
+
168
+ #### Memoization
169
+
170
+ You can change the behaviour of builder methods
171
+ so that they preserve existing objects when called a second time.
172
+ You do that by using the `memoize: true` keyword argument.
173
+
174
+ ```rb
175
+ transaction = Transaction.build do |t|
176
+ t.amount do |a|
177
+ a.value = 2.3
178
+ a.currency = 'PLN'
179
+ end
180
+ t.amount(memoize: true) do |a|
181
+ a.value = 10
182
+ end
183
+ end
184
+
185
+ transaction.amount.value #=> 10
186
+ transaction.amount.currency #=> 'PLN'
187
+ ```
188
+
148
189
  ### Collections
149
190
 
150
191
  Whenever you call a getter with a block for a collection attribute, the built object will be appended to the array.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Shale
4
4
  module Builder
5
- VERSION = '0.8.4'
5
+ VERSION = '0.9.0'
6
6
  end
7
7
  end
data/lib/shale/builder.rb CHANGED
@@ -130,27 +130,27 @@ module Shale
130
130
 
131
131
  if collection
132
132
  @builder_methods_module.class_eval <<~RUBY, __FILE__, __LINE__ + 1
133
- def #{name} # def clients
134
- return super unless block_given? # return super unless block_given?
135
- #
136
- arr = self.#{name} ||= [] # arr = self.clients ||= []
137
- object = #{shale_mapper}.new # object = Client.new
138
- yield(object) # yield(object)
139
- arr << object # arr << object
140
- object # object
141
- end # end
133
+ def #{name}
134
+ return super unless block_given?
135
+
136
+ arr = self.#{name} ||= []
137
+ object = #{shale_mapper}.new
138
+ yield(object)
139
+ arr << object
140
+ object
141
+ end
142
142
  RUBY
143
143
  return
144
144
  end
145
145
 
146
146
  @builder_methods_module.class_eval <<~RUBY, __FILE__, __LINE__ + 1
147
- def #{name} # def amount
148
- return super unless block_given? # return super unless block_given?
149
- #
150
- object = #{shale_mapper}.new # object = Amount.new
151
- yield(object) # yield(object)
152
- self.#{name} = object # self.amount = object
153
- end # end
147
+ def #{name}(memoize: false)
148
+ return super() unless block_given?
149
+
150
+ object = (memoize && self.#{name}) || #{shale_mapper}.new
151
+ yield(object)
152
+ self.#{name} = object
153
+ end
154
154
  RUBY
155
155
  end
156
156
 
@@ -18,7 +18,8 @@ module Tapioca
18
18
  class << self
19
19
  extend T::Sig
20
20
 
21
- sig { override.returns(T::Enumerable[Module]) }
21
+ # @override
22
+ #: -> T::Enumerable[Module]
22
23
  def gather_constants
23
24
  # Collect all the classes that inherit from Shale::Mapper
24
25
  all_classes.select { |c| c < ::Shale::Mapper }
@@ -27,7 +28,8 @@ module Tapioca
27
28
 
28
29
  SHALE_ATTRIBUTE_MODULE = 'ShaleAttributeMethods'
29
30
 
30
- sig { override.void }
31
+ # @override
32
+ #: -> void
31
33
  def decorate
32
34
  # Create a RBI definition for each class that inherits from Shale::Mapper
33
35
  root.create_path(constant) do |klass|
@@ -50,9 +52,9 @@ module Tapioca
50
52
  end
51
53
 
52
54
  if attribute.collection?
53
- getter_without_block_type = "T.nilable(T::Array[#{return_type}])"
55
+ getter_without_block_type = wrap_nilable_type(wrap_array_type(return_type))
54
56
  elsif nilable
55
- getter_without_block_type = "T.nilable(#{return_type})"
57
+ getter_without_block_type = wrap_nilable_type(return_type)
56
58
  else
57
59
  getter_without_block_type = return_type.to_s
58
60
  end
@@ -64,9 +66,9 @@ module Tapioca
64
66
  setter_type, nilable = shale_type_to_sorbet_setter_type(attribute)
65
67
  end
66
68
  if attribute.collection?
67
- setter_type_str = "T.nilable(T::Array[#{setter_type}])"
69
+ setter_type_str = wrap_nilable_type(wrap_array_type(setter_type))
68
70
  elsif nilable
69
- setter_type_str = "T.nilable(#{setter_type})"
71
+ setter_type_str = wrap_nilable_type(setter_type)
70
72
  else
71
73
  setter_type_str = setter_type.to_s
72
74
  end
@@ -92,48 +94,64 @@ module Tapioca
92
94
 
93
95
  end
94
96
 
95
- sig do
96
- params(
97
- mod: RBI::Scope,
98
- method_name: String,
99
- type: Object,
100
- getter_without_block_type: String,
101
- comments: T::Array[RBI::Comment],
102
- ).void
97
+ #: (untyped type) -> String
98
+ def wrap_nilable_type(type)
99
+ return "T.nilable(#{type})" if type != T.untyped
100
+
101
+ T.unsafe(type).to_s
102
+ end
103
+
104
+ #: (untyped type) -> String
105
+ def wrap_array_type(type)
106
+ "T::Array[#{type}]"
103
107
  end
108
+
109
+ #: (RBI::Scope mod, String method_name, Object type, String getter_without_block_type, Array[RBI::Comment] comments) -> void
104
110
  def generate_mapper_getter(mod, method_name, type, getter_without_block_type, comments)
105
111
  if mod.respond_to?(:create_sig)
106
112
  mod = T.unsafe(mod)
107
113
  # for tapioca < 0.16.0
108
- sigs = T.let([], T::Array[RBI::Sig])
114
+ sigs = [] #: Array[RBI::Sig]
109
115
 
110
116
  # simple getter
111
117
  sigs << mod.create_sig(
112
- parameters: { block: 'NilClass' },
118
+ parameters: {
119
+ memoize: 'FalseClass',
120
+ block: 'NilClass',
121
+ },
113
122
  return_type: getter_without_block_type,
114
123
  )
115
124
  # getter with block
116
125
  sigs << mod.create_sig(
117
- parameters: { block: "T.proc.params(arg0: #{type}).void" },
126
+ parameters: {
127
+ memoize: 'T::Boolean',
128
+ block: "T.proc.params(arg0: #{type}).void"
129
+ },
118
130
  return_type: type.to_s
119
131
  )
120
132
  mod.create_method_with_sigs(
121
133
  method_name,
122
134
  sigs: sigs,
123
135
  comments: comments,
124
- parameters: [RBI::BlockParam.new('block')],
136
+ parameters: [
137
+ RBI::KwOptParam.new('memoize', 'false'),
138
+ RBI::BlockParam.new('block'),
139
+ ],
125
140
  )
126
141
  else
127
142
  # for tapioca >= 0.16.0
128
143
  mod.create_method(method_name, comments: comments) do |method|
129
144
  method.add_block_param('block')
145
+ method.add_kw_opt_param('memoize', 'false')
130
146
 
131
147
  method.add_sig do |sig|
148
+ sig.add_param('memoize', 'FalseClass')
132
149
  sig.add_param('block', 'NilClass')
133
150
  sig.return_type = getter_without_block_type
134
151
  end
135
152
 
136
153
  method.add_sig do |sig|
154
+ sig.add_param('memoize', 'T::Boolean')
137
155
  sig.add_param('block', "T.proc.params(arg0: #{type}).void")
138
156
  sig.return_type = type.to_s
139
157
  end
@@ -143,51 +161,45 @@ module Tapioca
143
161
 
144
162
  private
145
163
 
146
- sig { params(klass: Class).returns(T.nilable(T::Boolean)) }
164
+ #: (Class klass) -> bool?
147
165
  def includes_shale_builder(klass)
148
166
  return false unless defined?(::Shale::Builder)
149
167
 
150
168
  klass < ::Shale::Builder
151
169
  end
152
170
 
153
- sig { returns(T::Boolean) }
171
+ #: -> bool
154
172
  def shale_builder_defined? = Boolean(defined?(::Shale::Builder))
155
173
 
156
174
  # Maps Shale return types to Sorbet types
157
- SHALE_RETURN_TYPES_MAP = T.let(
158
- {
159
- ::Shale::Type::Value => T.untyped,
160
- ::Shale::Type::String => String,
161
- ::Shale::Type::Float => Float,
162
- ::Shale::Type::Integer => Integer,
163
- ::Shale::Type::Time => Time,
164
- ::Shale::Type::Date => Date,
165
- ::Shale::Type::Boolean => T::Boolean,
166
- }.tap do |h|
167
- h[::Shale::Type::Decimal] = BigDecimal if defined?(::Shale::Type::Decimal)
168
- end.freeze,
169
- T::Hash[Class, Object],
170
- )
175
+ SHALE_RETURN_TYPES_MAP = {
176
+ ::Shale::Type::Value => T.untyped,
177
+ ::Shale::Type::String => String,
178
+ ::Shale::Type::Float => Float,
179
+ ::Shale::Type::Integer => Integer,
180
+ ::Shale::Type::Time => Time,
181
+ ::Shale::Type::Date => Date,
182
+ ::Shale::Type::Boolean => T::Boolean,
183
+ }.tap do |h|
184
+ h[::Shale::Type::Decimal] = BigDecimal if defined?(::Shale::Type::Decimal)
185
+ end.freeze #: Hash[Class, Object]
171
186
 
172
187
  # Maps Shale setter types to Sorbet types
173
- SHALE_SETTER_TYPES_MAP = T.let(
174
- {
175
- ::Shale::Type::Value => T.untyped,
176
- ::Shale::Type::String => String,
177
- ::Shale::Type::Float => Float,
178
- ::Shale::Type::Integer => Integer,
179
- ::Shale::Type::Time => Time,
180
- ::Shale::Type::Date => Date,
181
- ::Shale::Type::Boolean => Object,
182
- }.tap do |h|
183
- if defined?(::Shale::Type::Decimal)
184
- h[::Shale::Type::Decimal] = T.any(BigDecimal, String, Float, Integer, NilClass)
185
- end
186
- end.freeze,
187
- T::Hash[Class, Object],
188
- )
188
+ SHALE_SETTER_TYPES_MAP = {
189
+ ::Shale::Type::Value => T.untyped,
190
+ ::Shale::Type::String => String,
191
+ ::Shale::Type::Float => Float,
192
+ ::Shale::Type::Integer => Integer,
193
+ ::Shale::Type::Time => Time,
194
+ ::Shale::Type::Date => Date,
195
+ ::Shale::Type::Boolean => Object,
196
+ }.tap do |h|
197
+ if defined?(::Shale::Type::Decimal)
198
+ h[::Shale::Type::Decimal] = T.any(BigDecimal, String, Float, Integer, NilClass)
199
+ end
200
+ end.freeze #: Hash[Class, Object]
189
201
 
190
- sig { params(attribute: ::Shale::Attribute).returns([Object, T::Boolean]) }
202
+ #: (::Shale::Attribute attribute) -> [Object, bool]
191
203
  def shale_type_to_sorbet_return_type(attribute)
192
204
  return_type = SHALE_RETURN_TYPES_MAP[attribute.type]
193
205
  return complex_shale_type_to_sorbet_return_type(attribute) unless return_type
@@ -196,7 +208,7 @@ module Tapioca
196
208
  [return_type, true]
197
209
  end
198
210
 
199
- sig { params(attribute: ::Shale::Attribute).returns([Object, T::Boolean]) }
211
+ #: (::Shale::Attribute attribute) -> [Object, bool]
200
212
  def complex_shale_type_to_sorbet_return_type(attribute)
201
213
  return attribute.type, true unless attribute.type.respond_to?(:return_type)
202
214
 
@@ -204,7 +216,7 @@ module Tapioca
204
216
  [return_type_string, false]
205
217
  end
206
218
 
207
- sig { params(attribute: ::Shale::Attribute).returns([Object, T::Boolean]) }
219
+ #: (::Shale::Attribute attribute) -> [Object, bool]
208
220
  def shale_type_to_sorbet_setter_type(attribute)
209
221
  setter_type = SHALE_SETTER_TYPES_MAP[attribute.type]
210
222
  return complex_shale_type_to_sorbet_setter_type(attribute) unless setter_type
@@ -213,7 +225,7 @@ module Tapioca
213
225
  [setter_type, true]
214
226
  end
215
227
 
216
- sig { params(attribute: ::Shale::Attribute).returns([Object, T::Boolean]) }
228
+ #: (::Shale::Attribute attribute) -> [Object, bool]
217
229
  def complex_shale_type_to_sorbet_setter_type(attribute)
218
230
  if attribute.type.respond_to?(:setter_type)
219
231
  setter_type_string = attribute.type.setter_type
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shale-builder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.4
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mateusz Drewniak