synthra 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +4 -0
- data/.rubocop.yml +40 -0
- data/.yardopts +18 -0
- data/ACCESS_DOCS.md +116 -0
- data/ADVANCED_FEATURES_SUMMARY.md +245 -0
- data/CHANGELOG.md +498 -0
- data/CODE_OF_CONDUCT.md +122 -0
- data/CONTRIBUTING.md +307 -0
- data/DOCUMENTATION.md +204 -0
- data/DOCUMENTATION_GUIDE.md +194 -0
- data/LICENSE +22 -0
- data/README.md +1458 -0
- data/Rakefile +128 -0
- data/benchmark/README.md +591 -0
- data/benchmark/batch_generation.rb +63 -0
- data/benchmark/engine_comparison.rb +295 -0
- data/benchmark/single_record.rb +108 -0
- data/benchmark/streaming.rb +74 -0
- data/docs/ACTIVERECORD_INFERENCE.md +524 -0
- data/docs/API_SERVER.md +278 -0
- data/docs/CONFIG_FILE.md +315 -0
- data/docs/DATA_CONTRACTS.md +312 -0
- data/docs/ENHANCED_REPL.md +304 -0
- data/docs/FACTORY_BOT.md +271 -0
- data/docs/GITHUB_ACTION.md +420 -0
- data/docs/GRAPHQL_EXPORT.md +303 -0
- data/docs/GRAPHQL_FEDERATION.md +651 -0
- data/docs/HOW_TO_GENERATE_DOCS.md +252 -0
- data/docs/LSP.md +189 -0
- data/docs/MIGRATION_GENERATOR.md +547 -0
- data/docs/MOCK_SERVER.md +280 -0
- data/docs/NATIVE_ENGINE.md +298 -0
- data/docs/OPENAPI_EXPORT.md +252 -0
- data/docs/PERFORMANCE_MODE.md +228 -0
- data/docs/PERSONAS.md +547 -0
- data/docs/PROPERTY_TESTING.md +284 -0
- data/docs/PROTOBUF_EXPORT.md +276 -0
- data/docs/QUALITY_METRICS.md +555 -0
- data/docs/QUICK_REFERENCE.md +88 -0
- data/docs/RAILS_ENGINE.md +404 -0
- data/docs/RAILS_INTEGRATION.md +454 -0
- data/docs/README.md +175 -0
- data/docs/REPL_QUICK_REFERENCE.md +149 -0
- data/docs/RUST_INTEGRATION_GUIDE.md +1376 -0
- data/docs/SCENARIOS.md +576 -0
- data/docs/SECURITY_TESTING.md +318 -0
- data/docs/SNAPSHOT_TESTING.md +308 -0
- data/docs/STRING_TEXT_TYPES_REFERENCE.md +512 -0
- data/docs/TERRAFORM_EXPORT.md +447 -0
- data/docs/TIME_TRAVEL.md +507 -0
- data/docs/USAGE_GUIDE.md +980 -0
- data/docs/WEBHOOK_SIMULATOR.md +579 -0
- data/docs/advanced_features.md +183 -0
- data/docs/api_reference.md +153 -0
- data/docs/behaviors.md +138 -0
- data/docs/best_practices.md +191 -0
- data/docs/dsl_reference.md +172 -0
- data/docs/examples.md +162 -0
- data/docs/generation_modes.md +125 -0
- data/docs/getting_started.md +92 -0
- data/docs/index.html +177 -0
- data/docs/strategic/CLOUD_REGISTRY_ARCHITECTURE.md +268 -0
- data/docs/strategic/SIMD_OPTIMIZATION_STRATEGY.md +153 -0
- data/docs/troubleshooting.md +215 -0
- data/docs/type_reference.md +224 -0
- data/examples/hostile_mode_demo.rb +64 -0
- data/exe/synthra +6 -0
- data/ext/synthra_native/Cargo.lock +972 -0
- data/ext/synthra_native/Cargo.toml +46 -0
- data/ext/synthra_native/extconf.rb +41 -0
- data/ext/synthra_native/src/generator.rs +397 -0
- data/ext/synthra_native/src/lib.rs +229 -0
- data/ext/synthra_native/src/types.rs +1485 -0
- data/lib/generators/synthra/install_generator.rb +86 -0
- data/lib/generators/synthra/templates/api_response.dsl +15 -0
- data/lib/generators/synthra/templates/fake_data.rake +116 -0
- data/lib/generators/synthra/templates/synthra.yml +31 -0
- data/lib/generators/synthra/templates/synthra_support.rb +19 -0
- data/lib/generators/synthra/templates/user.dsl +11 -0
- data/lib/synthra/activerecord_inference.rb +394 -0
- data/lib/synthra/api.rb +381 -0
- data/lib/synthra/api_server.rb +718 -0
- data/lib/synthra/behaviors/applicator.rb +165 -0
- data/lib/synthra/behaviors/base.rb +155 -0
- data/lib/synthra/behaviors/close_connection.rb +60 -0
- data/lib/synthra/behaviors/deprecated.rb +100 -0
- data/lib/synthra/behaviors/failure.rb +69 -0
- data/lib/synthra/behaviors/latency.rb +103 -0
- data/lib/synthra/behaviors/partial_data.rb +83 -0
- data/lib/synthra/behaviors/randomize_order.rb +74 -0
- data/lib/synthra/behaviors/registry.rb +238 -0
- data/lib/synthra/behaviors/simulate_error.rb +79 -0
- data/lib/synthra/cli/commands/base.rb +105 -0
- data/lib/synthra/cli/commands/diff.rb +227 -0
- data/lib/synthra/cli/commands/docs.rb +67 -0
- data/lib/synthra/cli/commands/export.rb +150 -0
- data/lib/synthra/cli/commands/generate.rb +72 -0
- data/lib/synthra/cli/commands/import.rb +80 -0
- data/lib/synthra/cli/commands/lint.rb +53 -0
- data/lib/synthra/cli/commands/live.rb +57 -0
- data/lib/synthra/cli/commands/seed.rb +99 -0
- data/lib/synthra/cli/commands/validate.rb +28 -0
- data/lib/synthra/cli.rb +2471 -0
- data/lib/synthra/config_file.rb +129 -0
- data/lib/synthra/configuration.rb +281 -0
- data/lib/synthra/contracts_registry.rb +408 -0
- data/lib/synthra/database_seeder.rb +268 -0
- data/lib/synthra/deterministic_ids.rb +218 -0
- data/lib/synthra/documentation_generator.rb +414 -0
- data/lib/synthra/engine.rb +251 -0
- data/lib/synthra/errors.rb +1169 -0
- data/lib/synthra/export/base.rb +85 -0
- data/lib/synthra/export/csv.rb +101 -0
- data/lib/synthra/export/graphql.rb +266 -0
- data/lib/synthra/export/graphql_federation.rb +377 -0
- data/lib/synthra/export/graphviz.rb +258 -0
- data/lib/synthra/export/javascript.rb +327 -0
- data/lib/synthra/export/json_data.rb +61 -0
- data/lib/synthra/export/json_schema.rb +290 -0
- data/lib/synthra/export/openapi.rb +514 -0
- data/lib/synthra/export/protobuf.rb +483 -0
- data/lib/synthra/export/python.rb +560 -0
- data/lib/synthra/export/sql.rb +381 -0
- data/lib/synthra/export/sql_insert.rb +152 -0
- data/lib/synthra/export/terraform.rb +501 -0
- data/lib/synthra/export/type_mapping.rb +389 -0
- data/lib/synthra/export/typescript.rb +300 -0
- data/lib/synthra/export/xml_data.rb +104 -0
- data/lib/synthra/export/yaml_data.rb +56 -0
- data/lib/synthra/export.rb +404 -0
- data/lib/synthra/factory_bot_integration.rb +157 -0
- data/lib/synthra/field.rb +440 -0
- data/lib/synthra/functions/registry.rb +104 -0
- data/lib/synthra/generator/context.rb +372 -0
- data/lib/synthra/generator/engine.rb +336 -0
- data/lib/synthra/generator/faker_adapter.rb +425 -0
- data/lib/synthra/generator/modes.rb +444 -0
- data/lib/synthra/generator/resolver.rb +256 -0
- data/lib/synthra/generator/rng.rb +273 -0
- data/lib/synthra/generator/streamer.rb +63 -0
- data/lib/synthra/generator/uniqueness.rb +118 -0
- data/lib/synthra/initializer.rb +97 -0
- data/lib/synthra/limits.rb +279 -0
- data/lib/synthra/live_preview.rb +518 -0
- data/lib/synthra/loader_config.rb +78 -0
- data/lib/synthra/lsp/server.rb +689 -0
- data/lib/synthra/migration_generator.rb +301 -0
- data/lib/synthra/mixin.rb +174 -0
- data/lib/synthra/mock_server.rb +458 -0
- data/lib/synthra/native_engine.rb +304 -0
- data/lib/synthra/openapi_importer.rb +228 -0
- data/lib/synthra/output/json_formatter.rb +40 -0
- data/lib/synthra/output/ndjson_formatter.rb +55 -0
- data/lib/synthra/parser/ast.rb +1287 -0
- data/lib/synthra/parser/lexer.rb +1152 -0
- data/lib/synthra/parser/parser.rb +1664 -0
- data/lib/synthra/parser/tokens.rb +461 -0
- data/lib/synthra/performance_mode.rb +364 -0
- data/lib/synthra/personas.rb +397 -0
- data/lib/synthra/property_testing.rb +172 -0
- data/lib/synthra/quality_metrics.rb +405 -0
- data/lib/synthra/rails_test_helper.rb +248 -0
- data/lib/synthra/registry.rb +533 -0
- data/lib/synthra/relationships.rb +193 -0
- data/lib/synthra/repl/enhanced_repl.rb +607 -0
- data/lib/synthra/repl/formatter.rb +191 -0
- data/lib/synthra/scenarios.rb +423 -0
- data/lib/synthra/schema.rb +605 -0
- data/lib/synthra/schema_inheritance.rb +104 -0
- data/lib/synthra/schema_versioning.rb +212 -0
- data/lib/synthra/snapshot_testing.rb +199 -0
- data/lib/synthra/time_travel.rb +338 -0
- data/lib/synthra/type_definitions.rb +274 -0
- data/lib/synthra/types/address_location/addresses.rb +125 -0
- data/lib/synthra/types/address_location/airports.rb +200 -0
- data/lib/synthra/types/address_location/banks_hospitals.rb +207 -0
- data/lib/synthra/types/address_location/locations.rb +406 -0
- data/lib/synthra/types/base.rb +48 -0
- data/lib/synthra/types/commerce_products/commerce.rb +119 -0
- data/lib/synthra/types/commerce_products/companies.rb +71 -0
- data/lib/synthra/types/commerce_products/construction.rb +126 -0
- data/lib/synthra/types/commerce_products/products.rb +205 -0
- data/lib/synthra/types/core/collections.rb +513 -0
- data/lib/synthra/types/core/defaults.rb +76 -0
- data/lib/synthra/types/core/enums.rb +102 -0
- data/lib/synthra/types/core/identifiers.rb +445 -0
- data/lib/synthra/types/core/primitives.rb +586 -0
- data/lib/synthra/types/core/references.rb +466 -0
- data/lib/synthra/types/core/sequences.rb +304 -0
- data/lib/synthra/types/crypto/crypto.rb +162 -0
- data/lib/synthra/types/date_time/dates.rb +387 -0
- data/lib/synthra/types/finance_banking/banking.rb +424 -0
- data/lib/synthra/types/finance_banking/credit_cards.rb +24 -0
- data/lib/synthra/types/finance_banking/identifiers.rb +93 -0
- data/lib/synthra/types/formula.rb +184 -0
- data/lib/synthra/types/health_medical/medical.rb +150 -0
- data/lib/synthra/types/hostile_payloads.rb +122 -0
- data/lib/synthra/types/json_array.rb +51 -0
- data/lib/synthra/types/media_entertainment/media.rb +62 -0
- data/lib/synthra/types/naughty_string.rb +30 -0
- data/lib/synthra/types/personal_names/chinese.rb +41 -0
- data/lib/synthra/types/personal_names/identifiers.rb +187 -0
- data/lib/synthra/types/personal_names/names.rb +246 -0
- data/lib/synthra/types/personal_names/national_id.rb +89 -0
- data/lib/synthra/types/personal_names/titles_suffixes.rb +41 -0
- data/lib/synthra/types/regex.rb +248 -0
- data/lib/synthra/types/registry.rb +182 -0
- data/lib/synthra/types/repeating_element.rb +50 -0
- data/lib/synthra/types/scenario.rb +29 -0
- data/lib/synthra/types/technology_internet/apps.rb +67 -0
- data/lib/synthra/types/technology_internet/communication.rb +162 -0
- data/lib/synthra/types/technology_internet/devices.rb +80 -0
- data/lib/synthra/types/technology_internet/formats.rb +139 -0
- data/lib/synthra/types/technology_internet/networking.rb +143 -0
- data/lib/synthra/types/template.rb +92 -0
- data/lib/synthra/types/text_content/business.rb +128 -0
- data/lib/synthra/types/text_content/colors.rb +70 -0
- data/lib/synthra/types/text_content/misc.rb +237 -0
- data/lib/synthra/types/text_content/security.rb +130 -0
- data/lib/synthra/types/text_content/text_generation.rb +532 -0
- data/lib/synthra/types/travel/travel.rb +150 -0
- data/lib/synthra/utils/string_distance.rb +133 -0
- data/lib/synthra/validator/dsl_validator.rb +339 -0
- data/lib/synthra/validator/path_validator.rb +615 -0
- data/lib/synthra/version.rb +35 -0
- data/lib/synthra/webhook_simulator.rb +341 -0
- data/lib/synthra.rb +259 -0
- data/schemas/address.dsl +16 -0
- data/schemas/api_response.dsl +8 -0
- data/schemas/error_payload.dsl +9 -0
- data/schemas/order.dsl +10 -0
- data/schemas/order_item.dsl +8 -0
- data/schemas/payment.dsl +11 -0
- data/schemas/social_post.dsl +13 -0
- data/schemas/user.dsl +10 -0
- data/scripts/batch_fix_all.rb +96 -0
- data/scripts/delete_old_files.rb +32 -0
- data/scripts/fix_all_grouped_files.rb +137 -0
- data/scripts/fix_all_indentation.rb +48 -0
- data/scripts/fix_all_syntax.rb +124 -0
- data/scripts/fix_grouped_files.rb +184 -0
- data/scripts/fix_syntax_errors.rb +108 -0
- data/scripts/group_domain_types.rb +112 -0
- data/scripts/group_domain_types_fixed.rb +150 -0
- data/scripts/merge_domains_to_one_file.rb +68 -0
- data/scripts/move_existing_types.rb +130 -0
- data/scripts/split_grouped_types.rb +142 -0
- data/tech_docs/README.md +134 -0
- data/tech_docs/advanced/streaming.md +405 -0
- data/tech_docs/advanced/thread_safety.md +253 -0
- data/tech_docs/api/overview.md +485 -0
- data/tech_docs/appendices/type_chart.md +193 -0
- data/tech_docs/basic_concepts.md +383 -0
- data/tech_docs/behaviors/overview.md +346 -0
- data/tech_docs/dsl/complex_types.md +790 -0
- data/tech_docs/dsl/core_types.md +464 -0
- data/tech_docs/dsl/datetime_types.md +325 -0
- data/tech_docs/dsl/field_modifiers.md +414 -0
- data/tech_docs/dsl/grammar.md +431 -0
- data/tech_docs/dsl/schema_definition.md +399 -0
- data/tech_docs/export/README.md +276 -0
- data/tech_docs/installation.md +273 -0
- data/tech_docs/integration/ci_cd.md +707 -0
- data/tech_docs/integration/ci_cd_guide.md +579 -0
- data/tech_docs/integration/factory_bot.md +485 -0
- data/tech_docs/integration/rails.md +630 -0
- data/tech_docs/integration/rspec.md +449 -0
- data/tech_docs/modes/overview.md +350 -0
- data/tech_docs/performance/NATIVE_RUST_EXTENSION.md +1270 -0
- data/tech_docs/performance/OPTIMIZATION_GUIDE.md +901 -0
- data/tech_docs/quick_start.md +256 -0
- data/tech_docs/templates/README.md +805 -0
- data/tech_docs/tutorials/advanced.md +371 -0
- data/tech_docs/tutorials/getting_started.md +189 -0
- data/tech_docs/tutorials/intermediate.md +231 -0
- data/tech_docs/tutorials/template_gallery.md +569 -0
- data/tech_example/01_basic_usage.rb +238 -0
- data/tech_example/02_types_demo.rb +336 -0
- data/tech_example/04_cli_usage.md +429 -0
- data/tech_example/05_database_seeding.rb +283 -0
- data/tech_example/07_rspec_integration.rb +359 -0
- data/tech_example/10_custom_types.rb +387 -0
- data/tech_example/12_behaviors.rb +372 -0
- data/tech_example/13_twitter_dm_example.rb +120 -0
- data/tech_example/14_exact_json_structure.rb +253 -0
- data/tech_example/16_lsp_server.rb +56 -0
- data/tech_example/17_property_testing.rb +92 -0
- data/tech_example/18_enhanced_repl.rb +120 -0
- data/tech_example/19_security_fuzzing.rb +145 -0
- data/tech_example/NEW_FEATURES.md +169 -0
- data/tech_example/README.md +118 -0
- data/tech_example/schemas/api_response.dsl +123 -0
- data/tech_example/schemas/ecommerce.dsl +121 -0
- data/tech_example/schemas/twitter_dm.dsl +109 -0
- data/tech_example/schemas/user.dsl +36 -0
- data/vscode-extension/README.md +246 -0
- data/vscode-extension/language-configuration.json +31 -0
- data/vscode-extension/package.json +55 -0
- data/vscode-extension/snippets/fakedatadsl.json +198 -0
- data/vscode-extension/syntaxes/fakedatadsl.tmLanguage.json +128 -0
- metadata +478 -0
data/docs/TIME_TRAVEL.md
ADDED
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
# Time Travel for Data
|
|
2
|
+
|
|
3
|
+
FakeDataDSL's Time Travel feature lets you generate data **as it would have existed at a specific point in time**, or create realistic progressions of data over time. Perfect for testing time-sensitive features, historical reports, and temporal queries.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
# Generate data from 30 days ago
|
|
9
|
+
FakeDataDSL::TimeTravel.at(30.days.ago) do
|
|
10
|
+
user = FakeDataDSL.generate("User")
|
|
11
|
+
# user[:created_at] is 30 days ago
|
|
12
|
+
# user[:updated_at] is 30 days ago
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Generate data progression
|
|
16
|
+
orders = FakeDataDSL::TimeTravel.progression("Order",
|
|
17
|
+
from: 1.year.ago,
|
|
18
|
+
to: Time.current,
|
|
19
|
+
count: 12
|
|
20
|
+
)
|
|
21
|
+
# 12 orders spread across the past year
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Basic Usage
|
|
25
|
+
|
|
26
|
+
### Generate at Specific Time
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
# All timestamps generated within the block are relative to the travel time
|
|
30
|
+
FakeDataDSL::TimeTravel.at(Date.new(2025, 1, 1)) do
|
|
31
|
+
user = FakeDataDSL.generate("User")
|
|
32
|
+
# user[:created_at] => 2025-01-01 (random time on that day)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# With specific time
|
|
36
|
+
FakeDataDSL::TimeTravel.at(Time.new(2025, 6, 15, 14, 30, 0)) do
|
|
37
|
+
event = FakeDataDSL.generate("Event")
|
|
38
|
+
# event[:scheduled_at] => around 2025-06-15 14:30
|
|
39
|
+
end
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Generate Between Dates
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
# Timestamps fall randomly within the range
|
|
46
|
+
FakeDataDSL::TimeTravel.between(1.month.ago, Time.current) do
|
|
47
|
+
log = FakeDataDSL.generate("AuditLog")
|
|
48
|
+
# log[:created_at] is somewhere in the last month
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Generate multiple records spread across range
|
|
52
|
+
records = FakeDataDSL::TimeTravel.between(1.year.ago, Time.current) do
|
|
53
|
+
10.times.map { FakeDataDSL.generate("Transaction") }
|
|
54
|
+
end
|
|
55
|
+
# Transactions spread randomly across the year
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Time Progressions
|
|
59
|
+
|
|
60
|
+
### Linear Progression
|
|
61
|
+
|
|
62
|
+
Generate records evenly distributed over time:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
# 12 monthly reports
|
|
66
|
+
reports = FakeDataDSL::TimeTravel.progression("MonthlyReport",
|
|
67
|
+
from: 1.year.ago,
|
|
68
|
+
to: Time.current,
|
|
69
|
+
count: 12
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
reports.each_with_index do |report, i|
|
|
73
|
+
puts "#{report[:created_at].strftime('%B %Y')}: $#{report[:revenue]}"
|
|
74
|
+
end
|
|
75
|
+
# January 2025: $12,345
|
|
76
|
+
# February 2025: $13,456
|
|
77
|
+
# ...
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Custom Intervals
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
# Daily records
|
|
84
|
+
daily = FakeDataDSL::TimeTravel.progression("DailyMetric",
|
|
85
|
+
from: 7.days.ago,
|
|
86
|
+
to: Time.current,
|
|
87
|
+
interval: 1.day
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# Hourly records
|
|
91
|
+
hourly = FakeDataDSL::TimeTravel.progression("HourlyLog",
|
|
92
|
+
from: 24.hours.ago,
|
|
93
|
+
to: Time.current,
|
|
94
|
+
interval: 1.hour
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Weekly records
|
|
98
|
+
weekly = FakeDataDSL::TimeTravel.progression("WeeklyDigest",
|
|
99
|
+
from: 3.months.ago,
|
|
100
|
+
to: Time.current,
|
|
101
|
+
interval: 1.week
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### With Variations
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
# Add realistic variation to timestamps
|
|
109
|
+
orders = FakeDataDSL::TimeTravel.progression("Order",
|
|
110
|
+
from: 1.month.ago,
|
|
111
|
+
to: Time.current,
|
|
112
|
+
count: 30,
|
|
113
|
+
jitter: 6.hours # ±6 hours variation
|
|
114
|
+
)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Time Series Data
|
|
118
|
+
|
|
119
|
+
### Generate Time Series
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
# Stock prices over time
|
|
123
|
+
prices = FakeDataDSL::TimeTravel.time_series("StockPrice",
|
|
124
|
+
from: 1.year.ago,
|
|
125
|
+
to: Time.current,
|
|
126
|
+
interval: 1.day,
|
|
127
|
+
fields: {
|
|
128
|
+
open: { type: :trending, start: 100, volatility: 0.02 },
|
|
129
|
+
close: { type: :trending, start: 100, volatility: 0.02 },
|
|
130
|
+
volume: { type: :random, range: 1000..10000 }
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Trending Data
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
# Metrics that grow over time
|
|
139
|
+
FakeDataDSL::TimeTravel.time_series("UserMetrics",
|
|
140
|
+
from: 1.year.ago,
|
|
141
|
+
to: Time.current,
|
|
142
|
+
interval: 1.week,
|
|
143
|
+
trend: :growth, # :growth, :decline, :stable, :seasonal
|
|
144
|
+
growth_rate: 0.05 # 5% weekly growth
|
|
145
|
+
)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Seasonal Patterns
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
# E-commerce orders with seasonal variation
|
|
152
|
+
orders = FakeDataDSL::TimeTravel.time_series("Order",
|
|
153
|
+
from: 1.year.ago,
|
|
154
|
+
to: Time.current,
|
|
155
|
+
interval: 1.day,
|
|
156
|
+
seasonality: {
|
|
157
|
+
pattern: :weekly,
|
|
158
|
+
peaks: [5, 6], # Saturday, Sunday
|
|
159
|
+
peak_multiplier: 2.5
|
|
160
|
+
}
|
|
161
|
+
)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Schema Annotations
|
|
165
|
+
|
|
166
|
+
### In DSL Files
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
# db/schemas/historical_order.dsl
|
|
170
|
+
HistoricalOrder:
|
|
171
|
+
@time_travel -365.days..-1.day
|
|
172
|
+
|
|
173
|
+
id: uuid
|
|
174
|
+
created_at: timestamp
|
|
175
|
+
shipped_at: timestamp(after: created_at, within: 7.days)
|
|
176
|
+
delivered_at: timestamp(after: shipped_at, within: 14.days)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Relative Timestamps
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
# Timestamps that depend on each other
|
|
183
|
+
Order:
|
|
184
|
+
id: uuid
|
|
185
|
+
created_at: timestamp
|
|
186
|
+
confirmed_at: timestamp(after: created_at, within: 1.hour)
|
|
187
|
+
shipped_at: timestamp(after: confirmed_at, within: 3.days)
|
|
188
|
+
delivered_at: timestamp(after: shipped_at, within: 7.days)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
When time traveling:
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
FakeDataDSL::TimeTravel.at(30.days.ago) do
|
|
195
|
+
order = FakeDataDSL.generate("Order")
|
|
196
|
+
# order[:created_at] => ~30 days ago
|
|
197
|
+
# order[:confirmed_at] => ~30 days ago (within 1 hour of created_at)
|
|
198
|
+
# order[:shipped_at] => ~27 days ago (within 3 days of confirmed_at)
|
|
199
|
+
# order[:delivered_at] => ~20 days ago (within 7 days of shipped_at)
|
|
200
|
+
end
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Nested Time Contexts
|
|
204
|
+
|
|
205
|
+
### Time Blocks
|
|
206
|
+
|
|
207
|
+
```ruby
|
|
208
|
+
FakeDataDSL::TimeTravel.at(1.year.ago) do
|
|
209
|
+
old_user = FakeDataDSL.generate("User")
|
|
210
|
+
|
|
211
|
+
FakeDataDSL::TimeTravel.at(6.months.ago) do
|
|
212
|
+
mid_user = FakeDataDSL.generate("User")
|
|
213
|
+
|
|
214
|
+
FakeDataDSL::TimeTravel.at(Time.current) do
|
|
215
|
+
new_user = FakeDataDSL.generate("User")
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Progressive Nesting
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
# User signs up, then creates content over time
|
|
225
|
+
FakeDataDSL::TimeTravel.at(1.year.ago) do
|
|
226
|
+
user = FakeDataDSL.generate("User")
|
|
227
|
+
|
|
228
|
+
# User writes posts over the following months
|
|
229
|
+
posts = (1..12).map do |month|
|
|
230
|
+
FakeDataDSL::TimeTravel.at(1.year.ago + month.months) do
|
|
231
|
+
FakeDataDSL.generate("Post", user_id: user[:id])
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Integration with Scenarios
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
FakeDataDSL::Scenarios.define(:historical_activity) do
|
|
241
|
+
# User from a year ago
|
|
242
|
+
let(:veteran_user) {
|
|
243
|
+
FakeDataDSL::TimeTravel.at(1.year.ago) do
|
|
244
|
+
create(:user, name: "Veteran User")
|
|
245
|
+
end
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
# Their posts over time
|
|
249
|
+
let(:posts) {
|
|
250
|
+
FakeDataDSL::TimeTravel.progression("Post",
|
|
251
|
+
from: 11.months.ago,
|
|
252
|
+
to: Time.current,
|
|
253
|
+
count: 24
|
|
254
|
+
).map { |p| p.merge(user_id: veteran_user[:id]) }
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
# Recent new user
|
|
258
|
+
let(:new_user) {
|
|
259
|
+
FakeDataDSL::TimeTravel.at(1.week.ago) do
|
|
260
|
+
create(:user, name: "New User")
|
|
261
|
+
end
|
|
262
|
+
}
|
|
263
|
+
end
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Time Zone Handling
|
|
267
|
+
|
|
268
|
+
### Explicit Time Zones
|
|
269
|
+
|
|
270
|
+
```ruby
|
|
271
|
+
# Generate in specific timezone
|
|
272
|
+
FakeDataDSL::TimeTravel.at(Time.current, zone: "America/New_York") do
|
|
273
|
+
event = FakeDataDSL.generate("Event")
|
|
274
|
+
# event[:start_time] is in Eastern time
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Generate in user's timezone
|
|
278
|
+
FakeDataDSL::TimeTravel.at(Time.current, zone: user.time_zone) do
|
|
279
|
+
notification = FakeDataDSL.generate("Notification")
|
|
280
|
+
end
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Multiple Zones
|
|
284
|
+
|
|
285
|
+
```ruby
|
|
286
|
+
# Generate events across timezones
|
|
287
|
+
offices = ["America/New_York", "Europe/London", "Asia/Tokyo"]
|
|
288
|
+
events = offices.map do |zone|
|
|
289
|
+
FakeDataDSL::TimeTravel.at(Time.current, zone: zone) do
|
|
290
|
+
FakeDataDSL.generate("Meeting", timezone: zone)
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Testing Patterns
|
|
296
|
+
|
|
297
|
+
### Historical Data Tests
|
|
298
|
+
|
|
299
|
+
```ruby
|
|
300
|
+
RSpec.describe "ReportGenerator" do
|
|
301
|
+
it "generates monthly reports" do
|
|
302
|
+
# Create historical data
|
|
303
|
+
FakeDataDSL::TimeTravel.progression("Order",
|
|
304
|
+
from: 3.months.ago,
|
|
305
|
+
to: Time.current,
|
|
306
|
+
count: 90
|
|
307
|
+
).each { |order| Order.create!(order) }
|
|
308
|
+
|
|
309
|
+
report = ReportGenerator.monthly_summary(2.months.ago)
|
|
310
|
+
expect(report.total_orders).to be > 0
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Time-Sensitive Features
|
|
316
|
+
|
|
317
|
+
```ruby
|
|
318
|
+
RSpec.describe "SubscriptionExpiry" do
|
|
319
|
+
it "expires subscriptions correctly" do
|
|
320
|
+
# Create subscription that expires today
|
|
321
|
+
subscription = FakeDataDSL::TimeTravel.at(31.days.ago) do
|
|
322
|
+
Subscription.create!(
|
|
323
|
+
FakeDataDSL.generate("Subscription", duration: 30.days)
|
|
324
|
+
)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
expect(subscription).to be_expired
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Aging Data
|
|
333
|
+
|
|
334
|
+
```ruby
|
|
335
|
+
RSpec.describe "DataRetention" do
|
|
336
|
+
it "purges old records" do
|
|
337
|
+
# Create records at various ages
|
|
338
|
+
old_record = FakeDataDSL::TimeTravel.at(2.years.ago) do
|
|
339
|
+
AuditLog.create!(FakeDataDSL.generate("AuditLog"))
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
recent_record = FakeDataDSL::TimeTravel.at(1.month.ago) do
|
|
343
|
+
AuditLog.create!(FakeDataDSL.generate("AuditLog"))
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
DataRetention.purge_old_records!
|
|
347
|
+
|
|
348
|
+
expect { old_record.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
|
349
|
+
expect(recent_record.reload).to be_present
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## API Reference
|
|
355
|
+
|
|
356
|
+
### TimeTravel.at
|
|
357
|
+
|
|
358
|
+
```ruby
|
|
359
|
+
FakeDataDSL::TimeTravel.at(time, options = {}, &block)
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**Parameters:**
|
|
363
|
+
- `time` - Time/DateTime/Date to travel to
|
|
364
|
+
- `options[:zone]` - Time zone name (default: system)
|
|
365
|
+
- `block` - Block where all timestamps are relative to `time`
|
|
366
|
+
|
|
367
|
+
**Returns:** Result of the block
|
|
368
|
+
|
|
369
|
+
### TimeTravel.between
|
|
370
|
+
|
|
371
|
+
```ruby
|
|
372
|
+
FakeDataDSL::TimeTravel.between(start_time, end_time, options = {}, &block)
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
**Parameters:**
|
|
376
|
+
- `start_time` - Start of the time range
|
|
377
|
+
- `end_time` - End of the time range
|
|
378
|
+
- `options[:distribution]` - :uniform (default), :weighted_recent, :weighted_old
|
|
379
|
+
- `block` - Block where timestamps fall within range
|
|
380
|
+
|
|
381
|
+
**Returns:** Result of the block
|
|
382
|
+
|
|
383
|
+
### TimeTravel.progression
|
|
384
|
+
|
|
385
|
+
```ruby
|
|
386
|
+
FakeDataDSL::TimeTravel.progression(schema_name, options = {})
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
**Parameters:**
|
|
390
|
+
- `schema_name` - Name of the schema to generate
|
|
391
|
+
- `options[:from]` - Start time (required)
|
|
392
|
+
- `options[:to]` - End time (required)
|
|
393
|
+
- `options[:count]` - Number of records
|
|
394
|
+
- `options[:interval]` - Fixed interval between records
|
|
395
|
+
- `options[:jitter]` - Random variation for timestamps
|
|
396
|
+
- `options[:overrides]` - Field overrides
|
|
397
|
+
|
|
398
|
+
**Returns:** Array of generated records
|
|
399
|
+
|
|
400
|
+
### TimeTravel.time_series
|
|
401
|
+
|
|
402
|
+
```ruby
|
|
403
|
+
FakeDataDSL::TimeTravel.time_series(schema_name, options = {})
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
**Parameters:**
|
|
407
|
+
- `schema_name` - Name of the schema
|
|
408
|
+
- `options[:from]` - Start time
|
|
409
|
+
- `options[:to]` - End time
|
|
410
|
+
- `options[:interval]` - Time between data points
|
|
411
|
+
- `options[:trend]` - :growth, :decline, :stable, :seasonal
|
|
412
|
+
- `options[:growth_rate]` - Rate of change per interval
|
|
413
|
+
- `options[:seasonality]` - Seasonal pattern configuration
|
|
414
|
+
- `options[:fields]` - Field-specific configurations
|
|
415
|
+
|
|
416
|
+
**Returns:** Array of time series data points
|
|
417
|
+
|
|
418
|
+
## Configuration
|
|
419
|
+
|
|
420
|
+
```ruby
|
|
421
|
+
FakeDataDSL::TimeTravel.configure do |config|
|
|
422
|
+
# Default time zone
|
|
423
|
+
config.default_zone = "UTC"
|
|
424
|
+
|
|
425
|
+
# Default jitter for progressions
|
|
426
|
+
config.default_jitter = 0
|
|
427
|
+
|
|
428
|
+
# Whether to freeze time during generation
|
|
429
|
+
config.freeze_time = true
|
|
430
|
+
|
|
431
|
+
# Hook for before/after time travel
|
|
432
|
+
config.before_travel = ->(time) { Rails.logger.debug "Traveling to #{time}" }
|
|
433
|
+
config.after_travel = ->(time) { Rails.logger.debug "Returned from #{time}" }
|
|
434
|
+
end
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## Best Practices
|
|
438
|
+
|
|
439
|
+
### 1. Use Relative Times
|
|
440
|
+
|
|
441
|
+
```ruby
|
|
442
|
+
# Good: Relative times adapt to when tests run
|
|
443
|
+
FakeDataDSL::TimeTravel.at(30.days.ago) { ... }
|
|
444
|
+
|
|
445
|
+
# Avoid: Hardcoded dates become stale
|
|
446
|
+
FakeDataDSL::TimeTravel.at(Date.new(2025, 1, 1)) { ... }
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### 2. Be Explicit About Relationships
|
|
450
|
+
|
|
451
|
+
```ruby
|
|
452
|
+
# Good: Clear temporal relationships
|
|
453
|
+
Order:
|
|
454
|
+
created_at: timestamp
|
|
455
|
+
shipped_at: timestamp(after: created_at) # Explicit dependency
|
|
456
|
+
|
|
457
|
+
# Avoid: Implicit relationships that may not hold
|
|
458
|
+
Order:
|
|
459
|
+
created_at: timestamp
|
|
460
|
+
shipped_at: timestamp # Could be before created_at!
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### 3. Test Edge Cases
|
|
464
|
+
|
|
465
|
+
```ruby
|
|
466
|
+
# Test around daylight saving time
|
|
467
|
+
FakeDataDSL::TimeTravel.at(Time.new(2025, 3, 9, 2, 30, 0, "-05:00")) do
|
|
468
|
+
# DST transition edge case
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
# Test at year boundaries
|
|
472
|
+
FakeDataDSL::TimeTravel.at(Time.new(2025, 12, 31, 23, 59, 59)) do
|
|
473
|
+
# Year-end edge case
|
|
474
|
+
end
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
## Troubleshooting
|
|
478
|
+
|
|
479
|
+
### Timestamps Not Affected
|
|
480
|
+
|
|
481
|
+
```ruby
|
|
482
|
+
# Make sure you're using the block
|
|
483
|
+
FakeDataDSL::TimeTravel.at(1.month.ago) do
|
|
484
|
+
FakeDataDSL.generate("User") # ✓ Affected
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
FakeDataDSL.generate("User") # ✗ Not affected (outside block)
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Time Zone Confusion
|
|
491
|
+
|
|
492
|
+
```ruby
|
|
493
|
+
# Always be explicit when time zones matter
|
|
494
|
+
FakeDataDSL::TimeTravel.at(
|
|
495
|
+
Time.current.in_time_zone("America/New_York"),
|
|
496
|
+
zone: "America/New_York"
|
|
497
|
+
) do
|
|
498
|
+
# Clear time zone handling
|
|
499
|
+
end
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
## See Also
|
|
503
|
+
|
|
504
|
+
- [Scenarios](SCENARIOS.md)
|
|
505
|
+
- [Generation Modes](generation_modes.md)
|
|
506
|
+
- [Timestamp Type Reference](type_reference.md#timestamp)
|
|
507
|
+
- [Testing Best Practices](best_practices.md)
|