Class: Ruckt::StructGenerator
- Inherits:
-
Object
- Object
- Ruckt::StructGenerator
- Defined in:
- lib/ruckt/struct_generator.rb
Overview
The StructGenerator class is responsible for dynamically creating struct-like classes from hash data. It provides type safety and validation while maintaining a simple interface.
Defined Under Namespace
Modules: TypeValidation
Class Method Summary collapse
-
.generate(data) ⇒ Class
Generates a new struct-like class from a hash of data.
Class Method Details
.generate(data) ⇒ Class
Generates a new struct-like class from a hash of data
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/ruckt/struct_generator.rb', line 11 def generate(data) # If input isn't a hash, return it as-is (handles non-hash edge cases) return data unless data.is_a?(Hash) # Create a new anonymous class that will become our struct Class.new do # Include type validation functionality include TypeValidation # Store type information for all attributes # This will be used for validation when setting values @type_info = {} class << self # Make type information accessible to instances attr_reader :type_info # Determines the type of a value for validation purposes # @param value [Object] The value to infer the type from # @return [Class, Array<Class>] The inferred type(s) def infer_type(value) case value when Hash # Empty hashes stay as Hash, non-empty become nested structs value.empty? ? Hash : StructGenerator.generate(value) when TrueClass, FalseClass # Booleans can be either true or false, so we accept both types [TrueClass, FalseClass] else # For all other values, use their class as the type value.class end end end # Iterate through the data hash to define our struct's attributes data.each do |key, value| # Store the inferred type for this attribute @type_info[key] = infer_type(value) # Define a getter method that returns the instance variable define_method(key) do instance_variable_get("@#{key}") end # Define a setter method with type validation define_method("#{key}=") do |val| # Special handling for nested hash updates if val.is_a?(Hash) && self.class.type_info[key].is_a?(Class) && self.class.type_info[key].respond_to?(:type_info) # Create a temporary instance to validate the nested data # This ensures all nested values have correct types temp_instance = self.class.type_info[key].new val.each do |k, v| temp_instance.send("#{k}=", v) end instance_variable_set("@#{key}", temp_instance) else # For non-nested values, validate and set directly validate_type(key, val) instance_variable_set("@#{key}", val) end end # For boolean attributes, add a convenience query method # that ends with '?' (e.g., active?) if value.is_a?(TrueClass) || value.is_a?(FalseClass) define_method("#{key}?") { instance_variable_get("@#{key}") } end # For non-empty hashes, create a nested struct class if value.is_a?(Hash) && !value.empty? nested_class = StructGenerator.generate(value) const_set(key.to_s.capitalize, nested_class) end end # Define the initialize method that sets up a new instance define_method(:initialize) do |values = {}| values.each do |key, value| # Special handling for nested structs if self.class.type_info[key].is_a?(Class) && value.is_a?(Hash) && self.class.type_info[key].respond_to?(:type_info) instance_variable_set("@#{key}", self.class.type_info[key].new(value)) else # For non-nested values, use the setter for validation send("#{key}=", value) end end end end end |