FakeDataDSL Usage Guide
Complete guide on how to use FakeDataDSL: DSL syntax, types, formulas, unique constraints, and options.
Table of Contents
- Basic DSL Syntax
- Types
- Formula
- Unique Constraints
- Options and Modifiers
- Deterministic Generation (Same Data Every Time)
- Complete Examples
Basic DSL Syntax
Schema Definition
A schema starts with a name followed by a colon:
SchemaName:
field1: type1
field2: type2
Field Definition
Basic field syntax:
field_name: type
Example:
User:
id: uuid
name: name
email: email
age: number(18..65)
Comments
Use # for comments:
User:
# Primary identifier
id: uuid
name: name # User's full name
email: email
Types
Basic Types
FakeDataDSL provides many built-in types:
User:
# Identifiers
id: uuid
user_id: id_sequence
# Text
name: name
first_name: first_name
last_name: last_name
email: email
bio: text(50..500)
username: username
# Numbers
age: number(18..100)
score: number(min: 0, max: 100)
price: number(10.0..99.99)
# Boolean
active: boolean
verified: boolean(true: 80%) # 80% chance of true
# Dates
created_at: timestamp
birth_date: date
last_login: past_date(days: 30)
# Location
country: country
city: city
address: address
postal_code: postal_code
# Internet
website: url
ip_address: ip
mac_address: mac_address
user_agent: user_agent
Type Arguments
Many types accept arguments to customize generation:
Number with Range
age: number(18..65) # Range
price: number(min: 10, max: 100) # Min/max
score: number(0..100) # Integer range
Text with Length
title: text(10..50) # 10-50 characters
description: text(100) # Exactly 100 characters
bio: text(min: 50, max: 500)
Enum with Weights
status: enum(active, inactive, pending)
priority: enum(low: 50%, medium: 30%, high: 20%)
role: enum(user: 80%, admin: 10%, moderator: 10%)
Array Types
tags: array(text, 3..10) # Array of text, 3-10 items
scores: array(number(0..100), 5) # Array of numbers, exactly 5 items
items: array(Item, 1..5) # Array of nested schemas
Date Types
created_at: timestamp
birth_date: date
last_week: past_date(days: 7)
next_month: future_date(days: 30)
between: date_between(start: "2024-01-01", end: "2024-12-31")
All Available Types
See Type Reference for a complete list of all 80+ types including:
- Personal:
first_name,last_name,full_name,gender,age,username,email,phone,national_id - Location:
country,city,state,address,latitude,longitude,timezone,postal_code - Commerce:
product_name,product_category,sku,price,discount,tax_rate - Finance:
credit_card,bank_name,account_number,iban,money,currency,transaction_amount - Date & Time:
date,time,datetime,timestamp,past_date,future_date,date_between - Internet/IT:
uuid,ip,ipv6,mac_address,domain,url,user_agent - Crypto:
hash,bitcoin_address,ethereum_address,private_key - Health:
blood_type,bmi,heart_rate,medical_code - Travel:
airport_code,airline,flight_number,seat_number - Advanced:
sequence,default_value,formula,template,regex,json_array
Formula
The formula type computes values from other fields using mathematical expressions.
Basic Formula
Order:
quantity: number(1..10)
unit_price: number(10..100)
total: formula(quantity * unit_price)
Generated Example:
{
"quantity" => 5,
"unit_price" => 50,
"total" => 250
}
Complex Formulas
Order:
quantity: number(1..10)
unit_price: number(10..100)
subtotal: formula(quantity * unit_price)
tax_rate: number(0.05..0.15)
tax: formula(subtotal * tax_rate)
discount: number(0..20)
discount_amount: formula(subtotal * discount / 100)
total: formula(subtotal + tax - discount_amount)
Generated Example:
{
"quantity" => 3,
"unit_price" => 75,
"subtotal" => 225,
"tax_rate" => 0.08,
"tax" => 18,
"discount" => 10,
"discount_amount" => 22.5,
"total" => 220.5
}
Formula Operations
Formulas support:
- Arithmetic:
+,-,*,/,% - Parentheses:
(quantity * price) + tax - Field References: Use field names directly in expressions
- Numbers:
total * 0.08,price + 10
Example:
Product:
base_price: number(10..100)
markup: number(1.1..1.5)
final_price: formula(base_price * markup)
shipping: number(5..15)
total: formula(final_price + shipping)
Formula Limitations
- Only works with numeric field values
- Field must be generated before the formula field
- Returns
0if expression fails to evaluate - No string concatenation (use
templatetype instead)
Unique Constraints
The unique modifier ensures generated values are unique within a batch.
Basic Unique
User:
id: uuid unique
email: email unique
username: username unique
Unique with Types
Product:
sku: sku unique
code: text(10) unique
serial_number: sequence(start: 1000) unique
Unique with Enums
Order:
status: enum(pending, processing, completed) unique
# Each generated order will have a different status
How Unique Works
- Per-Batch: Uniqueness is enforced within a single
generate_manycall - Retries: Generator retries up to
max_unique_retriestimes (default: 100) - Error: Raises
UniquenessErrorif unique value cannot be generated
Example:
registry = FakeDataDSL::Registry.new
registry.load_string(<<~DSL)
User:
id: uuid unique
email: email unique
DSL
schema = registry.schema("User")
# Generate 10 unique users
users = schema.generate_many(10, seed: 42)
# All users will have different IDs and emails
Unique Configuration
Configure max retries:
FakeDataDSL.configure do |config|
config.max_unique_retries = 200 # Default: 100
end
Options and Modifiers
Nullable Fields
Add ? to make a field nullable (can be nil):
User:
middle_name: name? # Type is nullable
nickname: text? # May be nil
manager: User? # Reference can be nil
Optional Fields
Use optional modifier to omit fields sometimes:
User:
phone: phone optional # May be omitted from output
address: address optional # Sometimes not included
Difference:
- Nullable (
?): Field is always included, but value may benil - Optional: Field may be completely omitted from output
Conditional Fields
Use if to conditionally include fields:
User:
is_admin: boolean
admin_note: text if is_admin # Only if is_admin is true
employee_id: text if is_employee # Only if is_employee is true
Note: The condition field must be defined before the conditional field.
Type Options
Many types support options:
Number Options
age: number(18..65) # Range
price: number(min: 10, max: 100) # Named arguments
score: number(0..100, precision: 2) # With precision
Text Options
title: text(10..50) # Length range
description: text(min: 100, max: 500) # Named arguments
code: text(10, prefix: "CODE-") # With prefix
# Language support (English/Arabic)
english_text: text(50, language: :en) # English (default)
arabic_text: text(50, language: :ar) # Arabic
arabic_text: text(50, lang: :arabic) # Also accepts :arabic
Enum Options
status: enum(active, inactive)
priority: enum(low: 50%, medium: 30%, high: 20%) # With weights
role: enum(user: 80%, admin: 10%, moderator: 10%) # Percentages
Array Options
tags: array(text, 3..10) # Element type and size
items: array(Item, min: 1, max: 5) # Named arguments
scores: array(number(0..100), 5) # Fixed size
Date Options
created_at: timestamp
last_week: past_date(days: 7)
next_month: future_date(days: 30)
between: date_between(start: "2024-01-01", end: "2024-12-31")
Sequence Options
id: sequence(start: 1, step: 1)
code: sequence(start: "A001", step: 1, format: "ID-%03d")
Template Options
full_name: template("{{first_name}} {{last_name}}")
email: template("{{username}}@example.com")
Combining Modifiers
You can combine multiple modifiers:
User:
id: uuid unique # Unique constraint
email: email unique optional # Unique and optional
nickname: text? optional # Nullable and optional
admin_note: text? if is_admin # Nullable and conditional
Deterministic Generation (Same Data Every Time)
Use the seed parameter to generate the same data every time. This is essential for:
- Testing - Reproducible test data
- Debugging - Same data to debug issues
- Documentation - Consistent examples
- CI/CD - Predictable test results
Basic Usage
require 'fake_data_dsl'
registry = FakeDataDSL::Registry.new
registry.load_string(<<~DSL)
User:
id: uuid
name: name
email: email
age: number(18..65)
DSL
schema = registry.schema("User")
# Generate with seed - same data every time
user1 = schema.generate(seed: 42)
user2 = schema.generate(seed: 42)
# user1 and user2 will be IDENTICAL
puts user1 == user2 # => true
Generate Multiple Records
# Generate 10 records with same seed
users1 = schema.generate_many(10, seed: 12345)
users2 = schema.generate_many(10, seed: 12345)
# All records will be identical
users1.each_with_index do |user, i|
puts user == users2[i] # => true for all
end
Different Seeds = Different Data
# Different seeds produce different data
user1 = schema.generate(seed: 42)
user2 = schema.generate(seed: 43)
puts user1 == user2 # => false (different data)
Streaming with Seed
# Streaming also supports seeds for determinism
streamer = schema.generate_many_stream(count: 100, seed: 999)
# All records will be deterministic
streamer.each do |record|
# Process record
end
Complete Example
require 'fake_data_dsl'
# Define schema
registry = FakeDataDSL::Registry.new
registry.load_string(<<~DSL)
Product:
id: uuid
name: product_name
price: number(10..100)
category: product_category
in_stock: boolean
DSL
schema = registry.schema("Product")
# Generate same product every time
product = schema.generate(seed: 42)
puts product
# => {
# "id" => "550e8400-e29b-41d4-a716-446655440000",
# "name" => "Ergonomic Granite Chair",
# "price" => 45.67,
# "category" => "Electronics",
# "in_stock" => true
# }
# Generate again with same seed - IDENTICAL
product2 = schema.generate(seed: 42)
puts product == product2 # => true
# Different seed - different data
product3 = schema.generate(seed: 99)
puts product == product3 # => false
Seed Best Practices
Use Fixed Seeds for Tests
# In your test user = schema.generate(seed: 12345) expect(user["age"]).to be_between(18, 65)Use Different Seeds for Variety
# Generate varied test data (1..100).each do |seed| user = schema.generate(seed: seed) # Each user will be different endStore Seeds for Reproducibility
# Save seed with generated data seed = 42 user = schema.generate(seed: seed) user["_seed"] = seed # Store for later reproductionSeed Per Schema
# Each schema can have different seeds user = user_schema.generate(seed: 100) order = order_schema.generate(seed: 200)
Determinism Guarantees
✅ Guaranteed Deterministic:
- Same seed + same schema = identical output
- Works across multiple runs
- Works across different machines
- Works with
generate,generate_many, andgenerate_many_stream
⚠️ Not Deterministic:
- Without seed (uses random seed each time)
- Different Ruby versions (may vary)
- Different Faker versions (may vary)
- Schema changes (different schema = different output)
Example: Testing with Seeds
# spec/user_spec.rb
RSpec.describe "User generation" do
let(:schema) do
registry = FakeDataDSL::Registry.new
registry.load_string(<<~DSL)
User:
id: uuid
name: name
email: email
age: number(18..65)
DSL
registry.schema("User")
end
it "generates consistent data with seed" do
user1 = schema.generate(seed: 42)
user2 = schema.generate(seed: 42)
expect(user1).to eq(user2)
expect(user1["age"]).to be_between(18, 65)
end
it "generates different data with different seeds" do
user1 = schema.generate(seed: 1)
user2 = schema.generate(seed: 2)
expect(user1).not_to eq(user2)
end
end
Configuration
You can also set a default seed globally:
FakeDataDSL.configure do |config|
# Note: There's no global seed setting, always pass seed explicitly
# This ensures explicit control over determinism
end
# Always pass seed explicitly for clarity
schema.generate(seed: 42)
Fixed Values (Same Value Every Time)
Sometimes you need specific fields to always have the same value every time you generate data, regardless of seed. This is useful for:
- Timestamps - Fixed
created_atfor all records - Constants - Fixed status, category, or identifier
- Test Data - Known values for testing
Method 1: Using default_value Type (Recommended)
Use the default_value type to set a fixed value:
User:
id: uuid unique
username: username unique
email: email unique
first_name: first_name
last_name: last_name
full_name: template("{{first_name}} {{last_name}}")
age: number(18..100)
gender: gender
phone: phone? optional
address: address? optional
is_admin: boolean(true: 10%)
admin_level: enum(none: 90%, moderator: 8%, admin: 2%) if is_admin
created_at: default_value(value: "2024-01-15 10:30:00 UTC")
last_login: past_date(days: 30)? optional
For Timestamps:
User:
# Fixed timestamp (same for all records)
created_at: default_value(value: "2024-01-15T10:30:00Z")
# Or use Time object format
updated_at: default_value(value: "2024-01-15 10:30:00 UTC")
# Or Unix timestamp
timestamp: default_value(value: 1705315800)
For Other Types:
Product:
# Fixed string
status: default_value(value: "active")
# Fixed number
version: default_value(value: 1)
# Fixed boolean
enabled: default_value(value: true)
# Fixed array
tags: default_value(value: ["featured", "new"])
Method 2: Using Overrides
Override specific fields when generating:
require 'fake_data_dsl'
registry = FakeDataDSL::Registry.new
registry.load_string(<<~DSL)
User:
id: uuid unique
username: username unique
email: email unique
created_at: timestamp
DSL
schema = registry.schema("User")
# Generate with fixed created_at
fixed_time = Time.parse("2024-01-15 10:30:00 UTC")
user1 = schema.generate(
seed: 42,
overrides: { "created_at" => fixed_time }
)
user2 = schema.generate(
seed: 43,
overrides: { "created_at" => fixed_time }
)
# Both have same created_at
puts user1["created_at"] == user2["created_at"] # => true
For Multiple Records:
fixed_time = Time.parse("2024-01-15 10:30:00 UTC")
users = schema.generate_many(
10,
seed: 42,
overrides: { "created_at" => fixed_time }
)
# All users have the same created_at
users.each do |user|
puts user["created_at"] == fixed_time # => true
end
Method 3: Combining Seed + Overrides
Use seed for most fields, but override specific ones:
User:
id: uuid unique
username: username unique
email: email unique
first_name: first_name
last_name: last_name
full_name: template("{{first_name}} {{last_name}}")
age: number(18..100)
gender: gender
phone: phone? optional
address: address? optional
is_admin: boolean(true: 10%)
admin_level: enum(none: 90%, moderator: 8%, admin: 2%) if is_admin
created_at: timestamp # Will be overridden
last_login: past_date(days: 30)? optional
# Generate with seed for randomness, but fixed created_at
fixed_created_at = Time.parse("2024-01-15 10:30:00 UTC")
user = schema.generate(
seed: 42,
overrides: { "created_at" => fixed_created_at }
)
# Other fields vary with seed, but created_at is always the same
Complete Example
require 'fake_data_dsl'
registry = FakeDataDSL::Registry.new
registry.load_string(<<~DSL)
User:
id: uuid unique
username: username unique
email: email unique
first_name: first_name
last_name: last_name
full_name: template("{{first_name}} {{last_name}}")
age: number(18..100)
gender: gender
phone: phone? optional
address: address? optional
is_admin: boolean(true: 10%)
admin_level: enum(none: 90%, moderator: 8%, admin: 2%) if is_admin
created_at: default_value(value: "2024-01-15 10:30:00 UTC")
last_login: past_date(days: 30)? optional
DSL
schema = registry.schema("User")
# Generate multiple users - all have same created_at
users = schema.generate_many(10, seed: 42)
users.each do |user|
puts user["created_at"] # => "2024-01-15 10:30:00 UTC" (same for all)
puts user["first_name"] # => Different names (varies with seed)
end
When to Use Each Method
Use default_value when:
- ✅ Value should be the same in the DSL definition
- ✅ Value is a constant that never changes
- ✅ You want it defined in the schema itself
Use overrides when:
- ✅ Value needs to be set at generation time
- ✅ Value might vary per batch but same within batch
- ✅ You want flexibility to change it programmatically
Tips
Timestamp Format: Use ISO 8601 format for timestamps:
created_at: default_value(value: "2024-01-15T10:30:00Z")Time Objects: You can also use Time objects in overrides:
overrides: { "created_at" => Time.parse("2024-01-15 10:30:00 UTC") }Multiple Fixed Fields: Set multiple fixed values:
overrides: { "created_at" => fixed_time, "status" => "active", "version" => 1 }
Complete Examples
E-commerce Order
Order:
id: uuid unique
order_number: sequence(start: 1000) unique
customer_id: uuid
items: array(OrderItem, 1..5)
subtotal: formula(items.map(&:total).sum)
tax_rate: number(0.05..0.15)
tax: formula(subtotal * tax_rate)
shipping: number(5..25)
total: formula(subtotal + tax + shipping)
status: enum(pending: 20%, processing: 30%, shipped: 40%, delivered: 10%)
created_at: timestamp
shipped_at: timestamp? optional
OrderItem:
product_id: uuid
product_name: product_name
quantity: number(1..10)
unit_price: number(10..100)
total: formula(quantity * unit_price)
User Profile
User:
id: uuid unique
username: username unique
email: email unique
first_name: first_name
last_name: last_name
full_name: template("{{first_name}} {{last_name}}")
age: number(18..100)
gender: gender
phone: phone? optional
address: address? optional
is_admin: boolean(true: 10%)
admin_level: enum(none: 90%, moderator: 8%, admin: 2%) if is_admin
created_at: timestamp
last_login: past_date(days: 30)? optional
Financial Transaction
Transaction:
id: uuid unique
transaction_id: sequence(start: "TXN-0001", format: "TXN-%04d") unique
from_account: account_number
to_account: account_number
amount: transaction_amount(min: 1.0, max: 10000.0)
currency: currency
fee: formula(amount * 0.02)
total: formula(amount + fee)
status: enum(pending: 10%, processing: 20%, completed: 65%, failed: 5%)
created_at: timestamp
completed_at: timestamp? if status == "completed"
Product Catalog
Product:
id: uuid unique
sku: sku unique
name: product_name
category: product_category
description: text(50..500)
price: number(10.0..999.99, precision: 2)
discount: discount(min: 0, max: 50)
final_price: formula(price * (1 - discount / 100))
in_stock: boolean(true: 80%)
stock_quantity: number(0..1000) if in_stock
tags: array(text, 3..10)
created_at: timestamp
Quick Reference
Field Syntax
field_name: type(arguments) modifiers
Common Modifiers
unique- Ensure unique valuesoptional- May be omitted?- Nullable (can be nil)if condition- Conditional field
Common Types
uuid- UUID v4name- Full nameemail- Email addressnumber(range)- Number in rangetext(length)- Text with lengthboolean- True/falseenum(values)- Enum with valuesarray(type, size)- Array of typeformula(expression)- Computed valuesequence(start, step)- Sequential values
Type Arguments
number(18..65)- Rangenumber(min: 10, max: 100)- Named argumentstext(10..50)- Length rangeenum(low: 50%, high: 50%)- With weightsarray(text, 3..10)- Element type and size
Next Steps
- Getting Started - Quick start guide
- DSL Reference - Complete syntax reference
- Type Reference - All available types
- Examples - Real-world examples
- Advanced Features - References, custom functions, streaming