Class: Synthra::Schema

Inherits:
Object
  • Object
show all
Defined in:
lib/synthra/schema.rb

Overview

Represents a complete schema definition

A Schema is the parsed representation of a DSL schema definition. It contains all the fields, behaviors, and metadata needed to generate fake data records.

Schemas are typically created by loading DSL files through the Registry or Synthra.load, but can also be created programmatically.

Examples:

Load from file

schema = Synthra.load("schemas/user.dsl")

Generate single record

user = schema.generate
# => { "id" => "550e8400-...", "name" => "John Doe", ... }

Generate with options

user = schema.generate(
  seed: 12345,          # Deterministic output
  mode: :edge,          # Edge case values
  overrides: { "name" => "Custom Name" }
)

Generate multiple records

users = schema.generate_many(100, seed: 42)
# => [{ "id" => "...", ... }, { "id" => "...", ... }, ...]

With runtime behaviors

slow_schema = schema.with_behavior(:latency, 500..1000)
user = slow_schema.generate  # Will have 500-1000ms delay

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name:, fields: [], behaviors: [], metadata: {}, version: nil, deprecated: false, deprecation_message: nil) ⇒ Schema

Create a new Schema instance

Examples:

schema = Schema.new(
  name: "User",
  fields: [
    Field.new(name: "id", type_name: "uuid"),
    Field.new(name: "name", type_name: "name")
  ],
  behaviors: [{ name: :latency, value: 100 }],
  version: "1.0"
)

Parameters:

  • name (String)

    the schema name

  • fields (Array<Field>) (defaults to: [])

    field definitions

  • behaviors (Array<Hash>) (defaults to: [])

    schema-level behaviors

  • metadata (Hash) (defaults to: {})

    additional metadata

  • version (String, nil) (defaults to: nil)

    schema version

  • deprecated (Boolean) (defaults to: false)

    whether schema is deprecated

  • deprecation_message (String, nil) (defaults to: nil)

    deprecation message



136
137
138
139
140
141
142
143
144
145
# File 'lib/synthra/schema.rb', line 136

def initialize(name:, fields: [], behaviors: [], metadata: {}, version: nil, deprecated: false, deprecation_message: nil)
  @name = name
  @fields = fields
  @behaviors = behaviors
  @metadata = 
  @version = version
  @deprecated = deprecated
  @deprecation_message = deprecation_message
  @runtime_behaviors = []
end

Instance Attribute Details

#behaviorsObject (readonly)

Returns the value of attribute behaviors.



83
84
85
# File 'lib/synthra/schema.rb', line 83

def behaviors
  @behaviors
end

#deprecatedObject (readonly)

Returns the value of attribute deprecated.



104
105
106
# File 'lib/synthra/schema.rb', line 104

def deprecated
  @deprecated
end

#deprecation_messageObject (readonly)

Returns the value of attribute deprecation_message.



111
112
113
# File 'lib/synthra/schema.rb', line 111

def deprecation_message
  @deprecation_message
end

#fieldsObject (readonly)

Returns the value of attribute fields.



76
77
78
# File 'lib/synthra/schema.rb', line 76

def fields
  @fields
end

#metadataObject (readonly)

Returns the value of attribute metadata.



90
91
92
# File 'lib/synthra/schema.rb', line 90

def 
  @metadata
end

#nameObject (readonly)

Returns the value of attribute name.



69
70
71
# File 'lib/synthra/schema.rb', line 69

def name
  @name
end

#versionObject (readonly)

Returns the value of attribute version.



97
98
99
# File 'lib/synthra/schema.rb', line 97

def version
  @version
end

Instance Method Details

#all_behaviorsArray<Hash>

Get all behaviors (defined in DSL + added at runtime)

Examples:

schema.all_behaviors
# => [{ name: :latency, value: 100 }, { name: :seed, value: 12345 }]

Returns:

  • (Array<Hash>)

    all behavior configurations



531
532
533
# File 'lib/synthra/schema.rb', line 531

def all_behaviors
  behaviors + @runtime_behaviors
end

#apply_behaviors(result, rng) ⇒ Hash (private)

