factory_seeder 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/CHANGELOG.md +111 -0
- data/LICENSE.txt +21 -0
- data/README.md +445 -0
- data/app/assets/stylesheets/factory_seeder.css +637 -0
- data/app/controllers/factory_seeder/application_controller.rb +8 -0
- data/app/controllers/factory_seeder/custom_seeds_controller.rb +134 -0
- data/app/controllers/factory_seeder/dashboard_controller.rb +36 -0
- data/app/controllers/factory_seeder/factory_controller.rb +70 -0
- data/app/views/factory_seeder/custom_seeds/index.html.erb +51 -0
- data/app/views/factory_seeder/custom_seeds/show.html.erb +113 -0
- data/app/views/factory_seeder/dashboard/index.html.erb +99 -0
- data/app/views/factory_seeder/factory/index.html.erb +71 -0
- data/app/views/factory_seeder/factory/show.html.erb +108 -0
- data/app/views/factory_seeder/seeds/show.html.erb +2 -0
- data/app/views/layouts/factory_seeder/application.html.erb +25 -0
- data/bin/factory_seeder +27 -0
- data/config/factory_seeder.rb +24 -0
- data/config/routes.rb +20 -0
- data/lib/factory_seeder/asset_helper.rb +34 -0
- data/lib/factory_seeder/cli.rb +352 -0
- data/lib/factory_seeder/configuration.rb +32 -0
- data/lib/factory_seeder/custom_seed_loader.rb +39 -0
- data/lib/factory_seeder/engine.rb +16 -0
- data/lib/factory_seeder/execution_log_store.rb +48 -0
- data/lib/factory_seeder/factory_scanner.rb +149 -0
- data/lib/factory_seeder/loader.rb +26 -0
- data/lib/factory_seeder/rails_integration.rb +29 -0
- data/lib/factory_seeder/seed.rb +102 -0
- data/lib/factory_seeder/seed_builder.rb +67 -0
- data/lib/factory_seeder/seed_generator.rb +305 -0
- data/lib/factory_seeder/seed_manager.rb +128 -0
- data/lib/factory_seeder/seeder.rb +41 -0
- data/lib/factory_seeder/version.rb +5 -0
- data/lib/factory_seeder/web_interface.rb +119 -0
- data/lib/factory_seeder.rb +209 -0
- data/templates/seed_template.rb +84 -0
- metadata +276 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<div class="page-shell" data-factory-name="<%= @factory_name %>">
|
|
2
|
+
<div class="scanlines"></div>
|
|
3
|
+
<div class="grid-background"></div>
|
|
4
|
+
<div class="page-content">
|
|
5
|
+
<header class="section-title">Factory · <%= @factory_name %></header>
|
|
6
|
+
<p class="hero__subtitle">
|
|
7
|
+
Configure the execution parameters for this factory, toggle traits, and send the
|
|
8
|
+
job to the generator.
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<% if flash[:success] %>
|
|
12
|
+
<div class="alert-message alert-message--success"><%= flash[:success] %></div>
|
|
13
|
+
<% end %>
|
|
14
|
+
|
|
15
|
+
<% if flash[:error] %>
|
|
16
|
+
<div class="alert-message alert-message--error"><%= flash[:error] %></div>
|
|
17
|
+
<% end %>
|
|
18
|
+
|
|
19
|
+
<div class="factory-show-grid">
|
|
20
|
+
<section class="panel">
|
|
21
|
+
<div class="panel__header">
|
|
22
|
+
<div>
|
|
23
|
+
<div class="panel__title">Configuration</div>
|
|
24
|
+
<div class="factory-card__meta">Class: <%= @factory[:class_name] %></div>
|
|
25
|
+
</div>
|
|
26
|
+
<%= link_to "Back", factory_index_path, class: "btn btn-accent" %>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<%= form_tag generate_factory_path(@factory_name), method: :post do %>
|
|
30
|
+
<div class="panel__body">
|
|
31
|
+
<div class="form-group">
|
|
32
|
+
<label for="count">Records to generate</label>
|
|
33
|
+
<%= number_field_tag :count, 1, min: 1, max: 100, class: "form-control" %>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<% traits = Array(@factory[:traits]) %>
|
|
37
|
+
<% if traits.any? %>
|
|
38
|
+
<div class="form-group">
|
|
39
|
+
<label>Traits</label>
|
|
40
|
+
<div class="trait-grid">
|
|
41
|
+
<% traits.each do |trait| %>
|
|
42
|
+
<label class="trait-checkbox">
|
|
43
|
+
<%= check_box_tag "selected_traits[]", trait, false, id: "trait_#{trait}" %>
|
|
44
|
+
<span><%= trait %></span>
|
|
45
|
+
</label>
|
|
46
|
+
<% end %>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
<% end %>
|
|
50
|
+
|
|
51
|
+
<div class="form-group">
|
|
52
|
+
<label>Associations</label>
|
|
53
|
+
<p class="factory-card__meta">
|
|
54
|
+
<%= Array(@factory[:associations]).map { |assoc| assoc[:name] }.join(', ').presence || 'None' %>
|
|
55
|
+
</p>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div class="form-group">
|
|
59
|
+
<label>Attributes</label>
|
|
60
|
+
<div class="attribute-grid">
|
|
61
|
+
<% @factory[:attributes].each do |attr| %>
|
|
62
|
+
<div class="attribute-row">
|
|
63
|
+
<label for="attributes_<%= attr[:name] %>"><%= attr[:name] %> (<%= attr[:type] %>)</label>
|
|
64
|
+
<%= text_field_tag "attributes[#{attr[:name]}]", nil, class: "form-control", placeholder: "Use default" %>
|
|
65
|
+
</div>
|
|
66
|
+
<% end %>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div class="form-actions">
|
|
71
|
+
<%= submit_tag "Generate Seeds", class: "btn btn-primary" %>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
<% end %>
|
|
75
|
+
</section>
|
|
76
|
+
|
|
77
|
+
<section class="panel console">
|
|
78
|
+
<div class="console__header">
|
|
79
|
+
<span>Console Output</span>
|
|
80
|
+
<span class="factory-card__meta">Live logs will appear once a job runs.</span>
|
|
81
|
+
</div>
|
|
82
|
+
<div class="console__body">
|
|
83
|
+
<% logs = @execution_logs || [] %>
|
|
84
|
+
<% if logs.any? %>
|
|
85
|
+
<div class="space-y-1">
|
|
86
|
+
<% logs.each do |log| %>
|
|
87
|
+
<% entry = log.is_a?(Hash) ? log : log.to_h %>
|
|
88
|
+
<% message = entry['message'] || entry[:message] || entry.to_s %>
|
|
89
|
+
<% level = (entry['level'] || entry[:level] || :info).to_s %>
|
|
90
|
+
<% timestamp_value = entry['timestamp'] || entry[:timestamp] || Time.now %>
|
|
91
|
+
<% timestamp =
|
|
92
|
+
timestamp_value.respond_to?(:strftime) ? timestamp_value.strftime('%H:%M:%S') : timestamp_value.to_s %>
|
|
93
|
+
<div class="console-log console-log--<%= level %>">
|
|
94
|
+
<span class="text-muted-foreground">[<%= timestamp %>]</span>
|
|
95
|
+
<span><%= message %></span>
|
|
96
|
+
</div>
|
|
97
|
+
<% end %>
|
|
98
|
+
</div>
|
|
99
|
+
<% else %>
|
|
100
|
+
<div class="console-log">
|
|
101
|
+
<span>[ --:--:-- ]</span>Awaiting execution trigger...
|
|
102
|
+
</div>
|
|
103
|
+
<% end %>
|
|
104
|
+
</div>
|
|
105
|
+
</section>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>FactorySeeder - Database Seeding Made Easy</title>
|
|
7
|
+
<%= csrf_meta_tags %>
|
|
8
|
+
<%= csp_meta_tag %>
|
|
9
|
+
|
|
10
|
+
<!-- FactorySeeder CSS -->
|
|
11
|
+
<style>
|
|
12
|
+
<%= raw FactorySeeder::AssetHelper.css_content('factory_seeder.css') %>
|
|
13
|
+
</style>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<header class="app-header">
|
|
17
|
+
<div>
|
|
18
|
+
<div class="app-header__title">FACTORY / SEEDER by Wecasa</div>
|
|
19
|
+
<div class="app-header__tagline">Industrial-quality seeds powered by FactoryBot</div>
|
|
20
|
+
</div>
|
|
21
|
+
<%= link_to "Dashboard", root_path, class: "app-header__link" %>
|
|
22
|
+
</header>
|
|
23
|
+
<%= yield %>
|
|
24
|
+
</body>
|
|
25
|
+
</html>
|
data/bin/factory_seeder
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Essayer de charger l'environnement Rails s'il existe
|
|
5
|
+
begin
|
|
6
|
+
# Chercher le fichier config/environment.rb dans le répertoire courant ou parent
|
|
7
|
+
current_dir = Dir.pwd
|
|
8
|
+
rails_env_found = false
|
|
9
|
+
|
|
10
|
+
# Chercher dans le répertoire courant et ses parents
|
|
11
|
+
while current_dir != '/' && !rails_env_found
|
|
12
|
+
if File.exist?(File.join(current_dir, 'config', 'environment.rb'))
|
|
13
|
+
require File.join(current_dir, 'config', 'environment')
|
|
14
|
+
rails_env_found = true
|
|
15
|
+
puts "🌱 Rails environment chargé depuis: #{current_dir}" if ENV['FACTORY_SEEDER_DEBUG']
|
|
16
|
+
else
|
|
17
|
+
current_dir = File.dirname(current_dir)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
rescue LoadError => e
|
|
21
|
+
puts "⚠️ Rails environment non trouvé: #{e.message}" if ENV['FACTORY_SEEDER_DEBUG']
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
require 'bundler/setup'
|
|
25
|
+
require 'factory_seeder'
|
|
26
|
+
|
|
27
|
+
FactorySeeder::CLI.start(ARGV)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# FactorySeeder Configuration
|
|
4
|
+
FactorySeeder.configure do |config|
|
|
5
|
+
# Add custom factory paths if needed
|
|
6
|
+
# config.factory_paths << "custom/factories"
|
|
7
|
+
|
|
8
|
+
# Default options for seeding
|
|
9
|
+
config.default_count = 1
|
|
10
|
+
config.default_strategy = :create
|
|
11
|
+
|
|
12
|
+
# Environment-specific settings
|
|
13
|
+
config.environments = {
|
|
14
|
+
development: {
|
|
15
|
+
default_count: 10
|
|
16
|
+
},
|
|
17
|
+
test: {
|
|
18
|
+
default_count: 5
|
|
19
|
+
},
|
|
20
|
+
production: {
|
|
21
|
+
default_count: 1
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
end
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
FactorySeeder::Engine.routes.draw do
|
|
4
|
+
# Dashboard principal
|
|
5
|
+
root 'dashboard#index'
|
|
6
|
+
|
|
7
|
+
# Factory details et génération
|
|
8
|
+
get '/factory/:name', to: 'factory#show', as: :factory
|
|
9
|
+
get '/factory', to: 'factory#index', as: :factory_index
|
|
10
|
+
post '/factory/:name/generate', to: 'factory#generate', as: :generate_factory
|
|
11
|
+
|
|
12
|
+
# Seeds actions
|
|
13
|
+
post '/seeds/:name', to: 'dashboard#run_seed', as: :run_seed
|
|
14
|
+
post '/seeds', to: 'dashboard#run_all_seeds', as: :run_all_seeds
|
|
15
|
+
|
|
16
|
+
# Custom seeds
|
|
17
|
+
get '/custom_seeds', to: 'custom_seeds#index', as: :custom_seeds
|
|
18
|
+
get '/custom_seeds/:name', to: 'custom_seeds#show', as: :custom_seed
|
|
19
|
+
post '/custom_seeds/:name/generate', to: 'custom_seeds#create', as: :create_custom_seede
|
|
20
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FactorySeeder
|
|
4
|
+
module AssetHelper
|
|
5
|
+
def self.javascript_path(filename)
|
|
6
|
+
# Dans une gem, on peut servir les assets directement depuis le dossier app/assets
|
|
7
|
+
File.join(File.dirname(__FILE__), '..', '..', 'app', 'assets', 'javascript', filename)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.stylesheet_path(filename)
|
|
11
|
+
File.join(File.dirname(__FILE__), '..', '..', 'app', 'assets', 'stylesheets', filename)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.asset_content(filename)
|
|
15
|
+
path = javascript_path(filename)
|
|
16
|
+
File.read(path) if File.exist?(path)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.css_content(filename)
|
|
20
|
+
path = stylesheet_path(filename)
|
|
21
|
+
File.read(path) if File.exist?(path)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.available_assets
|
|
25
|
+
js_dir = File.join(File.dirname(__FILE__), '..', '..', 'app', 'assets', 'javascript')
|
|
26
|
+
Dir.glob(File.join(js_dir, '*.js')).map { |f| File.basename(f) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.available_stylesheets
|
|
30
|
+
css_dir = File.join(File.dirname(__FILE__), '..', '..', 'app', 'assets', 'stylesheets')
|
|
31
|
+
Dir.glob(File.join(css_dir, '*.css')).map { |f| File.basename(f) }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
4
|
+
|
|
5
|
+
module FactorySeeder
|
|
6
|
+
class CLI < Thor
|
|
7
|
+
class_option :verbose, type: :boolean, aliases: '-v', desc: 'Enable verbose output'
|
|
8
|
+
|
|
9
|
+
desc 'generate [FACTORY]', 'Generate seeds for a specific factory'
|
|
10
|
+
option :count, type: :numeric, desc: 'Number of records to create (defaults to config per environment)'
|
|
11
|
+
option :traits, type: :array, desc: 'Traits to apply'
|
|
12
|
+
option :strategy, type: :string, desc: 'Factory strategy (defaults to config per environment)'
|
|
13
|
+
option :attributes, type: :hash, desc: 'Additional attributes'
|
|
14
|
+
def generate(factory_name = nil)
|
|
15
|
+
if factory_name
|
|
16
|
+
generate_single_factory(factory_name)
|
|
17
|
+
else
|
|
18
|
+
interactive_generate
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
desc 'list', 'List all available factories with metadata'
|
|
23
|
+
option :verbose, type: :boolean, aliases: '-v'
|
|
24
|
+
def list
|
|
25
|
+
FactorySeeder.configuration.verbose = options[:verbose] if options[:verbose]
|
|
26
|
+
|
|
27
|
+
factories = FactorySeeder.scan_factories
|
|
28
|
+
|
|
29
|
+
if factories.empty?
|
|
30
|
+
puts '❌ No factories found. Make sure you have FactoryBot factories in spec/factories/ or test/factories/'
|
|
31
|
+
return
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
puts "📋 Found #{factories.count} factories:\n\n"
|
|
35
|
+
|
|
36
|
+
factories.sort.each do |name, info|
|
|
37
|
+
puts "🏭 #{name} (#{info[:class_name] || 'Unknown class'})"
|
|
38
|
+
puts " Traits: #{formatted_traits(info[:traits])}"
|
|
39
|
+
puts " Associations: #{formatted_associations(info[:associations])}"
|
|
40
|
+
puts " Attributes: #{formatted_attributes(info[:attributes])}"
|
|
41
|
+
puts
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
desc 'preview FACTORY_NAME', 'Preview factory data without creating records'
|
|
46
|
+
option :count, type: :numeric, aliases: '-c', desc: 'Number of preview records (defaults to config per environment)'
|
|
47
|
+
option :traits, type: :string, aliases: '-t'
|
|
48
|
+
option :attributes, type: :string, aliases: '-a'
|
|
49
|
+
def preview(factory_name)
|
|
50
|
+
FactorySeeder.configuration.verbose = true
|
|
51
|
+
|
|
52
|
+
# Vérifier si la factory existe
|
|
53
|
+
factory_names = FactorySeeder.list_factory_names
|
|
54
|
+
unless factory_names.include?(factory_name)
|
|
55
|
+
puts "❌ Factory '#{factory_name}' not found"
|
|
56
|
+
puts "Available factories: #{factory_names.first(10).join(', ')}"
|
|
57
|
+
return
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
traits = options[:traits]&.split(',')&.map(&:strip) || []
|
|
61
|
+
attributes = parse_attributes(options[:attributes])
|
|
62
|
+
|
|
63
|
+
generator = SeedGenerator.new
|
|
64
|
+
preview_data = generator.preview(factory_name, resolved_count, traits, attributes)
|
|
65
|
+
|
|
66
|
+
puts "🔍 Preview for #{factory_name}:"
|
|
67
|
+
puts JSON.pretty_generate(preview_data)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
desc 'web', 'Start web interface'
|
|
71
|
+
option :port, type: :numeric, default: 4567, aliases: '-p'
|
|
72
|
+
option :host, type: :string, default: 'localhost', aliases: '-h'
|
|
73
|
+
def web
|
|
74
|
+
puts "🌐 Starting FactorySeeder web interface on http://#{options[:host]}:#{options[:port]}"
|
|
75
|
+
puts 'Press Ctrl+C to stop'
|
|
76
|
+
|
|
77
|
+
WebInterface.set :port, options[:port]
|
|
78
|
+
WebInterface.set :bind, options[:host]
|
|
79
|
+
WebInterface.run!
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
desc 'init', 'Initialize FactorySeeder configuration'
|
|
83
|
+
def init
|
|
84
|
+
puts '🚀 Initializing FactorySeeder...'
|
|
85
|
+
|
|
86
|
+
seeds_file = Rails.root.join('db/seeds_factory_seeder.rb')
|
|
87
|
+
unless File.exist?(seeds_file)
|
|
88
|
+
File.write(seeds_file, <<~RUBY)
|
|
89
|
+
# FactorySeeder Seeds
|
|
90
|
+
# This file is automatically generated by FactorySeeder
|
|
91
|
+
|
|
92
|
+
FactorySeeder.generate do |seeder|
|
|
93
|
+
# Add your custom seeds here
|
|
94
|
+
end
|
|
95
|
+
RUBY
|
|
96
|
+
puts "✅ Seeds file created: #{seeds_file}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
initializer_file = Rails.root.join('config/initializers/factory_seeder.rb')
|
|
100
|
+
unless File.exist?(initializer_file)
|
|
101
|
+
File.write(initializer_file, <<~RUBY)
|
|
102
|
+
# frozen_string_literal: true
|
|
103
|
+
# FactorySeeder initializer
|
|
104
|
+
# This file is generated by FactorySeeder
|
|
105
|
+
|
|
106
|
+
FactorySeeder.configure do |config|
|
|
107
|
+
config.factory_paths << Rails.root.join('spec/factories').to_s if Dir.exist?('spec/factories')
|
|
108
|
+
config.factory_paths << Rails.root.join('test/factories').to_s if Dir.exist?('test/factories')
|
|
109
|
+
config.verbose = true
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Load custom seeds so they are available at boot
|
|
113
|
+
seeds_file = Rails.root.join('db/seeds_factory_seeder.rb')
|
|
114
|
+
load seeds_file if File.exist?(seeds_file)
|
|
115
|
+
RUBY
|
|
116
|
+
|
|
117
|
+
puts "✅ Initializer created: #{initializer_file}"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
puts '✅ FactorySeeder initialized!'
|
|
121
|
+
puts " Factory paths: #{FactorySeeder.configuration.factory_paths.join(', ')}"
|
|
122
|
+
puts " Verbose mode: #{FactorySeeder.configuration.verbose}"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
desc 'seeds [SEED_NAME]', 'Run seeds from db/seeds_factory_seeder.rb'
|
|
126
|
+
option :list, type: :boolean, default: false, desc: 'List all available seeds'
|
|
127
|
+
option :all, type: :boolean, default: false, desc: 'Run all defined seeds'
|
|
128
|
+
option :dry_run, type: :boolean, default: false, desc: 'Preview what would be generated'
|
|
129
|
+
def seeds(seed_name = nil)
|
|
130
|
+
seeds_file = 'db/seeds_factory_seeder.rb'
|
|
131
|
+
|
|
132
|
+
unless File.exist?(seeds_file)
|
|
133
|
+
puts "❌ Seeds file not found: #{seeds_file}"
|
|
134
|
+
puts "💡 Run 'factory_seeder init' to create the default seeds file"
|
|
135
|
+
return
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Charger le fichier de seeds pour avoir accès aux seeds définis
|
|
139
|
+
load seeds_file
|
|
140
|
+
|
|
141
|
+
if options[:list]
|
|
142
|
+
list_available_seeds
|
|
143
|
+
return
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
if options[:all]
|
|
147
|
+
run_all_seeds
|
|
148
|
+
return
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
if seed_name.nil?
|
|
152
|
+
puts '❌ Please specify a seed name, use --list to see available seeds, or --all to run all seeds'
|
|
153
|
+
puts '💡 Example: factory_seeder seeds development'
|
|
154
|
+
return
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
run_specific_seed(seed_name)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
private
|
|
161
|
+
|
|
162
|
+
def list_available_seeds
|
|
163
|
+
# Créer un générateur temporaire pour lister les seeds
|
|
164
|
+
generator = SeedGenerator.new
|
|
165
|
+
|
|
166
|
+
# Charger le fichier de seeds pour avoir accès aux seeds définis
|
|
167
|
+
load 'db/seeds_factory_seeder.rb'
|
|
168
|
+
|
|
169
|
+
seeds = generator.list_seeds
|
|
170
|
+
|
|
171
|
+
if seeds.empty?
|
|
172
|
+
puts '❌ No seeds defined in db/seeds_factory_seeder.rb'
|
|
173
|
+
puts '�� Add seeds using seeder.define_seed :name do ... end'
|
|
174
|
+
return
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
puts "🌱 Available seeds from db/seeds_factory_seeder.rb:\n\n"
|
|
178
|
+
seeds.each do |seed_name|
|
|
179
|
+
puts " • #{seed_name}"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
puts "\n💡 Run: factory_seeder seeds SEED_NAME"
|
|
183
|
+
puts '💡 Example: factory_seeder seeds development'
|
|
184
|
+
puts '💡 Run all: factory_seeder seeds --all'
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def run_all_seeds
|
|
188
|
+
puts '🌱 Running all seeds from db/seeds_factory_seeder.rb'
|
|
189
|
+
|
|
190
|
+
puts '🔍 DRY RUN MODE - No records will be created' if options[:dry_run]
|
|
191
|
+
|
|
192
|
+
begin
|
|
193
|
+
generator = SeedGenerator.new
|
|
194
|
+
generator.run_all_seeds
|
|
195
|
+
puts '✅ All seeds executed successfully'
|
|
196
|
+
rescue StandardError => e
|
|
197
|
+
puts "❌ Error running seeds: #{e.message}"
|
|
198
|
+
puts e.backtrace.first(5).join("\n") if options[:verbose]
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def run_specific_seed(seed_name)
|
|
203
|
+
puts "🌱 Running seed: #{seed_name}"
|
|
204
|
+
|
|
205
|
+
puts '🔍 DRY RUN MODE - No records will be created' if options[:dry_run]
|
|
206
|
+
|
|
207
|
+
begin
|
|
208
|
+
generator = SeedGenerator.new
|
|
209
|
+
|
|
210
|
+
# Charger le fichier de seeds pour avoir accès aux seeds définis
|
|
211
|
+
load 'db/seeds_factory_seeder.rb'
|
|
212
|
+
|
|
213
|
+
unless generator.has_seed?(seed_name)
|
|
214
|
+
puts "❌ Seed '#{seed_name}' not found"
|
|
215
|
+
puts "💡 Available seeds: #{generator.list_seeds.join(', ')}"
|
|
216
|
+
return
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
generator.run_seed(seed_name)
|
|
220
|
+
puts "✅ Seed '#{seed_name}' executed successfully"
|
|
221
|
+
rescue StandardError => e
|
|
222
|
+
puts "❌ Error running seed '#{seed_name}': #{e.message}"
|
|
223
|
+
puts e.backtrace.first(5).join("\n") if options[:verbose]
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def interactive_generate
|
|
228
|
+
factory_names = FactorySeeder.list_factory_names
|
|
229
|
+
|
|
230
|
+
if factory_names.empty?
|
|
231
|
+
puts '❌ No factories found'
|
|
232
|
+
return
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
puts '🏭 Available factories:'
|
|
236
|
+
factory_names.each_with_index do |name, index|
|
|
237
|
+
puts " #{index + 1}. #{name}"
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
print "\nSelect factory (number or name): "
|
|
241
|
+
selection = $stdin.gets.chomp
|
|
242
|
+
|
|
243
|
+
factory_name = if selection.match?(/^\d+$/)
|
|
244
|
+
index = selection.to_i - 1
|
|
245
|
+
factory_names[index]
|
|
246
|
+
else
|
|
247
|
+
selection
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
unless factory_name && factory_names.include?(factory_name)
|
|
251
|
+
puts '❌ Invalid selection'
|
|
252
|
+
return
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
generate_single_factory(factory_name)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def generate_single_factory(factory_name)
|
|
259
|
+
# Vérifier si la factory existe
|
|
260
|
+
factory_names = FactorySeeder.list_factory_names
|
|
261
|
+
unless factory_names.include?(factory_name)
|
|
262
|
+
puts "❌ Factory '#{factory_name}' not found"
|
|
263
|
+
return
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
traits = options[:traits] || []
|
|
267
|
+
attributes = options[:attributes] || {}
|
|
268
|
+
count = resolved_count
|
|
269
|
+
strategy = resolved_strategy
|
|
270
|
+
|
|
271
|
+
generator = SeedGenerator.new
|
|
272
|
+
result = generator.generate(factory_name, count, traits, attributes, strategy)
|
|
273
|
+
|
|
274
|
+
puts "✅ Generated #{result[:count]} #{factory_name} records"
|
|
275
|
+
puts "Strategy: #{result[:strategy]}"
|
|
276
|
+
puts "Traits: #{traits.join(', ')}" if traits.any?
|
|
277
|
+
puts "Attributes: #{attributes}" if attributes.any?
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def parse_attributes(attributes_string)
|
|
281
|
+
return {} unless attributes_string
|
|
282
|
+
|
|
283
|
+
trimmed = attributes_string.strip
|
|
284
|
+
if trimmed.start_with?('{') && trimmed.end_with?('}')
|
|
285
|
+
JSON.parse(trimmed)
|
|
286
|
+
else
|
|
287
|
+
parse_attribute_pairs(trimmed)
|
|
288
|
+
end
|
|
289
|
+
rescue JSON::ParserError => e
|
|
290
|
+
puts "⚠️ Could not parse attributes JSON: #{e.message}" if FactorySeeder.configuration.verbose
|
|
291
|
+
parse_attribute_pairs(attributes_string)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def parse_attribute_pairs(attributes_string)
|
|
295
|
+
attributes = {}
|
|
296
|
+
attributes_string.split(',').each do |pair|
|
|
297
|
+
next if pair.strip.empty?
|
|
298
|
+
key, value = pair.split('=').map(&:strip)
|
|
299
|
+
attributes[key] = value if key && value
|
|
300
|
+
end
|
|
301
|
+
attributes
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def resolved_count
|
|
305
|
+
options[:count] || FactorySeeder.configuration.default_count_for_environment
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def resolved_strategy
|
|
309
|
+
(options[:strategy] || FactorySeeder.configuration.default_strategy_for_environment).to_s
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def formatted_traits(traits)
|
|
313
|
+
traits = Array(traits)
|
|
314
|
+
return 'None' if traits.empty?
|
|
315
|
+
|
|
316
|
+
traits.join(', ')
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def formatted_associations(associations)
|
|
320
|
+
associations = Array(associations)
|
|
321
|
+
return 'None' if associations.empty?
|
|
322
|
+
|
|
323
|
+
associations.map { |assoc| format_association(assoc) }.join(', ')
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def formatted_attributes(attributes)
|
|
327
|
+
attributes = Array(attributes)
|
|
328
|
+
return 'None' if attributes.empty?
|
|
329
|
+
|
|
330
|
+
attributes.map do |attribute|
|
|
331
|
+
name = attribute[:name] || attribute['name']
|
|
332
|
+
type = attribute[:type] || attribute['type']
|
|
333
|
+
type_part = type ? " (#{type})" : ''
|
|
334
|
+
"#{name || 'unknown'}#{type_part}"
|
|
335
|
+
end.join(', ')
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def format_association(association)
|
|
339
|
+
return association.to_s unless association.respond_to?(:[])
|
|
340
|
+
|
|
341
|
+
name = association[:name] || association['name'] || 'association'
|
|
342
|
+
parts = []
|
|
343
|
+
factory = association[:factory] || association['factory']
|
|
344
|
+
strategy = association[:strategy] || association['strategy']
|
|
345
|
+
parts << "factory: #{factory}" if factory
|
|
346
|
+
parts << "strategy: #{strategy}" if strategy
|
|
347
|
+
return name if parts.empty?
|
|
348
|
+
|
|
349
|
+
"#{name} (#{parts.join(', ')})"
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FactorySeeder
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_accessor :factory_paths, :default_count, :default_strategy, :environments, :verbose
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@factory_paths = []
|
|
9
|
+
@default_count = 1
|
|
10
|
+
@default_strategy = :create
|
|
11
|
+
@verbose = false
|
|
12
|
+
@environments = {
|
|
13
|
+
development: { default_count: 10 },
|
|
14
|
+
test: { default_count: 5 },
|
|
15
|
+
production: { default_count: 1 }
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def environment_settings
|
|
20
|
+
current_env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
|
21
|
+
@environments[current_env.to_sym] || @environments[:development]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def default_count_for_environment
|
|
25
|
+
environment_settings[:default_count] || @default_count
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def default_strategy_for_environment
|
|
29
|
+
environment_settings[:default_strategy] || @default_strategy
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pathname'
|
|
4
|
+
|
|
5
|
+
module FactorySeeder
|
|
6
|
+
class CustomSeedLoader
|
|
7
|
+
class << self
|
|
8
|
+
def reload!
|
|
9
|
+
return unless seed_files.any?
|
|
10
|
+
|
|
11
|
+
FactorySeeder.seed_manager.clear
|
|
12
|
+
seed_files.each do |file|
|
|
13
|
+
load file
|
|
14
|
+
rescue StandardError => e
|
|
15
|
+
warn "⚠️ Could not load custom seed #{file}: #{e.message}"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def seed_files
|
|
20
|
+
return [] unless seeds_directory
|
|
21
|
+
|
|
22
|
+
Dir.glob(seeds_directory.join('**/*.rb'))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def seeds_directory
|
|
26
|
+
base_path = if defined?(Rails) && Rails.respond_to?(:root)
|
|
27
|
+
Rails.root
|
|
28
|
+
else
|
|
29
|
+
Pathname.new(Dir.pwd)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
path = base_path.join('db', 'factory_seeds')
|
|
33
|
+
return path if path.exist?
|
|
34
|
+
|
|
35
|
+
nil
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FactorySeeder
|
|
4
|
+
class Engine < ::Rails::Engine
|
|
5
|
+
isolate_namespace FactorySeeder
|
|
6
|
+
|
|
7
|
+
config.generators do |g|
|
|
8
|
+
g.test_framework :rspec
|
|
9
|
+
g.template_engine :erb
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
config.to_prepare do
|
|
13
|
+
FactorySeeder::CustomSeedLoader.reload!
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|