better_seeder 0.2.3 → 0.2.4
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 +4 -4
- 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 +91 -54
- data/lib/better_seeder/structure/utils.rb +2 -0
- data/lib/better_seeder/utils/common.rb +69 -0
- data/lib/better_seeder/utils/store.rb +36 -0
- data/lib/better_seeder/utils.rb +44 -6
- data/lib/better_seeder/version.rb +1 -1
- data/lib/better_seeder.rb +51 -68
- data/lib/generators/better_seeder/structure_generator.rb +6 -4
- metadata +14 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7efa6650656240adf416711cabab9debe997812e5e666472e3391c59ad74ce44
|
4
|
+
data.tar.gz: 504062b646d4800f75dbb20ef7e221fc0da730bf9aa207026795341e9e1c50e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bc24811f8a5ea605435701ee6ceda23927c1dad77327e054cd9b31756065037ef75a77f6b02bd1c2558f92870746de38366814bea90cd5597aba29a0b85b1e11
|
7
|
+
data.tar.gz: 34c2cfc41ca466c7c4e7e4db290a47fc0ffd92503b2fc65c567c716f7123138e2de4f2ce485f4b1c5dc1e56f40f3b8b205b4d96d51deec3da5d35ffc6bc124c0
|
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,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# lib/better_seeder/configuration.rb
|
2
4
|
|
3
5
|
module BetterSeeder
|
@@ -5,14 +7,12 @@ module BetterSeeder
|
|
5
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
|
-
@log_level = :info
|
11
13
|
@structure_path = Rails.root.join('db', 'seed', 'structure')
|
12
14
|
@preload_path = Rails.root.join('db', 'seed', 'preload')
|
13
15
|
else
|
14
|
-
@log_language = :en
|
15
|
-
@log_level = :info
|
16
16
|
@structure_path = File.join(Dir.pwd, 'db', 'seed', 'structure')
|
17
17
|
@preload_path = File.join(Dir.pwd, 'db', 'seed', 'preload')
|
18
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,11 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module BetterSeeder
|
2
4
|
module Farms
|
3
5
|
class Farmer
|
4
|
-
|
5
6
|
class << self
|
6
7
|
def generate(options = {})
|
7
|
-
model_name = options[:model] or raise ArgumentError,
|
8
|
-
count = options[:count] || 10
|
8
|
+
model_name = options[:model] or raise ArgumentError, 'Missing :model option'
|
9
9
|
|
10
10
|
# Costruisce il percorso del file di structure.
|
11
11
|
structure_file = File.expand_path(
|
@@ -14,7 +14,6 @@ module BetterSeeder
|
|
14
14
|
)
|
15
15
|
raise "Structure file not found: #{structure_file}" unless File.exist?(structure_file)
|
16
16
|
|
17
|
-
# Carica il file di structure.
|
18
17
|
load structure_file
|
19
18
|
|
20
19
|
# Costruisce il nome della classe di structure: es. "Media::Participant" => "Media::ParticipantStructure"
|
@@ -27,91 +26,130 @@ module BetterSeeder
|
|
27
26
|
raise error
|
28
27
|
end
|
29
28
|
|
30
|
-
|
29
|
+
seed_config = structure_class.respond_to?(:seed_config) ? structure_class.seed_config : {}
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
31
|
+
generated_records = []
|
32
|
+
if seed_config.key?(:childs)
|
33
|
+
# Logica per il modello child: il numero totale di record = count * childs_count
|
34
|
+
parent_count = seed_config[:count] || 10
|
35
|
+
childs_count = seed_config.dig(:childs, :count) || 10
|
36
|
+
|
37
|
+
parent_count.times do |_i|
|
38
|
+
childs_count.times do |child_index|
|
39
|
+
new_record = nil
|
40
|
+
loop do
|
41
|
+
# Passo l'indice del record figlio per far variare gli attributi definiti in childs[:attributes]
|
42
|
+
new_record = build_record(model_name, structure_class, child_index, child_mode: true)
|
43
|
+
new_record = inject_parent_keys(model_name, new_record, structure_class)
|
44
|
+
break if validate_record(new_record, structure_class) &&
|
45
|
+
!record_exists?(model_name, new_record, structure_class, generated_records)
|
46
|
+
end
|
47
|
+
generated_records.push(new_record)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
else
|
51
|
+
# Logica standard per i modelli parent (o modelli senza childs)
|
52
|
+
count = options[:count] || (seed_config[:count] || 10)
|
53
|
+
count.times do |index|
|
54
|
+
new_record = nil
|
55
|
+
loop do
|
56
|
+
new_record = build_record(model_name, structure_class, index)
|
57
|
+
new_record = inject_parent_keys(model_name, new_record, structure_class)
|
58
|
+
break if validate_record(new_record, structure_class) &&
|
59
|
+
!record_exists?(model_name, new_record, structure_class, generated_records)
|
60
|
+
end
|
61
|
+
generated_records.push(new_record)
|
39
62
|
end
|
40
|
-
generated_records.push(new_record)
|
41
63
|
end
|
42
64
|
|
43
65
|
generated_records
|
44
66
|
end
|
45
67
|
|
68
|
+
# Il metodo build_record ora supporta la modalità child_mode.
|
69
|
+
# Se child_mode è true e nella configurazione (seed_config) è definita la chiave childs con :attributes,
|
70
|
+
# per ogni attributo viene usato il valore dell'array corrispondente all'indice (child_index) passato.
|
71
|
+
def build_record(_model_name, structure_class, index, child_mode: false)
|
72
|
+
generation_rules = structure_class.structure
|
73
|
+
raise 'Structure must be a Hash' unless generation_rules.is_a?(Hash)
|
74
|
+
|
75
|
+
seed_config = structure_class.respond_to?(:seed_config) ? structure_class.seed_config : {}
|
76
|
+
|
77
|
+
record = {}
|
78
|
+
generation_rules.each do |attribute, rule|
|
79
|
+
generator = rule[1]
|
80
|
+
if child_mode && seed_config.dig(:childs, :attributes, attribute).is_a?(Array)
|
81
|
+
values = seed_config[:childs][:attributes][attribute]
|
82
|
+
value = values[index] # index viene passato dal loop interno
|
83
|
+
else
|
84
|
+
value = generator.respond_to?(:call) ? generator.call : generator
|
85
|
+
end
|
86
|
+
record[attribute] = value
|
87
|
+
end
|
88
|
+
|
89
|
+
record
|
90
|
+
end
|
91
|
+
|
92
|
+
# Restituisce il numero di record figli da generare per ciascun "record padre".
|
93
|
+
# Nel caso in cui nella configurazione sia presente la chiave childs, restituisce childs[:count],
|
94
|
+
# altrimenti default a 10.
|
95
|
+
def child_record_count(options = {})
|
96
|
+
model_name = options[:model] or raise ArgumentError, 'Missing :model option'
|
97
|
+
|
98
|
+
structure_file = File.expand_path(
|
99
|
+
File.join(BetterSeeder.configuration.structure_path, "#{model_name.underscore}_structure.rb"),
|
100
|
+
Dir.pwd
|
101
|
+
)
|
102
|
+
raise "Structure file not found: #{structure_file}" unless File.exist?(structure_file)
|
103
|
+
|
104
|
+
load structure_file
|
105
|
+
structure_class_name = "#{model_name}Structure"
|
106
|
+
structure_class = Object.const_get(structure_class_name)
|
107
|
+
seed_config = structure_class.respond_to?(:seed_config) ? structure_class.seed_config : {}
|
108
|
+
seed_config.dig(:childs, :count) || 10
|
109
|
+
end
|
110
|
+
|
46
111
|
private
|
47
112
|
|
48
|
-
def record_exists?(
|
49
|
-
# Se non è definito il metodo unique_keys, non eseguiamo il controllo
|
113
|
+
def record_exists?(_model_name, record, structure_class, generated_records)
|
50
114
|
return false unless structure_class.respond_to?(:unique_keys)
|
115
|
+
|
51
116
|
unique_key_sets = structure_class.unique_keys
|
52
117
|
return false if unique_key_sets.empty?
|
53
118
|
|
54
|
-
# Determina il modello associato: si assume che il nome del modello sia
|
55
|
-
# dato dalla rimozione della stringa "Structure" dal nome della classe di structure.
|
56
119
|
model_class_name = structure_class.to_s.sub(/Structure$/, '')
|
57
|
-
model_class
|
120
|
+
model_class = Object.const_get(model_class_name)
|
58
121
|
|
59
|
-
# Per ogni set di chiavi uniche, costruiamo le condizioni della query
|
60
122
|
unique_key_sets.each do |key_set|
|
61
123
|
conditions = {}
|
62
124
|
key_set.each do |col|
|
63
125
|
conditions[col] = record[col]
|
64
126
|
end
|
65
|
-
|
66
|
-
return true if generated_records.find do |record|
|
67
|
-
conditions.all? { |key, value| record[key] == value }
|
68
|
-
end.present?
|
127
|
+
return true if generated_records.find { |r| conditions.all? { |key, value| r[key] == value } }
|
69
128
|
return true if model_class.where(conditions).exists?
|
70
129
|
end
|
71
130
|
|
72
131
|
false
|
73
132
|
end
|
74
133
|
|
75
|
-
def
|
76
|
-
|
77
|
-
raise "Structure must be a Hash" unless generation_rules.is_a?(Hash)
|
78
|
-
|
79
|
-
record = {}
|
80
|
-
generation_rules.each do |attribute, rule|
|
81
|
-
# Ogni rule è un array nel formato [tipo, generatore]
|
82
|
-
generator = rule[1]
|
83
|
-
value = generator.respond_to?(:call) ? generator.call : generator
|
84
|
-
record[attribute] = value
|
85
|
-
end
|
86
|
-
|
87
|
-
record
|
88
|
-
end
|
89
|
-
|
90
|
-
def inject_parent_keys(model_name, record, structure_class)
|
91
|
-
config = structure_class.respond_to?(:seed_config) ? structure_class.seed_config : {}
|
134
|
+
def inject_parent_keys(_model_name, record, structure_class)
|
135
|
+
config = structure_class.respond_to?(:seed_config) ? structure_class.seed_config : {}
|
92
136
|
parents_spec = config[:parents]
|
93
|
-
return record
|
137
|
+
return record if parents_spec.blank?
|
94
138
|
|
95
139
|
parents_spec.each do |parent_config|
|
96
140
|
parent_model = parent_config[:model]
|
97
|
-
column
|
141
|
+
column = parent_config[:column]
|
142
|
+
pool_key = parent_model.to_s
|
98
143
|
|
99
|
-
|
100
|
-
# Usiamo il nome del modello come chiave nel pool.
|
101
|
-
pool_key = parent_model.to_s
|
102
|
-
parent_record = if defined?(BetterSeeder.generated_records) &&
|
144
|
+
unless defined?(BetterSeeder.generated_records) &&
|
103
145
|
BetterSeeder.generated_records[pool_key] &&
|
104
146
|
!BetterSeeder.generated_records[pool_key].empty?
|
105
|
-
|
106
|
-
|
107
|
-
BetterSeeder.generated_records[pool_key] = parent_model.all
|
108
|
-
BetterSeeder.generated_records[pool_key].sample
|
109
|
-
end
|
147
|
+
BetterSeeder.generated_records[pool_key] = parent_model.all
|
148
|
+
end
|
110
149
|
|
150
|
+
parent_record = BetterSeeder.generated_records[pool_key].sample
|
111
151
|
raise "Parent record not found for #{parent_model}" unless parent_record
|
112
152
|
|
113
|
-
# Inietta nel record la chiave esterna indicata nella configurazione.
|
114
|
-
# binding.pry if model_name == "Media::Participant"
|
115
153
|
record[column] = parent_record[:id]
|
116
154
|
end
|
117
155
|
|
@@ -127,7 +165,6 @@ module BetterSeeder
|
|
127
165
|
|
128
166
|
raise result.errors.to_h
|
129
167
|
end
|
130
|
-
|
131
168
|
end
|
132
169
|
end
|
133
170
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module BetterSeeder
|
2
|
+
module Utils
|
3
|
+
module Common
|
4
|
+
class << self
|
5
|
+
|
6
|
+
##
|
7
|
+
# Trasforma un nome di classe in snake_case e aggiunge il suffisso "_structure.rb".
|
8
|
+
#
|
9
|
+
# ==== Esempio
|
10
|
+
# transform_class_name("Campaigns::Campaign")
|
11
|
+
# # => "campaigns/campaign_structure.rb"
|
12
|
+
#
|
13
|
+
# ==== Parametri
|
14
|
+
# * +class_name+ - Stringa che rappresenta il nome della classe, eventualmente suddiviso in
|
15
|
+
# namespace separati da "::".
|
16
|
+
#
|
17
|
+
# ==== Ritorno
|
18
|
+
# Restituisce una stringa con il nome della classe in formato snake_case e l'ultimo elemento
|
19
|
+
# terminato con "_structure.rb".
|
20
|
+
#
|
21
|
+
def transform_class_name(class_name)
|
22
|
+
elements = class_name.split('::').map(&:underscore)
|
23
|
+
# Aggiunge "_structure.rb" all'ultimo elemento
|
24
|
+
elements[-1] = "#{elements[-1]}_structure.rb"
|
25
|
+
elements.join('/')
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Registra un messaggio usando il logger di Rails se disponibile, altrimenti lo stampa su standard output.
|
30
|
+
#
|
31
|
+
# ==== Parametri
|
32
|
+
# * +message+ - Il messaggio da loggare (può essere una stringa o nil).
|
33
|
+
#
|
34
|
+
# ==== Ritorno
|
35
|
+
# Non ritorna un valore significativo.
|
36
|
+
#
|
37
|
+
def logger(message: nil)
|
38
|
+
if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
39
|
+
Rails.logger.info message
|
40
|
+
else
|
41
|
+
puts message
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Configura il livello del logger per ActiveRecord in base alla configurazione definita in BetterSeeder.
|
47
|
+
#
|
48
|
+
# ==== Dettagli
|
49
|
+
# Il metodo imposta il livello del logger in base al valore di BetterSeeder.configuration.log_level:
|
50
|
+
# * +:debug+ -> Logger::DEBUG
|
51
|
+
# * +:info+ -> Logger::INFO
|
52
|
+
# * +:error+ -> Logger::ERROR
|
53
|
+
# Se il livello non corrisponde a nessuna delle opzioni previste, viene impostato il livello +Logger::DEBUG+.
|
54
|
+
#
|
55
|
+
def log_level_setup
|
56
|
+
level = case BetterSeeder.configuration.log_level
|
57
|
+
when :debug then Logger::DEBUG
|
58
|
+
when :info then Logger::INFO
|
59
|
+
when :error then Logger::ERROR
|
60
|
+
else Logger::DEBUG
|
61
|
+
end
|
62
|
+
|
63
|
+
ActiveRecord::Base.logger.level = level
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Questo file definisce il modulo +BetterSeeder::Utils::Store+ che offre
|
2
|
+
# metodi per memorizzare e recuperare record generati e per gestire la configurazione globale.
|
3
|
+
#
|
4
|
+
module BetterSeeder
|
5
|
+
module Utils
|
6
|
+
module Store
|
7
|
+
class << self
|
8
|
+
|
9
|
+
@generated_records = {}
|
10
|
+
|
11
|
+
##
|
12
|
+
# Restituisce l'hash contenente i record generati.
|
13
|
+
#
|
14
|
+
# @return [Hash] hash con i record generati per ciascun modello.
|
15
|
+
#
|
16
|
+
def generated_records
|
17
|
+
@generated_records
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Memorizza un record generato per il modello specificato.
|
22
|
+
#
|
23
|
+
# Se non esiste già, viene inizializzato un array vuoto per il modello.
|
24
|
+
#
|
25
|
+
# @param model_name [String, Symbol] il nome del modello
|
26
|
+
# @param record [Object] il record da memorizzare
|
27
|
+
#
|
28
|
+
def store_generated_record(model_name, record)
|
29
|
+
@generated_records[model_name.to_s] ||= []
|
30
|
+
@generated_records[model_name.to_s] << record
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/better_seeder/utils.rb
CHANGED
@@ -1,18 +1,47 @@
|
|
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
13
|
class << self
|
7
|
-
|
8
|
-
#
|
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
|
+
#
|
9
29
|
def transform_class_name(class_name)
|
10
|
-
elements
|
30
|
+
elements = class_name.split('::').map(&:underscore)
|
11
31
|
# Aggiunge "_structure.rb" all'ultimo elemento
|
12
32
|
elements[-1] = "#{elements[-1]}_structure.rb"
|
13
|
-
elements.join(
|
33
|
+
elements.join('/')
|
14
34
|
end
|
15
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
|
+
#
|
16
45
|
def logger(message: nil)
|
17
46
|
if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
18
47
|
Rails.logger.info message
|
@@ -21,6 +50,16 @@ module BetterSeeder
|
|
21
50
|
end
|
22
51
|
end
|
23
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
|
+
#
|
24
63
|
def log_level_setup
|
25
64
|
level = case BetterSeeder.configuration.log_level
|
26
65
|
when :debug then Logger::DEBUG
|
@@ -31,7 +70,6 @@ module BetterSeeder
|
|
31
70
|
|
32
71
|
ActiveRecord::Base.logger.level = level
|
33
72
|
end
|
34
|
-
|
35
73
|
end
|
36
74
|
end
|
37
75
|
end
|
data/lib/better_seeder.rb
CHANGED
@@ -1,32 +1,16 @@
|
|
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
|
-
class Configuration
|
13
|
-
attr_accessor :log_language, :structure_path, :preload_path
|
14
|
-
|
15
|
-
def initialize
|
16
|
-
if defined?(Rails) && Rails.respond_to?(:root)
|
17
|
-
@log_language = :en
|
18
|
-
@log_level = :info
|
19
|
-
@structure_path = Rails.root.join('db', 'seed', 'structure')
|
20
|
-
@preload_path = Rails.root.join('db', 'seed', 'preload')
|
21
|
-
else
|
22
|
-
@log_language = :en
|
23
|
-
@log_level = :info
|
24
|
-
@structure_path = File.join(Dir.pwd, 'db', 'seed', 'structure')
|
25
|
-
@preload_path = File.join(Dir.pwd, 'db', 'seed', 'preload')
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
14
|
# Definisce un hash che fungerà da pool per memorizzare i record generati per ciascun modello.
|
31
15
|
@generated_records = {}
|
32
16
|
|
@@ -65,7 +49,7 @@ module BetterSeeder
|
|
65
49
|
# - structure_path: percorso dove sono memorizzati i file di structure (default: Rails.root/db/seed/structure)
|
66
50
|
# - preload_path: percorso dove verranno salvati i file esportati (default: Rails.root/db/seed/preload)
|
67
51
|
def self.install
|
68
|
-
initializer_path =
|
52
|
+
initializer_path = Rails.root.join('config', 'initializers', 'better_seeder.rb').to_s
|
69
53
|
|
70
54
|
if File.exist?(initializer_path)
|
71
55
|
message = "BetterSeeder initializer already exists at #{initializer_path}"
|
@@ -116,8 +100,8 @@ module BetterSeeder
|
|
116
100
|
message = "[LOGGER] previous log level: #{previous_log_level}, actual log level: #{ActiveRecord::Base.logger.level}"
|
117
101
|
BetterSeeder::Utils.logger(message: message)
|
118
102
|
|
119
|
-
start_time
|
120
|
-
stats
|
103
|
+
start_time = Time.zone.now
|
104
|
+
stats = {} # Statistiche: modello => numero di record caricati
|
121
105
|
parent_loaded_records = {} # Per memorizzare i record creati per i modelli parent
|
122
106
|
|
123
107
|
ActiveRecord::Base.transaction do
|
@@ -129,7 +113,7 @@ module BetterSeeder
|
|
129
113
|
end
|
130
114
|
|
131
115
|
ActiveRecord::Base.logger.level = previous_log_level
|
132
|
-
total_time
|
116
|
+
total_time = Time.zone.now - start_time
|
133
117
|
log_statistics(stats, total_time)
|
134
118
|
end
|
135
119
|
|
@@ -137,8 +121,6 @@ module BetterSeeder
|
|
137
121
|
BetterSeeder::Builders::Structure.generate(model_name)
|
138
122
|
end
|
139
123
|
|
140
|
-
private
|
141
|
-
|
142
124
|
# Processa la configurazione per un singolo modello.
|
143
125
|
# Carica il file di structure corrispondente e recupera la configurazione tramite `seed_config`.
|
144
126
|
# Quindi, esegue il recupero o la generazione dei record, iniezione di eventuali foreign key per modelli child,
|
@@ -168,18 +150,18 @@ module BetterSeeder
|
|
168
150
|
|
169
151
|
# Recupera la configurazione specifica dal file di structure tramite il metodo seed_config.
|
170
152
|
# Se non definito, vengono usati dei valori di default.
|
171
|
-
seed_config
|
172
|
-
file_name
|
173
|
-
excluded_columns = if export_type.to_s.downcase
|
174
|
-
seed_config.dig(:columns, :excluded) || []
|
175
|
-
else
|
153
|
+
seed_config = structure_class.respond_to?(:seed_config) ? structure_class.seed_config : {}
|
154
|
+
file_name = seed_config[:file_name] || "#{model_name.underscore}_seed"
|
155
|
+
excluded_columns = if export_type.to_s.downcase == 'sql'
|
176
156
|
[]
|
157
|
+
else
|
158
|
+
seed_config.dig(:columns, :excluded) || []
|
177
159
|
end
|
178
|
-
generate_data
|
179
|
-
count
|
180
|
-
load_data
|
181
|
-
parent
|
182
|
-
superclass
|
160
|
+
generate_data = seed_config.fetch(:generate_data, true)
|
161
|
+
count = seed_config[:count] || 10
|
162
|
+
load_data = seed_config.fetch(:load_data, true)
|
163
|
+
parent = seed_config[:parent] # nil oppure valore (o array) per modelli child
|
164
|
+
superclass = seed_config[:superclass] # nil oppure valore (o array) per modelli child
|
183
165
|
|
184
166
|
# Log per indicare se il modello è parent o child.
|
185
167
|
message = if parent.nil?
|
@@ -190,41 +172,42 @@ module BetterSeeder
|
|
190
172
|
BetterSeeder::Utils.logger(message: message)
|
191
173
|
|
192
174
|
# Recupera la classe reale del modello (ActiveRecord).
|
193
|
-
model_class =
|
175
|
+
model_class = begin
|
176
|
+
Object.const_get(model_name)
|
177
|
+
rescue StandardError
|
178
|
+
nil
|
179
|
+
end
|
194
180
|
unless model_class
|
195
181
|
message = "[ERROR] Model #{model_name} not found."
|
196
182
|
BetterSeeder::Utils.logger(message: message)
|
197
|
-
|
198
183
|
raise Object.const_get(model_name)
|
199
184
|
end
|
200
185
|
|
201
186
|
# Se abilitato, carica i record nel database.
|
202
|
-
if load_data && File.exist?("#{BetterSeeder.configuration.preload_path
|
187
|
+
if load_data && File.exist?("#{BetterSeeder.configuration.preload_path}/#{seed_config[:file_name]}.sql")
|
203
188
|
load_data_from_file(seed_config)
|
204
|
-
stats[model_name] = model_class.
|
189
|
+
stats[model_name] = model_class.count
|
190
|
+
elsif generate_data
|
191
|
+
records = Farms::Farmer.generate(model: model_name, count: count)
|
192
|
+
total_records = records.size
|
193
|
+
stats[model_name] = total_records
|
194
|
+
created_records = load_records_into_db(model_class, records, total_records, model_name, superclass)
|
195
|
+
# Se il modello è parent, salva i record creati per poterli utilizzare in seguito per i modelli child.
|
196
|
+
parent_loaded_records[model_name] = created_records if parent.nil?
|
205
197
|
else
|
206
|
-
|
207
|
-
records = Farms::Farmer.generate(model: model_name, count: count)
|
208
|
-
total_records = records.size
|
209
|
-
stats[model_name] = total_records
|
210
|
-
created_records = load_records_into_db(model_class, records, total_records, model_name, superclass)
|
211
|
-
# Se il modello è parent, salva i record creati per poterli utilizzare in seguito per i modelli child.
|
212
|
-
parent_loaded_records[model_name] = created_records if parent.nil?
|
213
|
-
else
|
214
|
-
model_class.all.map(&:attributes)
|
215
|
-
end
|
198
|
+
model_class.all.map(&:attributes)
|
216
199
|
end
|
217
200
|
|
218
201
|
# Rimuove le colonne escluse.
|
219
|
-
|
220
|
-
processed_records = BetterSeeder.generated_records[(superclass || model_name).to_s]
|
221
|
-
processed_records = processed_records.map do |campaign|
|
222
|
-
campaign.attributes.except(*excluded_columns.map(&:to_s))
|
223
|
-
end
|
202
|
+
return if BetterSeeder.generated_records[(superclass || model_name).to_s].nil?
|
224
203
|
|
225
|
-
|
226
|
-
|
204
|
+
processed_records = BetterSeeder.generated_records[(superclass || model_name).to_s]
|
205
|
+
processed_records = processed_records.map do |record|
|
206
|
+
record.attributes.except(*excluded_columns.map(&:to_s))
|
227
207
|
end
|
208
|
+
|
209
|
+
# Esporta i record nel formato richiesto.
|
210
|
+
export_records(model_class, processed_records, export_type, file_name)
|
228
211
|
end
|
229
212
|
|
230
213
|
# Carica i record nel database, utilizzando una progress bar per monitorare il progresso.
|
@@ -233,7 +216,7 @@ module BetterSeeder
|
|
233
216
|
# @return [Array<Object>] Array dei record creati (istanze ActiveRecord)
|
234
217
|
def self.load_records_into_db(model_class, processed_records, total_records, model_name, superclass)
|
235
218
|
progressbar = ProgressBar.create(total: total_records, format: '%a %B %p%% %t')
|
236
|
-
message
|
219
|
+
message = "[INFO] Starting to load #{total_records} records for model #{model_name}..."
|
237
220
|
BetterSeeder::Utils.logger(message: message)
|
238
221
|
|
239
222
|
processed_records.each do |record|
|
@@ -270,14 +253,14 @@ module BetterSeeder
|
|
270
253
|
# Log finale con le statistiche raccolte e il tempo totale di esecuzione.
|
271
254
|
def self.log_statistics(stats, total_time)
|
272
255
|
stats_message = stats.map { |model, count| "#{model}: #{count} records" }.join("\n")
|
273
|
-
message
|
256
|
+
message = "[INFO] Finished processing all models in #{total_time.round(2)} seconds. Statistics: \n#{stats_message}"
|
274
257
|
BetterSeeder::Utils.logger(message: message)
|
275
258
|
end
|
276
259
|
|
277
260
|
# Metodo di utilità per trasformare il nome della classe in un formato in cui le lettere
|
278
261
|
# sono in minuscolo e separate da underscore.
|
279
262
|
def self.transform_class_name(class_name)
|
280
|
-
class_name.split(
|
263
|
+
class_name.split('::').map(&:underscore).join('_')
|
281
264
|
end
|
282
265
|
|
283
266
|
def self.load_data_from_file(seed_config)
|
@@ -299,7 +282,7 @@ module BetterSeeder
|
|
299
282
|
ActiveRecord::Base.connection.execute(sql)
|
300
283
|
BetterSeeder::Utils.logger(message: "[INFO] Loaded seed file: #{seed_file_path}")
|
301
284
|
true
|
302
|
-
rescue => e
|
285
|
+
rescue StandardError => e
|
303
286
|
BetterSeeder::Utils.logger(message: "[ERROR] Failed to load seed file: #{seed_file_path} - Error: #{e.message}")
|
304
287
|
false
|
305
288
|
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.4
|
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-
|
11
|
+
date: 2025-03-03 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
|
- - ">="
|
@@ -142,6 +142,8 @@ files:
|
|
142
142
|
- lib/better_seeder/farms/farmer.rb
|
143
143
|
- lib/better_seeder/structure/utils.rb
|
144
144
|
- lib/better_seeder/utils.rb
|
145
|
+
- lib/better_seeder/utils/common.rb
|
146
|
+
- lib/better_seeder/utils/store.rb
|
145
147
|
- lib/better_seeder/version.rb
|
146
148
|
- lib/generators/better_seeder/structure_generator.rb
|
147
149
|
homepage: https://github.com/alessiobussolari/better_seeder
|
@@ -151,6 +153,7 @@ metadata:
|
|
151
153
|
homepage_uri: https://github.com/alessiobussolari/better_seeder
|
152
154
|
source_code_uri: https://github.com/alessiobussolari/better_seeder
|
153
155
|
changelog_uri: https://github.com/alessiobussolari/better_seeder/blob/main/CHANGELOG.md
|
156
|
+
rubygems_mfa_required: 'true'
|
154
157
|
post_install_message:
|
155
158
|
rdoc_options: []
|
156
159
|
require_paths:
|