Apply schema-level behaviors to generated result

Parameters:

  • result (Hash)

    the generated record

  • rng (Generator::RNG)

    random number generator

Returns:

  • (Hash)

    the result after applying behaviors



601
602
603
# File 'lib/synthra/schema.rb', line 601

def apply_behaviors(result, rng)
  Behaviors::Applicator.new(self, rng).apply_schema_behaviors(result)
end

#behavior(name) ⇒ Hash?

Get a specific behavior configuration

Examples:

latency = schema.behavior(:latency)
latency[:value]  # => 100..500

Parameters:

  • name (Symbol, String)

    behavior name

Returns:

  • (Hash, nil)

    the behavior hash or nil if not found



561
562
563
# File 'lib/synthra/schema.rb', line 561

def behavior(name)
  all_behaviors.find { |b| b[:name] == name.to_sym }
end

#deprecated?Boolean

Check if schema is deprecated

Returns:

  • (Boolean)

    true if deprecated



153
154
155
# File 'lib/synthra/schema.rb', line 153

def deprecated?
  @deprecated
end

#field(field_name) ⇒ Field?

Get a field by name

Examples:

email_field = schema.field("email")
email_field.type_name  # => "email"

Parameters:

  • field_name (String, Symbol)

    the field name to find

Returns:

  • (Field, nil)

    the field or nil if not found



186
187
188
# File 'lib/synthra/schema.rb', line 186

def field(field_name)
  fields.find { |f| f.name == field_name.to_s }
end

#generate(seed: nil, mode: nil, overrides: {}, registry: nil, depth: 0, resolver: nil, parent_context: nil, shared_context: nil) ⇒ Hash

Generate a single record

Creates one fake data record based on the schema definition. The record is a Hash with field names as keys.

Examples:

Basic generation

user = schema.generate
# => { "id" => "550e8400-...", "name" => "John Doe", "email" => "john@..." }

Deterministic generation

user1 = schema.generate(seed: 12345)
user2 = schema.generate(seed: 12345)
user1 == user2  # => true

With overrides

user = schema.generate(overrides: { "name" => "Test User" })
user["name"]  # => "Test User"

Edge case mode

user = schema.generate(mode: :edge)
# May contain empty strings, nil values, boundary values

Parameters:

  • seed (Integer, nil) (defaults to: nil)

    random seed for deterministic output

  • mode (Symbol, nil) (defaults to: nil)

    generation mode (:random, :edge, :invalid, :mixed) Defaults to Synthra.configuration.default_mode

  • overrides (Hash) (defaults to: {})

    field values to use instead of generating

  • registry (Registry, nil) (defaults to: nil)

    registry for resolving cross-schema references

Returns:

  • (Hash)

    the generated record



251
252
253
254
255
256
257
258
# File 'lib/synthra/schema.rb', line 251

def generate(seed: nil, mode: nil, overrides: {}, registry: nil, depth: 0, resolver: nil, parent_context: nil, shared_context: nil)
  mode ||= Synthra.configuration.default_mode
  # Inherit shared_context from parent_context if not explicitly provided
  shared_context ||= parent_context&.instance_variable_get(:@shared_context)
  engine = Generator::Engine.new(self, registry: registry, resolver: resolver)
  result = engine.generate(seed: seed, mode: mode, overrides: overrides, depth: depth, parent_context: parent_context, shared_context: shared_context)
  apply_behaviors(result, engine.rng)
end

#generate_json(pretty: false, **options) ⇒ String

Generate a record as a JSON string

Convenience method that generates a record and serializes it to JSON.

Examples:

Minified JSON

json = schema.generate_json
# => '{"id":"550e8400-...","name":"John Doe"}'

Pretty-printed JSON

json = schema.generate_json(pretty: true)
# => '{\n  "id": "550e8400-...",\n  "name": "John Doe"\n}'

Parameters:

  • pretty (Boolean) (defaults to: false)

    if true, format with indentation

  • options (Hash)

    additional options passed to generate()

Returns:

  • (String)

    JSON string



457
458
459
460
461
462
463
464
# File 'lib/synthra/schema.rb', line 457

