Module: BetterSeeder

Defined in:
lib/better_seeder/utils.rb,
lib/better_seeder.rb,
lib/better_seeder/version.rb,
lib/better_seeder/utils/store.rb,
lib/better_seeder/farms/farmer.rb,
lib/better_seeder/utils/common.rb,
lib/better_seeder/configuration.rb,
lib/better_seeder/exporters/csv.rb,
lib/better_seeder/exporters/sql.rb,
lib/better_seeder/exporters/base.rb,
lib/better_seeder/exporters/json.rb,
lib/better_seeder/structure/utils.rb,
lib/better_seeder/builders/structure.rb,
lib/generators/better_seeder/structure_generator.rb

Overview

lib/better_seeder/exporter/base_exporter.rb

Defined Under Namespace

Modules: Builders, Exporters, Farms, Structure, Utils Classes: Configuration, StructureGenerator

Constant Summary collapse

VERSION =
'0.2.5'

Class Method Summary collapse

Class Method Details

.configurationObject

Restituisce l’istanza globale di configurazione. Se non esiste, viene creata con i valori di default.



27
28
29
# File 'lib/better_seeder.rb', line 27

def self.configuration
  @configuration ||= Configuration.new
end

.configure {|configuration| ... } ⇒ Object

Permette di configurare BetterSeeder tramite un blocco. Ad esempio, in config/initializers/better_seeder.rb:

BetterSeeder.configure do |config|
  config.log_language = :en
  config.structure_path = Rails.root.join('db', 'seed', 'structure')
  config.preload_path = Rails.root.join('db', 'seed', 'preload')
end

Se questo blocco viene eseguito, i valori della configurazione verranno aggiornati; altrimenti, verranno utilizzati quelli definiti nel costruttore (default).

Yields:



41
42
43
# File 'lib/better_seeder.rb', line 41

def self.configure
  yield(configuration)
end

.export_records(model_class, processed_records, export_type, file_name) ⇒ Object

Esporta i record nel formato specificato (json, csv, sql).



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/better_seeder.rb', line 235

def self.export_records(model_class, processed_records, export_type, file_name)
  exporter = case export_type.to_s.downcase
             when 'json'
               Exporters::Json.new(processed_records, output_path: file_name)
             when 'csv'
               Exporters::Csv.new(processed_records, output_path: file_name)
             when 'sql'
               table_name = model_class.respond_to?(:table_name) ? model_class.table_name : transform_class_name(model_class.name)
               Exporters::Sql.new(processed_records, output_path: file_name, table_name: table_name)
             else
               raise ArgumentError, "Unsupported export type: #{export_type}"
             end

  exporter.export
  message = "[INFO] Exported data for #{model_class.name} to #{file_name}"
  BetterSeeder::Utils.logger(message: message)
end

.generate_structure(model_name:) ⇒ Object



120
121
122
# File 'lib/better_seeder.rb', line 120

def self.generate_structure(model_name:)
  BetterSeeder::Builders::Structure.generate(model_name)
end

.generated_recordsObject



17
18
19
# File 'lib/better_seeder.rb', line 17

def self.generated_records
  @generated_records
end

.installObject

Metodo install che crea l’initializer di BetterSeeder in config/initializers con le seguenti impostazioni:

- log_language: lingua da usare per i log (es. :en, :it)
- log_level: livello di logging nell'output dei dati del seeder (:debug, :info, :error)
- structure_path: percorso dove sono memorizzati i file di structure (default: Rails.root/db/seed/structure)
- preload_path: percorso dove verranno salvati i file esportati (default: Rails.root/db/seed/preload)


51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/better_seeder.rb', line 51

def self.install
  initializer_path = Rails.root.join('config', 'initializers', 'better_seeder.rb').to_s

  if File.exist?(initializer_path)
    message = "BetterSeeder initializer already exists at #{initializer_path}"
  else
    content = <<~RUBY
      # BetterSeeder initializer
      # This file was generated by BetterSeeder.install
      BetterSeeder.configure do |config|
        config.log_language = :en
        config.log_level = :error
        config.structure_path = Rails.root.join('db', 'seed', 'structure')
        config.preload_path = Rails.root.join('db', 'seed', 'preload')
      end
    RUBY

    FileUtils.mkdir_p(File.dirname(initializer_path))
    File.write(initializer_path, content)
    message = "BetterSeeder initializer created at #{initializer_path}"
  end

  # Use Rails.logger if available, otherwise fallback to STDOUT.
  BetterSeeder::Utils.logger(message: message)
