shale-builder 0.4.1 → 0.5.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: 3a79ea1df97f6d5d49af0d596144d931b5aacea98e142866d7e322497fbeaae5
4
- data.tar.gz: 59f671f470be89ed4854798843657160c87163de286a867be73c4832bb4a2df2
3
+ metadata.gz: 5129b4e45147ec672d0e23aaf17ad57290bdc9f1f4a81b9d1756ebd44a738df3
4
+ data.tar.gz: 0e47f511519fe4003622c862799ccd34e51bf61bba7bbce3ca6d8e81bdac95d8
5
5
  SHA512:
6
- metadata.gz: f763a5973d2d15c2a2c314ae61ed61f7455724f624f38852f8cd4d174fe2bf12af7bee2eb37a847b5940f9779fa4ad2960eff6a99259c12d0445d16fb63b8775
7
- data.tar.gz: e872ffe315cec734fa77af67b1be80efded4b750194e66c2e41d069302b5dc18cc4bd0f3da2932528661cc2fa1d84ff90a08f3326c7052a65030987f5c4685eb
6
+ metadata.gz: 4682a5bf3469eec462ad9a191434bb761c633cf21117e2443dba85cefc4a2e2641104c4966cd113d76200b95954206b3292fc1182836b550a085cf4d144625c6
7
+ data.tar.gz: 349e8086105a41ee0b5decb2ebbedcb810f7a331ae8b0e2b6a1bdab051eeafb9939e5d36f7d4b841062d7eab3269f85fb0b07d77eb63d3abfdae24ea9b774b7d
data/CHANGELOG.md CHANGED
@@ -5,6 +5,22 @@ 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.5.1] - 2025-10-15
9
+
10
+ [Diff](https://github.com/Verseth/ruby-shale-builder/compare/v0.5.0...v0.5.1)
11
+
12
+ ### Changes
13
+ - Add `return_type` and `setter_type` overrides per attribute
14
+
15
+ ## [0.5.0] - 2025-10-15
16
+
17
+ [Diff](https://github.com/Verseth/ruby-shale-builder/compare/v0.4.1...v0.5.0)
18
+
19
+ ### Changes
20
+ - Add `Shale::Builder::AssignedAttributes` module which grants a shale mapper class with the ability to record which attributes have been assigned
21
+ - Add `Shale::Builder::Value` which represents a value of a shale attribute.
22
+ - Add `Shale::Builder#attribute_values`, a method that returns an array of `Shale::Builder::Value` objects for each attribute.
23
+
8
24
  ## [0.4.1] - 2025-10-14
9
25
 
10
26
  [Diff](https://github.com/Verseth/ruby-shale-builder/compare/v0.4.0...v0.4.1)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shale-builder (0.4.1)
4
+ shale-builder (0.5.1)
5
5
  booleans (>= 0.1)
6
6
  shale (< 2.0)
7
7
  sorbet-runtime (> 0.5)
data/README.md CHANGED
@@ -263,6 +263,41 @@ obj.errors.messages #=> {cvv_code: ["can't be blank"], "amount.value": ["can't b
263
263
 
264
264
  You MUST include `ActiveModel::Validations` before `Shale::Builder::NestedValidations`.
265
265
 
266
+ ### Recording Assigned Attributes
267
+
268
+ There is an additional module `Shale::Builder::AssignedAttributes` that provides
269
+ support for recording which attributes have been assigned in a shale mapper object.
270
+
271
+ In order to load it do:
272
+
273
+ ```rb
274
+ require 'shale/builder/assigned_attributes'
275
+ ```
276
+
277
+ Then you can use it like so
278
+
279
+ ```rb
280
+ class AmountType < ::Shale::Mapper
281
+ include ::Shale::Builder
282
+ include ::Shale::Builder::AssignedAttributes
283
+
284
+ attribute :value, ::Shale::Type::Float
285
+ attribute :currency, ::Shale::Type::String
286
+ end
287
+
288
+ obj = AmountType.new
289
+ obj.assigned_attribute_names #=> #<Set: {}>
290
+
291
+ obj.value = 3
292
+ obj.assigned_attribute_names #=> #<Set: {:value}>
293
+ obj.assigned_attributes #=> [#<Shale::Attribute:0x000000011e959b50 @collection=false, @default=nil, @doc=nil, @name=:value, @setter="value=", @type=Shale::Type::Float>]
294
+ obj.assigned_values #=> [#<Shale::Builder::Value:0x000000011d693318 @attribute=#<Shale::Attribute:0x000000011e959b50 @collection=false, @default=nil, @doc=nil, @name=:value, @setter="value=", @type=Shale::Type::Float>, @value=3.0>]
295
+ ```
296
+
297
+ - `assigned_attribute_names` returns a set of attribute names
298
+ - `assigned_attributes` returns an array of attribute definitions
299
+ - `assigned_values` returns an array of `Shale::Builder::Value`, which contains the current value of an attribute and a reference to its definition
300
+
266
301
  ### Attribute aliases
267
302
 
268
303
  You can easily create aliases for attributes using `alias_attribute`
@@ -7,17 +7,23 @@ module Shale
7
7
  class Attribute # rubocop:disable Style/Documentation
8
8
  extend T::Sig
9
9
 
10
+ #: untyped
11
+ attr_accessor :setter_type
12
+
13
+ #: untyped
14
+ attr_accessor :return_type
15
+
10
16
  # Contains the documentation comment for the shale attribute
11
17
  # in a Ruby String.
12
- sig { returns(T.nilable(String)) }
18
+ #: String?
13
19
  attr_accessor :doc
14
20
 
15
21
  # Contains the documentation comment for the shale attribute
16
22
  # in a Ruby String.
17
- sig { returns(T.nilable(T::Array[Symbol])) }
23
+ #: Array[Symbol]?
18
24
  attr_accessor :aliases
19
25
 
20
- sig { returns(T::Array[Symbol]) }
26
+ #: -> Array[Symbol]
21
27
  def all_names
22
28
  names = [name]
23
29
  aliases = self.aliases
@@ -25,5 +31,12 @@ module Shale
25
31
 
26
32
  names + aliases
27
33
  end
34
+
35
+ # Returns `true` if the attribute is handled by a shale mapper.
36
+ #
37
+ #: -> bool
38
+ def mapper?
39
+ type.is_a?(Class) && type < Shale::Mapper
40
+ end
28
41
  end
29
42
  end
@@ -0,0 +1,94 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require 'shale'
5
+ require 'booleans'
6
+
7
+ module Shale
8
+ module Builder
9
+ # Include in a class that already includes `Shale::Builder` to add support
10
+ # for getting a list of attributes that have been assigned.
11
+ #
12
+ # @requires_ancestor: Object
13
+ module AssignedAttributes
14
+ extend T::Sig
15
+ extend T::Helpers
16
+
17
+ class << self
18
+ extend T::Sig
19
+
20
+ # Gets called after including this module in a module or class.
21
+ #: (Module mod) -> void
22
+ def included(mod)
23
+ mod.extend ClassMethods
24
+ AssignedAttributes.prepare_mod(mod)
25
+ end
26
+
27
+ # Prepares the received module or class
28
+ # for dynamic method definition.
29
+ #: (Module mod) -> void
30
+ def prepare_mod(mod)
31
+ assigned_attributes_methods_module = ::Module.new
32
+ mod.instance_variable_set :@assigned_attributes_methods_module, assigned_attributes_methods_module
33
+ mod.include assigned_attributes_methods_module
34
+ end
35
+ end
36
+
37
+ # @requires_ancestor: singleton(Shale::Mapper)
38
+ module ClassMethods
39
+ extend T::Sig
40
+
41
+ # Contains overridden getter methods for attributes
42
+ # with complex types (so that they accept a block for building)
43
+ #: Module
44
+ attr_reader :assigned_attributes_methods_module
45
+
46
+ #: (String | Symbol name, Class type, ?collection: bool, ?default: Proc?, ?doc: String?, **untyped kwargs) ?{ -> void } -> void
47
+ def attribute(name, type, collection: false, default: nil, doc: nil, **kwargs, &block)
48
+ super
49
+
50
+ @assigned_attributes_methods_module.class_eval <<~RUBY, __FILE__, __LINE__ + 1
51
+ def #{name}=(val)
52
+ super
53
+ return unless @__initialized
54
+
55
+ self.assigned_attribute_names << #{name.to_sym.inspect}
56
+ end
57
+ RUBY
58
+ end
59
+ end
60
+ mixes_in_class_methods ClassMethods
61
+
62
+ # Returns a set of names of assigned shale attributes.
63
+ #
64
+ #: -> Set[Symbol]
65
+ def assigned_attribute_names
66
+ @assigned_attribute_names ||= Set.new
67
+ end
68
+
69
+ # Returns an array of shale attributes
70
+ # that have been assigned.
71
+ #
72
+ #: -> Array[Shale::Attribute]
73
+ def assigned_attributes
74
+ klass = self.class #: as untyped
75
+ assigned_attribute_names.map do |name|
76
+ klass.attributes.fetch(name)
77
+ end
78
+ end
79
+
80
+ # Returns an array of shale values
81
+ # that have been assigned.
82
+ #
83
+ #: -> Array[Shale::Builder::Value]
84
+ def assigned_values
85
+ klass = self.class #: as untyped
86
+ assigned_attribute_names.map do |name|
87
+ attr = klass.attributes.fetch(name)
88
+ Shale::Builder::Value.new(attr, public_send(attr.name))
89
+ end
90
+ end
91
+
92
+ end
93
+ end
94
+ end
@@ -6,7 +6,7 @@ require 'booleans'
6
6
 
7
7
  module Shale
8
8
  module Builder
9
- # Include in a class tha already includes `Shale::Builder` to add support
9
+ # Include in a class that already includes `Shale::Builder` to add support
10
10
  # for nested ActiveModel validations.
11
11
  #
12
12
  # @requires_ancestor: Object
@@ -0,0 +1,30 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require 'shale'
5
+
6
+ module Shale
7
+ module Builder
8
+ # Represents a value of a particular shale attribute.
9
+ # Hold the value and a reference to the attribute definition.
10
+ class Value
11
+ extend T::Sig
12
+
13
+ # Shale attribute definition
14
+ #
15
+ #: Shale::Attribute
16
+ attr_reader :attribute
17
+
18
+ # Value of the attribute.
19
+ #
20
+ #: untyped
21
+ attr_reader :value
22
+
23
+ #: (Shale::Attribute, untyped) -> void
24
+ def initialize(attribute, value)
25
+ @attribute = attribute
26
+ @value = value
27
+ end
28
+ end
29
+ end
30
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Shale
4
4
  module Builder
5
- VERSION = '0.4.1'
5
+ VERSION = '0.5.1'
6
6
  end
7
7
  end
data/lib/shale/builder.rb CHANGED
@@ -39,6 +39,7 @@ module Shale
39
39
  # end
40
40
  # end
41
41
  #
42
+ # @requires_ancestor: Object
42
43
  module Builder
43
44
  extend T::Sig
44
45
  extend T::Helpers
@@ -47,7 +48,7 @@ module Shale
47
48
  extend T::Sig
48
49
 
49
50
  # Gets called after including this module in a module or class.
50
- sig { params(mod: Module).void }
51
+ #: (Module mod) -> void
51
52
  def included(mod)
52
53
  mod.extend ClassMethods
53
54
  Builder.prepare_mod(mod)
@@ -55,7 +56,7 @@ module Shale
55
56
 
56
57
  # Prepares the received module or class
57
58
  # for dynamic method definition.
58
- sig { params(mod: Module).void }
59
+ #: (Module mod) -> void
59
60
  def prepare_mod(mod)
60
61
  builder_methods_module = ::Module.new
61
62
  mod.instance_variable_set :@builder_methods_module, builder_methods_module
@@ -71,7 +72,7 @@ module Shale
71
72
  abstract!
72
73
  has_attached_class!
73
74
 
74
- sig { params(subclass: Class).void }
75
+ #: (Class subclass) -> void
75
76
  def inherited(subclass)
76
77
  super
77
78
  Builder.prepare_mod(subclass)
@@ -79,10 +80,10 @@ module Shale
79
80
 
80
81
  # Contains overridden getter methods for attributes
81
82
  # with complex types (so that they accept a block for building)
82
- sig { returns(Module) }
83
+ #: Module
83
84
  attr_reader :builder_methods_module
84
85
 
85
- sig { params(_block: T.proc.params(arg0: T.attached_class).void).returns(T.attached_class) }
86
+ #: { (instance arg0) -> void } -> instance
86
87
  def build(&_block)
87
88
  body = new
88
89
  yield(body)
@@ -96,23 +97,34 @@ module Shale
96
97
  sig { abstract.returns(T::Hash[Symbol, Shale::Attribute]) }
97
98
  def attributes; end
98
99
 
99
- sig do
100
- params(
101
- name: T.any(String, Symbol),
102
- type: Class,
103
- collection: T::Boolean,
104
- default: T.nilable(Proc),
105
- doc: T.nilable(String),
106
- kwargs: Object,
107
- block: T.nilable(T.proc.void),
108
- ).void
109
- end
110
- def attribute(name, type, collection: false, default: nil, doc: nil, **kwargs, &block)
111
- super(name, type, collection:, default:, **kwargs, &block)
100
+ #: (
101
+ #| String | Symbol name,
102
+ #| Class shale_mapper,
103
+ #| ?collection: bool,
104
+ #| ?default: Proc?,
105
+ #| ?doc: String?,
106
+ #| ?return_type: untyped,
107
+ #| ?setter_type: untyped,
108
+ #| **Object kwargs
109
+ #| ) ?{ -> void } -> void
110
+ def attribute(
111
+ name,
112
+ shale_mapper,
113
+ collection: false,
114
+ default: nil,
115
+ doc: nil,
116
+ return_type: nil,
117
+ setter_type: nil,
118
+ **kwargs,
119
+ &block
120
+ )
121
+ super(name, shale_mapper, collection:, default:, **kwargs, &block)
112
122
  if (attr_def = attributes[name.to_sym])
113
123
  attr_def.doc = doc
124
+ attr_def.return_type = return_type
125
+ attr_def.setter_type = setter_type
114
126
  end
115
- return unless type < ::Shale::Mapper
127
+ return unless shale_mapper < ::Shale::Mapper
116
128
 
117
129
  if collection
118
130
  @builder_methods_module.class_eval <<~RUBY, __FILE__, __LINE__ + 1
@@ -120,7 +132,7 @@ module Shale
120
132
  return super unless block_given? # return super unless block_given?
121
133
  #
122
134
  arr = self.#{name} ||= [] # arr = self.clients ||= []
123
- object = #{type}.new # object = Client.new
135
+ object = #{shale_mapper}.new # object = Client.new
124
136
  yield(object) # yield(object)
125
137
  arr << object # arr << object
126
138
  object # object
@@ -133,7 +145,7 @@ module Shale
133
145
  def #{name} # def amount
134
146
  return super unless block_given? # return super unless block_given?
135
147
  #
136
- object = #{type}.new # object = Amount.new
148
+ object = #{shale_mapper}.new # object = Amount.new
137
149
  yield(object) # yield(object)
138
150
  self.#{name} = object # self.amount = object
139
151
  end # end
@@ -141,7 +153,7 @@ module Shale
141
153
  end
142
154
 
143
155
  # Create an alias for the getter and setter of an attribute.
144
- sig { params(new_name: Symbol, old_name: Symbol).void }
156
+ #: (Symbol new_name, Symbol old_name) -> void
145
157
  def alias_attribute(new_name, old_name)
146
158
  attr = attributes.fetch(old_name)
147
159
  (attr.aliases ||= []) << new_name
@@ -160,5 +172,26 @@ module Shale
160
172
  end
161
173
  mixes_in_class_methods(ClassMethods)
162
174
 
175
+ def initialize(*args, **kwargs, &block)
176
+ super
177
+ @__initialized = true
178
+ end
179
+
180
+ #: bool?
181
+ attr_reader :__initialized
182
+
183
+ # Returns an array of shale values
184
+ # that have been assigned.
185
+ #
186
+ #: -> Array[Shale::Builder::Value]
187
+ def attribute_values
188
+ klass = self.class #: as untyped
189
+ klass.attributes.map do |name, attr|
190
+ Shale::Builder::Value.new(attr, public_send(name))
191
+ end
192
+ end
193
+
163
194
  end
164
195
  end
196
+
197
+ require_relative 'builder/value'
@@ -37,9 +37,13 @@ module Tapioca
37
37
  # For each attribute defined in the class
38
38
  attribute_names = constant.attributes.keys.sort
39
39
  attribute_names.each do |attribute_name|
40
- attribute = T.let(constant.attributes[attribute_name], ::Shale::Attribute)
41
- return_type, nilable = shale_type_to_sorbet_return_type(attribute)
42
- comments = T.let([], T::Array[RBI::Comment])
40
+ attribute = constant.attributes[attribute_name] #: ::Shale::Attribute
41
+ if (type = attribute.return_type)
42
+ return_type = type
43
+ else
44
+ return_type, nilable = shale_type_to_sorbet_return_type(attribute)
45
+ end
46
+ comments = [] #: T::Array[RBI::Comment]
43
47
  if shale_builder_defined? && attribute.doc
44
48
  comments << RBI::Comment.new(T.must(attribute.doc))
45
49
  end
@@ -52,7 +56,11 @@ module Tapioca
52
56
  getter_without_block_type = return_type.to_s
53
57
  end
54
58
 
55
- setter_type, nilable = shale_type_to_sorbet_setter_type(attribute)
59
+ if (type = attribute.return_type || attribute.setter_type)
60
+ setter_type = type
61
+ else
62
+ setter_type, nilable = shale_type_to_sorbet_setter_type(attribute)
63
+ end
56
64
  if attribute.collection?
57
65
  setter_type_str = "T.nilable(T::Array[#{setter_type}])"
58
66
  elsif nilable
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.4.1
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mateusz Drewniak
@@ -69,7 +69,9 @@ files:
69
69
  - Rakefile
70
70
  - lib/shale/attribute.rb
71
71
  - lib/shale/builder.rb
72
+ - lib/shale/builder/assigned_attributes.rb
72
73
  - lib/shale/builder/nested_validations.rb
74
+ - lib/shale/builder/value.rb
73
75
  - lib/shale/builder/version.rb
74
76
  - lib/shale/mapper.rbi
75
77
  - lib/tapioca/dsl/compilers/shale.rb