Class: Lutaml::KeyValue::Transformation::RuleCompiler
- Inherits:
-
Object
- Object
- Lutaml::KeyValue::Transformation::RuleCompiler
- Defined in:
- lib/lutaml/key_value/transformation/rule_compiler.rb
Overview
Compiles mapping DSL rules into pre-compiled transformation rules.
This is an independent class with explicit dependencies that can be tested in isolation from Transformation.
Instance Attribute Summary collapse
-
#format ⇒ Symbol
readonly
The serialization format (:json, :yaml, :toml).
-
#model_class ⇒ Class
readonly
The model class being compiled.
-
#register_id ⇒ Symbol?
readonly
The register ID for attribute lookup.
-
#transformation_factory ⇒ Proc
readonly
Factory lambda for creating child transformations.
Instance Method Summary collapse
-
#compile(mapping_dsl) ⇒ Array<CompiledRule>
Compile key-value mapping DSL into pre-compiled rules.
-
#compile_rule(mapping_rule, mapping_dsl) ⇒ CompiledRule?
Compile a single mapping rule.
-
#initialize(model_class:, register_id:, format:, transformation_factory:) ⇒ RuleCompiler
constructor
Initialize the RuleCompiler with explicit dependencies.
-
#valid_mapping?(rule, options) ⇒ Boolean
Check if a mapping rule should be applied based on only/except options.
Constructor Details
#initialize(model_class:, register_id:, format:, transformation_factory:) ⇒ RuleCompiler
Initialize the RuleCompiler with explicit dependencies.
39 40 41 42 43 44 45 |
# File 'lib/lutaml/key_value/transformation/rule_compiler.rb', line 39 def initialize(model_class:, register_id:, format:, transformation_factory:) @model_class = model_class @register_id = register_id @format = format @transformation_factory = transformation_factory end |
Instance Attribute Details
#format ⇒ Symbol (readonly)
Returns The serialization format (:json, :yaml, :toml).
28 29 30 |
# File 'lib/lutaml/key_value/transformation/rule_compiler.rb', line 28 def format @format end |
#model_class ⇒ Class (readonly)
Returns The model class being compiled.
22 23 24 |
# File 'lib/lutaml/key_value/transformation/rule_compiler.rb', line 22 def model_class @model_class end |
#register_id ⇒ Symbol? (readonly)
Returns The register ID for attribute lookup.
25 26 27 |
# File 'lib/lutaml/key_value/transformation/rule_compiler.rb', line 25 def register_id @register_id end |
#transformation_factory ⇒ Proc (readonly)
Returns Factory lambda for creating child transformations.
31 32 33 |
# File 'lib/lutaml/key_value/transformation/rule_compiler.rb', line 31 def transformation_factory @transformation_factory end |
Instance Method Details
#compile(mapping_dsl) ⇒ Array<CompiledRule>
Compile key-value mapping DSL into pre-compiled rules.
This is the main entry point for rule compilation.
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/lutaml/key_value/transformation/rule_compiler.rb', line 53 def compile(mapping_dsl) return [] unless mapping_dsl rules = [] # Compile all mappings (key-value formats don't distinguish elements/attributes) mappings_to_compile = if @register_id && @register_id != :default && mapping_dsl.mappings(@register_id).any? mapping_dsl.mappings(@register_id) else mapping_dsl.mappings end mappings_to_compile.each do |mapping_rule| rule = compile_rule(mapping_rule, mapping_dsl) rules << rule if rule end rules.compact end |
#compile_rule(mapping_rule, mapping_dsl) ⇒ CompiledRule?
Compile a single mapping rule.
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 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/lutaml/key_value/transformation/rule_compiler.rb', line 78 def compile_rule(mapping_rule, mapping_dsl) # Access custom_methods and delegate early to check how to compile this rule custom_methods = mapping_rule.custom_methods delegate = mapping_rule.delegate attr_name = mapping_rule.to # For rules with custom methods but no 'to' attribute (e.g., with: { to: ... }), # we need to find the attribute name from the mapping if attr_name.nil? && !custom_methods.empty? # Try to infer attribute name from 'name' or 'from' # For multiple_mappings, name is an array - check each element attr_name = if mapping_rule.name names = mapping_rule.name.is_a?(Array) ? mapping_rule.name : [mapping_rule.name] names.map(&:to_sym).find do |n| model_class.attributes(register_id)&.key?(n) end elsif mapping_rule.from.is_a?(String) && model_class.attributes(register_id)&.key?(mapping_rule.from.to_sym) mapping_rule.from.to_sym end end # For custom methods without an inferred attribute, use a placeholder # The custom method will handle all serialization logic if attr_name.nil? && !custom_methods.empty? # Use serialized name as placeholder for attribute name # The custom method handles everything, so we don't need a real attribute # For multiple_mappings, use the first name element first_name = if mapping_rule.name.is_a?(Array) mapping_rule.name.first else mapping_rule.name end attr_name = first_name&.to_sym || :__custom_method__ # Create a dummy attribute type for custom methods attr_type = nil child_transformation = nil collection_info = nil value_transformer = nil else return nil unless attr_name # For delegated attributes, get attribute from delegated object's class if delegate # Get the delegate attribute from model to find the delegated class delegate_attr = model_class.attributes(register_id)&.[](delegate) return nil unless delegate_attr # Get the delegated class type delegated_class = delegate_attr.type(register_id) return nil unless delegated_class # Get the actual attribute from the delegated class attr = delegated_class.attributes&.[](attr_name) else # Get attribute definition from model class attr = model_class.attributes(register_id)&.[](attr_name) end return nil unless attr # Get attribute type attr_type = attr.type(register_id) # Build child transformation for nested models child_transformation = if attr_type.is_a?(Class) && attr_type < Lutaml::Model::Serialize build_child_transformation(attr_type) end # Build collection info (include child_mappings for keyed collections) collection_info = if attr.collection? info = { range: attr.[:collection] } # Add child_mappings if present (for map_key and map_value features) # The keyed collection info might be stored in different places: # 1. As child_mappings on the rule (from map_to_instance) # 2. As @key_mappings on the mapping_dsl (separate __key_mapping entry) # 3. As @value_mappings on the mapping_dsl (from map_value) child_mappings_value = nil # First try to get child_mappings from the rule if mapping_rule.child_mappings child_mappings_value = mapping_rule.child_mappings elsif mapping_rule.hash_mappings child_mappings_value = mapping_rule.hash_mappings end # If not found on the rule, check the mapping_dsl for key_mapping or value_mapping if child_mappings_value.nil? # Check for key_mapping (from map_key) key_mappings = mapping_dsl.key_mapping if key_mappings && !key_mappings.empty? # The key_mappings has :to_instance which tells us which attribute is the key to_instance = key_mappings[:to_instance] if to_instance # Create the child_mappings hash format: { id: :key } child_mappings_value = { to_instance.to_sym => :key } end end # Check for value_mapping (from map_value) if child_mappings_value.nil? value_mappings = mapping_dsl.value_mapping if value_mappings && !value_mappings.empty? # value_mappings is already in the correct format: { attr_name => :value } child_mappings_value = value_mappings end end end if child_mappings_value info[:child_mappings] = child_mappings_value end info end # Build value transformer (use delegate_attr for delegated attributes) value_transformer = build_value_transformer(mapping_rule, delegate ? delegate_attr : attr) end # Access value_map directly value_map = mapping_rule.raw_value_map # Check if this is a raw mapping (map_all directive) is_raw_mapping = mapping_rule.raw_mapping? # Get serialized name (key name in output) # For raw mappings, serialized_name is nil (content is merged directly) serialized_name = if is_raw_mapping nil # Raw content has no key name elsif !mapping_rule.name.nil? # For multiple_mappings, use first element as serialized name mapping_rule.name.is_a?(Array) ? mapping_rule.name.first.to_s : mapping_rule.name.to_s elsif !mapping_rule.from.nil? # For compatibility with multiple_mappings mapping_rule.from.is_a?(Array) ? mapping_rule.from.first.to_s : mapping_rule.from.to_s else attr_name.to_s end Lutaml::Model::CompiledRule.new( attribute_name: attr_name, serialized_name: serialized_name, attribute_type: attr_type, child_transformation: child_transformation, value_transformer: value_transformer, collection_info: collection_info, mapping_type: is_raw_mapping ? :raw : :key_value, render_default: mapping_rule.render_default, value_map: value_map, custom_methods: custom_methods, delegate: delegate, root_mappings: mapping_rule.root_mappings, ) end |
#valid_mapping?(rule, options) ⇒ Boolean
Check if a mapping rule should be applied based on only/except options.
241 242 243 244 245 246 247 248 |
# File 'lib/lutaml/key_value/transformation/rule_compiler.rb', line 241 def valid_mapping?(rule, ) only = [:only] except = [:except] name = rule.attribute_name (except.nil? || !except.include?(name)) && (only.nil? || only.include?(name)) end |