end

.load_data_from_file(seed_config) ⇒ Object



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/better_seeder.rb', line 266

def self.load_data_from_file(seed_config)
  config = seed_config
  return unless config[:load_data] # Se load_data non è abilitato, esce senza fare nulla.

  # Costruisce il nome del file di seed: ad esempio, se config[:file_name] è "my_model_seed",
  # il file atteso sarà "my_model_seed_seed.sql".
  seed_file_name = "#{config[:file_name]}.sql"
  seed_file_path = File.join(BetterSeeder.configuration.preload_path, seed_file_name)

  unless File.exist?(seed_file_path)
    BetterSeeder::Utils.logger(message: "[WARN] Seed file not found: #{seed_file_path}")
    return false
  end

  sql = File.read(seed_file_path)
  begin
    ActiveRecord::Base.connection.execute(sql)
    BetterSeeder::Utils.logger(message: "[INFO] Loaded seed file: #{seed_file_path}")
    true
  rescue StandardError => e
    BetterSeeder::Utils.logger(message: "[ERROR] Failed to load seed file: #{seed_file_path} - Error: #{e.message}")
    false
  end
end

.load_records_into_db(model_class, processed_records, total_records, model_name, superclass) ⇒ Array<Object>

Carica i record nel database, utilizzando una progress bar per monitorare il progresso. I log delle query SQL vengono temporaneamente disabilitati.

Returns:

  • (Array<Object>)

    Array dei record creati (istanze ActiveRecord)



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/better_seeder.rb', line 217

def self.load_records_into_db(model_class, processed_records, total_records, model_name, superclass)
  progressbar = ProgressBar.create(total: total_records, format: '%a %B %p%% %t')
  message     = "[INFO] Starting to load #{total_records} records for model #{model_name}..."
  BetterSeeder::Utils.logger(message: message)

  processed_records.each do |record|
    created = model_class.create!(record)
    BetterSeeder.store_generated_record(superclass || model_name, created)
    progressbar.increment
  end

  message = "[INFO] Finished loading #{total_records} records into model #{model_name}."
  BetterSeeder::Utils.logger(message: message)

  BetterSeeder.generated_records[model_name]
end

.log_statistics(stats, total_time) ⇒ Object

Log finale con le statistiche raccolte e il tempo totale di esecuzione.



254
255
256
257
258
# File 'lib/better_seeder.rb', line 254

def self.log_statistics(stats, total_time)
  stats_message = stats.map { |model, count| "#{model}: #{count} records" }.join("\n")
  message       = "[INFO] Finished processing all models in #{total_time.round(2)} seconds. Statistics: \n#{stats_message}"
  BetterSeeder::Utils.logger(message: message)
end

.magic(config) ⇒ Object

Metodo master della gemma.

La configurazione attesa è un hash con la seguente struttura:

{

configurations: { export_type: :sql },
data: [
  'Campaigns::Campaign',
  'Creators::Creator',
  'Media::Media',
  'Media::Participant'
]

}

Per ciascun modello (identificato da stringa), viene:

- Caricato il file di structure relativo, che definisce la configurazione specifica tramite `seed_config`
- Recuperati (o generati) i record, con eventuali controlli di esclusione e iniezione di foreign key per modelli child
- Se abilitato, i record vengono caricati nel database e successivamente esportati nel formato richiesto
- Vengono raccolte statistiche e loggato il tempo totale di esecuzione


96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/better_seeder.rb', line 96

def self.magic(config)
  previous_log_level = ActiveRecord::Base.logger.level
  BetterSeeder::Utils.log_level_setup

  message = "[LOGGER] previous log level: #{previous_log_level}, actual log level: #{ActiveRecord::Base.logger.level}"
  BetterSeeder::Utils.logger(message: message)

  start_time            = Time.zone.now
  stats                 = {} # Statistiche: modello => numero di record caricati
  parent_loaded_records = {} # Per memorizzare i record creati per i modelli parent

  ActiveRecord::Base.transaction do
    export_type = config[:configurations][:export_type]
    model_names = config[:data]
    model_names.each do |model_name|
      process_config(model_name, export_type, stats, parent_loaded_records)
    end
  end

  ActiveRecord::Base.logger.level = previous_log_level
  total_time                      = Time.zone.now - start_time
  log_statistics(stats, total_time)
end

.process_config(model_name, export_type, stats, parent_loaded_records) ⇒ Object

