seed-do 3.2.0 → 4.0.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 +4 -4
- data/README.md +40 -0
- data/lib/seed-do/active_record_extension.rb +2 -3
- data/lib/seed-do/bulk_seeder.rb +66 -0
- data/lib/seed-do/runner.rb +38 -17
- data/lib/seed-do/seeder.rb +18 -19
- data/lib/seed-do/version.rb +1 -1
- data/lib/seed-do.rb +12 -3
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c5486ba5dbc636b111be4e69d838b7f317b233ea9cf4b516d611fdf5070f0780
|
|
4
|
+
data.tar.gz: a04c1e585a72163d13184a317aecf059eb4c21f24d9b3e51fd208debf452a574
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a5d0cbf12b3790a33f0c362967c4a7b20a85ef7cdb3e9a0c15b47111d0a4fab0ae5912728c1fbab4be32697669f4ed672d221badc00ea5abca06999117e3a4fb
|
|
7
|
+
data.tar.gz: 0011f72cf7c27fcfef5d8dad96ca74525582eaf9ddc01e66dd0b1b575e255d28c72c49db6b81f0523704991ab12545d7a4ebfc75c83fe46ef8ede8a71448d423
|
data/README.md
CHANGED
|
@@ -142,6 +142,30 @@ Seed files can be run automatically using `rake db:seed_do`. There are two optio
|
|
|
142
142
|
|
|
143
143
|
You can also do a similar thing in your code by calling `SeedDo.seed(fixture_paths, filter)`.
|
|
144
144
|
|
|
145
|
+
## Bulk upsert
|
|
146
|
+
|
|
147
|
+
If you load a large amount of seed data, you can enable bulk upsert mode:
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
SeedDo.seed(bulk: true)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
In bulk mode, SeedDo buffers seed data for each file and writes it with `upsert_all`, which can significantly reduce the number of queries.
|
|
154
|
+
|
|
155
|
+
You can also control the batch size per `upsert_all` call. The default is `1000`.
|
|
156
|
+
|
|
157
|
+
```ruby
|
|
158
|
+
SeedDo.seed(bulk: { batch_size: 100 })
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Requirements and caveats
|
|
162
|
+
|
|
163
|
+
- Bulk mode relies on Rails `upsert_all`
|
|
164
|
+
- The seed constraints are passed to `unique_by`, so the related columns must have a unique index
|
|
165
|
+
- Bulk mode is supported only on databases that support conflict targets, such as PostgreSQL and SQLite
|
|
166
|
+
- Bulk mode is not available on MySQL
|
|
167
|
+
- `SeedDo.seed` and `SeedDo.seed(bulk: true)` may not always produce the same record order, so verify the behavior carefully before enabling it in an existing production environment
|
|
168
|
+
|
|
145
169
|
## Disable output
|
|
146
170
|
|
|
147
171
|
To disable output from Seed Do, set `SeedDo.quiet = true`.
|
|
@@ -200,6 +224,22 @@ DB=postgresql bundle exec rspec
|
|
|
200
224
|
|
|
201
225
|
The connection paramaters for each of these are specified in spec/connections/, which you can edit if necessary (for example to change the username/password).
|
|
202
226
|
|
|
227
|
+
### Dev Container
|
|
228
|
+
|
|
229
|
+
This repository includes a Dev Container setup for running the test suite locally with Ruby, SQLite, MySQL, and PostgreSQL ready to use.
|
|
230
|
+
|
|
231
|
+
1. Open the repository in a Dev Container.
|
|
232
|
+
2. Wait for `bundle install` to finish.
|
|
233
|
+
3. Run the specs you want:
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
bundle exec rspec
|
|
237
|
+
DB=mysql2 bundle exec rspec
|
|
238
|
+
DB=postgresql bundle exec rspec
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
The Dev Container config also sets `MYSQL_HOST=mysql` and `POSTGRES_HOST=postgres` automatically, so the database-backed specs work without editing local machine settings.
|
|
242
|
+
|
|
203
243
|
## Original Author
|
|
204
244
|
|
|
205
245
|
[Michael Bleigh](http://www.mbleigh.com/) is the original author
|
|
@@ -29,7 +29,7 @@ module SeedDo
|
|
|
29
29
|
# { :x => 5, :y => 9, :name => "Office" }
|
|
30
30
|
# )
|
|
31
31
|
def seed(*args, &block)
|
|
32
|
-
SeedDo
|
|
32
|
+
SeedDo.current_seeder.seed(self, *parse_seed_do_args(args, block))
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
# Has the same syntax as {#seed}, but if a record already exists with the same values for
|
|
@@ -40,8 +40,7 @@ module SeedDo
|
|
|
40
40
|
# Person.seed(:id, :id => 1, :name => "Bob") # => Name changed
|
|
41
41
|
# Person.seed_once(:id, :id => 1, :name => "Harry") # => Name *not* changed
|
|
42
42
|
def seed_once(*args, &block)
|
|
43
|
-
|
|
44
|
-
SeedDo::Seeder.new(self, constraints, data, insert_only: true).seed
|
|
43
|
+
SeedDo.current_seeder.seed_once(self, *parse_seed_do_args(args, block))
|
|
45
44
|
end
|
|
46
45
|
|
|
47
46
|
private
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module SeedDo
|
|
2
|
+
# Buffers seeds during a file run and flushes them in bulk.
|
|
3
|
+
class BulkSeeder
|
|
4
|
+
attr_reader :buffer
|
|
5
|
+
|
|
6
|
+
def initialize(batch_size: 1000)
|
|
7
|
+
@batch_size = batch_size
|
|
8
|
+
@buffer = nil
|
|
9
|
+
|
|
10
|
+
validate_support!
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def seed(model, constraints, data)
|
|
14
|
+
puts " - #{model} #{data.inspect}" unless SeedDo.quiet
|
|
15
|
+
buffer[:seed] << { model: model, constraints: constraints, data: data }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def seed_once(model, constraints, data)
|
|
19
|
+
puts " - #{model} #{data.inspect}" unless SeedDo.quiet
|
|
20
|
+
buffer[:seed_once] << { model: model, constraints: constraints, data: data }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def with_seed_file
|
|
24
|
+
@buffer = { seed: [], seed_once: [] }
|
|
25
|
+
yield
|
|
26
|
+
process_buffer
|
|
27
|
+
ensure
|
|
28
|
+
@buffer = nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def validate_support!
|
|
34
|
+
return if ActiveRecord::Base.connection.supports_insert_conflict_target?
|
|
35
|
+
|
|
36
|
+
raise ArgumentError,
|
|
37
|
+
"Bulk mode is not supported for #{ActiveRecord::Base.connection.adapter_name}. " \
|
|
38
|
+
'The database does not support upsert operations with conflict targets. ' \
|
|
39
|
+
'Please use SeedDo.seed without the bulk option.'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def process_buffer
|
|
43
|
+
return unless buffer
|
|
44
|
+
|
|
45
|
+
buffer.each do |type, operations|
|
|
46
|
+
next if operations.empty?
|
|
47
|
+
|
|
48
|
+
operations.chunk { |operation| [operation[:model], operation[:constraints]] }
|
|
49
|
+
.each do |(model, constraints), chunk|
|
|
50
|
+
flush_chunk(model, constraints, type, chunk)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def flush_chunk(model, constraints, type, chunk)
|
|
56
|
+
all_data = chunk.flat_map { |operation| operation[:data] }
|
|
57
|
+
|
|
58
|
+
options = { unique_by: constraints }
|
|
59
|
+
options[:on_duplicate] = :skip if type == :seed_once
|
|
60
|
+
|
|
61
|
+
all_data.each_slice(@batch_size) do |batch|
|
|
62
|
+
model.upsert_all(batch, **options)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
data/lib/seed-do/runner.rb
CHANGED
|
@@ -3,9 +3,7 @@ require 'active_support/core_ext/array/wrap'
|
|
|
3
3
|
|
|
4
4
|
module SeedDo
|
|
5
5
|
# Runs seed files.
|
|
6
|
-
#
|
|
7
|
-
# It is not recommended to use this class directly. Instead, use {SeedDo.seed SeedDo.seed}, which creates
|
|
8
|
-
# an instead of {Runner} and calls {#run #run}.
|
|
6
|
+
# It is not recommended to use this class directly. Instead, use {SeedDo.seed SeedDo.seed}, which creates an instead of {Runner} and calls {#run #run}.
|
|
9
7
|
#
|
|
10
8
|
# @see SeedDo.seed SeedDo.seed
|
|
11
9
|
class Runner
|
|
@@ -13,18 +11,31 @@ module SeedDo
|
|
|
13
11
|
# `SeedDo.fixture_paths` if {nil}. If the argument is not an array, it will be wrapped by one.
|
|
14
12
|
# @param [Regexp] filter If given, only seed files with a file name matching this pattern will
|
|
15
13
|
# be used
|
|
16
|
-
|
|
14
|
+
# @param [Boolean, Hash] bulk If true, use upsert_all to insert/update records in bulk.
|
|
15
|
+
# If a hash, can include :batch_size (default: 1000) to control the number of records
|
|
16
|
+
# per upsert_all call.
|
|
17
|
+
def initialize(fixture_paths = nil, filter = nil, bulk: false)
|
|
17
18
|
@fixture_paths = Array.wrap(fixture_paths || SeedDo.fixture_paths)
|
|
18
19
|
@filter = filter
|
|
20
|
+
@seeder = build_seeder(bulk)
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
# Run the seed files.
|
|
22
24
|
def run
|
|
25
|
+
SeedDo.current_seeder = @seeder
|
|
23
26
|
puts "\n== Filtering seed files against regexp: #{@filter.inspect}" if @filter && !SeedDo.quiet
|
|
24
27
|
|
|
25
|
-
filenames.each
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
filenames.each { |filename| run_file(filename) }
|
|
29
|
+
ensure
|
|
30
|
+
SeedDo.current_seeder = nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def seed(model, constraints, data)
|
|
34
|
+
@seeder.seed(model, constraints, data)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def seed_once(model, constraints, data)
|
|
38
|
+
@seeder.seed_once(model, constraints, data)
|
|
28
39
|
end
|
|
29
40
|
|
|
30
41
|
private
|
|
@@ -33,18 +44,28 @@ module SeedDo
|
|
|
33
44
|
puts "\n== Seed from #{filename}" unless SeedDo.quiet
|
|
34
45
|
|
|
35
46
|
ActiveRecord::Base.transaction do
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
@seeder.with_seed_file { _run_file(filename) }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def build_seeder(bulk)
|
|
52
|
+
return SeedDo::BulkSeeder.new(batch_size: bulk.fetch(:batch_size, 1000)) if bulk.is_a?(Hash)
|
|
53
|
+
|
|
54
|
+
bulk ? SeedDo::BulkSeeder.new : SeedDo::Seeder.new
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def _run_file(filename)
|
|
58
|
+
open(filename) do |file|
|
|
59
|
+
chunked_ruby = +''
|
|
60
|
+
file.each_line do |line|
|
|
61
|
+
if line == "# BREAK EVAL\n"
|
|
62
|
+
eval(chunked_ruby)
|
|
63
|
+
chunked_ruby = +''
|
|
64
|
+
else
|
|
65
|
+
chunked_ruby << line
|
|
45
66
|
end
|
|
46
|
-
eval(chunked_ruby) unless chunked_ruby == ''
|
|
47
67
|
end
|
|
68
|
+
eval(chunked_ruby) unless chunked_ruby == ''
|
|
48
69
|
end
|
|
49
70
|
end
|
|
50
71
|
|
data/lib/seed-do/seeder.rb
CHANGED
|
@@ -8,30 +8,31 @@ module SeedDo
|
|
|
8
8
|
#
|
|
9
9
|
# @see ActiveRecordExtension
|
|
10
10
|
class Seeder
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
def seed(model_class, constraints, data)
|
|
12
|
+
seed_records(model_class, constraints, data)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def seed_once(model_class, constraints, data)
|
|
16
|
+
seed_records(model_class, constraints, data, insert_only: true)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def with_seed_file
|
|
20
|
+
yield
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
# Insert/update the records as appropriate. Validation is skipped while saving.
|
|
26
|
+
# @return [Array<ActiveRecord::Base>] The records which have been seeded
|
|
27
|
+
def seed_records(model_class, constraints, data, options = {})
|
|
21
28
|
@model_class = model_class
|
|
22
29
|
@constraints = constraints.to_a.empty? ? [:id] : constraints
|
|
23
30
|
@data = data.to_a || []
|
|
24
31
|
@options = options.symbolize_keys
|
|
25
32
|
|
|
26
|
-
@options[:quiet] ||= SeedDo.quiet
|
|
27
|
-
|
|
28
33
|
validate_constraints!
|
|
29
34
|
validate_data!
|
|
30
|
-
end
|
|
31
35
|
|
|
32
|
-
# Insert/update the records as appropriate. Validation is skipped while saving.
|
|
33
|
-
# @return [Array<ActiveRecord::Base>] The records which have been seeded
|
|
34
|
-
def seed
|
|
35
36
|
records = @model_class.transaction do
|
|
36
37
|
@data.map { |record_data| seed_record(record_data.symbolize_keys) }
|
|
37
38
|
end
|
|
@@ -39,8 +40,6 @@ module SeedDo
|
|
|
39
40
|
records
|
|
40
41
|
end
|
|
41
42
|
|
|
42
|
-
private
|
|
43
|
-
|
|
44
43
|
def validate_constraints!
|
|
45
44
|
unknown_columns = @constraints.map(&:to_s) - @model_class.column_names
|
|
46
45
|
return if unknown_columns.empty?
|
|
@@ -62,7 +61,7 @@ module SeedDo
|
|
|
62
61
|
record = find_or_initialize_record(data)
|
|
63
62
|
return if @options[:insert_only] && !record.new_record?
|
|
64
63
|
|
|
65
|
-
puts " - #{@model_class} #{data.inspect}" unless
|
|
64
|
+
puts " - #{@model_class} #{data.inspect}" unless SeedDo.quiet
|
|
66
65
|
|
|
67
66
|
record.assign_attributes(data)
|
|
68
67
|
record.save(validate: false) || raise(ActiveRecord::RecordNotSaved, 'Record not saved!')
|
data/lib/seed-do/version.rb
CHANGED
data/lib/seed-do.rb
CHANGED
|
@@ -7,6 +7,7 @@ module SeedDo
|
|
|
7
7
|
autoload :Seeder, 'seed-do/seeder'
|
|
8
8
|
autoload :ActiveRecordExtension, 'seed-do/active_record_extension'
|
|
9
9
|
autoload :BlockHash, 'seed-do/block_hash'
|
|
10
|
+
autoload :BulkSeeder, 'seed-do/bulk_seeder'
|
|
10
11
|
autoload :Runner, 'seed-do/runner'
|
|
11
12
|
autoload :Writer, 'seed-do/writer'
|
|
12
13
|
|
|
@@ -17,12 +18,20 @@ module SeedDo
|
|
|
17
18
|
# plugin, SeedDo will set it to contain `Rails.root/db/fixtures` and
|
|
18
19
|
# `Rails.root/db/fixtures/Rails.env`
|
|
19
20
|
mattr_accessor :fixture_paths, default: ['db/fixtures']
|
|
20
|
-
|
|
21
21
|
# Load seed data from files
|
|
22
22
|
# @param [Array] fixture_paths The paths to look for seed files in
|
|
23
23
|
# @param [Regexp] filter If given, only filenames matching this expression will be loaded
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
# @param [Boolean] bulk If true, bulk insert/upsert will be used
|
|
25
|
+
def self.seed(fixture_paths = SeedDo.fixture_paths, filter = nil, bulk: false)
|
|
26
|
+
Runner.new(fixture_paths, filter, bulk: bulk).run
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.current_seeder
|
|
30
|
+
@current_seeder || Seeder.new
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.current_seeder=(seeder)
|
|
34
|
+
@current_seeder = seeder
|
|
26
35
|
end
|
|
27
36
|
end
|
|
28
37
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: seed-do
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 4.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shinichi Maeshima
|
|
@@ -66,6 +66,7 @@ files:
|
|
|
66
66
|
- lib/seed-do.rb
|
|
67
67
|
- lib/seed-do/active_record_extension.rb
|
|
68
68
|
- lib/seed-do/block_hash.rb
|
|
69
|
+
- lib/seed-do/bulk_seeder.rb
|
|
69
70
|
- lib/seed-do/capistrano.rb
|
|
70
71
|
- lib/seed-do/capistrano3.rb
|
|
71
72
|
- lib/seed-do/railtie.rb
|
|
@@ -94,7 +95,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
94
95
|
- !ruby/object:Gem::Version
|
|
95
96
|
version: '0'
|
|
96
97
|
requirements: []
|
|
97
|
-
rubygems_version:
|
|
98
|
+
rubygems_version: 4.0.6
|
|
98
99
|
specification_version: 4
|
|
99
100
|
summary: Easily manage seed data in your Active Record application
|
|
100
101
|
test_files: []
|