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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +111 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +445 -0
  5. data/app/assets/stylesheets/factory_seeder.css +637 -0
  6. data/app/controllers/factory_seeder/application_controller.rb +8 -0
  7. data/app/controllers/factory_seeder/custom_seeds_controller.rb +134 -0
  8. data/app/controllers/factory_seeder/dashboard_controller.rb +36 -0
  9. data/app/controllers/factory_seeder/factory_controller.rb +70 -0
  10. data/app/views/factory_seeder/custom_seeds/index.html.erb +51 -0
  11. data/app/views/factory_seeder/custom_seeds/show.html.erb +113 -0
  12. data/app/views/factory_seeder/dashboard/index.html.erb +99 -0
  13. data/app/views/factory_seeder/factory/index.html.erb +71 -0
  14. data/app/views/factory_seeder/factory/show.html.erb +108 -0
  15. data/app/views/factory_seeder/seeds/show.html.erb +2 -0
  16. data/app/views/layouts/factory_seeder/application.html.erb +25 -0
  17. data/bin/factory_seeder +27 -0
  18. data/config/factory_seeder.rb +24 -0
  19. data/config/routes.rb +20 -0
  20. data/lib/factory_seeder/asset_helper.rb +34 -0
  21. data/lib/factory_seeder/cli.rb +352 -0
  22. data/lib/factory_seeder/configuration.rb +32 -0
  23. data/lib/factory_seeder/custom_seed_loader.rb +39 -0
  24. data/lib/factory_seeder/engine.rb +16 -0
  25. data/lib/factory_seeder/execution_log_store.rb +48 -0
  26. data/lib/factory_seeder/factory_scanner.rb +149 -0
  27. data/lib/factory_seeder/loader.rb +26 -0
  28. data/lib/factory_seeder/rails_integration.rb +29 -0
  29. data/lib/factory_seeder/seed.rb +102 -0
  30. data/lib/factory_seeder/seed_builder.rb +67 -0
  31. data/lib/factory_seeder/seed_generator.rb +305 -0
  32. data/lib/factory_seeder/seed_manager.rb +128 -0
  33. data/lib/factory_seeder/seeder.rb +41 -0
  34. data/lib/factory_seeder/version.rb +5 -0
  35. data/lib/factory_seeder/web_interface.rb +119 -0
  36. data/lib/factory_seeder.rb +209 -0
  37. data/templates/seed_template.rb +84 -0
  38. 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,2 @@
1
+ <h1>Seed: <%= @seed.name %></h1>
2
+
@@ -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>
@@ -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