better_seeder 0.2.2.1 → 0.2.3.1
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 +4 -4
- data/README.md +3 -1
- data/lib/better_seeder/builders/structure.rb +15 -16
- data/lib/better_seeder/configuration.rb +5 -3
- data/lib/better_seeder/exporters/base.rb +7 -5
- data/lib/better_seeder/exporters/csv.rb +3 -0
- data/lib/better_seeder/exporters/json.rb +3 -3
- data/lib/better_seeder/exporters/sql.rb +4 -1
- data/lib/better_seeder/farms/farmer.rb +108 -92
- data/lib/better_seeder/structure/utils.rb +2 -0
- data/lib/better_seeder/utils.rb +66 -13
- data/lib/better_seeder/version.rb +1 -1
- data/lib/better_seeder.rb +104 -70
- data/lib/generators/better_seeder/structure_generator.rb +6 -4
- metadata +12 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d5b13470c3a8af2c331ef6254593d3268c5a82654802368c4af18608ad629185
|
4
|
+
data.tar.gz: ba63f74f95445e8b29bab7e33757c9ed460786a655fb9402733b0a7372436086
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8e215fd763c8348003cff4266ba1616eca6e3430e586c754b150ae2f68c97e969c23eec7ad946fd1ba7a1656feb3c507667ef45195834cd531546ea7ecba9e42
|
7
|
+
data.tar.gz: fc8b41742698a0cd0cf9b3db03a8b57bc2342b528bde096affe737a856f2a329b66063d5130b6b5a618caa1f8c87c169f60064ca4400575514ee1eca4e115feb
|
data/README.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# lib/better_seeder/generators/structure_generator.rb
|
2
4
|
require 'fileutils'
|
3
5
|
|
@@ -5,8 +7,8 @@ module BetterSeeder
|
|
5
7
|
module Builders
|
6
8
|
class Structure
|
7
9
|
TEMPLATE = <<~RUBY
|
8
|
-
module
|
9
|
-
class
|
10
|
+
module %<module_name>s
|
11
|
+
class %<class_name>sStructure < ::BetterSeeder::Structure::Utils
|
10
12
|
# Defines generators for each attribute.
|
11
13
|
def self.structure
|
12
14
|
{
|
@@ -23,15 +25,15 @@ module BetterSeeder
|
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
26
|
-
# Specific seeding configuration for
|
28
|
+
# Specific seeding configuration for %<class_name>s.
|
27
29
|
def self.seed_config
|
28
30
|
{
|
29
|
-
file_name: '
|
31
|
+
file_name: '%<file_name>s',
|
30
32
|
columns: { excluded: [] },
|
31
33
|
generate_data: true,
|
32
34
|
count: 10,
|
33
35
|
load_data: true,
|
34
|
-
|
36
|
+
# parents: [ { model: 'MyNamespace::MyModel', column: :my_column } ]
|
35
37
|
}
|
36
38
|
end
|
37
39
|
|
@@ -49,26 +51,23 @@ module BetterSeeder
|
|
49
51
|
# @return [String] The full path to the generated file.
|
50
52
|
def self.generate(model_name)
|
51
53
|
# Split the model name into module parts and the actual class name.
|
52
|
-
parts
|
53
|
-
class_name
|
54
|
-
module_name = parts.empty? ?
|
54
|
+
parts = model_name.split('::')
|
55
|
+
class_name = parts.pop
|
56
|
+
module_name = parts.empty? ? 'Main' : parts.join('::')
|
55
57
|
|
56
58
|
# Determine the file path.
|
57
59
|
# For example, for "MyNamespace::MyModel", the file will be placed in:
|
58
60
|
# lib/better_seeder/generators/my_namespace/my_model_structure.rb
|
59
61
|
folder_path = File.join(BetterSeeder.configuration.structure_path, *parts.map(&:underscore))
|
60
|
-
file_name
|
61
|
-
full_path
|
62
|
+
file_name = "#{class_name.underscore}_structure.rb"
|
63
|
+
full_path = File.join(folder_path, file_name)
|
62
64
|
|
63
65
|
# Ensure the target directory exists.
|
64
|
-
FileUtils.mkdir_p(folder_path)
|
66
|
+
FileUtils.mkdir_p(folder_path)
|
65
67
|
|
66
68
|
# Prepare the file content.
|
67
|
-
content = TEMPLATE
|
68
|
-
|
69
|
-
class_name: class_name,
|
70
|
-
file_name: "#{class_name.underscore}_seed"
|
71
|
-
}
|
69
|
+
content = format(TEMPLATE, module_name: module_name, class_name: class_name,
|
70
|
+
file_name: "#{class_name.underscore}_seed")
|
72
71
|
|
73
72
|
# Write the template to the file.
|
74
73
|
File.write(full_path, content)
|
@@ -1,16 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# lib/better_seeder/configuration.rb
|
2
4
|
|
3
5
|
module BetterSeeder
|
4
6
|
class Configuration
|
5
|
-
attr_accessor :log_language, :structure_path, :preload_path
|
7
|
+
attr_accessor :log_language, :log_level, :structure_path, :preload_path
|
6
8
|
|
7
9
|
def initialize
|
10
|
+
@log_language = :en
|
11
|
+
@log_level = :info
|
8
12
|
if defined?(Rails) && Rails.respond_to?(:root)
|
9
|
-
@log_language = :en
|
10
13
|
@structure_path = Rails.root.join('db', 'seed', 'structure')
|
11
14
|
@preload_path = Rails.root.join('db', 'seed', 'preload')
|
12
15
|
else
|
13
|
-
@log_language = :en
|
14
16
|
@structure_path = File.join(Dir.pwd, 'db', 'seed', 'structure')
|
15
17
|
@preload_path = File.join(Dir.pwd, 'db', 'seed', 'preload')
|
16
18
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# lib/better_seeder/exporter/base_exporter.rb
|
2
4
|
|
3
5
|
module BetterSeeder
|
@@ -23,10 +25,10 @@ module BetterSeeder
|
|
23
25
|
# @param output_path [String] Nome del file (senza estensione).
|
24
26
|
# @param table_name [String] Nome della tabella (usato in SqlExporter).
|
25
27
|
def initialize(data, output_path:, table_name: 'my_table')
|
26
|
-
@data
|
28
|
+
@data = data
|
27
29
|
# Utilizza il preload_path definito nella configurazione BetterSeeder (impostato nell'initializer).
|
28
30
|
@output_path = File.join(BetterSeeder.configuration.preload_path, output_path)
|
29
|
-
@table_name
|
31
|
+
@table_name = table_name
|
30
32
|
end
|
31
33
|
|
32
34
|
# Restituisce la directory in cui salvare i file.
|
@@ -39,7 +41,7 @@ module BetterSeeder
|
|
39
41
|
|
40
42
|
# Verifica che la directory di output esista; se non esiste, la crea.
|
41
43
|
def ensure_output_directory
|
42
|
-
FileUtils.mkdir_p(output_directory)
|
44
|
+
FileUtils.mkdir_p(output_directory)
|
43
45
|
end
|
44
46
|
|
45
47
|
# Costruisce il percorso completo del file di output, combinando la directory, l'output_path e l'estensione.
|
@@ -53,12 +55,12 @@ module BetterSeeder
|
|
53
55
|
# Metodo astratto per ottenere l'estensione del file (es. ".json", ".csv", ".sql").
|
54
56
|
# Le classi derivate devono implementarlo.
|
55
57
|
def extension
|
56
|
-
raise NotImplementedError,
|
58
|
+
raise NotImplementedError, 'Subclasses must implement #extension'
|
57
59
|
end
|
58
60
|
|
59
61
|
# Metodo astratto per effettuare l'export.
|
60
62
|
def export
|
61
|
-
raise NotImplementedError,
|
63
|
+
raise NotImplementedError, 'Subclasses must implement the export method'
|
62
64
|
end
|
63
65
|
end
|
64
66
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module BetterSeeder
|
2
4
|
module Exporters
|
3
5
|
class Csv < Base
|
@@ -5,6 +7,7 @@ module BetterSeeder
|
|
5
7
|
# Se la cartella non esiste, viene creata automaticamente.
|
6
8
|
def export
|
7
9
|
return if data.empty?
|
10
|
+
|
8
11
|
headers = data.first.keys
|
9
12
|
|
10
13
|
# Costruisce il percorso completo del file di output
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module BetterSeeder
|
2
4
|
module Exporters
|
3
5
|
class Json < Base
|
@@ -7,9 +9,7 @@ module BetterSeeder
|
|
7
9
|
# Imposta la directory di output
|
8
10
|
full_path = File.join(full_output_path)
|
9
11
|
|
10
|
-
File.
|
11
|
-
file.write(JSON.pretty_generate(data))
|
12
|
-
end
|
12
|
+
File.write(full_path, JSON.pretty_generate(data))
|
13
13
|
end
|
14
14
|
|
15
15
|
def extension
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module BetterSeeder
|
2
4
|
module Exporters
|
3
5
|
class Sql < Base
|
@@ -11,6 +13,7 @@ module BetterSeeder
|
|
11
13
|
# ... ;
|
12
14
|
def export
|
13
15
|
return if data.empty?
|
16
|
+
|
14
17
|
columns = data.first.keys
|
15
18
|
|
16
19
|
# Crea l'array delle tuple di valori per ciascun record.
|
@@ -18,7 +21,7 @@ module BetterSeeder
|
|
18
21
|
row_values = columns.map do |col|
|
19
22
|
value = row[col]
|
20
23
|
# Se il valore è nil restituisce NULL, altrimenti esegue l'escaping delle virgolette singole.
|
21
|
-
value.nil? ?
|
24
|
+
value.nil? ? 'NULL' : "'#{value.to_s.gsub("'", "''")}'"
|
22
25
|
end
|
23
26
|
"(#{row_values.join(', ')})"
|
24
27
|
end
|
@@ -1,116 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module BetterSeeder
|
2
4
|
module Farms
|
3
5
|
class Farmer
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
6
|
+
class << self
|
7
|
+
def generate(options = {})
|
8
|
+
model_name = options[:model] or raise ArgumentError, 'Missing :model option'
|
9
|
+
count = options[:count] || 10
|
10
|
+
|
11
|
+
# Costruisce il percorso del file di structure.
|
12
|
+
structure_file = File.expand_path(
|
13
|
+
File.join(BetterSeeder.configuration.structure_path, "#{model_name.underscore}_structure.rb"),
|
14
|
+
Dir.pwd
|
15
|
+
)
|
16
|
+
raise "Structure file not found: #{structure_file}" unless File.exist?(structure_file)
|
17
|
+
|
18
|
+
# Carica il file di structure.
|
19
|
+
load structure_file
|
20
|
+
|
21
|
+
# Costruisce il nome della classe di structure: es. "Media::Participant" => "Media::ParticipantStructure"
|
22
|
+
structure_class_name = "#{model_name}Structure"
|
23
|
+
begin
|
24
|
+
structure_class = Object.const_get(structure_class_name)
|
25
|
+
rescue error
|
26
|
+
message = "Structure class not found: #{structure_class_name}"
|
27
|
+
BetterSeeder::Utils.logger(message: message)
|
28
|
+
raise error
|
29
|
+
end
|
30
|
+
|
31
|
+
generated_records = []
|
32
|
+
|
33
|
+
while generated_records.size < count
|
34
|
+
new_record = nil
|
35
|
+
loop do
|
36
|
+
new_record = build_record(model_name, structure_class)
|
37
|
+
new_record = inject_parent_keys(model_name, new_record, structure_class)
|
38
|
+
break if validate_record(new_record, structure_class) &&
|
39
|
+
!record_exists?(model_name, new_record, structure_class, generated_records)
|
40
|
+
end
|
41
|
+
generated_records.push(new_record)
|
42
|
+
end
|
43
|
+
|
44
|
+
generated_records
|
43
45
|
end
|
44
46
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
#
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
47
|
+
private
|
48
|
+
|
49
|
+
def record_exists?(_model_name, record, structure_class, generated_records)
|
50
|
+
# Se non è definito il metodo unique_keys, non eseguiamo il controllo
|
51
|
+
return false unless structure_class.respond_to?(:unique_keys)
|
52
|
+
|
53
|
+
unique_key_sets = structure_class.unique_keys
|
54
|
+
return false if unique_key_sets.empty?
|
55
|
+
|
56
|
+
# Determina il modello associato: si assume che il nome del modello sia
|
57
|
+
# dato dalla rimozione della stringa "Structure" dal nome della classe di structure.
|
58
|
+
model_class_name = structure_class.to_s.sub(/Structure$/, '')
|
59
|
+
model_class = Object.const_get(model_class_name)
|
60
|
+
|
61
|
+
# Per ogni set di chiavi uniche, costruiamo le condizioni della query
|
62
|
+
unique_key_sets.each do |key_set|
|
63
|
+
conditions = {}
|
64
|
+
key_set.each do |col|
|
65
|
+
conditions[col] = record[col]
|
66
|
+
end
|
67
|
+
# Se esiste un record nel database che soddisfa le condizioni, restituisce true.
|
68
|
+
return true if generated_records.find do |record|
|
69
|
+
conditions.all? { |key, value| record[key] == value }
|
70
|
+
end.present?
|
71
|
+
return true if model_class.where(conditions).exists?
|
64
72
|
end
|
65
|
-
|
73
|
+
|
74
|
+
false
|
66
75
|
end
|
67
76
|
|
68
|
-
|
69
|
-
|
70
|
-
|
77
|
+
def build_record(_model_name, structure_class)
|
78
|
+
generation_rules = structure_class.structure
|
79
|
+
raise 'Structure must be a Hash' unless generation_rules.is_a?(Hash)
|
71
80
|
|
72
|
-
# Continua a generare record finché non si raggiunge il numero richiesto.
|
73
|
-
while generated_records.size < count
|
74
|
-
attempts += 1
|
75
81
|
record = {}
|
76
82
|
generation_rules.each do |attribute, rule|
|
77
|
-
# Ogni rule è un array
|
78
|
-
generator
|
79
|
-
value
|
83
|
+
# Ogni rule è un array nel formato [tipo, generatore]
|
84
|
+
generator = rule[1]
|
85
|
+
value = generator.respond_to?(:call) ? generator.call : generator
|
80
86
|
record[attribute] = value
|
81
87
|
end
|
82
88
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
89
|
+
record
|
90
|
+
end
|
91
|
+
|
92
|
+
def inject_parent_keys(_model_name, record, structure_class)
|
93
|
+
config = structure_class.respond_to?(:seed_config) ? structure_class.seed_config : {}
|
94
|
+
parents_spec = config[:parents]
|
95
|
+
return record if parents_spec.blank?
|
96
|
+
|
97
|
+
parents_spec.each do |parent_config|
|
98
|
+
parent_model = parent_config[:model]
|
99
|
+
column = parent_config[:column]
|
100
|
+
|
101
|
+
# Tenta di ottenere un record del parent dal pool BetterSeeder.generated_records se disponibile.
|
102
|
+
# Usiamo il nome del modello come chiave nel pool.
|
103
|
+
pool_key = parent_model.to_s
|
104
|
+
unless defined?(BetterSeeder.generated_records) &&
|
105
|
+
BetterSeeder.generated_records[pool_key] &&
|
106
|
+
!BetterSeeder.generated_records[pool_key].empty?
|
107
|
+
BetterSeeder.generated_records[pool_key] = parent_model.all
|
91
108
|
end
|
92
|
-
|
109
|
+
parent_record = BetterSeeder.generated_records[pool_key].sample
|
93
110
|
|
94
|
-
|
95
|
-
duplicate = unique_sets.any? do |unique_set|
|
96
|
-
composite_key = unique_set[:columns].map { |col| record[col].to_s }.join("_")
|
97
|
-
unique_set[:set].include?(composite_key)
|
98
|
-
end
|
99
|
-
next if duplicate
|
111
|
+
raise "Parent record not found for #{parent_model}" unless parent_record
|
100
112
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
unique_set[:set].add(composite_key)
|
113
|
+
# Inietta nel record la chiave esterna indicata nella configurazione.
|
114
|
+
# binding.pry if model_name == "Media::Participant"
|
115
|
+
record[column] = parent_record[:id]
|
105
116
|
end
|
106
117
|
|
107
|
-
|
108
|
-
progressbar.increment
|
118
|
+
record
|
109
119
|
end
|
110
120
|
|
111
|
-
|
112
|
-
|
113
|
-
|
121
|
+
def validate_record(record, structure_class)
|
122
|
+
return true unless structure_class.respond_to?(:seed_schema_validation)
|
123
|
+
|
124
|
+
schema = structure_class.seed_schema_validation
|
125
|
+
result = schema.call(record)
|
126
|
+
return true if result.success?
|
127
|
+
|
128
|
+
raise result.errors.to_h
|
129
|
+
end
|
114
130
|
end
|
115
131
|
end
|
116
132
|
end
|
data/lib/better_seeder/utils.rb
CHANGED
@@ -1,21 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# lib/better_seeder/utils.rb
|
4
|
+
#
|
5
|
+
# = BetterSeeder::Utils
|
6
|
+
#
|
7
|
+
# Questo modulo fornisce metodi di utilità per la gestione dei seed. In particolare,
|
8
|
+
# consente di trasformare i nomi delle classi in formato snake_case con il suffisso "_structure.rb",
|
9
|
+
# gestire i messaggi di log e configurare il livello del logger per ActiveRecord.
|
2
10
|
|
3
11
|
module BetterSeeder
|
4
12
|
module Utils
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
#
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
+
class << self
|
14
|
+
##
|
15
|
+
# Trasforma un nome di classe in snake_case e aggiunge il suffisso "_structure.rb".
|
16
|
+
#
|
17
|
+
# ==== Esempio
|
18
|
+
# transform_class_name("Campaigns::Campaign")
|
19
|
+
# # => "campaigns/campaign_structure.rb"
|
20
|
+
#
|
21
|
+
# ==== Parametri
|
22
|
+
# * +class_name+ - Stringa che rappresenta il nome della classe, eventualmente suddiviso in
|
23
|
+
# namespace separati da "::".
|
24
|
+
#
|
25
|
+
# ==== Ritorno
|
26
|
+
# Restituisce una stringa con il nome della classe in formato snake_case e l'ultimo elemento
|
27
|
+
# terminato con "_structure.rb".
|
28
|
+
#
|
29
|
+
def transform_class_name(class_name)
|
30
|
+
elements = class_name.split('::').map(&:underscore)
|
31
|
+
# Aggiunge "_structure.rb" all'ultimo elemento
|
32
|
+
elements[-1] = "#{elements[-1]}_structure.rb"
|
33
|
+
elements.join('/')
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Registra un messaggio usando il logger di Rails se disponibile, altrimenti lo stampa su standard output.
|
38
|
+
#
|
39
|
+
# ==== Parametri
|
40
|
+
# * +message+ - Il messaggio da loggare (può essere una stringa o nil).
|
41
|
+
#
|
42
|
+
# ==== Ritorno
|
43
|
+
# Non ritorna un valore significativo.
|
44
|
+
#
|
45
|
+
def logger(message: nil)
|
46
|
+
if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
47
|
+
Rails.logger.info message
|
48
|
+
else
|
49
|
+
puts message
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Configura il livello del logger per ActiveRecord in base alla configurazione definita in BetterSeeder.
|
55
|
+
#
|
56
|
+
# ==== Dettagli
|
57
|
+
# Il metodo imposta il livello del logger in base al valore di BetterSeeder.configuration.log_level:
|
58
|
+
# * +:debug+ -> Logger::DEBUG
|
59
|
+
# * +:info+ -> Logger::INFO
|
60
|
+
# * +:error+ -> Logger::ERROR
|
61
|
+
# Se il livello non corrisponde a nessuna delle opzioni previste, viene impostato il livello +Logger::DEBUG+.
|
62
|
+
#
|
63
|
+
def log_level_setup
|
64
|
+
level = case BetterSeeder.configuration.log_level
|
65
|
+
when :debug then Logger::DEBUG
|
66
|
+
when :info then Logger::INFO
|
67
|
+
when :error then Logger::ERROR
|
68
|
+
else Logger::DEBUG
|
69
|
+
end
|
13
70
|
|
14
|
-
|
15
|
-
if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
16
|
-
Rails.logger.info message
|
17
|
-
else
|
18
|
-
puts message
|
71
|
+
ActiveRecord::Base.logger.level = level
|
19
72
|
end
|
20
73
|
end
|
21
74
|
end
|
data/lib/better_seeder.rb
CHANGED
@@ -1,30 +1,44 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative
|
6
|
-
require_relative
|
7
|
-
require_relative
|
8
|
-
require_relative
|
9
|
-
require_relative
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'better_seeder/utils'
|
4
|
+
require_relative 'better_seeder/configuration'
|
5
|
+
require_relative 'better_seeder/structure/utils'
|
6
|
+
require_relative 'better_seeder/farms/farmer'
|
7
|
+
require_relative 'better_seeder/exporters/base'
|
8
|
+
require_relative 'better_seeder/exporters/json'
|
9
|
+
require_relative 'better_seeder/exporters/csv'
|
10
|
+
require_relative 'better_seeder/exporters/sql'
|
11
|
+
require_relative 'better_seeder/builders/structure'
|
10
12
|
|
11
13
|
module BetterSeeder
|
12
14
|
class Configuration
|
13
15
|
attr_accessor :log_language, :structure_path, :preload_path
|
14
16
|
|
15
17
|
def initialize
|
18
|
+
@log_language = :en
|
19
|
+
@log_level = :info
|
16
20
|
if defined?(Rails) && Rails.respond_to?(:root)
|
17
|
-
@log_language = :en
|
18
21
|
@structure_path = Rails.root.join('db', 'seed', 'structure')
|
19
22
|
@preload_path = Rails.root.join('db', 'seed', 'preload')
|
20
23
|
else
|
21
|
-
@log_language = :en
|
22
24
|
@structure_path = File.join(Dir.pwd, 'db', 'seed', 'structure')
|
23
25
|
@preload_path = File.join(Dir.pwd, 'db', 'seed', 'preload')
|
24
26
|
end
|
25
27
|
end
|
26
28
|
end
|
27
29
|
|
30
|
+
# Definisce un hash che fungerà da pool per memorizzare i record generati per ciascun modello.
|
31
|
+
@generated_records = {}
|
32
|
+
|
33
|
+
def self.generated_records
|
34
|
+
@generated_records
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.store_generated_record(model_name, record)
|
38
|
+
@generated_records[model_name.to_s] ||= []
|
39
|
+
@generated_records[model_name.to_s] << record
|
40
|
+
end
|
41
|
+
|
28
42
|
# Restituisce l'istanza globale di configurazione. Se non esiste, viene creata con i valori di default.
|
29
43
|
def self.configuration
|
30
44
|
@configuration ||= Configuration.new
|
@@ -47,10 +61,11 @@ module BetterSeeder
|
|
47
61
|
# Metodo install che crea l'initializer di BetterSeeder in config/initializers
|
48
62
|
# con le seguenti impostazioni:
|
49
63
|
# - log_language: lingua da usare per i log (es. :en, :it)
|
64
|
+
# - log_level: livello di logging nell'output dei dati del seeder (:debug, :info, :error)
|
50
65
|
# - structure_path: percorso dove sono memorizzati i file di structure (default: Rails.root/db/seed/structure)
|
51
66
|
# - preload_path: percorso dove verranno salvati i file esportati (default: Rails.root/db/seed/preload)
|
52
67
|
def self.install
|
53
|
-
initializer_path =
|
68
|
+
initializer_path = Rails.root.join('config', 'initializers', 'better_seeder.rb').to_s
|
54
69
|
|
55
70
|
if File.exist?(initializer_path)
|
56
71
|
message = "BetterSeeder initializer already exists at #{initializer_path}"
|
@@ -60,6 +75,7 @@ module BetterSeeder
|
|
60
75
|
# This file was generated by BetterSeeder.install
|
61
76
|
BetterSeeder.configure do |config|
|
62
77
|
config.log_language = :en
|
78
|
+
config.log_level = :error
|
63
79
|
config.structure_path = Rails.root.join('db', 'seed', 'structure')
|
64
80
|
config.preload_path = Rails.root.join('db', 'seed', 'preload')
|
65
81
|
end
|
@@ -94,9 +110,15 @@ module BetterSeeder
|
|
94
110
|
# - Se abilitato, i record vengono caricati nel database e successivamente esportati nel formato richiesto
|
95
111
|
# - Vengono raccolte statistiche e loggato il tempo totale di esecuzione
|
96
112
|
def self.magic(config)
|
97
|
-
|
98
|
-
|
99
|
-
|
113
|
+
previous_log_level = ActiveRecord::Base.logger.level
|
114
|
+
BetterSeeder::Utils.log_level_setup
|
115
|
+
|
116
|
+
message = "[LOGGER] previous log level: #{previous_log_level}, actual log level: #{ActiveRecord::Base.logger.level}"
|
117
|
+
BetterSeeder::Utils.logger(message: message)
|
118
|
+
|
119
|
+
start_time = Time.zone.now
|
120
|
+
stats = {} # Statistiche: modello => numero di record caricati
|
121
|
+
parent_loaded_records = {} # Per memorizzare i record creati per i modelli parent
|
100
122
|
|
101
123
|
ActiveRecord::Base.transaction do
|
102
124
|
export_type = config[:configurations][:export_type]
|
@@ -106,16 +128,15 @@ module BetterSeeder
|
|
106
128
|
end
|
107
129
|
end
|
108
130
|
|
109
|
-
|
131
|
+
ActiveRecord::Base.logger.level = previous_log_level
|
132
|
+
total_time = Time.zone.now - start_time
|
110
133
|
log_statistics(stats, total_time)
|
111
134
|
end
|
112
135
|
|
113
|
-
def self.generate_structure(model_name:
|
136
|
+
def self.generate_structure(model_name:)
|
114
137
|
BetterSeeder::Builders::Structure.generate(model_name)
|
115
138
|
end
|
116
139
|
|
117
|
-
private
|
118
|
-
|
119
140
|
# Processa la configurazione per un singolo modello.
|
120
141
|
# Carica il file di structure corrispondente e recupera la configurazione tramite `seed_config`.
|
121
142
|
# Quindi, esegue il recupero o la generazione dei record, iniezione di eventuali foreign key per modelli child,
|
@@ -145,13 +166,18 @@ module BetterSeeder
|
|
145
166
|
|
146
167
|
# Recupera la configurazione specifica dal file di structure tramite il metodo seed_config.
|
147
168
|
# Se non definito, vengono usati dei valori di default.
|
148
|
-
seed_config
|
169
|
+
seed_config = structure_class.respond_to?(:seed_config) ? structure_class.seed_config : {}
|
149
170
|
file_name = seed_config[:file_name] || "#{model_name.underscore}_seed"
|
150
|
-
excluded_columns =
|
171
|
+
excluded_columns = if export_type.to_s.downcase == 'sql'
|
172
|
+
[]
|
173
|
+
else
|
174
|
+
seed_config.dig(:columns, :excluded) || []
|
175
|
+
end
|
151
176
|
generate_data = seed_config.fetch(:generate_data, true)
|
152
177
|
count = seed_config[:count] || 10
|
153
178
|
load_data = seed_config.fetch(:load_data, true)
|
154
|
-
parent = seed_config[:parent]
|
179
|
+
parent = seed_config[:parent] # nil oppure valore (o array) per modelli child
|
180
|
+
superclass = seed_config[:superclass] # nil oppure valore (o array) per modelli child
|
155
181
|
|
156
182
|
# Log per indicare se il modello è parent o child.
|
157
183
|
message = if parent.nil?
|
@@ -162,7 +188,11 @@ module BetterSeeder
|
|
162
188
|
BetterSeeder::Utils.logger(message: message)
|
163
189
|
|
164
190
|
# Recupera la classe reale del modello (ActiveRecord).
|
165
|
-
model_class =
|
191
|
+
model_class = begin
|
192
|
+
Object.const_get(model_name)
|
193
|
+
rescue StandardError
|
194
|
+
nil
|
195
|
+
end
|
166
196
|
unless model_class
|
167
197
|
message = "[ERROR] Model #{model_name} not found."
|
168
198
|
BetterSeeder::Utils.logger(message: message)
|
@@ -170,45 +200,28 @@ module BetterSeeder
|
|
170
200
|
raise Object.const_get(model_name)
|
171
201
|
end
|
172
202
|
|
173
|
-
# Recupera o genera i record.
|
174
|
-
records = if generate_data
|
175
|
-
Farms::Farmer.generate(model: model_name, count: count)
|
176
|
-
else
|
177
|
-
model_class.all.map(&:attributes)
|
178
|
-
end
|
179
|
-
|
180
|
-
# Rimuove le colonne escluse.
|
181
|
-
processed_records = records.map do |record|
|
182
|
-
record.reject { |key, _| excluded_columns.include?(key.to_sym) }
|
183
|
-
end
|
184
|
-
|
185
|
-
# Se il modello è child, inietta le foreign key.
|
186
|
-
if parent
|
187
|
-
Array(parent).each do |parent_model_name|
|
188
|
-
parent_records = parent_loaded_records[parent_model_name]
|
189
|
-
if parent_records.nil? || parent_records.empty?
|
190
|
-
message = "[ERROR] No loaded records found for parent model #{parent_model_name}. Cannot assign foreign key for #{model_name}."
|
191
|
-
BetterSeeder::Utils.logger(message: message)
|
192
|
-
else
|
193
|
-
# Il nome della foreign key è ottenuto prendendo l'ultima parte del nome del modello padre,
|
194
|
-
# trasformandola in minuscolo e in forma singolare, e aggiungendo "_id".
|
195
|
-
foreign_key = parent_model_name.split("::").last.underscore.singularize + "_id"
|
196
|
-
processed_records.each do |record|
|
197
|
-
record[foreign_key] = parent_records.sample.id
|
198
|
-
end
|
199
|
-
end
|
200
|
-
end
|
201
|
-
end
|
202
|
-
|
203
203
|
# Se abilitato, carica i record nel database.
|
204
|
-
if load_data
|
205
|
-
|
206
|
-
stats[model_name] =
|
207
|
-
|
204
|
+
if load_data && File.exist?("#{BetterSeeder.configuration.preload_path}/#{seed_config[:file_name]}.sql")
|
205
|
+
load_data_from_file(seed_config)
|
206
|
+
stats[model_name] = model_class.count
|
207
|
+
elsif generate_data
|
208
|
+
records = Farms::Farmer.generate(model: model_name, count: count)
|
209
|
+
total_records = records.size
|
210
|
+
stats[model_name] = total_records
|
211
|
+
created_records = load_records_into_db(model_class, records, total_records, model_name,
|
212
|
+
superclass)
|
208
213
|
# Se il modello è parent, salva i record creati per poterli utilizzare in seguito per i modelli child.
|
209
214
|
parent_loaded_records[model_name] = created_records if parent.nil?
|
210
215
|
else
|
211
|
-
|
216
|
+
model_class.all.map(&:attributes)
|
217
|
+
end
|
218
|
+
|
219
|
+
# Rimuove le colonne escluse.
|
220
|
+
return if BetterSeeder.generated_records[(superclass || model_name).to_s].nil?
|
221
|
+
|
222
|
+
processed_records = BetterSeeder.generated_records[(superclass || model_name).to_s]
|
223
|
+
processed_records = processed_records.map do |campaign|
|
224
|
+
campaign.attributes.except(*excluded_columns.map(&:to_s))
|
212
225
|
end
|
213
226
|
|
214
227
|
# Esporta i record nel formato richiesto.
|
@@ -219,25 +232,21 @@ module BetterSeeder
|
|
219
232
|
# I log delle query SQL vengono temporaneamente disabilitati.
|
220
233
|
#
|
221
234
|
# @return [Array<Object>] Array dei record creati (istanze ActiveRecord)
|
222
|
-
def self.load_records_into_db(model_class, processed_records, total_records, model_name)
|
223
|
-
created_records = []
|
235
|
+
def self.load_records_into_db(model_class, processed_records, total_records, model_name, superclass)
|
224
236
|
progressbar = ProgressBar.create(total: total_records, format: '%a %B %p%% %t')
|
225
|
-
message
|
237
|
+
message = "[INFO] Starting to load #{total_records} records for model #{model_name}..."
|
226
238
|
BetterSeeder::Utils.logger(message: message)
|
227
239
|
|
228
|
-
previous_level = ActiveRecord::Base.logger.level
|
229
|
-
ActiveRecord::Base.logger.level = Logger::ERROR
|
230
|
-
|
231
240
|
processed_records.each do |record|
|
232
241
|
created = model_class.create!(record)
|
233
|
-
|
242
|
+
BetterSeeder.store_generated_record(superclass || model_name, created)
|
234
243
|
progressbar.increment
|
235
244
|
end
|
236
245
|
|
237
|
-
ActiveRecord::Base.logger.level = previous_level
|
238
246
|
message = "[INFO] Finished loading #{total_records} records into model #{model_name}."
|
239
247
|
BetterSeeder::Utils.logger(message: message)
|
240
|
-
|
248
|
+
|
249
|
+
BetterSeeder.generated_records[model_name]
|
241
250
|
end
|
242
251
|
|
243
252
|
# Esporta i record nel formato specificato (json, csv, sql).
|
@@ -261,14 +270,39 @@ module BetterSeeder
|
|
261
270
|
|
262
271
|
# Log finale con le statistiche raccolte e il tempo totale di esecuzione.
|
263
272
|
def self.log_statistics(stats, total_time)
|
264
|
-
stats_message = stats.map { |model, count| "#{model}: #{count} records" }.join("
|
265
|
-
message
|
273
|
+
stats_message = stats.map { |model, count| "#{model}: #{count} records" }.join("\n")
|
274
|
+
message = "[INFO] Finished processing all models in #{total_time.round(2)} seconds. Statistics: \n#{stats_message}"
|
266
275
|
BetterSeeder::Utils.logger(message: message)
|
267
276
|
end
|
268
277
|
|
269
278
|
# Metodo di utilità per trasformare il nome della classe in un formato in cui le lettere
|
270
279
|
# sono in minuscolo e separate da underscore.
|
271
280
|
def self.transform_class_name(class_name)
|
272
|
-
class_name.split(
|
281
|
+
class_name.split('::').map(&:underscore).join('_')
|
282
|
+
end
|
283
|
+
|
284
|
+
def self.load_data_from_file(seed_config)
|
285
|
+
config = seed_config
|
286
|
+
return unless config[:load_data] # Se load_data non è abilitato, esce senza fare nulla.
|
287
|
+
|
288
|
+
# Costruisce il nome del file di seed: ad esempio, se config[:file_name] è "my_model_seed",
|
289
|
+
# il file atteso sarà "my_model_seed_seed.sql".
|
290
|
+
seed_file_name = "#{config[:file_name]}.sql"
|
291
|
+
seed_file_path = File.join(BetterSeeder.configuration.preload_path, seed_file_name)
|
292
|
+
|
293
|
+
unless File.exist?(seed_file_path)
|
294
|
+
BetterSeeder::Utils.logger(message: "[WARN] Seed file not found: #{seed_file_path}")
|
295
|
+
return false
|
296
|
+
end
|
297
|
+
|
298
|
+
sql = File.read(seed_file_path)
|
299
|
+
begin
|
300
|
+
ActiveRecord::Base.connection.execute(sql)
|
301
|
+
BetterSeeder::Utils.logger(message: "[INFO] Loaded seed file: #{seed_file_path}")
|
302
|
+
true
|
303
|
+
rescue StandardError => e
|
304
|
+
BetterSeeder::Utils.logger(message: "[ERROR] Failed to load seed file: #{seed_file_path} - Error: #{e.message}")
|
305
|
+
false
|
306
|
+
end
|
273
307
|
end
|
274
308
|
end
|
@@ -1,5 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# lib/generators/better_seeder/structure_generator.rb
|
2
|
-
require
|
4
|
+
require 'rails/generators'
|
3
5
|
|
4
6
|
module BetterSeeder
|
5
7
|
class StructureGenerator < Rails::Generators::NamedBase
|
@@ -7,9 +9,9 @@ module BetterSeeder
|
|
7
9
|
# source_root File.expand_path("templates", __dir__)
|
8
10
|
|
9
11
|
def create_structure_file
|
10
|
-
say_status(
|
12
|
+
say_status('info', "Generating structure file for #{name}", :green)
|
11
13
|
file_path = BetterSeeder.generate_structure(model_name: name)
|
12
|
-
say_status(
|
14
|
+
say_status('info', "Structure file created at #{file_path}", :green)
|
13
15
|
end
|
14
16
|
end
|
15
|
-
end
|
17
|
+
end
|
metadata
CHANGED
@@ -1,31 +1,31 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: better_seeder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- alessio_bussolari
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-02-
|
11
|
+
date: 2025-02-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: dry-schema
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '1.5'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '1.5'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name: dry-
|
28
|
+
name: dry-types
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
@@ -39,19 +39,19 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1.5'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: ffaker
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '2.19'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '2.19'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: ruby-progressbar
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -67,7 +67,7 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '1.11'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: activerecord
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
@@ -81,7 +81,7 @@ dependencies:
|
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '4.2'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: activesupport
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - ">="
|
@@ -151,6 +151,7 @@ metadata:
|
|
151
151
|
homepage_uri: https://github.com/alessiobussolari/better_seeder
|
152
152
|
source_code_uri: https://github.com/alessiobussolari/better_seeder
|
153
153
|
changelog_uri: https://github.com/alessiobussolari/better_seeder/blob/main/CHANGELOG.md
|
154
|
+
rubygems_mfa_required: 'true'
|
154
155
|
post_install_message:
|
155
156
|
rdoc_options: []
|
156
157
|
require_paths:
|