better_seeder 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +215 -0
- data/lib/better_seeder/configuration.rb +38 -0
- data/lib/better_seeder/exporters/base.rb +65 -0
- data/lib/better_seeder/exporters/csv.rb +23 -0
- data/lib/better_seeder/exporters/json.rb +20 -0
- data/lib/better_seeder/exporters/sql.rb +41 -0
- data/lib/better_seeder/generators/data_generator.rb +117 -0
- data/lib/better_seeder/structure/utils.rb +15 -0
- data/lib/better_seeder/utils.rb +22 -0
- data/lib/better_seeder/version.rb +5 -0
- data/lib/better_seeder.rb +269 -0
- metadata +171 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7d791cd07db093b5651fff8ee43c8a9b04360efd6ad7aaa83978f9233f53f7a2
|
4
|
+
data.tar.gz: 4b4ca980a85b832b9261750db2742a89f61a18bc6a33f253a6486076a9a572ea
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 765e67ac2cac0af3e1b3a443f9896ce1212a0a6e0e6815b4bd7e7e148f75aabab3b6da279f2b5dc32c5a070c02024567524e175c710a5604e297221aa70157e8
|
7
|
+
data.tar.gz: a87f473eb71a6156dfdb829435323b67bcfe5645ebb30e32143071ba94107cdd99d730452032b6360c870212967d4d8e8c8b4f6fb496d6ba723384aa1e52e03e
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2025 alessiobussolari
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
# BetterSeeder
|
2
|
+
|
3
|
+
**BetterSeeder** is a Rails gem designed to simplify and centralize your application's seeding process. It provides a flexible system to generate dynamic data, validate it using Dry-schema, enforce uniqueness constraints, load it into the database, and export it in various formats (SQL, CSV, JSON). Configuration is centralized via a Rails initializer, while model-specific logic is defined in dedicated structure files.
|
4
|
+
|
5
|
+
---
|
6
|
+
|
7
|
+
## Features
|
8
|
+
|
9
|
+
- **Dynamic Data Generation**
|
10
|
+
Define custom data generators for each model in dedicated structure files.
|
11
|
+
|
12
|
+
- **Validation and Uniqueness**
|
13
|
+
Validate generated records using Dry-schema and enforce uniqueness constraints (both single and multi-column).
|
14
|
+
|
15
|
+
- **Loading & Exporting**
|
16
|
+
- Load generated data directly into your database (with support for parent/child relationships).
|
17
|
+
- Export data as a single SQL INSERT statement, or in CSV or JSON formats.
|
18
|
+
|
19
|
+
- **Centralized Configuration**
|
20
|
+
Easily customize settings such as `log_language`, `structure_path`, and `preload_path` via a Rails initializer. If no initializer is provided, default values are used.
|
21
|
+
|
22
|
+
- **Automatic Initializer Installation**
|
23
|
+
Use the `BetterSeeder.install` method to automatically create the initializer file in your Rails app.
|
24
|
+
|
25
|
+
---
|
26
|
+
|
27
|
+
## Installation
|
28
|
+
|
29
|
+
Add the gem to your Gemfile:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
gem 'better_seeder'
|
33
|
+
```
|
34
|
+
|
35
|
+
Then run:
|
36
|
+
|
37
|
+
```bash
|
38
|
+
bundle install
|
39
|
+
```
|
40
|
+
|
41
|
+
---
|
42
|
+
|
43
|
+
## Configuration
|
44
|
+
|
45
|
+
BetterSeeder uses a centralized configuration defined in `BetterSeeder.configuration`. You can override the default settings via an initializer. For example, create a file:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
# config/initializers/better_seeder.rb
|
49
|
+
require 'better_seeder'
|
50
|
+
|
51
|
+
BetterSeeder.configure do |config|
|
52
|
+
config.log_language = :en
|
53
|
+
config.structure_path = Rails.root.join('db', 'seed', 'structure')
|
54
|
+
config.preload_path = Rails.root.join('db', 'seed', 'preload')
|
55
|
+
end
|
56
|
+
```
|
57
|
+
|
58
|
+
If these values are set in the initializer, they will be used; otherwise, the gem will fall back to its default values.
|
59
|
+
|
60
|
+
---
|
61
|
+
|
62
|
+
## Install Method
|
63
|
+
|
64
|
+
BetterSeeder provides an `install` method that automatically creates an initializer file. To run the installer, simply execute in your Rails console:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
BetterSeeder.install
|
68
|
+
```
|
69
|
+
|
70
|
+
This command creates (if not already present) the file `config/initializers/better_seeder.rb` with content similar to:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
# BetterSeeder initializer
|
74
|
+
BetterSeeder.configure do |config|
|
75
|
+
config.log_language = :en
|
76
|
+
config.structure_path = Rails.root.join('db', 'seed', 'structure')
|
77
|
+
config.preload_path = Rails.root.join('db', 'seed', 'preload')
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
---
|
82
|
+
|
83
|
+
## Structure Files
|
84
|
+
|
85
|
+
For each model, create a structure file that centralizes the logic for generating, validating, and configuring seed data. Each structure file should define at least the following methods:
|
86
|
+
|
87
|
+
- **`structure`**
|
88
|
+
Returns a hash where each key represents an attribute and its value is an array in the format `[type, lambda_generator]`.
|
89
|
+
|
90
|
+
- **`seed_schema` (Optional)**
|
91
|
+
Defines a Dry-schema for validating the generated records.
|
92
|
+
|
93
|
+
- **`seed_config`**
|
94
|
+
Returns a hash with model-specific seeding settings:
|
95
|
+
- `file_name`: The output file name (without extension)
|
96
|
+
- `columns: { excluded: [...] }`: Columns to exclude from the generated data
|
97
|
+
- `generate_data`: Boolean flag indicating whether to generate data dynamically (if false, existing records are used)
|
98
|
+
- `count`: The number of records to generate (default: 10)
|
99
|
+
- `load_data`: Boolean flag indicating whether the generated records should be inserted into the database
|
100
|
+
- `parent`: For child models, this specifies the parent model(s) used for injecting foreign keys
|
101
|
+
|
102
|
+
- **`unique_keys` (Optional)**
|
103
|
+
Returns an array of column groups (each group is an array of symbols) that must be unique.
|
104
|
+
For example:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
def self.unique_keys
|
108
|
+
[[:email], [:first_name, :last_name]]
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
### Example Structure File
|
113
|
+
|
114
|
+
For a generic model `MyModel` in the namespace `MyNamespace`, create a file at `db/seed/structure/my_namespace/my_model_structure.rb`:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
# db/seed/structure/my_namespace/my_model_structure.rb
|
118
|
+
module MyNamespace
|
119
|
+
class MyModelStructure < BetterSeeder::StructureBase
|
120
|
+
# Defines generators for each attribute.
|
121
|
+
def self.structure
|
122
|
+
{
|
123
|
+
name: [:string, -> { FFaker::Name.name }],
|
124
|
+
email: [:string, -> { FFaker::Internet.email }],
|
125
|
+
created_at: [:datetime, -> { Time.zone.now }]
|
126
|
+
}
|
127
|
+
end
|
128
|
+
|
129
|
+
# Optional: Validate generated records using Dry-schema.
|
130
|
+
def self.seed_schema
|
131
|
+
Dry::Schema.Params do
|
132
|
+
required(:name).filled(:string)
|
133
|
+
required(:email).filled(:string)
|
134
|
+
required(:created_at).filled(:time)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Specific seeding configuration for MyModel.
|
139
|
+
def self.seed_config
|
140
|
+
{
|
141
|
+
file_name: 'my_model_seed',
|
142
|
+
columns: { excluded: [:id, :updated_at] },
|
143
|
+
generate_data: true,
|
144
|
+
count: 50,
|
145
|
+
load_data: true,
|
146
|
+
parent: nil
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
# Optional: Uniqueness constraints; for example, email must be unique.
|
151
|
+
def self.unique_keys
|
152
|
+
[[:email]]
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
```
|
157
|
+
|
158
|
+
---
|
159
|
+
|
160
|
+
## How It Works
|
161
|
+
|
162
|
+
When you call `BetterSeeder.magic` with a configuration that contains an array of model names (as strings), the gem will:
|
163
|
+
|
164
|
+
1. **Load Structure Files**
|
165
|
+
For each model, the gem loads the corresponding structure file from `BetterSeeder.configuration.structure_path`.
|
166
|
+
|
167
|
+
2. **Retrieve Seeding Configurations**
|
168
|
+
It calls the model's `seed_config` method to get its specific settings.
|
169
|
+
|
170
|
+
3. **Generate or Retrieve Records**
|
171
|
+
Using the `structure` method, the gem generates data dynamically (or retrieves existing records) and validates them with `seed_schema` if provided. Uniqueness is enforced based on `unique_keys`.
|
172
|
+
|
173
|
+
4. **Handle Parent/Child Relationships**
|
174
|
+
For child models, foreign keys are automatically injected using the records from the parent models.
|
175
|
+
|
176
|
+
5. **Load and Export**
|
177
|
+
If enabled (`load_data: true`), the generated records are inserted into the database and then exported in the specified format (SQL, CSV, or JSON). Export files are saved in the directory defined by `BetterSeeder.configuration.preload_path` (default: `db/seed/preload`).
|
178
|
+
|
179
|
+
### Example Usage
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
BetterSeeder.magic(
|
183
|
+
{
|
184
|
+
configurations: { export_type: :sql },
|
185
|
+
data: [
|
186
|
+
'MyNamespace::MyModel',
|
187
|
+
'OtherNamespace::OtherModel'
|
188
|
+
]
|
189
|
+
}
|
190
|
+
)
|
191
|
+
```
|
192
|
+
|
193
|
+
This command processes each model by:
|
194
|
+
|
195
|
+
- Reading its structure file and retrieving its configuration via `seed_config`.
|
196
|
+
- Generating or fetching data according to the specified rules.
|
197
|
+
- Inserting the data into the database (if `load_data` is enabled).
|
198
|
+
- Exporting the data as an SQL file (or CSV/JSON, depending on `export_type`).
|
199
|
+
|
200
|
+
---
|
201
|
+
|
202
|
+
## Conclusion
|
203
|
+
|
204
|
+
BetterSeeder provides a modular, configurable, and extensible system for seeding your Rails application's data:
|
205
|
+
|
206
|
+
- **Centralized Configuration:**
|
207
|
+
Configure paths and logging via a Rails initializer.
|
208
|
+
|
209
|
+
- **Modular Structure Files:**
|
210
|
+
Define generation, validation, and configuration logic for each model in dedicated structure files.
|
211
|
+
|
212
|
+
- **Seamless Data Handling:**
|
213
|
+
Automatically generate, validate, load, and export seed data with support for parent/child relationships and various export formats.
|
214
|
+
|
215
|
+
For further details or contributions, please refer to the official repository or documentation.
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# lib/better_seeder/configuration.rb
|
2
|
+
|
3
|
+
module BetterSeeder
|
4
|
+
class Configuration
|
5
|
+
attr_accessor :log_language, :structure_path, :preload_path
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
if defined?(Rails) && Rails.respond_to?(:root)
|
9
|
+
@log_language = :en
|
10
|
+
@structure_path = Rails.root.join('db', 'seed', 'structure')
|
11
|
+
@preload_path = Rails.root.join('db', 'seed', 'preload')
|
12
|
+
else
|
13
|
+
@log_language = :en
|
14
|
+
@structure_path = File.join(Dir.pwd, 'db', 'seed', 'structure')
|
15
|
+
@preload_path = File.join(Dir.pwd, 'db', 'seed', 'preload')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Restituisce l'istanza globale di configurazione. Se non esiste, viene creata con i valori di default.
|
21
|
+
def self.configuration
|
22
|
+
@configuration ||= Configuration.new
|
23
|
+
end
|
24
|
+
|
25
|
+
# Permette di configurare BetterSeeder tramite un blocco. Ad esempio, in config/initializers/better_seeder.rb:
|
26
|
+
#
|
27
|
+
# BetterSeeder.configure do |config|
|
28
|
+
# config.log_language = :en
|
29
|
+
# config.structure_path = Rails.root.join('db', 'seed', 'structure')
|
30
|
+
# config.preload_path = Rails.root.join('db', 'seed', 'preload')
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# Se questo blocco viene eseguito, i valori della configurazione verranno aggiornati; altrimenti,
|
34
|
+
# verranno utilizzati quelli definiti nel costruttore (default).
|
35
|
+
def self.configure
|
36
|
+
yield(configuration)
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# lib/better_seeder/exporter/base_exporter.rb
|
2
|
+
|
3
|
+
module BetterSeeder
|
4
|
+
module Exporters
|
5
|
+
class Base
|
6
|
+
# I dati da esportare.
|
7
|
+
# output_path: nome del file (senza estensione) da concatenare al preload_path configurato.
|
8
|
+
# table_name: (opzionale) nome della tabella, utile ad esempio per SqlExporter.
|
9
|
+
attr_reader :data, :output_path, :table_name
|
10
|
+
|
11
|
+
# Inizializza l'exporter.
|
12
|
+
#
|
13
|
+
# Esempio d'uso:
|
14
|
+
# data = [
|
15
|
+
# { id: 1, name: "Alice", email: "alice@example.com" },
|
16
|
+
# { id: 2, name: "Bob", email: "bob@example.com" }
|
17
|
+
# ]
|
18
|
+
#
|
19
|
+
# json_exporter = BetterSeeder::Performers::JsonExporter.new(data, output_path: 'users', table_name: 'users')
|
20
|
+
# json_exporter.export
|
21
|
+
#
|
22
|
+
# @param data [Array<Hash>] I dati da esportare.
|
23
|
+
# @param output_path [String] Nome del file (senza estensione).
|
24
|
+
# @param table_name [String] Nome della tabella (usato in SqlExporter).
|
25
|
+
def initialize(data, output_path:, table_name: 'my_table')
|
26
|
+
@data = data
|
27
|
+
# Utilizza il preload_path definito nella configurazione BetterSeeder (impostato nell'initializer).
|
28
|
+
@output_path = File.join(BetterSeeder.configuration.preload_path, output_path)
|
29
|
+
@table_name = table_name
|
30
|
+
end
|
31
|
+
|
32
|
+
# Restituisce la directory in cui salvare i file.
|
33
|
+
# In questo caso, utilizza la configurazione BetterSeeder.configuration.preload_path.
|
34
|
+
#
|
35
|
+
# @return [String] il percorso della directory di output.
|
36
|
+
def output_directory
|
37
|
+
BetterSeeder.configuration.preload_path.to_s
|
38
|
+
end
|
39
|
+
|
40
|
+
# Verifica che la directory di output esista; se non esiste, la crea.
|
41
|
+
def ensure_output_directory
|
42
|
+
FileUtils.mkdir_p(output_directory) unless Dir.exist?(output_directory)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Costruisce il percorso completo del file di output, combinando la directory, l'output_path e l'estensione.
|
46
|
+
#
|
47
|
+
# @return [String] il percorso completo del file.
|
48
|
+
def full_output_path
|
49
|
+
ensure_output_directory
|
50
|
+
"#{output_path}#{extension}"
|
51
|
+
end
|
52
|
+
|
53
|
+
# Metodo astratto per ottenere l'estensione del file (es. ".json", ".csv", ".sql").
|
54
|
+
# Le classi derivate devono implementarlo.
|
55
|
+
def extension
|
56
|
+
raise NotImplementedError, "Subclasses must implement #extension"
|
57
|
+
end
|
58
|
+
|
59
|
+
# Metodo astratto per effettuare l'export.
|
60
|
+
def export
|
61
|
+
raise NotImplementedError, "Subclasses must implement the export method"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module BetterSeeder
|
2
|
+
module Exporters
|
3
|
+
class Csv < Base
|
4
|
+
# Esporta i dati in formato CSV e li salva in un file nella cartella "db/seed/preload".
|
5
|
+
# Se la cartella non esiste, viene creata automaticamente.
|
6
|
+
def export
|
7
|
+
return if data.empty?
|
8
|
+
headers = data.first.keys
|
9
|
+
|
10
|
+
# Costruisce il percorso completo del file di output
|
11
|
+
full_path = File.join(full_output_path)
|
12
|
+
|
13
|
+
CSV.open(full_path, 'w', write_headers: true, headers: headers) do |csv|
|
14
|
+
data.each { |row| csv << row.values }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def extension
|
19
|
+
'.csv'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module BetterSeeder
|
2
|
+
module Exporters
|
3
|
+
class Json < Base
|
4
|
+
# Esporta i dati in formato JSON e li salva in un file nella cartella "db/seed/preload".
|
5
|
+
# Se la cartella non esiste, viene creata automaticamente.
|
6
|
+
def export
|
7
|
+
# Imposta la directory di output
|
8
|
+
full_path = File.join(full_output_path)
|
9
|
+
|
10
|
+
File.open(full_path, 'w') do |file|
|
11
|
+
file.write(JSON.pretty_generate(data))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def extension
|
16
|
+
'.json'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module BetterSeeder
|
2
|
+
module Exporters
|
3
|
+
class Sql < Base
|
4
|
+
# Esporta i dati in formato SQL, generando una singola istruzione INSERT
|
5
|
+
# che inserisce in blocco tutti i record.
|
6
|
+
#
|
7
|
+
# Il metodo costruisce una stringa con la sintassi:
|
8
|
+
# INSERT INTO table_name (col1, col2, ...) VALUES
|
9
|
+
# (val11, val12, ...),
|
10
|
+
# (val21, val22, ...),
|
11
|
+
# ... ;
|
12
|
+
def export
|
13
|
+
return if data.empty?
|
14
|
+
columns = data.first.keys
|
15
|
+
|
16
|
+
# Crea l'array delle tuple di valori per ciascun record.
|
17
|
+
values_list = data.map do |row|
|
18
|
+
row_values = columns.map do |col|
|
19
|
+
value = row[col]
|
20
|
+
# Se il valore è nil restituisce NULL, altrimenti esegue l'escaping delle virgolette singole.
|
21
|
+
value.nil? ? "NULL" : "'#{value.to_s.gsub("'", "''")}'"
|
22
|
+
end
|
23
|
+
"(#{row_values.join(', ')})"
|
24
|
+
end
|
25
|
+
|
26
|
+
# Costruisce la query INSERT unica
|
27
|
+
insert_statement = "INSERT INTO #{table_name} (#{columns.join(', ')}) VALUES #{values_list.join(', ')};"
|
28
|
+
|
29
|
+
full_path = File.join(full_output_path)
|
30
|
+
|
31
|
+
File.open(full_path, 'w') do |file|
|
32
|
+
file.puts(insert_statement)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def extension
|
37
|
+
'.sql'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module BetterSeeder
|
2
|
+
module Generators
|
3
|
+
class DataGenerator
|
4
|
+
# Genera dati fittizi per il modello specificato utilizzando il file di structure.
|
5
|
+
#
|
6
|
+
# Opzioni attese (Hash):
|
7
|
+
# :model => Nome del modello come stringa, es. 'Media::Participant'
|
8
|
+
# :count => Numero di record da generare (default: 10)
|
9
|
+
#
|
10
|
+
# Se la classe di structure definisce il metodo `unique_keys` (che deve restituire un array di array,
|
11
|
+
# es. [[:media_id, :creator_id], [:role]]), verrà controllato che ogni record generato sia univoco,
|
12
|
+
# sia in memoria (tra quelli generati in questa esecuzione) che rispetto ai dati già presenti nel database.
|
13
|
+
# Se il record è duplicato, verrà rigenerato.
|
14
|
+
#
|
15
|
+
# Se la classe di structure definisce il metodo `seed_schema`, il record verrà validato tramite Dry-schema.
|
16
|
+
#
|
17
|
+
# @return [Array<Hash>] Array di record validati e univoci generati
|
18
|
+
def self.generate(options = {})
|
19
|
+
model_name = options[:model] or raise ArgumentError, "Missing :model option"
|
20
|
+
count = options[:count] || 10
|
21
|
+
|
22
|
+
# Costruisce il percorso del file di structure.
|
23
|
+
# Ad esempio, per il modello "Media::Participant", il file atteso sarà:
|
24
|
+
# "db/seed/structure/media/participant_structure.rb"
|
25
|
+
structure_file = File.expand_path(
|
26
|
+
File.join(BetterSeeder.configuration.structure_path, "#{model_name.underscore}_structure.rb"),
|
27
|
+
Dir.pwd
|
28
|
+
)
|
29
|
+
raise "Structure file not found: #{structure_file}" unless File.exist?(structure_file)
|
30
|
+
|
31
|
+
# Carica il file di structure.
|
32
|
+
load structure_file
|
33
|
+
|
34
|
+
# Costruisce il nome della classe di structure: semplice concatenazione.
|
35
|
+
# Es: "Media::Participant" => "Media::ParticipantStructure"
|
36
|
+
structure_class_name = "#{model_name}Structure"
|
37
|
+
begin
|
38
|
+
structure_class = Object.const_get(structure_class_name)
|
39
|
+
rescue error
|
40
|
+
message = "Structure class not found: #{structure_class_name}"
|
41
|
+
BetterSeeder::Utils.logger(message: message)
|
42
|
+
raise error
|
43
|
+
end
|
44
|
+
|
45
|
+
generation_rules = structure_class.structure
|
46
|
+
raise "Structure must be a Hash" unless generation_rules.is_a?(Hash)
|
47
|
+
|
48
|
+
# Recupera lo schema per la validazione, se definito.
|
49
|
+
schema = structure_class.respond_to?(:seed_schema) ? structure_class.seed_schema : nil
|
50
|
+
|
51
|
+
# Gestione dei vincoli di unicità.
|
52
|
+
# Se il metodo unique_keys è definito, lo si aspetta come array di array,
|
53
|
+
# ad esempio: [[:media_id, :creator_id], [:role]]
|
54
|
+
unique_key_sets = structure_class.respond_to?(:unique_keys) ? structure_class.unique_keys : []
|
55
|
+
# Pre-carica i valori già presenti nel database per ciascun gruppo di colonne.
|
56
|
+
unique_sets = unique_key_sets.map do |cols|
|
57
|
+
existing_keys = Set.new
|
58
|
+
# Usa pluck per recuperare le colonne specificate dal modello.
|
59
|
+
# Se il gruppo è di una sola colonna, pluck restituirà un array di valori; se multi, un array di array.
|
60
|
+
db_rows = Object.const_get(model_name).pluck(*cols)
|
61
|
+
db_rows.each do |row|
|
62
|
+
composite_key = cols.size == 1 ? row.to_s : row.join("_")
|
63
|
+
existing_keys.add(composite_key)
|
64
|
+
end
|
65
|
+
{ columns: cols, set: existing_keys }
|
66
|
+
end
|
67
|
+
|
68
|
+
generated_records = []
|
69
|
+
progressbar = ProgressBar.create(total: count, format: '%a %B %p%% %t')
|
70
|
+
attempts = 0
|
71
|
+
|
72
|
+
# Continua a generare record finché non si raggiunge il numero richiesto.
|
73
|
+
while generated_records.size < count
|
74
|
+
attempts += 1
|
75
|
+
record = {}
|
76
|
+
generation_rules.each do |attribute, rule|
|
77
|
+
# Ogni rule è un array: [tipo, generatore]
|
78
|
+
generator = rule[1]
|
79
|
+
value = generator.respond_to?(:call) ? generator.call : generator
|
80
|
+
record[attribute] = value
|
81
|
+
end
|
82
|
+
|
83
|
+
# Se è definito uno schema, valida il record.
|
84
|
+
if schema
|
85
|
+
result = schema.call(record)
|
86
|
+
unless result.success?
|
87
|
+
message = "[ERROR] Record validation failed for #{model_name}: #{result.errors.to_h}"
|
88
|
+
BetterSeeder::Utils.logger(message: message)
|
89
|
+
progressbar.increment
|
90
|
+
next # Rigenera il record se la validazione fallisce.
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Controlla i vincoli di unicità: verifica che il record non sia già presente
|
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
|
100
|
+
|
101
|
+
# Aggiorna le strutture per il controllo di unicità con il nuovo record.
|
102
|
+
unique_sets.each do |unique_set|
|
103
|
+
composite_key = unique_set[:columns].map { |col| record[col].to_s }.join("_")
|
104
|
+
unique_set[:set].add(composite_key)
|
105
|
+
end
|
106
|
+
|
107
|
+
generated_records << record
|
108
|
+
progressbar.increment
|
109
|
+
end
|
110
|
+
|
111
|
+
message = "[INFO] Generated #{generated_records.size} unique records for #{model_name} after #{attempts} attempts."
|
112
|
+
BetterSeeder::Utils.logger(message: message)
|
113
|
+
generated_records
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'dry-types'
|
2
|
+
|
3
|
+
module BetterSeeder
|
4
|
+
# Superclasse per tutte le structures.
|
5
|
+
# Fornisce il modulo Types da utilizzare per la definizione dei tipi.
|
6
|
+
module Structure
|
7
|
+
class Utils
|
8
|
+
module Types
|
9
|
+
include Dry.Types()
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
BetterSeederTypes = BetterSeeder::Structure::Utils::Types
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# lib/better_seeder/utils.rb
|
2
|
+
|
3
|
+
module BetterSeeder
|
4
|
+
module Utils
|
5
|
+
# Trasforma un nome di classe in snake_case.
|
6
|
+
# Esempio: "Campaigns::Campaign" => "campaigns_campaign"
|
7
|
+
def self.transform_class_name(class_name)
|
8
|
+
elements = class_name.split("::").map(&:underscore)
|
9
|
+
# Aggiunge "_structure.rb" all'ultimo elemento
|
10
|
+
elements[-1] = "#{elements[-1]}_structure.rb"
|
11
|
+
elements.join("/")
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.logger(message: nil)
|
15
|
+
if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
16
|
+
Rails.logger.info message
|
17
|
+
else
|
18
|
+
puts message
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,269 @@
|
|
1
|
+
require_relative "better_seeder/utils"
|
2
|
+
require_relative "better_seeder/configuration"
|
3
|
+
require_relative "better_seeder/structure/utils"
|
4
|
+
require_relative "better_seeder/generators/data_generator"
|
5
|
+
require_relative "better_seeder/exporters/base"
|
6
|
+
require_relative "better_seeder/exporters/json"
|
7
|
+
require_relative "better_seeder/exporters/csv"
|
8
|
+
require_relative "better_seeder/exporters/sql"
|
9
|
+
|
10
|
+
module BetterSeeder
|
11
|
+
class Configuration
|
12
|
+
attr_accessor :log_language, :structure_path, :preload_path
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
if defined?(Rails) && Rails.respond_to?(:root)
|
16
|
+
@log_language = :en
|
17
|
+
@structure_path = Rails.root.join('db', 'seed', 'structure')
|
18
|
+
@preload_path = Rails.root.join('db', 'seed', 'preload')
|
19
|
+
else
|
20
|
+
@log_language = :en
|
21
|
+
@structure_path = File.join(Dir.pwd, 'db', 'seed', 'structure')
|
22
|
+
@preload_path = File.join(Dir.pwd, 'db', 'seed', 'preload')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Restituisce l'istanza globale di configurazione. Se non esiste, viene creata con i valori di default.
|
28
|
+
def self.configuration
|
29
|
+
@configuration ||= Configuration.new
|
30
|
+
end
|
31
|
+
|
32
|
+
# Permette di configurare BetterSeeder tramite un blocco. Ad esempio, in config/initializers/better_seeder.rb:
|
33
|
+
#
|
34
|
+
# BetterSeeder.configure do |config|
|
35
|
+
# config.log_language = :en
|
36
|
+
# config.structure_path = Rails.root.join('db', 'seed', 'structure')
|
37
|
+
# config.preload_path = Rails.root.join('db', 'seed', 'preload')
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# Se questo blocco viene eseguito, i valori della configurazione verranno aggiornati; altrimenti,
|
41
|
+
# verranno utilizzati quelli definiti nel costruttore (default).
|
42
|
+
def self.configure
|
43
|
+
yield(configuration)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Metodo install che crea l'initializer di BetterSeeder in config/initializers
|
47
|
+
# con le seguenti impostazioni:
|
48
|
+
# - log_language: lingua da usare per i log (es. :en, :it)
|
49
|
+
# - structure_path: percorso dove sono memorizzati i file di structure (default: Rails.root/db/seed/structure)
|
50
|
+
# - preload_path: percorso dove verranno salvati i file esportati (default: Rails.root/db/seed/preload)
|
51
|
+
def self.install
|
52
|
+
initializer_path = File.join(Rails.root, "config", "initializers", "better_seeder.rb")
|
53
|
+
|
54
|
+
if File.exist?(initializer_path)
|
55
|
+
message = "BetterSeeder initializer already exists at #{initializer_path}"
|
56
|
+
else
|
57
|
+
content = <<~RUBY
|
58
|
+
# BetterSeeder initializer
|
59
|
+
# This file was generated by BetterSeeder.install
|
60
|
+
BetterSeeder.configure do |config|
|
61
|
+
config.log_language = :en
|
62
|
+
config.structure_path = Rails.root.join('db', 'seed', 'structure')
|
63
|
+
config.preload_path = Rails.root.join('db', 'seed', 'preload')
|
64
|
+
end
|
65
|
+
RUBY
|
66
|
+
|
67
|
+
FileUtils.mkdir_p(File.dirname(initializer_path))
|
68
|
+
File.write(initializer_path, content)
|
69
|
+
message = "BetterSeeder initializer created at #{initializer_path}"
|
70
|
+
end
|
71
|
+
|
72
|
+
# Use Rails.logger if available, otherwise fallback to STDOUT.
|
73
|
+
BetterSeeder::Utils.logger(message: message)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Metodo master della gemma.
|
77
|
+
#
|
78
|
+
# La configurazione attesa è un hash con la seguente struttura:
|
79
|
+
#
|
80
|
+
# {
|
81
|
+
# configurations: { export_type: :sql },
|
82
|
+
# data: [
|
83
|
+
# 'Campaigns::Campaign',
|
84
|
+
# 'Creators::Creator',
|
85
|
+
# 'Media::Media',
|
86
|
+
# 'Media::Participant'
|
87
|
+
# ]
|
88
|
+
# }
|
89
|
+
#
|
90
|
+
# Per ciascun modello (identificato da stringa), viene:
|
91
|
+
# - Caricato il file di structure relativo, che definisce la configurazione specifica tramite `seed_config`
|
92
|
+
# - Recuperati (o generati) i record, con eventuali controlli di esclusione e iniezione di foreign key per modelli child
|
93
|
+
# - Se abilitato, i record vengono caricati nel database e successivamente esportati nel formato richiesto
|
94
|
+
# - Vengono raccolte statistiche e loggato il tempo totale di esecuzione
|
95
|
+
def self.magic(config)
|
96
|
+
start_time = Time.now
|
97
|
+
stats = {} # Statistiche: modello => numero di record caricati
|
98
|
+
parent_loaded_records = {} # Per memorizzare i record creati per i modelli parent
|
99
|
+
|
100
|
+
ActiveRecord::Base.transaction do
|
101
|
+
export_type = config[:configurations][:export_type]
|
102
|
+
model_names = config[:data]
|
103
|
+
model_names.each do |model_name|
|
104
|
+
process_config(model_name, export_type, stats, parent_loaded_records)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
total_time = Time.now - start_time
|
109
|
+
log_statistics(stats, total_time)
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
# Processa la configurazione per un singolo modello.
|
115
|
+
# Carica il file di structure corrispondente e recupera la configurazione tramite `seed_config`.
|
116
|
+
# Quindi, esegue il recupero o la generazione dei record, iniezione di eventuali foreign key per modelli child,
|
117
|
+
# caricamento nel database (se abilitato) ed esportazione dei dati.
|
118
|
+
def self.process_config(model_name, export_type, stats, parent_loaded_records)
|
119
|
+
# Costruisce il percorso del file di structure in base al nome del modello.
|
120
|
+
# Esempio: per "Campaigns::Campaign", si attende "db/seed/structure/campaigns/campaign_structure.rb"
|
121
|
+
structure_file = File.expand_path(
|
122
|
+
File.join(BetterSeeder.configuration.structure_path, "#{model_name.underscore}_structure.rb"),
|
123
|
+
Dir.pwd
|
124
|
+
)
|
125
|
+
raise "Structure file not found: #{structure_file}" unless File.exist?(structure_file)
|
126
|
+
|
127
|
+
# Carica il file di structure.
|
128
|
+
load structure_file
|
129
|
+
|
130
|
+
# Il nome della classe di structure viene ottenuto semplicemente concatenando "Structure" al nome del modello.
|
131
|
+
# Es: "Campaigns::Campaign" => "Campaigns::CampaignStructure"
|
132
|
+
structure_class_name = "#{model_name}Structure"
|
133
|
+
begin
|
134
|
+
structure_class = Object.const_get(structure_class_name)
|
135
|
+
rescue error
|
136
|
+
message = "[ERROR] Structure class not found: #{structure_class_name}"
|
137
|
+
BetterSeeder::Utils.logger(message: message)
|
138
|
+
raise error
|
139
|
+
end
|
140
|
+
|
141
|
+
# Recupera la configurazione specifica dal file di structure tramite il metodo seed_config.
|
142
|
+
# Se non definito, vengono usati dei valori di default.
|
143
|
+
seed_config = structure_class.respond_to?(:seed_config) ? structure_class.seed_config : {}
|
144
|
+
file_name = seed_config[:file_name] || "#{model_name.underscore}_seed"
|
145
|
+
excluded_columns = seed_config.dig(:columns, :excluded) || []
|
146
|
+
generate_data = seed_config.fetch(:generate_data, true)
|
147
|
+
count = seed_config[:count] || 10
|
148
|
+
load_data = seed_config.fetch(:load_data, true)
|
149
|
+
parent = seed_config[:parent] # nil oppure valore (o array) per modelli child
|
150
|
+
|
151
|
+
# Log per indicare se il modello è parent o child.
|
152
|
+
message = if parent.nil?
|
153
|
+
"[INFO] Processing parent model #{model_name}"
|
154
|
+
else
|
155
|
+
"[INFO] Processing child model #{model_name} (parent: #{parent.inspect})"
|
156
|
+
end
|
157
|
+
BetterSeeder::Utils.logger(message: message)
|
158
|
+
|
159
|
+
# Recupera la classe reale del modello (ActiveRecord).
|
160
|
+
model_class = Object.const_get(model_name) rescue nil
|
161
|
+
unless model_class
|
162
|
+
message = "[ERROR] Model #{model_name} not found."
|
163
|
+
BetterSeeder::Utils.logger(message: message)
|
164
|
+
|
165
|
+
raise Object.const_get(model_name)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Recupera o genera i record.
|
169
|
+
records = if generate_data
|
170
|
+
Generators::DataGenerator.generate(model: model_name, count: count)
|
171
|
+
else
|
172
|
+
model_class.all.map(&:attributes)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Rimuove le colonne escluse.
|
176
|
+
processed_records = records.map do |record|
|
177
|
+
record.reject { |key, _| excluded_columns.include?(key.to_sym) }
|
178
|
+
end
|
179
|
+
|
180
|
+
# Se il modello è child, inietta le foreign key.
|
181
|
+
if parent
|
182
|
+
Array(parent).each do |parent_model_name|
|
183
|
+
parent_records = parent_loaded_records[parent_model_name]
|
184
|
+
if parent_records.nil? || parent_records.empty?
|
185
|
+
message = "[ERROR] No loaded records found for parent model #{parent_model_name}. Cannot assign foreign key for #{model_name}."
|
186
|
+
BetterSeeder::Utils.logger(message: message)
|
187
|
+
else
|
188
|
+
# Il nome della foreign key è ottenuto prendendo l'ultima parte del nome del modello padre,
|
189
|
+
# trasformandola in minuscolo e in forma singolare, e aggiungendo "_id".
|
190
|
+
foreign_key = parent_model_name.split("::").last.underscore.singularize + "_id"
|
191
|
+
processed_records.each do |record|
|
192
|
+
record[foreign_key] = parent_records.sample.id
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Se abilitato, carica i record nel database.
|
199
|
+
if load_data
|
200
|
+
total_records = processed_records.size
|
201
|
+
stats[model_name] = total_records
|
202
|
+
created_records = load_records_into_db(model_class, processed_records, total_records, model_name)
|
203
|
+
# Se il modello è parent, salva i record creati per poterli utilizzare in seguito per i modelli child.
|
204
|
+
parent_loaded_records[model_name] = created_records if parent.nil?
|
205
|
+
else
|
206
|
+
stats[model_name] = 0
|
207
|
+
end
|
208
|
+
|
209
|
+
# Esporta i record nel formato richiesto.
|
210
|
+
export_records(model_class, processed_records, export_type, file_name)
|
211
|
+
end
|
212
|
+
|
213
|
+
# Carica i record nel database, utilizzando una progress bar per monitorare il progresso.
|
214
|
+
# I log delle query SQL vengono temporaneamente disabilitati.
|
215
|
+
#
|
216
|
+
# @return [Array<Object>] Array dei record creati (istanze ActiveRecord)
|
217
|
+
def self.load_records_into_db(model_class, processed_records, total_records, model_name)
|
218
|
+
created_records = []
|
219
|
+
progressbar = ProgressBar.create(total: total_records, format: '%a %B %p%% %t')
|
220
|
+
message = "[INFO] Starting to load #{total_records} records for model #{model_name}..."
|
221
|
+
BetterSeeder::Utils.logger(message: message)
|
222
|
+
|
223
|
+
previous_level = ActiveRecord::Base.logger.level
|
224
|
+
ActiveRecord::Base.logger.level = Logger::ERROR
|
225
|
+
|
226
|
+
processed_records.each do |record|
|
227
|
+
created = model_class.create!(record)
|
228
|
+
created_records << created
|
229
|
+
progressbar.increment
|
230
|
+
end
|
231
|
+
|
232
|
+
ActiveRecord::Base.logger.level = previous_level
|
233
|
+
message = "[INFO] Finished loading #{total_records} records into model #{model_name}."
|
234
|
+
BetterSeeder::Utils.logger(message: message)
|
235
|
+
created_records
|
236
|
+
end
|
237
|
+
|
238
|
+
# Esporta i record nel formato specificato (json, csv, sql).
|
239
|
+
def self.export_records(model_class, processed_records, export_type, file_name)
|
240
|
+
exporter = case export_type.to_s.downcase
|
241
|
+
when 'json'
|
242
|
+
Exporters::Json.new(processed_records, output_path: file_name)
|
243
|
+
when 'csv'
|
244
|
+
Exporters::Csv.new(processed_records, output_path: file_name)
|
245
|
+
when 'sql'
|
246
|
+
table_name = model_class.respond_to?(:table_name) ? model_class.table_name : transform_class_name(model_class.name)
|
247
|
+
Exporters::Sql.new(processed_records, output_path: file_name, table_name: table_name)
|
248
|
+
else
|
249
|
+
raise ArgumentError, "Unsupported export type: #{export_type}"
|
250
|
+
end
|
251
|
+
|
252
|
+
exporter.export
|
253
|
+
message = "[INFO] Exported data for #{model_class.name} to #{file_name}"
|
254
|
+
BetterSeeder::Utils.logger(message: message)
|
255
|
+
end
|
256
|
+
|
257
|
+
# Log finale con le statistiche raccolte e il tempo totale di esecuzione.
|
258
|
+
def self.log_statistics(stats, total_time)
|
259
|
+
stats_message = stats.map { |model, count| "#{model}: #{count} records" }.join(", ")
|
260
|
+
message = "[INFO] Finished processing all models in #{total_time.round(2)} seconds. Statistics: #{stats_message}"
|
261
|
+
BetterSeeder::Utils.logger(message: message)
|
262
|
+
end
|
263
|
+
|
264
|
+
# Metodo di utilità per trasformare il nome della classe in un formato in cui le lettere
|
265
|
+
# sono in minuscolo e separate da underscore.
|
266
|
+
def self.transform_class_name(class_name)
|
267
|
+
class_name.split("::").map(&:underscore).join("_")
|
268
|
+
end
|
269
|
+
end
|
metadata
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: better_seeder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- alessio_bussolari
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-02-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ffaker
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.19'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.19'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: dry-schema
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.5'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: dry-types
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.5'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: ruby-progressbar
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.11'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.11'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: activesupport
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '4.2'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '4.2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: activerecord
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '4.2'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '4.2'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: bundler
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rake
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '13.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '13.0'
|
125
|
+
description: A Rails gem that provides simple methods to optimize and maintain seed
|
126
|
+
data, making seeding more efficient and your code more maintainable and performant.
|
127
|
+
email:
|
128
|
+
- alessio.bussolari@pandev.it
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- LICENSE.txt
|
134
|
+
- README.md
|
135
|
+
- lib/better_seeder.rb
|
136
|
+
- lib/better_seeder/configuration.rb
|
137
|
+
- lib/better_seeder/exporters/base.rb
|
138
|
+
- lib/better_seeder/exporters/csv.rb
|
139
|
+
- lib/better_seeder/exporters/json.rb
|
140
|
+
- lib/better_seeder/exporters/sql.rb
|
141
|
+
- lib/better_seeder/generators/data_generator.rb
|
142
|
+
- lib/better_seeder/structure/utils.rb
|
143
|
+
- lib/better_seeder/utils.rb
|
144
|
+
- lib/better_seeder/version.rb
|
145
|
+
homepage: https://github.com/alessiobussolari/better_seeder
|
146
|
+
licenses:
|
147
|
+
- MIT
|
148
|
+
metadata:
|
149
|
+
homepage_uri: https://github.com/alessiobussolari/better_seeder
|
150
|
+
source_code_uri: https://github.com/alessiobussolari/better_seeder
|
151
|
+
changelog_uri: https://github.com/alessiobussolari/better_seeder/blob/main/CHANGELOG.md
|
152
|
+
post_install_message:
|
153
|
+
rdoc_options: []
|
154
|
+
require_paths:
|
155
|
+
- lib
|
156
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: 3.0.0
|
161
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - ">="
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
requirements: []
|
167
|
+
rubygems_version: 3.5.11
|
168
|
+
signing_key:
|
169
|
+
specification_version: 4
|
170
|
+
summary: 'BetterSeeder: Simplify and optimize seeding.'
|
171
|
+
test_files: []
|