def generate_json(pretty: false, **options)
  result = generate(**options)
  if pretty
    JSON.pretty_generate(result)
  else
    JSON.generate(result)
  end
end

#generate_many(count, seed: nil, mode: nil, overrides: {}, registry: nil, shared_context: nil, engine: nil, locale: "en", threads: nil) ⇒ Array<Hash>

Generate multiple records

Creates multiple fake data records in a batch. More efficient than calling generate() multiple times.

Examples:

Generate 100 users

users = schema.generate_many(100)
users.length  # => 100

Deterministic batch

users1 = schema.generate_many(10, seed: 42)
users2 = schema.generate_many(10, seed: 42)
users1 == users2  # => true

Parameters:

  • count (Integer)

    number of records to generate

  • seed (Integer, nil) (defaults to: nil)

    random seed for deterministic output

  • mode (Symbol, nil) (defaults to: nil)

    generation mode

  • overrides (Hash) (defaults to: {})

    field values to override for all records

  • registry (Registry, nil) (defaults to: nil)

    registry for cross-schema references

Returns:

  • (Array<Hash>)

    array of generated records



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/synthra/schema.rb', line 283

def generate_many(count, seed: nil, mode: nil, overrides: {}, registry: nil, shared_context: nil, engine: nil, locale: "en", threads: nil)
  mode ||= Synthra.configuration.default_mode
  
  # Use native engine if requested and available
  if engine == :native
    return generate_many_native(count, seed: seed, locale: locale, threads: threads)
  end
  
  # ponytail: fast_mode once selected a Generator::FastEngine that was never implemented (it
  # crashed). The real high-throughput path is the native Rust engine (engine: :native); the
  # standard engine handles everything else.
  gen = Generator::Engine.new(self, registry: registry)

  gen.generate_many(count, seed: seed, mode: mode, overrides: overrides, shared_context: shared_context)
end

#generate_many_fast(count, seed: nil, mode: nil, overrides: {}, registry: nil) ⇒ Array<Hash>

Generate multiple records using FastEngine (high performance)

Uses the optimized FastEngine for 2-3x faster generation. NOT thread-safe - use only in single-threaded contexts.

Examples:

Fast batch generation

users = schema.generate_many_fast(100_000, seed: 42)

Parameters:

  • count (Integer)

    number of records to generate

  • seed (Integer, nil) (defaults to: nil)

    random seed for deterministic output

  • mode (Symbol, nil) (defaults to: nil)

    generation mode

  • overrides (Hash) (defaults to: {})

    field values to override for all records

  • registry (Registry, nil) (defaults to: nil)

    registry for cross-schema references

Returns:

  • (Array<Hash>)

    array of generated records



374
375
376
377
378
379
# File 'lib/synthra/schema.rb', line 374

def generate_many_fast(count, seed: nil, mode: nil, overrides: {}, registry: nil)
  mode ||= Synthra.configuration.default_mode
  # ponytail: kept as an alias for the standard engine — FastEngine was never implemented.
  engine = Generator::Engine.new(self, registry: registry)
  engine.generate_many(count, seed: seed, mode: mode, overrides: overrides)
end

#generate_many_native(count, seed: nil, locale: "en", threads: nil) ⇒ Array<Hash>

Generate multiple records using the Native Rust engine (maximum performance)

Uses the Rust native extension with fake-rs for 100-250x faster generation. Requires the native extension to be compiled.

Examples:

Native engine generation

users = schema.generate_many_native(1_000_000, seed: 42)

With locale

users = schema.generate_many_native(100_000, locale: "ja_jp")

With limited threads

users = schema.generate_many_native(100_000, threads: 4)

Parameters:

  • count (Integer)

    number of records to generate

  • seed (Integer, nil) (defaults to: nil)

    random seed for deterministic output

  • locale (String) (defaults to: "en")

    locale for data generation (en, fr_fr, de_de, ja_jp, etc.)

  • threads (Integer, nil) (defaults to: nil)

    number of threads (nil/0 = use all cores)

Returns:

  • (Array<Hash>)

    array of generated records

Raises:

  • (LoadError)

    if native extension is not available



