structure 3.0.0 → 3.1.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 +4 -4
- data/lib/structure/builder.rb +34 -4
- data/lib/structure/types.rb +32 -19
- data/lib/structure/version.rb +1 -1
- data/lib/structure.rb +19 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 91e04b84b8e6a19728bf6212bdfd08895187bbe59ebf56b47288697962ed31b2
|
4
|
+
data.tar.gz: '028e8df792d344faa2850f82504d223a6691c195203c51acd53581ee9e97064c'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e6fbbd3104fc0ed9001d66395606ab436a907cf3b1ab09f692c458b99e999e648e7b558dd0b2e8e4fb298d2c1c25decf51bef0ad5889bc9ff14f6d821c62ede
|
7
|
+
data.tar.gz: 74752c31b2898fea6c7d5c94e568f7fa9ca2724e116aa62e9d9c30f0da77d3f6f7de7b73540a8837834536bc2aa9b87f9d5b596ddd0bfd2089222e3fbec07334
|
data/lib/structure/builder.rb
CHANGED
@@ -5,7 +5,7 @@ require "structure/types"
|
|
5
5
|
module Structure
|
6
6
|
# Builder class for accumulating attribute definitions
|
7
7
|
class Builder
|
8
|
-
attr_reader :mappings, :types, :defaults
|
8
|
+
attr_reader :mappings, :types, :defaults, :after_parse_callback
|
9
9
|
|
10
10
|
def initialize
|
11
11
|
@mappings = {}
|
@@ -13,6 +13,25 @@ module Structure
|
|
13
13
|
@defaults = {}
|
14
14
|
end
|
15
15
|
|
16
|
+
# DSL method for defining attributes with optional type coercion
|
17
|
+
#
|
18
|
+
# @param name [Symbol] The attribute name
|
19
|
+
# @param type [Class, Symbol, Array, nil] Type for coercion (e.g., String, :boolean, [String])
|
20
|
+
# @param from [String, nil] Source key in the data hash (defaults to name.to_s)
|
21
|
+
# @param default [Object, nil] Default value if attribute is missing
|
22
|
+
# @yield [value] Block for custom transformation
|
23
|
+
# @raise [ArgumentError] If both type and block are provided
|
24
|
+
#
|
25
|
+
# @example With type coercion
|
26
|
+
# attribute :age, Integer
|
27
|
+
#
|
28
|
+
# @example With custom source key
|
29
|
+
# attribute :created_at, Time, from: "CreatedAt"
|
30
|
+
#
|
31
|
+
# @example With transformation block
|
32
|
+
# attribute :price do |value|
|
33
|
+
# Money.new(value["amount"], value["currency"])
|
34
|
+
# end
|
16
35
|
def attribute(name, type = nil, from: nil, default: nil, &block)
|
17
36
|
# Always store in mappings - use attribute name as default source
|
18
37
|
@mappings[name] = from || name.to_s
|
@@ -27,15 +46,26 @@ module Structure
|
|
27
46
|
end
|
28
47
|
end
|
29
48
|
|
30
|
-
#
|
49
|
+
# Defines a callback to run after parsing
|
50
|
+
#
|
51
|
+
# @yield [instance] Block that receives the parsed instance
|
52
|
+
# @return [void]
|
53
|
+
#
|
54
|
+
# @example Validation
|
55
|
+
# after_parse do |order|
|
56
|
+
# raise "Invalid order" if order.total < 0
|
57
|
+
# end
|
58
|
+
def after_parse(&block)
|
59
|
+
@after_parse_callback = block
|
60
|
+
end
|
61
|
+
|
31
62
|
def attributes
|
32
63
|
@mappings.keys
|
33
64
|
end
|
34
65
|
|
35
|
-
# Deduced from types that are boolean
|
36
66
|
def predicate_methods
|
37
67
|
@types.filter_map do |name, type_lambda|
|
38
|
-
if type_lambda == Types.boolean
|
68
|
+
if type_lambda == Types.boolean && !name.to_s.end_with?("?")
|
39
69
|
predicate_name = "#{name}?"
|
40
70
|
[predicate_name.to_sym, name]
|
41
71
|
end
|
data/lib/structure/types.rb
CHANGED
@@ -10,37 +10,35 @@ module Structure
|
|
10
10
|
BOOLEAN_TRUTHY = [true, 1, "1", "t", "T", "true", "TRUE", "on", "ON"].freeze
|
11
11
|
private_constant :BOOLEAN_TRUTHY
|
12
12
|
|
13
|
-
# Boolean conversion
|
14
13
|
def boolean
|
15
14
|
@boolean ||= ->(val) { BOOLEAN_TRUTHY.include?(val) }
|
16
15
|
end
|
17
16
|
|
18
|
-
# Generic handler for classes with kernel methods (String, Integer, Float, etc.)
|
19
|
-
def kernel(type)
|
20
|
-
->(val) { Kernel.send(type.name, val) }
|
21
|
-
end
|
22
|
-
|
23
|
-
# Handler for nested Structure classes
|
24
|
-
def structure(type)
|
25
|
-
->(val) { type.parse(val) }
|
26
|
-
end
|
27
|
-
|
28
|
-
# Create coercer for array elements
|
29
|
-
def array(element_type)
|
30
|
-
element_coercer = coerce(element_type)
|
31
|
-
->(array) { array.map { |element| element_coercer.call(element) } }
|
32
|
-
end
|
33
|
-
|
34
17
|
# Main factory method for creating type coercers
|
18
|
+
#
|
19
|
+
# @param type [Class, Symbol, Array] Type specification
|
20
|
+
# @return [Proc, Object] Coercion proc or the type itself if no coercion available
|
21
|
+
#
|
22
|
+
# @example Boolean type
|
23
|
+
# coerce(:boolean) # => boolean proc
|
24
|
+
#
|
25
|
+
# @example Kernel types
|
26
|
+
# coerce(Integer) # => proc that calls Kernel.Integer
|
27
|
+
#
|
28
|
+
# @example Parseable types
|
29
|
+
# coerce(Date) # => proc that calls Date.parse
|
30
|
+
#
|
31
|
+
# @example Array types
|
32
|
+
# coerce([String]) # => proc that coerces array elements to String
|
35
33
|
def coerce(type)
|
36
34
|
case type
|
37
35
|
when :boolean
|
38
36
|
boolean
|
39
|
-
when Class
|
37
|
+
when Class, Module
|
40
38
|
if type.name && Kernel.respond_to?(type.name)
|
41
39
|
kernel(type)
|
42
40
|
elsif type.respond_to?(:parse)
|
43
|
-
|
41
|
+
parseable(type)
|
44
42
|
else
|
45
43
|
type
|
46
44
|
end
|
@@ -54,5 +52,20 @@ module Structure
|
|
54
52
|
type
|
55
53
|
end
|
56
54
|
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def kernel(type)
|
59
|
+
->(val) { Kernel.send(type.name, val) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def parseable(type)
|
63
|
+
->(val) { type.parse(val) }
|
64
|
+
end
|
65
|
+
|
66
|
+
def array(element_type)
|
67
|
+
element_coercer = coerce(element_type)
|
68
|
+
->(array) { array.map { |element| element_coercer.call(element) } }
|
69
|
+
end
|
57
70
|
end
|
58
71
|
end
|
data/lib/structure/version.rb
CHANGED
data/lib/structure.rb
CHANGED
@@ -5,6 +5,20 @@ require "structure/builder"
|
|
5
5
|
# A library for parsing data into immutable Ruby Data objects with type coercion
|
6
6
|
module Structure
|
7
7
|
class << self
|
8
|
+
# Creates a new Data class with attribute definitions and type coercion
|
9
|
+
#
|
10
|
+
# @yield [Builder] Block for defining attributes using the DSL
|
11
|
+
# @return [Class] A Data class with a parse method
|
12
|
+
#
|
13
|
+
# @example Basic usage
|
14
|
+
# Person = Structure.new do
|
15
|
+
# attribute :name, String
|
16
|
+
# attribute :age, Integer
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# person = Person.parse(name: "Alice", age: "30")
|
20
|
+
# person.name # => "Alice"
|
21
|
+
# person.age # => 30
|
8
22
|
def new(&block)
|
9
23
|
builder = Builder.new
|
10
24
|
builder.instance_eval(&block) if block
|
@@ -23,6 +37,7 @@ module Structure
|
|
23
37
|
types = builder.types
|
24
38
|
defaults = builder.defaults
|
25
39
|
attributes = builder.attributes
|
40
|
+
after_parse_callback = builder.after_parse_callback
|
26
41
|
|
27
42
|
data_class.define_singleton_method(:parse) do |data = {}, **kwargs|
|
28
43
|
# Merge kwargs into data - kwargs take priority as overrides
|
@@ -48,7 +63,10 @@ module Structure
|
|
48
63
|
|
49
64
|
final_kwargs[attr] = value
|
50
65
|
end
|
51
|
-
|
66
|
+
|
67
|
+
instance = new(**final_kwargs)
|
68
|
+
after_parse_callback&.call(instance)
|
69
|
+
instance
|
52
70
|
end
|
53
71
|
|
54
72
|
data_class
|
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.
|
4
|
+
version: 3.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hakan Ensari
|
@@ -9,7 +9,8 @@ bindir: bin
|
|
9
9
|
cert_chain: []
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies: []
|
12
|
-
description:
|
12
|
+
description: Provides a DSL for generating immutable Ruby Data objects with type coercion
|
13
|
+
and data transformation capabilities.
|
13
14
|
executables: []
|
14
15
|
extensions: []
|
15
16
|
extra_rdoc_files: []
|
@@ -38,5 +39,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
38
39
|
requirements: []
|
39
40
|
rubygems_version: 3.6.9
|
40
41
|
specification_version: 4
|
41
|
-
summary: Structure your data
|
42
|
+
summary: Structure your data
|
42
43
|
test_files: []
|