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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7f0f08ca763d757b47a9ff12c51168361a37acdfec5db9d7c45a82c15f9059fb
4
- data.tar.gz: 7ea79c6470d4fabe005474f572bf3f9564217dca45f18b118babf94c6e8ef36f
3
+ metadata.gz: 91e04b84b8e6a19728bf6212bdfd08895187bbe59ebf56b47288697962ed31b2
4
+ data.tar.gz: '028e8df792d344faa2850f82504d223a6691c195203c51acd53581ee9e97064c'
5
5
  SHA512:
6
- metadata.gz: c37505be3556ec54d0277008506868f4fade809f8baa800336f54b32ceca265c61ee232ceca7326b2104793d6af8a6022fb193c61fbb28aaeb9d0493223b6677
7
- data.tar.gz: 725e923fddcb8cca372340d12c9cf67c38d20782dac2a1fc2b1273e06e12d6cc712668c6a634d32af4661d8b70c8369e25fda5109e6e80e97ad65ddc0babf8d7
6
+ metadata.gz: 4e6fbbd3104fc0ed9001d66395606ab436a907cf3b1ab09f692c458b99e999e648e7b558dd0b2e8e4fb298d2c1c25decf51bef0ad5889bc9ff14f6d821c62ede
7
+ data.tar.gz: 74752c31b2898fea6c7d5c94e568f7fa9ca2724e116aa62e9d9c30f0da77d3f6f7de7b73540a8837834536bc2aa9b87f9d5b596ddd0bfd2089222e3fbec07334
@@ -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
- # Deduced from mappings - maintains order of definition
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
@@ -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
- structure(type)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Structure
4
- VERSION = "3.0.0"
4
+ VERSION = "3.1.1"
5
5
  end
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
- new(**final_kwargs)
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.0.0
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: Parse data into immutable Ruby Data objects with type coercion
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: []