322
323
324
325
# File 'lib/synthra/schema.rb', line 322

def generate_many_native(count, seed: nil, locale: "en", threads: nil)
  native_engine = NativeEngine.new(self, locale: locale, threads: threads)
  native_engine.generate_many(count, seed: seed)
end

#generate_many_stream(count, seed: nil, mode: nil, overrides: {}, registry: nil, shared_context: nil) ⇒ Enumerator

Note:

For small batches (< 1000 records), generate_many is more efficient. Use generate_many_stream for large batches to reduce memory usage.

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.

Examples:

Stream processing for large batches

schema.generate_many_stream(10000, seed: 42).each do |record|
  process(record)  # Records generated one at a time
end

Parameters:

  • count (Integer)

    total number of records to generate

  • seed (Integer, nil) (defaults to: nil)

    random seed for deterministic output

  • mode (Symbol, nil) (defaults to: nil)

    generation mode

  • overrides (Hash) (defaults to: {})

    field values to override for all records

  • registry (Registry, nil) (defaults to: nil)

    registry for cross-schema references

Returns:

  • (Enumerator)

    lazy enumerator of records



404
405
406
407
408
409
410
411
# File 'lib/synthra/schema.rb', line 404

def generate_many_stream(count, seed: nil, mode: nil, overrides: {}, registry: nil, shared_context: nil)
  mode ||= Synthra.configuration.default_mode
  
  # ponytail: fast_mode's FastEngine was never implemented; use the standard engine.
  engine = Generator::Engine.new(self, registry: registry)

  engine.generate_many_stream(count, seed: seed, mode: mode, overrides: overrides, shared_context: shared_context)
end

#generate_stream(count:, **options) ⇒ Enumerator

Generate 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.

Examples:

Stream processing

schema.generate_stream(count: 10000).each do |record|
  process(record)  # Records generated one at a time
end

Convert to NDJSON

schema.generate_stream(count: 1000).each do |record|
  puts record.to_json
end

Parameters:

  • count (Integer)

    total number of records to generate

  • options (Hash)

    generation options (seed, mode, etc.)

Returns:

  • (Enumerator)

    lazy enumerator of records



488
489
490
# File 'lib/synthra/schema.rb', line 488

def generate_stream(count:, **options)
  Generator::Streamer.new(self, **options).generate_stream(count: count)
end

#generate_stream_fast(count, seed: nil, mode: nil, overrides: {}, registry: nil) ⇒ Enumerator

Generate stream using FastEngine (high performance)

Uses the optimized FastEngine for 2-3x faster streaming. NOT thread-safe - use only in single-threaded contexts.

Examples:

Fast streaming

schema.generate_stream_fast(1_000_000, seed: 42).each do |record|
  process(record)
end

Parameters:

  • count (Integer)

    number of records to generate

  • seed (Integer, nil) (defaults to: nil)

    random seed for deterministic output

  • mode (Symbol, nil) (defaults to: nil)

    generation mode

  • overrides (Hash) (defaults to: {})

    field values to override for all records

  • registry (Registry, nil) (defaults to: nil)

    registry for cross-schema references

Returns:

  • (Enumerator)

    lazy enumerator of records



432
433
434
435
436
437
# File 'lib/synthra/schema.rb', line 432

def generate_stream_fast(count, seed: nil, mode: nil, overrides: {}, registry: nil)
  mode ||= Synthra.configuration.default_mode
  # ponytail: kept as an alias for the standard engine — FastEngine was never implemented.
  engine = Generator::Engine.new(self, registry: registry)
  engine.generate_many_stream(count, seed: seed, mode: mode, overrides: overrides)
end

#generate_to_file(count, path, seed: nil, locale: "en", format: "jsonl", threads: nil) ⇒ Integer

Generate records directly to a file using the Native Rust engine

This is the most efficient method for generating large amounts of data. Uses the Rust native extension for maximum performance.

Examples:

Generate 10 million records to JSONL

schema.generate_to_file(10_000_000, "users.jsonl", format: "jsonl")

Generate CSV with seed

schema.generate_to_file(1_000_000, "users.csv", seed: 42, format: "csv")

