structure 3.6.1 → 3.6.2

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: 0c4c9274f8938bdb75aee48a480ea324b9167ea1bb6d27aff8f831908e4083bb
4
- data.tar.gz: 9daeadaf79271deb8ea3469195081a6e7e3a318572bd4e1aa7997b2436223589
3
+ metadata.gz: 49afeef61e56c6e3f65ad2eff0855af54552ee4dbcab2282a8d0194e30156362
4
+ data.tar.gz: 1b7889d683dd5a129d35446773729212ea0f1348958688e4520f98fab8d90e8f
5
5
  SHA512:
6
- metadata.gz: 29ebca3cdff8e8750284b8f1c88b9fd5201ed93b387b2aa8c81872669fce7e52d8496b14d3546fa35b878df87a10e2539aa96789d9a9f0a9589a2c0f5bcf0867
7
- data.tar.gz: b176b7dad3f24d4bf54cf80d69dd115ccba5d75396e9d69ee319b09fee77460cf4f69348ade08a45bc6e07f836b8dc06b644c96548bf4e4efcb0ac8b29255fe5
6
+ metadata.gz: 1c6ab2be186a6a1eec57cc1bdad3627cccbcd91c20ff803a69d789cd694568c128853431ccb14b7694980700ef95675555919e5e944bf5df54c13c11497c2a19
7
+ data.tar.gz: 6d942ea5cc6bb8d012c8c7245480f7bb66857fb30ec20d8afd948183c7c29220baa9129abca1f17037c4db62c159e6860fe0a82c6c837639741c5c9ccec43c7a
@@ -69,7 +69,7 @@ module Structure
69
69
  if type == :boolean
70
70
  ["#{name}?".to_sym, name] unless name.to_s.end_with?("?")
71
71
  end
72
- end.compact.to_h
72
+ end.to_h
73
73
  end
74
74
  end
75
75
  end
@@ -75,7 +75,7 @@ module Structure
75
75
  private
76
76
 
77
77
  def boolean
78
- @boolean ||= ->(val) { BOOLEAN_TRUTHY.include?(val) }
78
+ ->(val) { BOOLEAN_TRUTHY.include?(val) }
79
79
  end
80
80
 
81
81
  def self_referential
@@ -92,8 +92,15 @@ module Structure
92
92
 
93
93
  def string_class(class_name, context_class)
94
94
  resolved_class = nil
95
+ mutex = Mutex.new
96
+
95
97
  proc do |value|
96
- resolved_class ||= Structure::Types.resolve_class(class_name, context_class)
98
+ unless resolved_class
99
+ mutex.synchronize do
100
+ resolved_class ||= Structure::Types.resolve_class(class_name, context_class)
101
+ end
102
+ end
103
+
97
104
  if resolved_class.respond_to?(:parse)
98
105
  resolved_class.parse(value) # steep:ignore
99
106
  else
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Structure
4
- VERSION = "3.6.1"
4
+ VERSION = "3.6.2"
5
5
  end
data/lib/structure.rb CHANGED
@@ -26,21 +26,24 @@ module Structure
26
26
  # @type var klass: untyped
27
27
  klass = Data.define(*builder.attributes)
28
28
 
29
- # capture metadata and attach to class
29
+ # capture all metadata and attach to class - no closure capture needed
30
+ mappings = builder.mappings
31
+ coercions = builder.coercions(klass)
32
+ predicates = builder.predicate_methods
33
+ after = builder.after_parse_callback
34
+
30
35
  meta = {
31
36
  attributes: builder.attributes.freeze,
32
37
  types: builder.types.freeze,
33
38
  defaults: builder.defaults.freeze,
39
+ mappings: mappings.freeze,
40
+ coercions: coercions.freeze,
41
+ predicates: predicates.freeze,
42
+ after: after,
34
43
  }.freeze
35
44
  klass.instance_variable_set(:@__structure_meta__, meta)
36
45
  klass.singleton_class.attr_reader(:__structure_meta__)
37
46
 
38
- # capture locals for method generation
39
- mappings = builder.mappings
40
- coercions = builder.coercions(klass)
41
- predicates = builder.predicate_methods
42
- after = builder.after_parse_callback
43
-
44
47
  # Define predicate methods
45
48
  predicates.each do |pred, attr|
46
49
  klass.define_method(pred) { !!public_send(attr) }
@@ -62,55 +65,59 @@ module Structure
62
65
  h
63
66
  end
64
67
 
