db_seed_dump 1.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 +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
|