Generate with limited threads (leaves other cores free)

schema.generate_to_file(10_000_000, "users.jsonl", threads: 4)

Parameters:

  • count (Integer)

    number of records to generate

  • path (String)

    output file path

  • seed (Integer, nil) (defaults to: nil)

    random seed for deterministic output

  • locale (String) (defaults to: "en")

    locale for data generation

  • format (String) (defaults to: "jsonl")

    output format: "jsonl", "json", "csv"

  • threads (Integer, nil) (defaults to: nil)

    number of threads (nil/0 = use all cores)

Returns:

  • (Integer)

    number of records written

Raises:

  • (LoadError)

    if native extension is not available



352
353
354
355
# File 'lib/synthra/schema.rb', line 352

def generate_to_file(count, path, seed: nil, locale: "en", format: "jsonl", threads: nil)
  native_engine = NativeEngine.new(self, locale: locale, threads: threads)
  native_engine.generate_to_file(count, path, seed: seed, format: format)
end

#has_behavior?(name) ⇒ Boolean

Check if schema has a specific behavior

Examples:

schema.has_behavior?(:latency)  # => true
schema.has_behavior?(:failure)  # => false

Parameters:

  • name (Symbol, String)

    behavior name to check

Returns:

  • (Boolean)

    true if the behavior is defined



546
547
548
# File 'lib/synthra/schema.rb', line 546

def has_behavior?(name)
  all_behaviors.any? { |b| b[:name] == name.to_sym }
end

#infoHash

Get schema info hash for display

Returns:

  • (Hash)

    schema information



163
164
165
166
167
168
169
170
171
172
173
# File 'lib/synthra/schema.rb', line 163

def info
  {
    name: @name,
    version: @version,
    deprecated: @deprecated,
    deprecation_message: @deprecation_message,
    field_count: @fields.length,
    behavior_count: @behaviors.length,
    fields: @fields.map(&:name)
  }
end

#optional_fieldsArray<Field>

Get all optional fields

Examples:

schema.optional_fields.each do |field|
  puts "#{field.name} is optional"
end

Returns:

  • (Array<Field>)

    fields marked with ? suffix



216
217
218
# File 'lib/synthra/schema.rb', line 216

def optional_fields
  fields.select(&:optional?)
end

#required_fieldsArray<Field>

Get all required (non-optional) fields

Examples:

schema.required_fields.each do |field|
  puts "#{field.name} is required"
end

Returns:

  • (Array<Field>)

    fields that are not marked as optional



201
202
203
# File 'lib/synthra/schema.rb', line 201

def required_fields
  fields.reject(&:optional?)
end

#seedInteger?

Get the seed from metadata if specified

Examples:

# DSL: @seed 12345
schema.seed  # => 12345

Returns:

  • (Integer, nil)

    the seed value or nil



575
576
577
# File 'lib/synthra/schema.rb', line 575

def seed
  [:seed]
end

#to_sString Also known as: inspect

String representation

Returns:

  • (String)

    summary of the schema



585
586
587
# File 'lib/synthra/schema.rb', line 585

def to_s
  "#<Schema #{name} fields=#{fields.length} behaviors=#{all_behaviors.length}>"
end

#with_behavior(name, value) ⇒ Schema

Create a new schema with an additional runtime behavior

Returns a new Schema instance with the behavior added. The original schema is not modified.

Examples:

Add latency behavior

slow_schema = schema.with_behavior(:latency, 500..1000)
slow_schema.generate  # Will have 500-1000ms delay

Chain multiple behaviors

test_schema = schema
  .with_behavior(:latency, 100)
  .with_behavior(:failure, 10)

Parameters:

  • name (Symbol, String)

    behavior name (:latency, :failure, etc.)

  • value (Object)

    behavior configuration value

Returns:

  • (Schema)

    new schema with the behavior added



512
513
514
515
516
517
518
519
# File 'lib/synthra/schema.rb', line 512

def with_behavior(name, value)
  new_schema = dup
  new_schema.instance_variable_set(
    :@runtime_behaviors,
    @runtime_behaviors + [{ name: name.to_sym, value: value }]
  )
  new_schema
end