Processa la configurazione per un singolo modello. Carica il file di structure corrispondente e recupera la configurazione tramite ‘seed_config`. Quindi, esegue il recupero o la generazione dei record, iniezione di eventuali foreign key per modelli child, caricamento nel database (se abilitato) ed esportazione dei dati.



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/better_seeder.rb', line 128

def self.process_config(model_name, export_type, stats, parent_loaded_records)
  # Costruisce il percorso del file di structure in base al nome del modello.
  # Esempio: per "Campaigns::Campaign", si attende "db/seed/structure/campaigns/campaign_structure.rb"
  structure_file = File.expand_path(
    File.join(BetterSeeder.configuration.structure_path, "#{model_name.underscore}_structure.rb"),
    Dir.pwd
  )
  raise "Structure file not found: #{structure_file}" unless File.exist?(structure_file)

  # Carica il file di structure.
  load structure_file

  # Il nome della classe di structure viene ottenuto semplicemente concatenando "Structure" al nome del modello.
  # Es: "Campaigns::Campaign" => "Campaigns::CampaignStructure"
  structure_class_name = "#{model_name}Structure"
  begin
    structure_class = Object.const_get(structure_class_name)
  rescue error
    message = "[ERROR] Structure class not found: #{structure_class_name}"
    BetterSeeder::Utils.logger(message: message)
    raise error
  end

  # Recupera la configurazione specifica dal file di structure tramite il metodo seed_config.
  # Se non definito, vengono usati dei valori di default.
  seed_config      = structure_class.respond_to?(:seed_config) ? structure_class.seed_config : {}
  file_name        = seed_config[:file_name] || "#{model_name.underscore}_seed"
  excluded_columns = if export_type.to_s.downcase == 'sql'
                       []
                     else
                       seed_config.dig(:columns, :excluded) || []
                     end
  generate_data    = seed_config.fetch(:generate_data, true)
  count            = seed_config[:count] || 10
  load_data        = seed_config.fetch(:load_data, true)
  parent           = seed_config[:parent] # nil oppure valore (o array) per modelli child
  superclass       = seed_config[:superclass] # nil oppure valore (o array) per modelli child

  # Log per indicare se il modello è parent o child.
  message = if parent.nil?
              "[INFO] Processing parent model #{model_name}"
            else
              "[INFO] Processing child model #{model_name} (parent: #{parent.inspect})"
            end
  BetterSeeder::Utils.logger(message: message)

  # Recupera la classe reale del modello (ActiveRecord).
  model_class = begin
                  Object.const_get(model_name)
                rescue StandardError
                  nil
                end
  unless model_class
    message = "[ERROR] Model #{model_name} not found."
    BetterSeeder::Utils.logger(message: message)
    raise Object.const_get(model_name)
  end

  # Se abilitato, carica i record nel database.
  if load_data && File.exist?("#{BetterSeeder.configuration.preload_path}/#{seed_config[:file_name]}.sql")
    load_data_from_file(seed_config)
    stats[model_name] = model_class.count
  elsif generate_data
    records                           = Farms::Farmer.generate(model: model_name, count: count)
    total_records                     = records.size
    stats[model_name]                 = total_records
    created_records                   = load_records_into_db(model_class, records, total_records, model_name, superclass)
    # Se il modello è parent, salva i record creati per poterli utilizzare in seguito per i modelli child.
    parent_loaded_records[model_name] = created_records if parent.nil?
  else
    model_class.all.map(&:attributes)
  end

  # Rimuove le colonne escluse.
  return if BetterSeeder.generated_records[(superclass || model_name).to_s].nil?

  processed_records = BetterSeeder.generated_records[(superclass || model_name).to_s]
  processed_records = processed_records.map do |record|
    record.attributes.except(*excluded_columns.map(&:to_s))
  end

  # Esporta i record nel formato richiesto.
  export_records(model_class, processed_records, export_type, file_name)
end

.store_generated_record(model_name, record) ⇒ Object



21
22
23
24
# File 'lib/better_seeder.rb', line 21

def self.store_generated_record(model_name, record)
  @generated_records[model_name.to_s] ||= []
  @generated_records[model_name.to_s] << record
end

.transform_class_name(class_name) ⇒ Object

Metodo di utilità per trasformare il nome della classe in un formato in cui le lettere sono in minuscolo e separate da underscore.



262
263
264
# File 'lib/better_seeder.rb', line 262

def self.transform_class_name(class_name)
  class_name.split('::').map(&:underscore).join('_')
end