Class: Synthra::Generator::Engine
- Inherits:
-
Object
- Object
- Synthra::Generator::Engine
- Defined in:
- lib/synthra/generator/engine.rb
Overview
Core generation engine
The Engine orchestrates the data generation process, handling:
- Field iteration and value generation
- Type lookup and invocation
- Uniqueness constraint enforcement
- Optional and conditional field handling
- Field-level behavior application
- Deterministic seeding via RNG
Instance Attribute Summary collapse
-
#registry ⇒ Object
readonly
Returns the value of attribute registry.
-
#rng ⇒ Object
readonly
Returns the value of attribute rng.
-
#schema ⇒ Object
readonly
Returns the value of attribute schema.
Instance Method Summary collapse
- #emit_observability(config, field, value, duration_ms) ⇒ Object private
-
#generate(seed: nil, mode: :random, overrides: {}, depth: 0, parent_context: nil, shared_context: nil) ⇒ Hash
Generate a single record.
-
#generate_field(field, context, mode) ⇒ Object
private
Generate a single field value.
-
#generate_many(count, seed: nil, mode: :random, overrides: {}, shared_context: nil) ⇒ Array<Hash>
Generate multiple records.
-
#generate_many_stream(count, seed: nil, mode: :random, overrides: {}, shared_context: nil) ⇒ Enumerator
Generate multiple records as a lazy stream (Enumerator).
-
#generate_single(mode:, overrides:, depth: 0, parent_context: nil, shared_context: nil) ⇒ Hash
private
Generate a single record (internal).
-
#initialize(schema, registry: nil, resolver: nil) ⇒ Engine
constructor
Create a new Engine instance.
-
#setup_rng(seed) ⇒ void
private
Setup RNG and FakerAdapter for deterministic output.
Constructor Details
#initialize(schema, registry: nil, resolver: nil) ⇒ Engine
Create a new Engine instance
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/synthra/generator/engine.rb', line 79 def initialize(schema, registry: nil, resolver: nil) @schema = schema @registry = registry @rng = nil @faker_adapter = nil @uniqueness = Uniqueness.new(schema_name: schema.name) # Use provided resolver or create new one if registry is available # Reusing resolver ensures @generating set is shared for cycle detection @resolver = resolver || (registry ? Resolver.new(registry) : nil) # Pre-compute observability flag at initialization to avoid repeated # config lookups in the hot path (called once per Engine, not per record) @observability_enabled = Synthra.configuration.observability_enabled? end |
Instance Attribute Details
#registry ⇒ Object (readonly)
Returns the value of attribute registry.
66 67 68 |
# File 'lib/synthra/generator/engine.rb', line 66 def registry @registry end |
#rng ⇒ Object (readonly)
Returns the value of attribute rng.
59 60 61 |
# File 'lib/synthra/generator/engine.rb', line 59 def rng @rng end |
#schema ⇒ Object (readonly)
Returns the value of attribute schema.
52 53 54 |
# File 'lib/synthra/generator/engine.rb', line 52 def schema @schema end |
Instance Method Details
#emit_observability(config, field, value, duration_ms) ⇒ Object (private)
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 |
# File 'lib/synthra/generator/engine.rb', line 275 def emit_observability(config, field, value, duration_ms) if config.metrics_collector config.metrics_collector.timing( "synthra.field_generation", duration_ms, schema: @schema.name, field: field.name, type: field.type_name ) end config.logger&.debug( "Generated field #{@schema.name}.#{field.name} (#{field.type_name}) in #{duration_ms}ms" ) config.on_field_generated&.call(@schema.name, field.name, value, duration_ms) end |
#generate(seed: nil, mode: :random, overrides: {}, depth: 0, parent_context: nil, shared_context: nil) ⇒ Hash
Generate a single record
Creates one fake data record based on the schema definition. Use the seed parameter for deterministic/reproducible output.
116 117 118 119 120 121 |
# File 'lib/synthra/generator/engine.rb', line 116 def generate(seed: nil, mode: :random, overrides: {}, depth: 0, parent_context: nil, shared_context: nil) # Reset RNG if seed is provided, or create one if none exists setup_rng(seed) if seed || @rng.nil? generate_single(mode: mode, overrides: overrides, depth: depth, parent_context: parent_context, shared_context: shared_context) end |
#generate_field(field, context, mode) ⇒ Object (private)
Generate a single field value
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 |
# File 'lib/synthra/generator/engine.rb', line 302 def generate_field(field, context, mode) type_name = field.type_name type_args = field.type_args # Look up the type generator - first try Types::Registry type_gen = begin Types::Registry.lookup(type_name) rescue Errors::UnknownTypeError # If not a registered type, check if it's a schema reference if @registry && @registry.schema?(type_name) # Create an ObjectType for the schema reference Types::Core::ObjectType.new(type_name) else raise # Re-raise the UnknownTypeError if not a schema either end end # Pass faker_adapter in context for type generators to use context.faker_adapter = @faker_adapter if @faker_adapter # Handle uniqueness constraint if field.unique? @uniqueness.generate_unique(field.name) do type_gen.generate(@rng, context, type_args, mode) end else type_gen.generate(@rng, context, type_args, mode) end end |
#generate_many(count, seed: nil, mode: :random, overrides: {}, shared_context: nil) ⇒ Array<Hash>
For large batches (10K+ records), consider using generate_many_stream for memory-efficient lazy generation.
Generate multiple records
Creates multiple fake data records efficiently. Uniqueness constraints are enforced across the entire batch.
145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/synthra/generator/engine.rb', line 145 def generate_many(count, seed: nil, mode: :random, overrides: {}, shared_context: nil) setup_rng(seed) @uniqueness.clear # Reset uniqueness tracking for new batch # Create shared context for batch - values persist across all records shared_context ||= {} count.times.map do generate_single(mode: mode, overrides: overrides, depth: 0, shared_context: shared_context) end end |
#generate_many_stream(count, seed: nil, mode: :random, overrides: {}, shared_context: nil) ⇒ Enumerator
Generate multiple records as a lazy stream (Enumerator)
Returns an Enumerator that generates records on-demand. This is memory-efficient for large batches as records are not all held in memory at once.
176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/synthra/generator/engine.rb', line 176 def generate_many_stream(count, seed: nil, mode: :random, overrides: {}, shared_context: nil) setup_rng(seed) @uniqueness.clear # Reset uniqueness tracking for new batch # Create shared context for batch - values persist across all records shared_context ||= {} Enumerator.new(count) do |yielder| count.times do yielder << generate_single(mode: mode, overrides: overrides, depth: 0, shared_context: shared_context) end end end |
#generate_single(mode:, overrides:, depth: 0, parent_context: nil, shared_context: nil) ⇒ Hash (private)
Generate a single record (internal)
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
# File 'lib/synthra/generator/engine.rb', line 214 def generate_single(mode:, overrides:, depth: 0, parent_context: nil, shared_context: nil) # Cache config locally to avoid repeated method calls config = Synthra.configuration presence_rate = config.optional_field_presence_rate || Synthra::OPTIONAL_FIELD_PRESENCE_RATE null_rate = config.nullable_field_null_rate || Synthra::NULLABLE_FIELD_NULL_RATE # Create context with proper initialization context = Context.new( {}, registry: @registry, depth: depth, parent_context: parent_context, shared_context: shared_context || {}, resolver: @resolver, faker_adapter: @faker_adapter ) result = {} applicator = Behaviors::Applicator.new(@schema, @rng) # Iterate fields in definition order (important for copy() to work) @schema.fields.each do |field| # Skip if override provided if overrides.key?(field.name) || overrides.key?(field.name.to_sym) result[field.name] = overrides[field.name] || overrides[field.name.to_sym] context[field.name] = result[field.name] next end # Handle conditional fields if field.conditional? next unless context[field.condition] end # Handle optional fields next if field.optional? && !@rng.boolean(presence_rate) # Generate the field value with optional timing start_time = @observability_enabled ? Process.clock_gettime(Process::CLOCK_MONOTONIC) : nil value = generate_field(field, context, mode) # Apply field-level behaviors value = applicator.apply_field_behaviors(field, value) next if value == :omit # Handle nullable fields value = nil if field.nullable? && @rng.boolean(null_rate) # Observability hooks (only if enabled - checked once at init) if @observability_enabled duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round emit_observability(config, field, value, duration_ms) end result[field.name] = value context[field.name] = value end result end |
#setup_rng(seed) ⇒ void (private)
This method returns an undefined value.
Setup RNG and FakerAdapter for deterministic output
199 200 201 202 |
# File 'lib/synthra/generator/engine.rb', line 199 def setup_rng(seed) @rng = RNG.new(seed) @faker_adapter = FakerAdapter.new(@rng) end |