Class: Tina4::FakeData
- Inherits:
-
Object
- Object
- Tina4::FakeData
- Defined in:
- lib/tina4/seeder.rb
Overview
Zero-dependency fake data generator with deterministic seeding. Uses Ruby's built-in Random for reproducible data generation.
Determinism is PER-LANGUAGE, not cross-language (SEED-DETERMINISM-PERLANG):
FakeData.new(seed: 42) reproduces the identical sequence on every run
within Ruby, but the same seed on Python/PHP/Node's FakeData will NOT
produce the same values -- each language uses its own PRNG (Ruby's
Random, Python's Mersenne Twister, PHP's per-instance Mt19937, Node's
mulberry32). There is no shared cross-language PRNG, and hand-rolling one
would add cost for no real benefit -- use a seed to make ONE language's
run reproducible, never to compare output across languages.
NOT FOR SECRETS (SEED-SECRETS-DOC): this is a non-cryptographic PRNG meant
for realistic-looking fixtures and test data. Never use it to generate API
keys, passwords, tokens, or anything else that must be unguessable -- use
SecureRandom (or Tina4::Auth for password hashing) instead.
Constant Summary collapse
- FIRST_NAMES =
%w[ James Mary Robert Patricia John Jennifer Michael Linda David Elizabeth William Barbara Richard Susan Joseph Jessica Thomas Sarah Charles Karen Christopher Lisa Daniel Nancy Matthew Betty Anthony Margaret Mark Sandra Donald Ashley Steven Dorothy Paul Kimberly Andrew Emily Joshua Donna Kenneth Michelle Kevin Carol Brian Amanda George Melissa Timothy Deborah Ronald Stephanie Edward Rebecca Jason Sharon Jeffrey Laura Ryan Cynthia Jacob Kathleen Gary Amy Nicholas Angela Eric Shirley Jonathan Anna Stephen Brenda Larry Pamela Justin Emma Scott Nicole Brandon Helen Benjamin Samantha Samuel Katherine Raymond Christine Gregory Debra Frank Rachel Alexander Carolyn Patrick Janet Jack Catherine Andre Aisha Wei Yuki Carlos Fatima Raj Priya Mohammed Sophia Liam Olivia Noah Ava Ethan Mia Lucas Isabella Mason Charlotte Logan Amelia Aiden Harper ].freeze
- LAST_NAMES =
%w[ Smith Johnson Williams Brown Jones Garcia Miller Davis Rodriguez Martinez Hernandez Lopez Gonzalez Wilson Anderson Thomas Taylor Moore Jackson Martin Lee Perez Thompson White Harris Sanchez Clark Ramirez Lewis Robinson Walker Young Allen King Wright Scott Torres Nguyen Hill Flores Green Adams Nelson Baker Hall Rivera Campbell Mitchell Carter Roberts Gomez Phillips Evans Turner Diaz Parker Cruz Edwards Collins Reyes Stewart Morris Morales Murphy Cook Rogers Gutierrez Ortiz Morgan Cooper Peterson Bailey Reed Kelly Howard Ramos Kim Cox Ward Richardson Watson Brooks Chavez Wood James Bennett Gray Mendoza Ruiz Hughes Price Alvarez Castillo Sanders Patel Müller Nakamura Singh Chen Silva Ali Okafor ].freeze
- WORDS =
%w[ the be to of and a in that have it for not on with he as you do at this but his by from they we say her she or an will my one all would there their what so up out if about who get which go me when make can like time no just him know take people into year your good some could them see other than then now look only come its over think also back after use two how our work first well way even new want because any these give day most us great small large every found still between name should home big end along each much both help line turn move thing right same old better point long real system data report order product service customer account payment record total status market world company project team value process business group result information development management quality performance technology support research design program network ].freeze
- CITIES =
[ "New York", "London", "Tokyo", "Paris", "Berlin", "Sydney", "Toronto", "Mumbai", "São Paulo", "Cairo", "Lagos", "Dubai", "Singapore", "Hong Kong", "Seoul", "Mexico City", "Bangkok", "Istanbul", "Moscow", "Rome", "Barcelona", "Amsterdam", "Nairobi", "Cape Town", "Johannesburg", "Buenos Aires", "Lima", "Santiago", "Jakarta", "Manila", "Kuala Lumpur", "Auckland", "Vancouver", "Chicago", "San Francisco", "Los Angeles", "Miami", "Boston", "Seattle", "Denver" ].freeze
- COUNTRIES =
[ "United States", "United Kingdom", "Canada", "Australia", "Germany", "France", "Japan", "Brazil", "India", "South Africa", "Nigeria", "Egypt", "Kenya", "Mexico", "Argentina", "Chile", "Colombia", "Spain", "Italy", "Netherlands", "Sweden", "Norway", "Denmark", "Finland", "Switzerland", "Belgium", "Austria", "New Zealand", "Singapore", "South Korea", "Thailand", "Indonesia", "Philippines", "Vietnam", "Malaysia", "United Arab Emirates", "Saudi Arabia", "Turkey", "Poland" ].freeze
- DOMAINS =
%w[ example.com test.org sample.net demo.io mail.com inbox.org webmail.net company.com corp.io biz.net ].freeze
- STREETS =
%w[Main Oak Pine Maple Cedar Elm Park Lake Hill River Church Market King Queen High].freeze
- STREET_TYPES =
%w[Street Avenue Road Drive Lane Boulevard Way Place].freeze
- COMPANY_WORDS =
%w[Tech Global Apex Nova Core Prime Next Blue Bright Smart Swift Peak Fusion Pulse Vertex].freeze
- COMPANY_SUFFIXES =
%w[Inc Corp Ltd LLC Group Solutions Systems Labs].freeze
- JOB_TITLES =
[ "Software Engineer", "Product Manager", "Designer", "Data Analyst", "DevOps Engineer", "CEO", "CTO", "Sales Manager", "Marketing Lead", "Accountant", "Operations Manager", "QA Engineer", "UX Researcher", "Support Specialist", "HR Manager", "Technical Writer" ].freeze
- CURRENCIES =
%w[USD EUR GBP JPY CAD AUD CHF ZAR INR CNY].freeze
- CREDIT_CARD_PREFIXES =
%w[4111 4242 5500 5105].freeze
- PRODUCT_ADJECTIVES =
Product-name vocabulary (adjective + noun). Seeds a generic +name+/+full_name+ column on a product-ish table with "Wireless Keyboard" instead of a person name. Mirrors the Python master's word banks.
%w[ Wireless Organic Premium Classic Eco Smart Portable Deluxe Compact Rustic Handcrafted Vintage Modern Ergonomic Stainless Bamboo Recycled Artisan Professional Ultra Insulated Lightweight Adjustable Foldable ].freeze
- PRODUCT_NOUNS =
[ "Keyboard", "Coffee Beans", "Backpack", "Water Bottle", "Desk Lamp", "Headphones", "Notebook", "Sneakers", "Sunglasses", "Wallet", "Mug", "Chair", "Blender", "Speaker", "Charger", "Umbrella", "Toothbrush", "Jacket", "Watch", "Kettle", "Picture Frame", "Planter", "Cutlery Set", "Yoga Mat", "Phone Case" ].freeze
- PRODUCT_TABLE_HINTS =
A table/model whose name contains any of these gets product names on its generic +name+/+full_name+ column instead of a person name.
%w[ product item catalog inventory goods merchandise sku listing stock ware ].freeze
Class Method Summary collapse
-
.product_table?(table) ⇒ Boolean
True when the table/model name looks like a product catalogue, so a generic +name+/+full_name+ column seeds a product name, not a person name.
-
.seed(seed) ⇒ Object
Static factory — create a seeded FakeData instance.
Instance Method Summary collapse
- #address ⇒ Object
- #blob(size: 64) ⇒ Object
- #boolean ⇒ Object
- #choice(items) ⇒ Object
- #city ⇒ Object
- #color_hex ⇒ Object
- #company ⇒ Object
- #country ⇒ Object
-
#credit_card ⇒ Object
Generate a fake credit card number (test numbers only, e.g. 4111...).
- #currency ⇒ Object
- #date(start_year: 2020, end_year: 2026) ⇒ Object
- #datetime(start_year: 2020, end_year: 2026) ⇒ Object
- #email(from_name: nil) ⇒ Object
- #first_name ⇒ Object
-
#for_field(field_def, column_name = nil, table = nil) ⇒ Object
Generate appropriate data based on field definition and column name.
-
#initialize(seed: nil) ⇒ FakeData
constructor
A new instance of FakeData.
- #integer(min: 0, max: 10_000) ⇒ Object
- #ip_address ⇒ Object
- #job_title ⇒ Object
- #json_data(keys: nil) ⇒ Object
- #last_name ⇒ Object
- #name ⇒ Object
- #numeric(min: 0.0, max: 1000.0, decimals: 2) ⇒ Object
- #paragraph(sentences: 3) ⇒ Object
- #password(length: 16) ⇒ Object
- #phone ⇒ Object
-
#product ⇒ Object
A plausible product name, e.g.
-
#run(count = 1, &block) ⇒ Object
Run a generator block
counttimes and return the results. - #sentence(words: 6) ⇒ Object
- #slug(words: 3) ⇒ Object
- #text(max_length: 200) ⇒ Object
- #timestamp(start_year: 2020, end_year: 2026) ⇒ Object
- #url ⇒ Object
- #uuid ⇒ Object
- #word ⇒ Object
- #zip_code ⇒ Object
Constructor Details
#initialize(seed: nil) ⇒ FakeData
Returns a new instance of FakeData.
135 136 137 |
# File 'lib/tina4/seeder.rb', line 135 def initialize(seed: nil) @rng = seed ? Random.new(seed) : Random.new end |
Class Method Details
.product_table?(table) ⇒ Boolean
True when the table/model name looks like a product catalogue, so a generic
+name+/+full_name+ column seeds a product name, not a person name. With no
table context (nil/"") this is false, so the person-name default is kept
(back-compat). Mirrors the Python master's _is_product_table.
286 287 288 289 |
# File 'lib/tina4/seeder.rb', line 286 def self.product_table?(table) down = table.to_s.downcase PRODUCT_TABLE_HINTS.any? { |hint| down.include?(hint) } end |
.seed(seed) ⇒ Object
Static factory — create a seeded FakeData instance. fake = FakeData.seed(42) fake.name # deterministic
142 143 144 |
# File 'lib/tina4/seeder.rb', line 142 def self.seed(seed) new(seed: seed) end |
Instance Method Details
#address ⇒ Object
255 256 257 |
# File 'lib/tina4/seeder.rb', line 255 def address "#{@rng.rand(1..9999)} #{STREETS[@rng.rand(STREETS.length)]} #{STREET_TYPES[@rng.rand(STREET_TYPES.length)]}" end |
#blob(size: 64) ⇒ Object
230 231 232 |
# File 'lib/tina4/seeder.rb', line 230 def blob(size: 64) SecureRandom.random_bytes(size) end |
#boolean ⇒ Object
211 212 213 |
# File 'lib/tina4/seeder.rb', line 211 def boolean @rng.rand(2) == 1 end |
#choice(items) ⇒ Object
243 244 245 |
# File 'lib/tina4/seeder.rb', line 243 def choice(items) items[@rng.rand(items.length)] end |
#city ⇒ Object
247 248 249 |
# File 'lib/tina4/seeder.rb', line 247 def city CITIES[@rng.rand(CITIES.length)] end |
#color_hex ⇒ Object
306 307 308 |
# File 'lib/tina4/seeder.rb', line 306 def color_hex "#%06x" % @rng.rand(0..0xFFFFFF) end |
#company ⇒ Object
263 264 265 266 267 268 |
# File 'lib/tina4/seeder.rb', line 263 def company w1 = COMPANY_WORDS[@rng.rand(COMPANY_WORDS.length)] w2 = COMPANY_WORDS[@rng.rand(COMPANY_WORDS.length)] suffix = COMPANY_SUFFIXES[@rng.rand(COMPANY_SUFFIXES.length)] "#{w1}#{w2} #{suffix}" end |
#country ⇒ Object
251 252 253 |
# File 'lib/tina4/seeder.rb', line 251 def country COUNTRIES[@rng.rand(COUNTRIES.length)] end |
#credit_card ⇒ Object
Generate a fake credit card number (test numbers only, e.g. 4111...).
300 301 302 303 304 |
# File 'lib/tina4/seeder.rb', line 300 def credit_card prefix = CREDIT_CARD_PREFIXES[@rng.rand(CREDIT_CARD_PREFIXES.length)] rest = Array.new(12) { @rng.rand(0..9) }.join prefix + rest end |
#currency ⇒ Object
291 292 293 |
# File 'lib/tina4/seeder.rb', line 291 def currency CURRENCIES[@rng.rand(CURRENCIES.length)] end |
#date(start_year: 2020, end_year: 2026) ⇒ Object
222 223 224 |
# File 'lib/tina4/seeder.rb', line 222 def date(start_year: 2020, end_year: 2026) datetime(start_year: start_year, end_year: end_year).strftime("%Y-%m-%d") end |
#datetime(start_year: 2020, end_year: 2026) ⇒ Object
215 216 217 218 219 220 |
# File 'lib/tina4/seeder.rb', line 215 def datetime(start_year: 2020, end_year: 2026) start_time = Time.new(start_year, 1, 1) end_time = Time.new(end_year, 12, 31, 23, 59, 59) delta = (end_time - start_time).to_i Time.at(start_time.to_i + @rng.rand(0..delta)) end |
#email(from_name: nil) ⇒ Object
158 159 160 161 162 163 164 165 166 |
# File 'lib/tina4/seeder.rb', line 158 def email(from_name: nil) if from_name local = from_name.downcase.split.join(".") else local = "#{first_name.downcase}.#{last_name.downcase}" end local += @rng.rand(1..999).to_s "#{local}@#{DOMAINS[@rng.rand(DOMAINS.length)]}" end |
#first_name ⇒ Object
146 147 148 |
# File 'lib/tina4/seeder.rb', line 146 def first_name FIRST_NAMES[@rng.rand(FIRST_NAMES.length)] end |
#for_field(field_def, column_name = nil, table = nil) ⇒ Object
Generate appropriate data based on field definition and column name.
table (optional) is the table/model name. When it names a product-ish
catalogue (see FakeData.product_table?) a generic +name+/+full_name+/
fullname column yields a product name ("Wireless Keyboard") instead of a
person name. With no table context the person-name default is kept
(back-compat). first_name/last_name/user_name stay person either way.
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 |
# File 'lib/tina4/seeder.rb', line 332 def for_field(field_def, column_name = nil, table = nil) col = (column_name || "").to_s.downcase type = field_def[:type] # Skip auto-increment primary keys return nil if field_def[:primary_key] && field_def[:auto_increment] case type when :integer return integer(min: 18, max: 85) if col.include?("age") return integer(min: 1950, max: 2026) if col.include?("year") return integer(min: 1, max: 100) if col =~ /quantity|qty|count/ return boolean if col =~ /active|enabled|visible|^is_/ return integer(min: 1, max: 10) if col =~ /rating|score/ integer(min: 1, max: 10_000) when :float, :decimal decimals = field_def[:scale] || 2 return numeric(min: 0.01, max: 9999.99, decimals: decimals) if col =~ /price|cost|amount|total|fee/ return numeric(min: 0.0, max: 100.0, decimals: decimals) if col =~ /rate|percent|ratio/ return numeric(min: -90.0, max: 90.0, decimals: 6) if col.include?("lat") return numeric(min: -180.0, max: 180.0, decimals: 6) if col =~ /lon|lng/ numeric(min: 0.0, max: 10_000.0, decimals: decimals) when :date date when :datetime, :timestamp when :boolean boolean when :blob blob when :json json_data when :string, :text max_len = field_def[:length] || 255 val = generate_string_for(col, max_len, table) val.length > max_len ? val[0...max_len] : val else word end end |
#integer(min: 0, max: 10_000) ⇒ Object
202 203 204 |
# File 'lib/tina4/seeder.rb', line 202 def integer(min: 0, max: 10_000) @rng.rand(min..max) end |
#ip_address ⇒ Object
295 296 297 |
# File 'lib/tina4/seeder.rb', line 295 def ip_address "#{@rng.rand(1..255)}.#{@rng.rand(0..255)}.#{@rng.rand(0..255)}.#{@rng.rand(1..254)}" end |
#job_title ⇒ Object
270 271 272 |
# File 'lib/tina4/seeder.rb', line 270 def job_title JOB_TITLES[@rng.rand(JOB_TITLES.length)] end |
#json_data(keys: nil) ⇒ Object
234 235 236 237 238 239 240 241 |
# File 'lib/tina4/seeder.rb', line 234 def json_data(keys: nil) if keys keys.each_with_object({}) { |k, h| h[k] = word } else n = @rng.rand(2..5) n.times.each_with_object({}) { |_, h| h[word] = word } end end |
#last_name ⇒ Object
150 151 152 |
# File 'lib/tina4/seeder.rb', line 150 def last_name LAST_NAMES[@rng.rand(LAST_NAMES.length)] end |
#name ⇒ Object
154 155 156 |
# File 'lib/tina4/seeder.rb', line 154 def name "#{first_name} #{last_name}" end |
#numeric(min: 0.0, max: 1000.0, decimals: 2) ⇒ Object
206 207 208 209 |
# File 'lib/tina4/seeder.rb', line 206 def numeric(min: 0.0, max: 1000.0, decimals: 2) val = min + @rng.rand * (max - min) val.round(decimals) end |
#paragraph(sentences: 3) ⇒ Object
181 182 183 |
# File 'lib/tina4/seeder.rb', line 181 def paragraph(sentences: 3) Array.new(sentences) { sentence(words: @rng.rand(5..12)) }.join(" ") end |
#password(length: 16) ⇒ Object
315 316 317 318 |
# File 'lib/tina4/seeder.rb', line 315 def password(length: 16) chars = [*"a".."z", *"A".."Z", *"0".."9"] Array.new(length) { chars[@rng.rand(chars.length)] }.join end |
#phone ⇒ Object
168 169 170 171 172 173 |
# File 'lib/tina4/seeder.rb', line 168 def phone area = @rng.rand(200..999) mid = @rng.rand(100..999) tail = @rng.rand(1000..9999) "+1 (#{area}) #{mid}-#{tail}" end |
#product ⇒ Object
A plausible product name, e.g. "Wireless Keyboard" or "Organic Coffee Beans". Deterministic under a seed like every other generator (draws from the same @rng), so a seeded run reproduces it. Mirrors the Python master's FakeData.product().
278 279 280 |
# File 'lib/tina4/seeder.rb', line 278 def product "#{PRODUCT_ADJECTIVES[@rng.rand(PRODUCT_ADJECTIVES.length)]} #{PRODUCT_NOUNS[@rng.rand(PRODUCT_NOUNS.length)]}" end |
#run(count = 1, &block) ⇒ Object
Run a generator block count times and return the results.
321 322 323 |
# File 'lib/tina4/seeder.rb', line 321 def run(count = 1, &block) Array.new(count) { block.call } end |
#sentence(words: 6) ⇒ Object
175 176 177 178 179 |
# File 'lib/tina4/seeder.rb', line 175 def sentence(words: 6) w = Array.new(words) { WORDS[@rng.rand(WORDS.length)] } w[0] = w[0].capitalize "#{w.join(' ')}." end |
#slug(words: 3) ⇒ Object
194 195 196 |
# File 'lib/tina4/seeder.rb', line 194 def slug(words: 3) Array.new(words) { WORDS[@rng.rand(WORDS.length)] }.join("-") end |
#text(max_length: 200) ⇒ Object
185 186 187 188 |
# File 'lib/tina4/seeder.rb', line 185 def text(max_length: 200) t = paragraph(sentences: 2) t.length > max_length ? t[0...max_length] : t end |
#timestamp(start_year: 2020, end_year: 2026) ⇒ Object
226 227 228 |
# File 'lib/tina4/seeder.rb', line 226 def (start_year: 2020, end_year: 2026) datetime(start_year: start_year, end_year: end_year).strftime("%Y-%m-%d %H:%M:%S") end |
#url ⇒ Object
198 199 200 |
# File 'lib/tina4/seeder.rb', line 198 def url "https://#{DOMAINS[@rng.rand(DOMAINS.length)]}/#{slug}" end |
#uuid ⇒ Object
310 311 312 313 |
# File 'lib/tina4/seeder.rb', line 310 def uuid h = Array.new(32) { "0123456789abcdef"[@rng.rand(16)] }.join "#{h[0..7]}-#{h[8..11]}-#{h[12..15]}-#{h[16..19]}-#{h[20..31]}" end |
#word ⇒ Object
190 191 192 |
# File 'lib/tina4/seeder.rb', line 190 def word WORDS[@rng.rand(WORDS.length)] end |
#zip_code ⇒ Object
259 260 261 |
# File 'lib/tina4/seeder.rb', line 259 def zip_code @rng.rand(10_000..99_999).to_s end |