db_seed_dump 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/.rspec +2 -0
- data/.rubocop.yml +92 -0
- data/.rubocop_todo.yml +118 -0
- data/Gemfile +24 -0
- data/MIT-LICENSE +20 -0
- data/README.md +125 -0
- data/Rakefile +32 -0
- data/VERSION +1 -0
- data/bin/fastcheck +1 -0
- data/db_seed_dump.gemspec +80 -0
- data/lib/seed_dump/dependency_unwrangler.rb +51 -0
- data/lib/seed_dump/dump_methods/enumeration.rb +86 -0
- data/lib/seed_dump/dump_methods.rb +173 -0
- data/lib/seed_dump/environment.rb +182 -0
- data/lib/seed_dump/railtie.rb +7 -0
- data/lib/seed_dump.rb +15 -0
- data/lib/tasks/seed_dump.rake +8 -0
- data/spec/dump_methods_spec.rb +200 -0
- data/spec/environment_spec.rb +160 -0
- data/spec/factories/another_samples.rb +14 -0
- data/spec/factories/samples.rb +16 -0
- data/spec/factories/yet_another_samples.rb +14 -0
- data/spec/helpers.rb +92 -0
- data/spec/spec_helper.rb +36 -0
- metadata +211 -0
@@ -0,0 +1,173 @@
|
|
1
|
+
class SeedDump
|
2
|
+
module DumpMethods
|
3
|
+
APPEND_FILE_MODE = 'a+'.freeze
|
4
|
+
OVERWRITE_FILE_MODE = 'w+'.freeze
|
5
|
+
include Enumeration
|
6
|
+
|
7
|
+
def dump(records, options = {})
|
8
|
+
return nil if records.count.zero?
|
9
|
+
|
10
|
+
io = open_io(options)
|
11
|
+
|
12
|
+
write_records_to_io(records, io, options)
|
13
|
+
ensure
|
14
|
+
io.close if io.present?
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def dump_record(record, options)
|
20
|
+
attribute_strings = []
|
21
|
+
|
22
|
+
# We select only string attribute names to avoid conflict
|
23
|
+
# with the composite_primary_keys gem (it returns composite
|
24
|
+
# primary key attribute names as hashes).
|
25
|
+
record.attributes.select { |key| key.is_a?(String) || key.is_a?(Symbol) }.each do |attribute, value|
|
26
|
+
unless options[:exclude].include?(attribute.to_sym)
|
27
|
+
attribute_strings << dump_attribute_new(attribute, value,
|
28
|
+
options)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
open_character, close_character = options[:import] ? ['[', ']'] : ['{', '}']
|
33
|
+
|
34
|
+
"#{open_character}#{attribute_strings.join(', ')}#{close_character}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def dump_attribute_new(attribute, value, options)
|
38
|
+
options[:import] ? value_to_s(value) : "#{attribute}: #{value_to_s(value)}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def value_to_s(value)
|
42
|
+
value = case value
|
43
|
+
when BigDecimal, IPAddr
|
44
|
+
value.to_s
|
45
|
+
when Date, Time, DateTime
|
46
|
+
value.to_s(:db)
|
47
|
+
when Range
|
48
|
+
range_to_string(value)
|
49
|
+
when ->(v) { v.class.ancestors.map(&:to_s).include?('RGeo::Feature::Instance') }
|
50
|
+
value.to_s
|
51
|
+
else
|
52
|
+
value
|
53
|
+
end
|
54
|
+
|
55
|
+
value.inspect
|
56
|
+
end
|
57
|
+
|
58
|
+
def range_to_string(object)
|
59
|
+
from = object.begin.respond_to?(:infinite?) && object.begin.infinite? ? '' : object.begin
|
60
|
+
to = object.end.respond_to?(:infinite?) && object.end.infinite? ? '' : object.end
|
61
|
+
"[#{from},#{to}#{object.exclude_end? ? ')' : ']'}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def open_io(options)
|
65
|
+
if options[:file].present?
|
66
|
+
mode = options[:append] ? APPEND_FILE_MODE : OVERWRITE_FILE_MODE
|
67
|
+
file_path = if options[:file_split_limit]
|
68
|
+
file_path_with_file_index(options)
|
69
|
+
else
|
70
|
+
options[:file]
|
71
|
+
end
|
72
|
+
|
73
|
+
File.open(file_path, mode)
|
74
|
+
else
|
75
|
+
StringIO.new('', OVERWRITE_FILE_MODE)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def file_path_with_file_index(options)
|
80
|
+
base_name = File.basename(options[:file], '.*')
|
81
|
+
options[:file].reverse.sub(
|
82
|
+
base_name.reverse,
|
83
|
+
[
|
84
|
+
base_name,
|
85
|
+
(options[:current_file_index]&.to_i || 1)
|
86
|
+
].join('_').reverse
|
87
|
+
).reverse
|
88
|
+
end
|
89
|
+
|
90
|
+
def write_records_to_io(records, io, options)
|
91
|
+
options[:exclude] ||= %i[id created_at updated_at]
|
92
|
+
|
93
|
+
setup_io(io, options, records)
|
94
|
+
|
95
|
+
enumeration_method = if records.is_a?(ActiveRecord::Relation) || records.is_a?(Class)
|
96
|
+
:active_record_enumeration
|
97
|
+
else
|
98
|
+
:enumerable_enumeration
|
99
|
+
end
|
100
|
+
|
101
|
+
send(enumeration_method, records, io, options) do |record_strings, last_batch, file_split_required|
|
102
|
+
io.write(record_strings.join(",\n "))
|
103
|
+
|
104
|
+
io.write(",\n ") unless last_batch
|
105
|
+
|
106
|
+
if options[:file].present? && file_split_required
|
107
|
+
options[:current_file_index] = ((options[:current_file_index]&.to_i || 1) + 1)
|
108
|
+
io.write("\n]#{active_record_import_options(options)})\n")
|
109
|
+
io = open_io(options)
|
110
|
+
setup_io(io, options, records)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
io.write("\n]#{active_record_import_options(options)})\n")
|
115
|
+
|
116
|
+
if options[:file].present?
|
117
|
+
nil
|
118
|
+
else
|
119
|
+
io.rewind
|
120
|
+
io.read
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def setup_io(io, options, records)
|
125
|
+
method = chosen_creation_method(options)
|
126
|
+
io.write("#{model_for(records)}.#{method}(")
|
127
|
+
if options[:import]
|
128
|
+
io.write("[#{attribute_names(records, options).map { |name| name.to_sym.inspect }.join(', ')}], ")
|
129
|
+
end
|
130
|
+
io.write("[\n ")
|
131
|
+
end
|
132
|
+
|
133
|
+
def chosen_creation_method(options)
|
134
|
+
if options[:import]
|
135
|
+
'import'
|
136
|
+
elsif options[:insert_all]
|
137
|
+
'insert_all'
|
138
|
+
else
|
139
|
+
'create!'
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def active_record_import_options(options)
|
144
|
+
return unless options[:import].is_a?(Hash) || options[:import_options].present?
|
145
|
+
|
146
|
+
if options[:import].is_a?(Hash)
|
147
|
+
', ' + options[:import].map { |key, value| "#{key}: #{value}" }.join(', ')
|
148
|
+
else
|
149
|
+
', ' + options[:import_options]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def attribute_names(records, options)
|
154
|
+
attribute_names = if records.is_a?(ActiveRecord::Relation) || records.is_a?(Class)
|
155
|
+
records.attribute_names
|
156
|
+
else
|
157
|
+
records[0].attribute_names
|
158
|
+
end
|
159
|
+
|
160
|
+
attribute_names.reject { |name| options[:exclude].include?(name.to_sym) }
|
161
|
+
end
|
162
|
+
|
163
|
+
def model_for(records)
|
164
|
+
if records.is_a?(Class)
|
165
|
+
records
|
166
|
+
elsif records.respond_to?(:model)
|
167
|
+
records.model
|
168
|
+
else
|
169
|
+
records[0].class
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
class SeedDump
|
2
|
+
module Environment
|
3
|
+
def dump_using_environment(env = {})
|
4
|
+
Rails.application.eager_load!
|
5
|
+
|
6
|
+
models = retrieve_models(env) - retrieve_models_exclude(env)
|
7
|
+
|
8
|
+
current_file_index = 1
|
9
|
+
limit = retrieve_limit_value(env)
|
10
|
+
append = retrieve_append_value(env)
|
11
|
+
|
12
|
+
# Eliminate HABTM models that have the same underlying table; otherwise
|
13
|
+
# they'll be dumped twice, once in each direction. Probably should apply
|
14
|
+
# to all models, but it's possible there are edge cases in which this
|
15
|
+
# is not the right behavior.
|
16
|
+
|
17
|
+
habtm, non_habtm = models.partition { |m| m.name =~ /^HABTM_/ }
|
18
|
+
models = non_habtm + habtm.uniq(&:table_name)
|
19
|
+
|
20
|
+
resolved_dependencies = DependencyUnwrangler.new(models).evaluation_order - retrieve_models_exclude(env)
|
21
|
+
models = resolved_dependencies unless resolved_dependencies.empty?
|
22
|
+
models.each do |model|
|
23
|
+
model = model.limit(limit) if limit.present?
|
24
|
+
options = {
|
25
|
+
append: append,
|
26
|
+
batch_size: retrieve_batch_size_value(env),
|
27
|
+
exclude: retrieve_dump_all_value(env) ? [] : retrieve_exclude_value(env),
|
28
|
+
insert_all: retrieve_insert_all_value(env),
|
29
|
+
file_split_limit: retreive_file_split_limit_value(env),
|
30
|
+
file: retrieve_file_value(env),
|
31
|
+
import: retrieve_import_value(env),
|
32
|
+
current_file_index: current_file_index,
|
33
|
+
import_options: retrieve_import_options(env)
|
34
|
+
}
|
35
|
+
|
36
|
+
SeedDump.dump(model, options)
|
37
|
+
|
38
|
+
append = true # Always append for every model after the first
|
39
|
+
# (append for the first model is determined by
|
40
|
+
# the APPEND environment variable).
|
41
|
+
current_file_index = options[:current_file_index]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Internal: Array of Strings corresponding to Active Record model class names
|
48
|
+
# that should be excluded from the dump.
|
49
|
+
ACTIVE_RECORD_INTERNAL_MODELS = ['ActiveRecord::SchemaMigration',
|
50
|
+
'ActiveRecord::InternalMetadata'].freeze
|
51
|
+
|
52
|
+
# Internal: Retrieves an Array of Active Record model class constants to be
|
53
|
+
# dumped.
|
54
|
+
#
|
55
|
+
# If a "MODEL" or "MODELS" environment variable is specified, there will be
|
56
|
+
# an attempt to parse the environment variable String by splitting it on
|
57
|
+
# commmas and then converting it to constant.
|
58
|
+
#
|
59
|
+
# Model classes that do not have corresponding database tables or database
|
60
|
+
# records will be filtered out, as will model classes internal to Active
|
61
|
+
# Record.
|
62
|
+
#
|
63
|
+
# env - Hash of environment variables from which to parse Active Record
|
64
|
+
# model classes. The Hash is not optional but the "MODEL" and "MODELS"
|
65
|
+
# keys are optional.
|
66
|
+
#
|
67
|
+
# Returns the Array of Active Record model classes to be dumped.
|
68
|
+
def retrieve_models(env)
|
69
|
+
# Parse either the "MODEL" environment variable or the "MODELS"
|
70
|
+
# environment variable, with "MODEL" taking precedence.
|
71
|
+
models_env = env['MODEL'] || env['MODELS']
|
72
|
+
|
73
|
+
# If there was a use models environment variable, split it and
|
74
|
+
# convert the given model string (e.g. "User") to an actual
|
75
|
+
# model constant (e.g. User).
|
76
|
+
#
|
77
|
+
# If a models environment variable was not given, use descendants of
|
78
|
+
# ActiveRecord::Base as the target set of models. This should be all
|
79
|
+
# model classes in the project.
|
80
|
+
models = if models_env
|
81
|
+
models_env.split(',')
|
82
|
+
.collect { |x| x.strip.underscore.singularize.camelize.constantize }
|
83
|
+
else
|
84
|
+
ActiveRecord::Base.descendants
|
85
|
+
end
|
86
|
+
|
87
|
+
# Filter the set of models to exclude:
|
88
|
+
# - The ActiveRecord::SchemaMigration model which is internal to Rails
|
89
|
+
# and should not be part of the dumped data.
|
90
|
+
# - Models that don't have a corresponding table in the database.
|
91
|
+
# - Models whose corresponding database tables are empty.
|
92
|
+
filtered_models = models.select do |model|
|
93
|
+
!ACTIVE_RECORD_INTERNAL_MODELS.include?(model.to_s) && \
|
94
|
+
model.table_exists? && \
|
95
|
+
model.exists?
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Internal: Returns a Boolean indicating whether the value for the "APPEND"
|
100
|
+
# key in the given Hash is equal to the String "true" (ignoring case),
|
101
|
+
# false if no value exists.
|
102
|
+
def retrieve_append_value(env)
|
103
|
+
parse_boolean_value(env['APPEND'])
|
104
|
+
end
|
105
|
+
|
106
|
+
# Internal: Returns a Boolean indicating whether the value for the "FILE_SPLIT_COUNT"
|
107
|
+
# key in the given Hash is equal to the String "true" (ignoring case),
|
108
|
+
# false if no value exists.
|
109
|
+
def retreive_file_split_limit_value(env)
|
110
|
+
retrieve_integer_value('FILE_SPLIT_LIMIT', env)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Internal: Returns a Boolean indicating whether the value for the "IMPORT"
|
114
|
+
# key in the given Hash is equal to the String "true" (ignoring case),
|
115
|
+
# false if no value exists.
|
116
|
+
def retrieve_import_value(env)
|
117
|
+
parse_boolean_value(env['IMPORT'])
|
118
|
+
end
|
119
|
+
|
120
|
+
def retrieve_import_options(env)
|
121
|
+
env['IMPORT_OPTIONS']
|
122
|
+
end
|
123
|
+
|
124
|
+
# Internal: Returns a Boolean indicating whether the value for the "INSERT_ALL"
|
125
|
+
# key in the given Hash is equal to the String "true" (ignoring case),
|
126
|
+
# false if no value exists.
|
127
|
+
def retrieve_insert_all_value(env)
|
128
|
+
parse_boolean_value(env['INSERT_ALL'])
|
129
|
+
end
|
130
|
+
|
131
|
+
# Internal: Returns a Boolean indicating whether the value for the "DUMP_ALL"
|
132
|
+
# key in the given Hash is equal to the String "true" (ignoring case),
|
133
|
+
# false if no value exists.
|
134
|
+
def retrieve_dump_all_value(env)
|
135
|
+
parse_boolean_value(env['DUMP_ALL'])
|
136
|
+
end
|
137
|
+
|
138
|
+
# Internal: Retrieves an Array of Class constants parsed from the value for
|
139
|
+
# the "MODELS_EXCLUDE" key in the given Hash, and an empty Array if such
|
140
|
+
# key exists.
|
141
|
+
def retrieve_models_exclude(env)
|
142
|
+
env['MODELS_EXCLUDE'].to_s
|
143
|
+
.split(',')
|
144
|
+
.collect { |x| x.strip.underscore.singularize.camelize.constantize }
|
145
|
+
end
|
146
|
+
|
147
|
+
# Internal: Retrieves an Integer from the value for the "LIMIT" key in the
|
148
|
+
# given Hash, and nil if no such key exists.
|
149
|
+
def retrieve_limit_value(env)
|
150
|
+
retrieve_integer_value('LIMIT', env)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Internal: Retrieves an Array of Symbols from the value for the "EXCLUDE"
|
154
|
+
# key from the given Hash, and nil if no such key exists.
|
155
|
+
def retrieve_exclude_value(env)
|
156
|
+
env['EXCLUDE'] ? env['EXCLUDE'].split(',').map { |e| e.strip.to_sym } : nil
|
157
|
+
end
|
158
|
+
|
159
|
+
# Internal: Retrieves the value for the "FILE" key from the given Hash, and
|
160
|
+
# 'db/seeds.rb' if no such key exists.
|
161
|
+
def retrieve_file_value(env)
|
162
|
+
env['FILE'] || 'db/seeds.rb'
|
163
|
+
end
|
164
|
+
|
165
|
+
# Internal: Retrieves an Integer from the value for the "BATCH_SIZE" key in
|
166
|
+
# the given Hash, and nil if no such key exists.
|
167
|
+
def retrieve_batch_size_value(env)
|
168
|
+
retrieve_integer_value('BATCH_SIZE', env)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Internal: Retrieves an Integer from the value for the given key in
|
172
|
+
# the given Hash, and nil if no such key exists.
|
173
|
+
def retrieve_integer_value(key, hash)
|
174
|
+
hash[key]&.to_i
|
175
|
+
end
|
176
|
+
|
177
|
+
# Internal: Parses a Boolean from the given value.
|
178
|
+
def parse_boolean_value(value)
|
179
|
+
value.to_s.casecmp('true').zero?
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
data/lib/seed_dump.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
require 'rgl/adjacency'
|
3
|
+
require 'rgl/topsort'
|
4
|
+
|
5
|
+
require 'seed_dump/dump_methods/enumeration'
|
6
|
+
require 'seed_dump/dump_methods'
|
7
|
+
require 'seed_dump/environment'
|
8
|
+
require 'seed_dump/dependency_unwrangler'
|
9
|
+
|
10
|
+
class SeedDump
|
11
|
+
extend Environment
|
12
|
+
extend DumpMethods
|
13
|
+
|
14
|
+
require 'seed_dump/railtie' if defined?(Rails)
|
15
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SeedDump do
|
4
|
+
def expected_output(operation: 'create!', include_id: false, id_offset: 0)
|
5
|
+
output = "Sample.#{operation}([\n "
|
6
|
+
|
7
|
+
data = []
|
8
|
+
((1 + id_offset)..(3 + id_offset)).each do |i|
|
9
|
+
data << "{#{include_id ? "id: #{i}, " : ''}string: \"string\", text: \"text\", integer: 42, float: 3.14, decimal: \"2.72\", datetime: \"1776-07-04 19:14:00\", time: \"2000-01-01 03:15:00\", date: \"1863-11-19\", binary: \"binary\", boolean: false}"
|
10
|
+
end
|
11
|
+
|
12
|
+
"#{output}#{data.join(",\n ")}\n])\n"
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '.dump' do
|
16
|
+
before do
|
17
|
+
Rails.application.eager_load!
|
18
|
+
|
19
|
+
create_db
|
20
|
+
|
21
|
+
FactoryBot.create_list(:sample, 3)
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'without file option' do
|
25
|
+
it 'returns the dump of the models passed in' do
|
26
|
+
described_class.dump(Sample).should eq(expected_output)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'with file option' do
|
31
|
+
before do
|
32
|
+
@filename = Tempfile.new(File.join(Dir.tmpdir, 'foo'), nil)
|
33
|
+
end
|
34
|
+
|
35
|
+
after do
|
36
|
+
File.unlink(@filename)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'dumps the models to the specified file' do
|
40
|
+
described_class.dump(Sample, file: @filename)
|
41
|
+
|
42
|
+
File.open(@filename) { |file| file.read.should eq(expected_output) }
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'with append option' do
|
46
|
+
it 'appends to the file rather than overwriting it' do
|
47
|
+
described_class.dump(Sample, file: @filename)
|
48
|
+
described_class.dump(Sample, file: @filename, append: true)
|
49
|
+
|
50
|
+
File.open(@filename) { |file| file.read.should eq(expected_output + expected_output) }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'with file option and file split option' do
|
56
|
+
let(:file_path) { Tempfile.new('./foo').path }
|
57
|
+
let(:result_file_path) { [file_path, '1'].join('_') }
|
58
|
+
|
59
|
+
after do
|
60
|
+
File.unlink(result_file_path)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'stores the information in file_path with file index' do
|
64
|
+
described_class.dump(Sample, file: file_path, file_split_limit: 5)
|
65
|
+
|
66
|
+
File.open(result_file_path) { |file| expect(file.read).to eq(expected_output) }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'ActiveRecord relation' do
|
71
|
+
it 'returns nil if the count is 0' do
|
72
|
+
described_class.dump(EmptyModel).should be(nil)
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'with an order parameter' do
|
76
|
+
it 'dumps the models in the specified order' do
|
77
|
+
Sample.delete_all
|
78
|
+
samples = 3.times { |i| FactoryBot.create(:sample, integer: i) }
|
79
|
+
|
80
|
+
described_class.dump(Sample.order('integer DESC')).should eq("Sample.create!([\n {string: \"string\", text: \"text\", integer: 2, float: 3.14, decimal: \"2.72\", datetime: \"1776-07-04 19:14:00\", time: \"2000-01-01 03:15:00\", date: \"1863-11-19\", binary: \"binary\", boolean: false},\n {string: \"string\", text: \"text\", integer: 1, float: 3.14, decimal: \"2.72\", datetime: \"1776-07-04 19:14:00\", time: \"2000-01-01 03:15:00\", date: \"1863-11-19\", binary: \"binary\", boolean: false},\n {string: \"string\", text: \"text\", integer: 0, float: 3.14, decimal: \"2.72\", datetime: \"1776-07-04 19:14:00\", time: \"2000-01-01 03:15:00\", date: \"1863-11-19\", binary: \"binary\", boolean: false}\n])\n")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'without an order parameter' do
|
85
|
+
it 'dumps the models sorted by primary key ascending' do
|
86
|
+
described_class.dump(Sample).should eq(expected_output)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'with a limit parameter' do
|
91
|
+
it 'dumps the number of models specified by the limit when the limit is smaller than the batch size' do
|
92
|
+
expected_output = "Sample.create!([\n {string: \"string\", text: \"text\", integer: 42, float: 3.14, decimal: \"2.72\", datetime: \"1776-07-04 19:14:00\", time: \"2000-01-01 03:15:00\", date: \"1863-11-19\", binary: \"binary\", boolean: false}\n])\n"
|
93
|
+
|
94
|
+
described_class.dump(Sample.limit(1)).should eq(expected_output)
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'dumps the number of models specified by the limit when the limit is larger than the batch size but not a multiple of the batch size' do
|
98
|
+
Sample.delete_all
|
99
|
+
FactoryBot.create_list(:sample, 4)
|
100
|
+
|
101
|
+
described_class.dump(Sample.limit(3), batch_size: 2).should eq(
|
102
|
+
expected_output(include_id: false,
|
103
|
+
id_offset: 3)
|
104
|
+
)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'with a batch_size parameter' do
|
110
|
+
it 'does not raise an exception' do
|
111
|
+
described_class.dump(Sample, batch_size: 100)
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'does not cause records to not be dumped' do
|
115
|
+
described_class.dump(Sample, batch_size: 2).should eq(expected_output)
|
116
|
+
|
117
|
+
described_class.dump(Sample, batch_size: 1).should eq(expected_output)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context 'Array' do
|
122
|
+
it 'returns the dump of the models passed in' do
|
123
|
+
described_class.dump(Sample.all.to_a, batch_size: 2).should eq(expected_output)
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'returns nil if the array is empty' do
|
127
|
+
described_class.dump([]).should be(nil)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'with an exclude parameter' do
|
132
|
+
it 'excludes the specified attributes from the dump' do
|
133
|
+
expected_output = "Sample.create!([\n {text: \"text\", integer: 42, decimal: \"2.72\", time: \"2000-01-01 03:15:00\", date: \"1863-11-19\", binary: \"binary\", boolean: false},\n {text: \"text\", integer: 42, decimal: \"2.72\", time: \"2000-01-01 03:15:00\", date: \"1863-11-19\", binary: \"binary\", boolean: false},\n {text: \"text\", integer: 42, decimal: \"2.72\", time: \"2000-01-01 03:15:00\", date: \"1863-11-19\", binary: \"binary\", boolean: false}\n])\n"
|
134
|
+
|
135
|
+
described_class.dump(Sample,
|
136
|
+
exclude: %i[id created_at updated_at string float datetime]).should eq(expected_output)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context 'Range' do
|
141
|
+
it 'dumps a class with ranges' do
|
142
|
+
expected_output = "RangeSample.create!([\n {range_with_end_included: \"[1,3]\", range_with_end_excluded: \"[1,3)\", positive_infinite_range: \"[1,]\", negative_infinite_range: \"[,1]\", infinite_range: \"[,]\"}\n])\n"
|
143
|
+
|
144
|
+
described_class.dump([RangeSample.new]).should eq(expected_output)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
context 'activerecord-insert-all' do
|
149
|
+
it 'dumps in the activerecord-insert-all format when insert-all is true' do
|
150
|
+
described_class.dump(Sample, insert_all: true).should eq(expected_output(operation: 'insert_all'))
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context 'activerecord-import' do
|
155
|
+
it 'dumps in the activerecord-import format when import is true' do
|
156
|
+
described_class.dump(Sample, import: true, exclude: []).should eq <<~RUBY
|
157
|
+
Sample.import([:id, :string, :text, :integer, :float, :decimal, :datetime, :time, :date, :binary, :boolean, :created_at, :updated_at], [
|
158
|
+
[1, "string", "text", 42, 3.14, "2.72", "1776-07-04 19:14:00", "2000-01-01 03:15:00", "1863-11-19", "binary", false, "1969-07-20 20:18:00", "1989-11-10 04:20:00"],
|
159
|
+
[2, "string", "text", 42, 3.14, "2.72", "1776-07-04 19:14:00", "2000-01-01 03:15:00", "1863-11-19", "binary", false, "1969-07-20 20:18:00", "1989-11-10 04:20:00"],
|
160
|
+
[3, "string", "text", 42, 3.14, "2.72", "1776-07-04 19:14:00", "2000-01-01 03:15:00", "1863-11-19", "binary", false, "1969-07-20 20:18:00", "1989-11-10 04:20:00"]
|
161
|
+
])
|
162
|
+
RUBY
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'omits excluded columns if they are specified' do
|
166
|
+
described_class.dump(Sample, import: true, exclude: %i[id created_at updated_at]).should eq <<~RUBY
|
167
|
+
Sample.import([:string, :text, :integer, :float, :decimal, :datetime, :time, :date, :binary, :boolean], [
|
168
|
+
["string", "text", 42, 3.14, "2.72", "1776-07-04 19:14:00", "2000-01-01 03:15:00", "1863-11-19", "binary", false],
|
169
|
+
["string", "text", 42, 3.14, "2.72", "1776-07-04 19:14:00", "2000-01-01 03:15:00", "1863-11-19", "binary", false],
|
170
|
+
["string", "text", 42, 3.14, "2.72", "1776-07-04 19:14:00", "2000-01-01 03:15:00", "1863-11-19", "binary", false]
|
171
|
+
])
|
172
|
+
RUBY
|
173
|
+
end
|
174
|
+
|
175
|
+
context 'should add the params to the output if they are specified' do
|
176
|
+
it 'dumps in the activerecord-import format when import is true' do
|
177
|
+
described_class.dump(Sample, import: { validate: false }, exclude: []).should eq <<~RUBY
|
178
|
+
Sample.import([:id, :string, :text, :integer, :float, :decimal, :datetime, :time, :date, :binary, :boolean, :created_at, :updated_at], [
|
179
|
+
[1, "string", "text", 42, 3.14, "2.72", "1776-07-04 19:14:00", "2000-01-01 03:15:00", "1863-11-19", "binary", false, "1969-07-20 20:18:00", "1989-11-10 04:20:00"],
|
180
|
+
[2, "string", "text", 42, 3.14, "2.72", "1776-07-04 19:14:00", "2000-01-01 03:15:00", "1863-11-19", "binary", false, "1969-07-20 20:18:00", "1989-11-10 04:20:00"],
|
181
|
+
[3, "string", "text", 42, 3.14, "2.72", "1776-07-04 19:14:00", "2000-01-01 03:15:00", "1863-11-19", "binary", false, "1969-07-20 20:18:00", "1989-11-10 04:20:00"]
|
182
|
+
], validate: false)
|
183
|
+
RUBY
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class RangeSample
|
191
|
+
def attributes
|
192
|
+
{
|
193
|
+
'range_with_end_included' => (1..3),
|
194
|
+
'range_with_end_excluded' => (1...3),
|
195
|
+
'positive_infinite_range' => (1..Float::INFINITY),
|
196
|
+
'negative_infinite_range' => (-Float::INFINITY..1),
|
197
|
+
'infinite_range' => (-Float::INFINITY..Float::INFINITY)
|
198
|
+
}
|
199
|
+
end
|
200
|
+
end
|