seed_dumpling 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +127 -0
- data/lib/seed_dumpling/dump_methods/enumeration.rb +63 -0
- data/lib/seed_dumpling/dump_methods.rb +226 -0
- data/lib/seed_dumpling/environment.rb +152 -0
- data/lib/seed_dumpling/railtie.rb +10 -0
- data/lib/seed_dumpling/version.rb +5 -0
- data/lib/seed_dumpling.rb +15 -0
- data/lib/tasks/seed_dumpling.rake +10 -0
- metadata +87 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 34ece3f93f37d8317c2a9d7ea58dfcd05eb4c041c5bb39a8a0e835b15eb9d9ec
|
4
|
+
data.tar.gz: 8ebb42ccb0329c74629bc8b3bc536da1430a360a97b9b598ea8b8425c794018f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b968a70bcb038cd358d14f467cd98961c5dcad03d2452470d45ed9e878c8732dc3334b2cdfdffae4e0ea9057c32522162e75eb8b89eb5d55312ff15338600eee
|
7
|
+
data.tar.gz: 4573204701121d23e60047e80464b6b4674932e32e020184ee6ecba3998dc940fa8cd5ccb97e408f506757268f8f499d49c054c6f4a94f7b185e3574e706e5fb
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Mikael Henriksson
|
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,127 @@
|
|
1
|
+
Seed Dump
|
2
|
+
========
|
3
|
+
|
4
|
+
Seed Dump is a Rails 7+ that adds a rake task named `db:seed:dump`.
|
5
|
+
It allows you to create seed data files from the existing data in your database.
|
6
|
+
You can also use Seed Dump from the Rails console. See below for usage examples.
|
7
|
+
|
8
|
+
Installation
|
9
|
+
------------
|
10
|
+
|
11
|
+
Add it to your Gemfile with:
|
12
|
+
```ruby
|
13
|
+
gem 'seed_dumpling'
|
14
|
+
```
|
15
|
+
Or install it by hand:
|
16
|
+
```sh
|
17
|
+
gem install seed_dumpling
|
18
|
+
```
|
19
|
+
Examples
|
20
|
+
--------
|
21
|
+
|
22
|
+
### Rake task
|
23
|
+
|
24
|
+
Dump all data directly to `db/seeds.rb`:
|
25
|
+
```sh
|
26
|
+
rake db:seed:dump
|
27
|
+
```
|
28
|
+
Result:
|
29
|
+
```ruby
|
30
|
+
Product.create!([
|
31
|
+
{ category_id: 1, description: "Long Sleeve Shirt", name: "Long Sleeve Shirt" },
|
32
|
+
{ category_id: 3, description: "Plain White Tee Shirt", name: "Plain T-Shirt" }
|
33
|
+
])
|
34
|
+
User.create!([
|
35
|
+
{ password: "123456", username: "test_1" },
|
36
|
+
{ password: "234567", username: "test_2" }
|
37
|
+
])
|
38
|
+
```
|
39
|
+
|
40
|
+
Dump only data from the users table and dump a maximum of 1 record:
|
41
|
+
```sh
|
42
|
+
$ rake db:seed:dump MODELS=User LIMIT=1
|
43
|
+
```
|
44
|
+
|
45
|
+
Result:
|
46
|
+
```ruby
|
47
|
+
User.create!([
|
48
|
+
{ password: "123456", username: "test_1" }
|
49
|
+
])
|
50
|
+
```
|
51
|
+
|
52
|
+
Append to `db/seeds.rb` instead of overwriting it:
|
53
|
+
```sh
|
54
|
+
rake db:seed:dump APPEND=true
|
55
|
+
```
|
56
|
+
|
57
|
+
Use another output file instead of `db/seeds.rb`:
|
58
|
+
```sh
|
59
|
+
rake db:seed:dump FILE=db/seeds/users.rb
|
60
|
+
```
|
61
|
+
|
62
|
+
Exclude `name` and `age` from the dump:
|
63
|
+
```sh
|
64
|
+
rake db:seed:dump EXCLUDE=name,age
|
65
|
+
```
|
66
|
+
|
67
|
+
There are more options that can be set— see below for all of them.
|
68
|
+
|
69
|
+
### Console
|
70
|
+
|
71
|
+
Output a dump of all User records:
|
72
|
+
```ruby
|
73
|
+
irb(main):001:0> puts SeedDumpling.dump(User)
|
74
|
+
User.create!([
|
75
|
+
{ password: "123456", username: "test_1" },
|
76
|
+
{ password: "234567", username: "test_2" }
|
77
|
+
])
|
78
|
+
```
|
79
|
+
|
80
|
+
Write the dump to a file:
|
81
|
+
```ruby
|
82
|
+
irb(main):002:0> SeedDumpling.dump(User, file: 'db/seeds.rb')
|
83
|
+
```
|
84
|
+
|
85
|
+
Append the dump to a file:
|
86
|
+
```ruby
|
87
|
+
irb(main):003:0> SeedDumpling.dump(User, file: 'db/seeds.rb', append: true)
|
88
|
+
```
|
89
|
+
|
90
|
+
Exclude `name` and `age` from the dump:
|
91
|
+
```ruby
|
92
|
+
irb(main):004:0> SeedDumpling.dump(User, exclude: [:name, :age])
|
93
|
+
```
|
94
|
+
|
95
|
+
Options are specified as a Hash for the second argument.
|
96
|
+
|
97
|
+
In the console, any relation of ActiveRecord rows can be dumped (not individual objects though)
|
98
|
+
```ruby
|
99
|
+
irb(main):001:0> puts SeedDumpling.dump(User.where(is_admin: false))
|
100
|
+
User.create!([
|
101
|
+
{ password: "123456", username: "test_1", is_admin: false },
|
102
|
+
{ password: "234567", username: "test_2", is_admin: false }
|
103
|
+
])
|
104
|
+
```
|
105
|
+
|
106
|
+
Options
|
107
|
+
-------
|
108
|
+
|
109
|
+
Options are common to both the Rake task and the console, except where noted.
|
110
|
+
|
111
|
+
`append`: If set to `true`, append the data to the file instead of overwriting it. Default: `false`.
|
112
|
+
|
113
|
+
`batch_size`: Controls the number of records that are written to file at a given time. Default: 1000. If you're running out of memory when dumping, try decreasing this. If things are dumping too slow, trying increasing this.
|
114
|
+
|
115
|
+
`exclude`: Attributes to be excluded from the dump. Pass a comma-separated list to the Rake task (i.e. `name,age`) and an array on the console (i.e. `[:name, :age]`). Default: `[:id, :created_at, :updated_at]`.
|
116
|
+
|
117
|
+
`file`: Write to the specified output file. The Rake task default is `db/seeds.rb`. The console returns the dump as a string by default.
|
118
|
+
|
119
|
+
`import`: If `true`, output will be in the format needed by the [activerecord-import](https://github.com/zdennis/activerecord-import) gem, rather than the default format. Default: `false`.
|
120
|
+
|
121
|
+
`limit`: Dump no more than this amount of data. Default: no limit. Rake task only. In the console just pass in an ActiveRecord::Relation with the appropriate limit (e.g. `SeedDumpling.dump(User.limit(5))`).
|
122
|
+
|
123
|
+
`conditions`: Dump only specific records. In the console just pass in an ActiveRecord::Relation with the appropriate conditions (e.g. `SeedDumpling.dump(User.where(state: :active))`).
|
124
|
+
|
125
|
+
`model[s]`: Restrict the dump to the specified comma-separated list of models. Default: all models. If you are using a Rails engine you can dump a specific model by passing "EngineName::ModelName". Rake task only. Example: `rake db:seed:dump MODELS="User, Position, Function"`
|
126
|
+
|
127
|
+
`models_exclude`: Exclude the specified comma-separated list of models from the dump. Default: no models excluded. Rake task only. Example: `rake db:seed:dump MODELS_EXCLUDE="User"`
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class SeedDumpling
|
4
|
+
module DumpMethods
|
5
|
+
#
|
6
|
+
# Helpermethods for enumerating active records
|
7
|
+
#
|
8
|
+
module Enumeration
|
9
|
+
def active_record_enumeration(records, options) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
10
|
+
# Ensure records are ordered by primary key if not already ordered
|
11
|
+
unless records.respond_to?(:arel) && records.arel.orders.present?
|
12
|
+
records = records.order("#{records.quoted_table_name}.#{records.quoted_primary_key} ASC")
|
13
|
+
end
|
14
|
+
|
15
|
+
num_of_batches, batch_size, last_batch_size = batch_params_from(records, options)
|
16
|
+
|
17
|
+
# Iterate over each batch
|
18
|
+
(1..num_of_batches).each do |batch_number|
|
19
|
+
last_batch = (batch_number == num_of_batches)
|
20
|
+
current_batch_size = last_batch ? last_batch_size : batch_size
|
21
|
+
|
22
|
+
# Fetch and process records for the current batch
|
23
|
+
record_strings = records
|
24
|
+
.offset((batch_number - 1) * batch_size)
|
25
|
+
.limit(current_batch_size)
|
26
|
+
.map { |record| dump_record(record, options) }
|
27
|
+
|
28
|
+
yield record_strings, last_batch
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def enumerable_enumeration(records, options)
|
33
|
+
_, batch_size = batch_params_from(records, options)
|
34
|
+
record_strings = []
|
35
|
+
|
36
|
+
records.each_with_index do |record, index|
|
37
|
+
record_strings << dump_record(record, options)
|
38
|
+
last_batch = (index == records.size - 1)
|
39
|
+
|
40
|
+
if record_strings.size == batch_size || last_batch
|
41
|
+
yield record_strings, last_batch
|
42
|
+
record_strings = []
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def batch_params_from(records, options)
|
48
|
+
batch_size = batch_size_from(options)
|
49
|
+
total_count = records.count
|
50
|
+
|
51
|
+
num_of_batches = (total_count.to_f / batch_size).ceil
|
52
|
+
remainder = total_count % batch_size
|
53
|
+
last_batch_size = remainder.zero? ? batch_size : remainder
|
54
|
+
|
55
|
+
[num_of_batches, batch_size, last_batch_size]
|
56
|
+
end
|
57
|
+
|
58
|
+
def batch_size_from(options)
|
59
|
+
options[:batch_size]&.to_i || 1000
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_text"
|
4
|
+
require "active_storage"
|
5
|
+
|
6
|
+
class SeedDumpling
|
7
|
+
# Provides methods for dumping database records into Ruby code that can be used
|
8
|
+
# as seeds. Handles various data types and supports both create and import formats.
|
9
|
+
module DumpMethods # rubocop:disable Metrics/ModuleLength
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
include Enumeration
|
13
|
+
|
14
|
+
class_methods do
|
15
|
+
def dump(...)
|
16
|
+
new.dump(...)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def dump(records, options = {})
|
21
|
+
return nil if records.none?
|
22
|
+
|
23
|
+
io = prepare_io(options)
|
24
|
+
write_records(records, io, options)
|
25
|
+
ensure
|
26
|
+
io&.close
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def dump_record(record, options) # rubocop:disable Metrics/AbcSize
|
32
|
+
attributes = filter_attributes(record.attributes, options[:exclude])
|
33
|
+
|
34
|
+
# Make sure we include attachments and rich text in the final output
|
35
|
+
attributes["avatar"] = record.avatar if record.respond_to?(:avatar) && defined?(ActiveStorage::Attached::One)
|
36
|
+
|
37
|
+
attributes["photos"] = record.photos if record.respond_to?(:photos) && defined?(ActiveStorage::Attached::Many)
|
38
|
+
|
39
|
+
attributes["content"] = record.content if record.respond_to?(:content) && record.content.is_a?(ActionText::RichText)
|
40
|
+
|
41
|
+
formatted_attributes = format_attributes(attributes, options[:import])
|
42
|
+
wrap_attributes(formatted_attributes, options[:import])
|
43
|
+
end
|
44
|
+
|
45
|
+
def filter_attributes(attributes, exclude)
|
46
|
+
attributes.select do |key, _|
|
47
|
+
(key.is_a?(String) || key.is_a?(Symbol)) && exclude.exclude?(key.to_sym)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def format_attributes(attributes, import_format)
|
52
|
+
attributes.map do |attribute, value|
|
53
|
+
import_format ? value_to_s(value) : "#{attribute}: #{value_to_s(value)}"
|
54
|
+
end.join(", ")
|
55
|
+
end
|
56
|
+
|
57
|
+
def wrap_attributes(attribute_string, import_format)
|
58
|
+
open_char, close_char = import_format ? ["[", "]"] : ["{", "}"]
|
59
|
+
"#{open_char}#{attribute_string}#{close_char}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def value_to_s(value) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity
|
63
|
+
formatted_value = case value
|
64
|
+
when BigDecimal, IPAddr, ->(v) { rgeo_instance?(v) }
|
65
|
+
value.to_s
|
66
|
+
when Date, Time, DateTime
|
67
|
+
value.to_fs(:db)
|
68
|
+
when Range
|
69
|
+
range_to_string(value)
|
70
|
+
when ->(v) { defined?(ActiveStorage::Attached::One) && v.is_a?(ActiveStorage::Attached::One) }
|
71
|
+
handle_active_storage_one(value)
|
72
|
+
when ->(v) { defined?(ActiveStorage::Attached::Many) && v.is_a?(ActiveStorage::Attached::Many) }
|
73
|
+
handle_active_storage_many(value)
|
74
|
+
when ->(v) { defined?(ActionText::RichText) && v.is_a?(ActionText::RichText) }
|
75
|
+
handle_rich_text(value)
|
76
|
+
else
|
77
|
+
value
|
78
|
+
end
|
79
|
+
formatted_value.inspect
|
80
|
+
end
|
81
|
+
|
82
|
+
def range_to_string(range)
|
83
|
+
from = infinite_value?(range.begin) ? "" : range.begin
|
84
|
+
to = infinite_value?(range.end) ? "" : range.end
|
85
|
+
exclude_end = range.exclude_end? ? ")" : "]"
|
86
|
+
"[#{from},#{to}#{exclude_end}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def rgeo_instance?(value)
|
90
|
+
value.class.ancestors.any? { |ancestor| ancestor.to_s == "RGeo::Feature::Instance" }
|
91
|
+
end
|
92
|
+
|
93
|
+
def infinite_value?(value)
|
94
|
+
value.respond_to?(:infinite?) && value.infinite?
|
95
|
+
end
|
96
|
+
|
97
|
+
def prepare_io(options)
|
98
|
+
if options[:file].present?
|
99
|
+
mode = options[:append] ? "a+" : "w+"
|
100
|
+
File.open(options[:file], mode)
|
101
|
+
else
|
102
|
+
StringIO.new
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def write_records(records, io, options) # rubocop:disable Metrics/MethodLength
|
107
|
+
options[:exclude] ||= %i[id created_at updated_at]
|
108
|
+
|
109
|
+
method_call = build_method_call(records, options)
|
110
|
+
io.write(method_call)
|
111
|
+
io.write("[\n ")
|
112
|
+
|
113
|
+
enumeration_method = select_enumeration_method(records)
|
114
|
+
send(enumeration_method, records, options) do |record_strings, last_batch|
|
115
|
+
io.write(record_strings.join(",\n "))
|
116
|
+
io.write(",\n ") unless last_batch
|
117
|
+
end
|
118
|
+
|
119
|
+
io.write("\n]#{format_import_options(options)})\n")
|
120
|
+
|
121
|
+
return if options[:file].present?
|
122
|
+
|
123
|
+
io.rewind
|
124
|
+
io.read
|
125
|
+
end
|
126
|
+
|
127
|
+
def build_method_call(records, options)
|
128
|
+
method = options[:import] ? "import" : "create!"
|
129
|
+
model_name = determine_model(records)
|
130
|
+
if options[:import]
|
131
|
+
attributes_list = attribute_names(records, options).map { _1.to_sym.inspect }.join(", ")
|
132
|
+
"#{model_name}.#{method}([#{attributes_list}], "
|
133
|
+
else
|
134
|
+
"#{model_name}.#{method}("
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def format_import_options(options)
|
139
|
+
return "" unless options[:import].is_a?(Hash)
|
140
|
+
|
141
|
+
options_string = options[:import].map { |key, value| "#{key}: #{value}" }.join(", ")
|
142
|
+
", #{options_string}"
|
143
|
+
end
|
144
|
+
|
145
|
+
def attribute_names(records, options)
|
146
|
+
attributes = records.respond_to?(:attribute_names) ? records.attribute_names : records.first.attribute_names
|
147
|
+
attributes.reject { |name| options[:exclude].include?(name.to_sym) }
|
148
|
+
end
|
149
|
+
|
150
|
+
def determine_model(records)
|
151
|
+
if records.is_a?(Class)
|
152
|
+
records
|
153
|
+
elsif records.respond_to?(:model)
|
154
|
+
records.model
|
155
|
+
else
|
156
|
+
records.first.class
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def select_enumeration_method(records)
|
161
|
+
if records.is_a?(ActiveRecord::Relation) || records.is_a?(Class)
|
162
|
+
:active_record_enumeration
|
163
|
+
else
|
164
|
+
:enumerable_enumeration
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def handle_active_storage_one(attachment)
|
169
|
+
return nil unless attachment.attached?
|
170
|
+
|
171
|
+
copy_attachment_to_seeds_dir(attachment)
|
172
|
+
|
173
|
+
{
|
174
|
+
io: "File.open(Rails.root.join('db/seeds/files', '#{attachment.filename}'))",
|
175
|
+
filename: attachment.filename.to_s,
|
176
|
+
content_type: attachment.blob.content_type,
|
177
|
+
}
|
178
|
+
end
|
179
|
+
|
180
|
+
def handle_active_storage_many(attachments)
|
181
|
+
return [] unless attachments.attached?
|
182
|
+
|
183
|
+
attachments.map do |attachment|
|
184
|
+
copy_attachment_to_seeds_dir(attachment)
|
185
|
+
|
186
|
+
{
|
187
|
+
io: "File.open(Rails.root.join('db/seeds/files', '#{attachment.filename}'))",
|
188
|
+
filename: attachment.filename.to_s,
|
189
|
+
content_type: attachment.blob.content_type,
|
190
|
+
}
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def copy_attachment_to_seeds_dir(attachment) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
195
|
+
return unless attachment&.blob
|
196
|
+
|
197
|
+
seeds_dir = Rails.root.join("db/seeds/files")
|
198
|
+
FileUtils.mkdir_p(seeds_dir)
|
199
|
+
|
200
|
+
target_path = seeds_dir.join(attachment.filename.to_s)
|
201
|
+
|
202
|
+
# Skip if file already exists
|
203
|
+
return if File.exist?(target_path)
|
204
|
+
|
205
|
+
begin
|
206
|
+
# Try to get direct file path first
|
207
|
+
source_path = attachment.blob.service.path_for(attachment.blob.key)
|
208
|
+
if source_path && File.exist?(source_path)
|
209
|
+
FileUtils.cp(source_path, target_path)
|
210
|
+
else
|
211
|
+
# Fallback to downloading for cloud storage
|
212
|
+
File.open(target_path, "wb") do |file|
|
213
|
+
attachment.download { |chunk| file.write(chunk) }
|
214
|
+
end
|
215
|
+
end
|
216
|
+
rescue StandardError => e
|
217
|
+
Rails.logger.error "Failed to copy attachment #{attachment.filename}: #{e.message}"
|
218
|
+
raise e
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def handle_rich_text(rich_text)
|
223
|
+
rich_text.body&.to_html
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class SeedDumpling
|
4
|
+
# Provides functionality for dumping database records to seed files using
|
5
|
+
# environment variables to configure the dump process. Supports model filtering,
|
6
|
+
# batch processing, and various output options.
|
7
|
+
module Environment
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
class_methods do
|
11
|
+
def dump_using_environment(...)
|
12
|
+
new.dump_using_environment(...)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def dump_using_environment(env = {}) # rubocop:disable Metrics/MethodLength
|
17
|
+
Rails.application.eager_load!
|
18
|
+
|
19
|
+
models = retrieve_models(env) - retrieve_models_exclude(env)
|
20
|
+
|
21
|
+
limit = retrieve_limit_value(env)
|
22
|
+
append = retrieve_append_value(env)
|
23
|
+
models.each do |model|
|
24
|
+
model = model.limit(limit) if limit.present?
|
25
|
+
|
26
|
+
SeedDumpling.dump(model,
|
27
|
+
append: append,
|
28
|
+
batch_size: retrieve_batch_size_value(env),
|
29
|
+
exclude: retrieve_exclude_value(env),
|
30
|
+
file: retrieve_file_value(env),
|
31
|
+
import: retrieve_import_value(env),
|
32
|
+
)
|
33
|
+
|
34
|
+
append = true # Always append for every model after the first
|
35
|
+
# (append for the first model is determined by
|
36
|
+
# the APPEND environment variable).
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# Internal: Array of Strings corresponding to Active Record model class names
|
43
|
+
# that should be excluded from the dump.
|
44
|
+
ACTIVE_RECORD_INTERNAL_MODELS = ["ActiveRecord::SchemaMigration",
|
45
|
+
"ActiveRecord::InternalMetadata"].freeze
|
46
|
+
|
47
|
+
# Internal: Retrieves an Array of Active Record model class constants to be
|
48
|
+
# dumped.
|
49
|
+
#
|
50
|
+
# If a "MODEL" or "MODELS" environment variable is specified, there will be
|
51
|
+
# an attempt to parse the environment variable String by splitting it on
|
52
|
+
# commmas and then converting it to constant.
|
53
|
+
#
|
54
|
+
# Model classes that do not have corresponding database tables or database
|
55
|
+
# records will be filtered out, as will model classes internal to Active
|
56
|
+
# Record.
|
57
|
+
#
|
58
|
+
# env - Hash of environment variables from which to parse Active Record
|
59
|
+
# model classes. The Hash is not optional but the "MODEL" and "MODELS"
|
60
|
+
# keys are optional.
|
61
|
+
#
|
62
|
+
# Returns the Array of Active Record model classes to be dumped.
|
63
|
+
def retrieve_models(env) # rubocop:disable Metrics/AbcSize
|
64
|
+
# Parse either the "MODEL" environment variable or the "MODELS"
|
65
|
+
# environment variable, with "MODEL" taking precedence.
|
66
|
+
models_env = env["MODEL"] || env["MODELS"]
|
67
|
+
|
68
|
+
# If there was a use models environment variable, split it and
|
69
|
+
# convert the given model string (e.g. "User") to an actual
|
70
|
+
# model constant (e.g. User).
|
71
|
+
#
|
72
|
+
# If a models environment variable was not given, use descendants of
|
73
|
+
# ActiveRecord::Base as the target set of models. This should be all
|
74
|
+
# model classes in the project.
|
75
|
+
models = if models_env
|
76
|
+
models_env.split(",")
|
77
|
+
.collect { |x| x.strip.underscore.singularize.camelize.constantize }
|
78
|
+
else
|
79
|
+
ActiveRecord::Base.descendants
|
80
|
+
end
|
81
|
+
|
82
|
+
# Filter the set of models to exclude:
|
83
|
+
# - The ActiveRecord::SchemaMigration model which is internal to Rails
|
84
|
+
# and should not be part of the dumped data.
|
85
|
+
# - Models that don't have a corresponding table in the database.
|
86
|
+
# - Models whose corresponding database tables are empty.
|
87
|
+
models.select do |model|
|
88
|
+
ACTIVE_RECORD_INTERNAL_MODELS.exclude?(model.to_s) &&
|
89
|
+
model.table_exists? &&
|
90
|
+
model.exists?
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Internal: Returns a Boolean indicating whether the value for the "APPEND"
|
95
|
+
# key in the given Hash is equal to the String "true" (ignoring case),
|
96
|
+
# false if no value exists.
|
97
|
+
def retrieve_append_value(env)
|
98
|
+
parse_boolean_value(env["APPEND"])
|
99
|
+
end
|
100
|
+
|
101
|
+
# Internal: Returns a Boolean indicating whether the value for the "IMPORT"
|
102
|
+
# key in the given Hash is equal to the String "true" (ignoring case),
|
103
|
+
# false if no value exists.
|
104
|
+
def retrieve_import_value(env)
|
105
|
+
parse_boolean_value(env["IMPORT"])
|
106
|
+
end
|
107
|
+
|
108
|
+
# Internal: Retrieves an Array of Class constants parsed from the value for
|
109
|
+
# the "MODELS_EXCLUDE" key in the given Hash, and an empty Array if such
|
110
|
+
# key exists.
|
111
|
+
def retrieve_models_exclude(env)
|
112
|
+
env["MODELS_EXCLUDE"].to_s
|
113
|
+
.split(",")
|
114
|
+
.collect { |x| x.strip.underscore.singularize.camelize.constantize }
|
115
|
+
end
|
116
|
+
|
117
|
+
# Internal: Retrieves an Integer from the value for the "LIMIT" key in the
|
118
|
+
# given Hash, and nil if no such key exists.
|
119
|
+
def retrieve_limit_value(env)
|
120
|
+
retrieve_integer_value("LIMIT", env)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Internal: Retrieves an Array of Symbols from the value for the "EXCLUDE"
|
124
|
+
# key from the given Hash, and nil if no such key exists.
|
125
|
+
def retrieve_exclude_value(env)
|
126
|
+
env["EXCLUDE"]&.split(",")&.map { |e| e.strip.to_sym }
|
127
|
+
end
|
128
|
+
|
129
|
+
# Internal: Retrieves the value for the "FILE" key from the given Hash, and
|
130
|
+
# 'db/seeds.rb' if no such key exists.
|
131
|
+
def retrieve_file_value(env)
|
132
|
+
env["FILE"] || "db/seeds.rb"
|
133
|
+
end
|
134
|
+
|
135
|
+
# Internal: Retrieves an Integer from the value for the "BATCH_SIZE" key in
|
136
|
+
# the given Hash, and nil if no such key exists.
|
137
|
+
def retrieve_batch_size_value(env)
|
138
|
+
retrieve_integer_value("BATCH_SIZE", env)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Internal: Retrieves an Integer from the value for the given key in
|
142
|
+
# the given Hash, and nil if no such key exists.
|
143
|
+
def retrieve_integer_value(key, hash)
|
144
|
+
hash[key]&.to_i
|
145
|
+
end
|
146
|
+
|
147
|
+
# Internal: Parses a Boolean from the given value.
|
148
|
+
def parse_boolean_value(value)
|
149
|
+
value.to_s.downcase == "true"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ipaddr"
|
4
|
+
require "seed_dumpling/dump_methods/enumeration"
|
5
|
+
require "seed_dumpling/dump_methods"
|
6
|
+
require "seed_dumpling/environment"
|
7
|
+
|
8
|
+
# Tool for extracting database records into a format suitable for
|
9
|
+
# Rails' db/seeds.rb. Supports various data types and configurable output formats.
|
10
|
+
class SeedDumpling
|
11
|
+
include Environment
|
12
|
+
include DumpMethods
|
13
|
+
|
14
|
+
require "seed_dumpling/railtie" if defined?(Rails)
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: seed_dumpling
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mikael Henriksson
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-01-06 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: activerecord-import
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: rails
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '7.0'
|
33
|
+
- - "<"
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '9.0'
|
36
|
+
type: :runtime
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '7.0'
|
43
|
+
- - "<"
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '9.0'
|
46
|
+
description: Dump (parts) of your database to db/seedspec.rb to get a headstart creating
|
47
|
+
a meaningful seedspec.rb file.
|
48
|
+
email: mikael@mhenrixon.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- LICENSE.txt
|
54
|
+
- README.md
|
55
|
+
- lib/seed_dumpling.rb
|
56
|
+
- lib/seed_dumpling/dump_methods.rb
|
57
|
+
- lib/seed_dumpling/dump_methods/enumeration.rb
|
58
|
+
- lib/seed_dumpling/environment.rb
|
59
|
+
- lib/seed_dumpling/railtie.rb
|
60
|
+
- lib/seed_dumpling/version.rb
|
61
|
+
- lib/tasks/seed_dumpling.rake
|
62
|
+
homepage: https://github.com/mhenrixon/dumpling
|
63
|
+
licenses:
|
64
|
+
- MIT
|
65
|
+
metadata:
|
66
|
+
rubygems_mfa_required: 'true'
|
67
|
+
homepage_uri: https://github.com/mhenrixon/dumpling
|
68
|
+
source_code_uri: https://github.com/mhenrixon/dumpling
|
69
|
+
changelog_uri: https://github.com/mhenrixon/dumpling/blob/up/CHANGELOG.md
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 3.2.0
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
requirements: []
|
84
|
+
rubygems_version: 3.6.2
|
85
|
+
specification_version: 4
|
86
|
+
summary: Seed Dumper for Rails
|
87
|
+
test_files: []
|