65
- # parse accepts JSON-ish hashes + kwargs override
66
- klass.define_singleton_method(:parse) do |data = {}, **kwargs|
67
- return data if data.is_a?(self)
68
+ # parse accepts JSON-ish hashes + kwargs override - using string eval to avoid closure capture
69
+ klass.singleton_class.class_eval(<<~RUBY)
70
+ def parse(data = {}, **kwargs)
71
+ return data if data.is_a?(self)
68
72
 
69
- unless data.respond_to?(:merge!)
70
- raise TypeError, "can't convert #{data.class} into #{self}"
71
- end
73
+ unless data.respond_to?(:merge!)
74
+ raise TypeError, "can't convert \#{data.class} into \#{self}"
75
+ end
72
76
 
73
- # @type var kwargs: Hash[Symbol, untyped]
74
- string_kwargs = kwargs.transform_keys(&:to_s)
75
- data.merge!(string_kwargs)
76
- # @type self: singleton(Data) & _StructuredDataClass
77
- # @type var final: Hash[Symbol, untyped]
78
- final = {}
79
-
80
- # TODO: `__structure_meta__` exists but seems not to return the types it defines, so going untyped for now
81
- #
82
- # @type var meta: untyped
83
- meta = __structure_meta__
84
-
85
- attributes = meta.fetch(:attributes)
86
- defaults = meta.fetch(:defaults)
87
-
88
- attributes.each do |attr|
89
- source = mappings[attr] || attr.to_s
90
- value =
91
- if data.key?(source) then data[source]
92
- elsif data.key?(source.to_sym) then data[source.to_sym]
93
- elsif defaults.key?(attr) then defaults[attr]
94
- end
77
+ # @type var kwargs: Hash[Symbol, untyped]
78
+ string_kwargs = kwargs.transform_keys(&:to_s)
79
+ data.merge!(string_kwargs)
80
+ # @type self: singleton(Data) & _StructuredDataClass
81
+ # @type var final: Hash[Symbol, untyped]
82
+ final = {}
83
+
84
+ # @type var meta: untyped
85
+ meta = __structure_meta__
86
+
87
+ attributes = meta.fetch(:attributes)
88
+ defaults = meta.fetch(:defaults)
89
+ mappings = meta.fetch(:mappings)
90
+ coercions = meta.fetch(:coercions)
91
+ after = meta.fetch(:after)
95
92
 
96
- coercion = coercions[attr]
97
- if coercion && !value.nil?
98
- # self-referential types need class context to call parse
93
+ attributes.each do |attr|
94
+ source = mappings[attr] || attr.to_s
99
95
  value =
100
- if coercion.is_a?(Proc) && !coercion.lambda?
101
- instance_exec(value, &coercion) # steep:ignore
102
- else
103
- coercion.call(value)
96
+ if data.key?(source) then data[source]
97
+ elsif data.key?(source.to_sym) then data[source.to_sym]
98
+ elsif defaults.key?(attr) then defaults[attr]
104
99
  end
100
+
101
+ coercion = coercions[attr]
102
+ if coercion && !value.nil?
103
+ # Procs (not lambdas) need class context for self-referential parsing
104
+ # Lambdas and other callables use direct invocation
105
+ value =
106
+ if coercion.is_a?(Proc) && !coercion.lambda?
107
+ instance_exec(value, &coercion) # steep:ignore
108
+ else
109
+ coercion.call(value)
110
+ end
111
+ end
112
+
113
+ final[attr] = value
105
114
  end
106
115
 
107
- final[attr] = value
116
+ obj = new(**final)
117
+ after&.call(obj) if after
118
+ obj
108
119
  end
109
-
110
- obj = new(**final)
111
- after&.call(obj)
112
- obj
113
- end
120
+ RUBY
114
121
 
115
122
  klass
116
123
  end
data/sig/structure.rbs CHANGED
@@ -3,9 +3,12 @@ module Structure
3
3
  def __structure_meta__: () -> {
4
4
  attributes: Array[Symbol],
5
5
  types: Hash[Symbol, untyped],
6
- defaults: Hash[Symbol, untyped]
6
+ defaults: Hash[Symbol, untyped],
7
+ mappings: Hash[Symbol, String],
8
+ coercions: Hash[Symbol, untyped],
9
+ predicates: Hash[Symbol, Symbol],
10
+ after: untyped
7
11
  }
8
- def parse: (?Hash[String | Symbol, untyped] data, **untyped kwargs) -> instance
9
12
  end
10
13
 
11
14
  def self.new: () ?{ (Structure::Builder) [self: Structure::Builder] -> void } -> untyped
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: structure
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.6.1
4
+ version: 3.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hakan Ensari