Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,4 @@ jobs:
bundler-cache: true

- name: Run tests
run: bundle exec rake test
run: bundle exec rake test LAZY_LOAD=1
10 changes: 10 additions & 0 deletions benchmark/load.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

require 'benchmark/ips'

Benchmark.ips do |x|
x.report('require') { system('ruby load_faker.rb') }
x.report('lazyload') { system('LAZY_LOAD=1 ruby load_faker.rb') }

x.compare!(order: :baseline)
end
8 changes: 8 additions & 0 deletions benchmark/load_faker.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

if defined?(Faker)
raise 'fake is already defined...'
end

load('/Users/stefannibrasil/projects/faker/lib/faker.rb')
3 changes: 2 additions & 1 deletion doc/internet/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
Available since version next.

# Keyword arguments: group

```ruby
Faker::Internet::HTTP.status_code #=> 418
Faker::Internet::HTTP.status_code(group: :information) #=> 102
Faker::Internet::HTTP.status_code(group: :successful) #=> 200
Faker::Internet::HTTP.status_code(group: :redirect) #=> 306
Faker::Internet::HTTP.status_code(group: :client_error) #=> 451
Faker::Internet::HTTP.status_code(group: :server_error) #=> 502
```
```
94 changes: 94 additions & 0 deletions experiments/lazy_loading.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Lazy load experiment results

Branch: sb-ta/lazy-load-experiment
Date: February 10th, 2026
Owner(s): Stefanni Brasil and Thiago Araujo

## Impact

Using `const_missing` to lazy load generators was an idea from talking with Jeremy Evans, who kindly responded our questions about improving faker's performance.

### Changes needed

Would require loading `faker/music` and `faker/internet` first, before the nested namespaces such as `Faker::Music::BossaNova`,
as they inherit from class `Music`.

#### File location changes

To prevent other generators from erroring out due to namespace clashing, some generators have to be moved around (ex. `Faker::Quote` was moved from `/faker/quotes/quote` to `faker/default/quote`). Users can still use the generators as before, their namespaces didn't change.

### Benefits

- no additional dependencies needed
- code is extremely faster
- we can enable this as an opt-in configuration

## Results

Machine specs: Apple M1 Pro 16GB memory on MacOS Sequoia 15.7.3..

profiler:

[LAZY_LOAD=1 bundle exec vernier run -- ruby -e "require 'faker'"](https://share.firefox.dev/46biNAs)
[bundle exec vernier run -- ruby -e "require 'faker'"](https://share.firefox.dev/4ams9vM)

benchmark:

```sh
benchmark % ruby load.rb
ruby 3.3.10 (2025-10-23 revision 343ea05002) [arm64-darwin24]
Warming up --------------------------------------
require 1.000 i/100ms
lazyload 1.000 i/100ms
Calculating -------------------------------------
require 4.698 (± 0.0%) i/s (212.88 ms/i) - 24.000 in 5.133782s
lazyload 9.751 (± 0.0%) i/s (102.55 ms/i) - 49.000 in 5.032161s

Comparison:
require: 4.7 i/s
lazyload: 9.8 i/s - 2.08x faster
```

## Artifacts

### Scripts

Constants were registered using this script:

```ruby
# lazy_load.rb

CATEGORIES = {
Blockchain: 'blockchain',
Books: 'books',
Creature: 'creature',
Default: 'default',
Fantasy: 'fantasy',
Games: 'games',
JapaneseMedia: 'japanese_media',
Locations: 'locations',
Movies: 'movies',
Music: 'music',
Quotes: 'quotes',
Religion: 'religion',
Sports: 'sports',
Travel: 'travel',
TvShows: 'tv_shows'
}.freeze

def template(key)
"# frozen_string_literal: true

module Faker
class #{key}
if ENV['LAZY_LOAD'] == '1'
Faker.lazy_load(self)
end
end
end"
end

CATEGORIES.each do |key, value|
File.write(File.join('lib', 'faker', "#{value}.rb"), template(key))
end
```
44 changes: 42 additions & 2 deletions lib/faker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,47 @@ def disable_enforce_available_locales
end
end
end

if ENV['LAZY_LOAD'] == '1'
def self.load_path(*constants)
constants.map do |class_name|
class_name
.to_s
.gsub('::', '/')
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
.tr('-', '_')
.downcase
end.join('/')
end

def self.lazy_load(klass)
def klass.const_missing(class_name)
load_path = case class_name
when :DnD
Faker.load_path('faker/games/dnd')
else
Faker.load_path(name, class_name)
end

begin
require(load_path)
rescue LoadError
require(load_path.gsub('faker/', 'faker/default/'))
end

const_get(class_name)
end
end

lazy_load(self)
end
end

# require faker objects
Dir.glob(File.join(mydir, 'faker', '/**/*.rb')).each { |file| require file }
if ENV['LAZY_LOAD'] != '1'
rb_files = []
rb_files << File.join(mydir, 'faker', '*.rb')
rb_files << File.join(mydir, 'faker', '/**/*.rb')

Dir.glob(rb_files).each { |file| require file }
end
9 changes: 9 additions & 0 deletions lib/faker/blockchain.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

module Faker
class Blockchain
if ENV['LAZY_LOAD'] == '1'
Faker.lazy_load(self)
end
end
end
9 changes: 9 additions & 0 deletions lib/faker/books.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

module Faker
class Books
if ENV['LAZY_LOAD'] == '1'
Faker.lazy_load(self)
end
end
end
9 changes: 9 additions & 0 deletions lib/faker/creature.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

module Faker
class Creature
if ENV['LAZY_LOAD'] == '1'
Faker.lazy_load(self)
end
end
end
9 changes: 9 additions & 0 deletions lib/faker/default.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

module Faker
class Default
if ENV['LAZY_LOAD'] == '1'
Faker.lazy_load(self)
end
end
end
File renamed without changes.
File renamed without changes.
6 changes: 3 additions & 3 deletions lib/faker/default/html.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class << self
# @faker.version 3.2.1
def heading
level = rand(1..6)
"<h#{level}>#{Lorem.word.capitalize}</h#{level}>"
"<h#{level}>#{Faker::Lorem.word.capitalize}</h#{level}>"
end

##
Expand Down Expand Up @@ -97,7 +97,7 @@ def unordered_list
#
# @faker.version 3.2.1
def code
"<code>#{Lorem.sentence(word_count: 1)}</code>"
"<code>#{Faker::Lorem.sentence(word_count: 1)}</code>"
end

##
Expand Down Expand Up @@ -165,7 +165,7 @@ def link(rel: 'stylesheet')
# Faker::HTML.element(tag: 'div', content: "This is a div with XSS attributes.", attributes: {class: 'xss', onclick: "alert('XSS')"}) #=> "<div class=\"xss\" onclick=\"alert('XSS')\">This is a div with XSS attributes.</div>"
#
# @faker.version 3.2.1
def element(tag: 'div', content: Lorem.sentence(word_count: 3), attributes: { class: Lorem.word, onclick: "#{Lorem.word}()" })
def element(tag: 'div', content: Faker::Lorem.sentence(word_count: 3), attributes: { class: Faker::Lorem.word, onclick: "#{Faker::Lorem.word}()" })
attribute_string = attributes.map { |key, value| "#{key}=\"#{value}\"" }.join(' ')
"<#{tag} #{attribute_string}>#{content}</#{tag}>"
end
Expand Down
4 changes: 2 additions & 2 deletions lib/faker/default/lorem.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def words(number: 3, supplemental: false, exclude_words: nil)
#
# @faker.version 2.1.3
def character
sample(Types::CHARACTERS)
sample(Faker::Types::CHARACTERS)
end

##
Expand All @@ -78,7 +78,7 @@ def character
#
# @faker.version 2.1.3
def characters(number: 255, min_alpha: 0, min_numeric: 0)
Alphanumeric.alphanumeric(number: number, min_alpha: min_alpha, min_numeric: min_numeric)
Faker::Alphanumeric.alphanumeric(number: number, min_alpha: min_alpha, min_numeric: min_numeric)
end

##
Expand Down
6 changes: 3 additions & 3 deletions lib/faker/default/markdown.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class << self
#
# @faker.version 1.8.0
def headers
"#{fetch('markdown.headers')} #{Lorem.word.capitalize}"
"#{fetch('markdown.headers')} #{Faker::Lorem.word.capitalize}"
end

##
Expand Down Expand Up @@ -95,7 +95,7 @@ def inline_code
#
# @faker.version 1.8.0
def block_code
"```ruby\n#{Lorem.sentence(word_count: 1)}\n```"
"```ruby\n#{Faker::Lorem.sentence(word_count: 1)}\n```"
end

##
Expand All @@ -110,7 +110,7 @@ def block_code
def table
table = []
3.times do
table << "#{Lorem.word} | #{Lorem.word} | #{Lorem.word}"
table << "#{Faker::Lorem.word} | #{Faker::Lorem.word} | #{Faker::Lorem.word}"
end
table.insert(1, '---- | ---- | ----')
table.join("\n")
Expand Down
File renamed without changes.
Loading
Loading