live_fixtures 3.0.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/CHANGELOG.md +15 -0
- data/README.md +22 -2
- data/lib/live_fixtures/export/fixture.rb +68 -60
- data/lib/live_fixtures/export.rb +84 -81
- data/lib/live_fixtures/import/fixtures.rb +96 -92
- data/lib/live_fixtures/import/insertion_order_computer.rb +104 -102
- data/lib/live_fixtures/import.rb +176 -171
- data/lib/live_fixtures/version.rb +3 -1
- data/lib/live_fixtures.rb +13 -10
- metadata +9 -162
@@ -1,127 +1,129 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LiveFixtures
|
4
|
+
class Import
|
4
5
|
# :nodoc:
|
5
|
-
class
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
6
|
+
class InsertionOrderComputer
|
7
|
+
# :nodoc:
|
8
|
+
class Node
|
9
|
+
attr_reader :path, :class_name, :klass
|
10
|
+
|
11
|
+
# The classes this node depends on
|
12
|
+
attr_reader :dependencies
|
13
|
+
|
14
|
+
def initialize(path, class_name, klass)
|
15
|
+
@path = path
|
16
|
+
@class_name = class_name
|
17
|
+
@klass = klass
|
18
|
+
@dependencies = Set.new
|
19
|
+
end
|
18
20
|
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.compute(table_names, class_names = {}, polymorphic_associations = {})
|
22
|
-
new(table_names, class_names, polymorphic_associations).compute
|
23
|
-
end
|
24
21
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
@polymorphic_associations = polymorphic_associations
|
29
|
-
end
|
22
|
+
def self.compute(table_names, class_names = {}, polymorphic_associations = {})
|
23
|
+
new(table_names, class_names, polymorphic_associations).compute
|
24
|
+
end
|
30
25
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
26
|
+
def initialize(table_names, class_names = {}, polymorphic_associations = {})
|
27
|
+
@table_names = table_names
|
28
|
+
@class_names = class_names
|
29
|
+
@polymorphic_associations = polymorphic_associations
|
30
|
+
end
|
35
31
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
# using their class names.
|
40
|
-
def build_nodes
|
41
|
-
# Create a Hash[Class => Node] for each table/class
|
42
|
-
nodes = {}
|
43
|
-
@table_names.each do |path|
|
44
|
-
table_name = path.tr "/", "_"
|
45
|
-
class_name = @class_names[table_name.to_sym] || table_name.classify
|
46
|
-
klass = class_name.constantize
|
47
|
-
nodes[klass] = Node.new(path, class_name, klass)
|
32
|
+
def compute
|
33
|
+
nodes = build_nodes
|
34
|
+
compute_insert_order(nodes)
|
48
35
|
end
|
49
36
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
37
|
+
private
|
38
|
+
|
39
|
+
# Builds an Array of Nodes, each containing dependencies to other nodes
|
40
|
+
# using their class names.
|
41
|
+
def build_nodes
|
42
|
+
# Create a Hash[Class => Node] for each table/class
|
43
|
+
nodes = {}
|
44
|
+
@table_names.each do |path|
|
45
|
+
table_name = path.tr '/', '_'
|
46
|
+
class_name = @class_names[table_name.to_sym] || table_name.classify
|
47
|
+
klass = class_name.constantize
|
48
|
+
nodes[klass] = Node.new(path, class_name, klass)
|
49
|
+
end
|
50
|
+
|
51
|
+
# First iniitalize dependencies from polymorphic associations that we
|
52
|
+
# explicitly found in the yaml files.
|
53
|
+
@polymorphic_associations.each do |klass, associations|
|
54
|
+
associations.each do |association|
|
55
|
+
node = nodes[klass]
|
56
|
+
next unless node
|
57
|
+
next unless nodes.key?(association)
|
57
58
|
|
58
|
-
|
59
|
+
node.dependencies << association
|
60
|
+
end
|
59
61
|
end
|
60
|
-
end
|
61
62
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
63
|
+
# Compute dependencies between nodes/classes by reflecting on their
|
64
|
+
# ActiveRecord associations.
|
65
|
+
nodes.each_value do |node|
|
66
|
+
klass = node.klass
|
67
|
+
klass.reflect_on_all_associations.each do |assoc|
|
68
|
+
# We can't handle polymorphic associations, but the concrete types
|
69
|
+
# should have been deduced from the yaml files contents
|
70
|
+
next if assoc.polymorphic?
|
71
|
+
|
72
|
+
# Don't add a dependency if the class is not in the given table names
|
73
|
+
next unless nodes.key?(assoc.klass)
|
74
|
+
|
75
|
+
# A class might depend on itself, but we don't add it as a dependency
|
76
|
+
# because otherwise we'll never make it (the class can probably be created
|
77
|
+
# just fine and these dependencies are optional/nilable)
|
78
|
+
next if klass == assoc.klass
|
79
|
+
|
80
|
+
case assoc.macro
|
81
|
+
when :belongs_to
|
82
|
+
node.dependencies << assoc.klass
|
83
|
+
when :has_one, :has_many
|
84
|
+
# Skip `through` association becuase it will be already computed
|
85
|
+
# for the related `has_one`/`has_many` association
|
86
|
+
next if assoc.options[:through]
|
87
|
+
|
88
|
+
nodes[assoc.klass].dependencies << klass
|
89
|
+
end
|
88
90
|
end
|
89
91
|
end
|
90
|
-
end
|
91
92
|
|
92
|
-
|
93
|
-
|
94
|
-
|
93
|
+
# Finally sort all values by name for consistent results
|
94
|
+
nodes.values.sort_by { |node| node.klass.name }
|
95
|
+
end
|
95
96
|
|
96
|
-
|
97
|
-
|
97
|
+
def compute_insert_order(nodes)
|
98
|
+
insert_order = []
|
99
|
+
|
100
|
+
until nodes.empty?
|
101
|
+
# Pick a node that has no dependencies
|
102
|
+
free_node = nodes.find { |node| node.dependencies.empty? }
|
103
|
+
|
104
|
+
if free_node.nil?
|
105
|
+
msg = "Can't compute an insert order.\n\n"
|
106
|
+
msg << "These models seem to depend on each other:\n"
|
107
|
+
nodes.each do |node|
|
108
|
+
msg << " #{node.klass.name}\n"
|
109
|
+
msg << " - depends on: #{node.dependencies.map(&:name).join(', ')}\n"
|
110
|
+
end
|
111
|
+
raise msg
|
112
|
+
end
|
98
113
|
|
99
|
-
|
100
|
-
# Pick a node that has no dependencies
|
101
|
-
free_node = nodes.find { |node| node.dependencies.empty? }
|
114
|
+
insert_order << free_node.path
|
102
115
|
|
103
|
-
|
104
|
-
msg = "Can't compute an insert order.\n\n"
|
105
|
-
msg << "These models seem to depend on each other:\n"
|
116
|
+
# Delete this node from the other nodes' dependencies
|
106
117
|
nodes.each do |node|
|
107
|
-
|
108
|
-
msg << " - depends on: #{node.dependencies.map(&:name).join(", ")}\n"
|
118
|
+
node.dependencies.delete(free_node.klass)
|
109
119
|
end
|
110
|
-
raise msg
|
111
|
-
end
|
112
120
|
|
113
|
-
|
114
|
-
|
115
|
-
# Delete this node from the other nodes' dependencies
|
116
|
-
nodes.each do |node|
|
117
|
-
node.dependencies.delete(free_node.klass)
|
121
|
+
# And delete this node because we are done with it
|
122
|
+
nodes.delete(free_node)
|
118
123
|
end
|
119
124
|
|
120
|
-
|
121
|
-
nodes.delete(free_node)
|
125
|
+
insert_order
|
122
126
|
end
|
123
|
-
|
124
|
-
insert_order
|
125
127
|
end
|
126
128
|
end
|
127
129
|
end
|
data/lib/live_fixtures/import.rb
CHANGED
@@ -1,199 +1,204 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'benchmark'
|
2
4
|
|
3
5
|
# An object that facilitates the import of fixtures into a database.
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
@table_names = insert_order
|
45
|
-
|
46
|
-
|
47
|
-
|
6
|
+
module LiveFixtures
|
7
|
+
class Import
|
8
|
+
NO_LABEL = nil
|
9
|
+
|
10
|
+
# Returns the insert order that was specified in the constructor or
|
11
|
+
# the inferred one if none was specified.
|
12
|
+
attr_reader :insert_order
|
13
|
+
|
14
|
+
# Map of table_name to import routine
|
15
|
+
# @return [Hash<String => Proc>]
|
16
|
+
attr_reader :alternate_imports
|
17
|
+
|
18
|
+
# Accessor for string label for a given fixture mapping to it's db id
|
19
|
+
attr_reader :label_to_id
|
20
|
+
|
21
|
+
# Instantiate a new Import with the directory containing your fixtures, and
|
22
|
+
# the order in which to import them. The order should ensure fixtures
|
23
|
+
# containing references to another fixture are imported AFTER the referenced
|
24
|
+
# fixture.
|
25
|
+
# @raise [ArgumentError] raises an argument error if not every element in the insert_order has a corresponding yml file.
|
26
|
+
# @param root_path [String] path to the directory containing the yml files to import.
|
27
|
+
# @param insert_order [Array<String> | Nil] a list of yml files (without .yml extension) in the order they should be imported, or `nil` if these order is to be inferred by this class.
|
28
|
+
# @param class_names [Hash{Symbol => String}] a mapping table name => Model class, for any that don't follow convention.
|
29
|
+
# @param [Hash] opts export configuration options
|
30
|
+
# @option opts [Boolean] show_progress whether or not to show the progress bar
|
31
|
+
# @option opts [Boolean] skip_missing_tables when false, an error will be raised if a yaml file isn't found for each table in insert_order
|
32
|
+
# @option opts [Boolean] skip_missing_refs when false, an error will be raised if an ID isn't found for a label.
|
33
|
+
# @option opts [Boolean] use_insert_order_as_table_names when true, table names will be those passed in insert_order, not read from yaml files
|
34
|
+
# @return [LiveFixtures::Import] an importer
|
35
|
+
# @see LiveFixtures::Export::Reference
|
36
|
+
def initialize(root_path, insert_order = nil, class_names = {}, **opts)
|
37
|
+
defaut_options = {
|
38
|
+
show_progress: true,
|
39
|
+
skip_missing_tables: false,
|
40
|
+
skip_missing_refs: false,
|
41
|
+
use_insert_order_as_table_names: false
|
42
|
+
}
|
43
|
+
@options = defaut_options.merge(opts)
|
44
|
+
@root_path = root_path
|
45
|
+
|
46
|
+
@table_names = if insert_order && @options[:use_insert_order_as_table_names]
|
47
|
+
insert_order
|
48
|
+
else
|
49
|
+
Dir.glob(File.join(@root_path, '{*,**}/*.yml')).map do |filepath|
|
50
|
+
File.basename filepath, '.yml'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
@class_names = class_names
|
55
|
+
@table_names.each do |n|
|
56
|
+
@class_names[n.tr('/', '_').to_sym] ||= n.classify if n.include?('/')
|
48
57
|
end
|
49
|
-
end
|
50
58
|
|
51
|
-
|
52
|
-
|
53
|
-
@class_names[n.tr('/', '_').to_sym] ||= n.classify if n.include?('/')
|
54
|
-
}
|
59
|
+
@insert_order = insert_order
|
60
|
+
@insert_order ||= InsertionOrderComputer.compute(@table_names, @class_names, compute_polymorphic_associations)
|
55
61
|
|
56
|
-
|
57
|
-
|
62
|
+
@table_names = @insert_order.select { |table_name| @table_names.include? table_name }
|
63
|
+
if @table_names.size < @insert_order.size && !@options[:skip_missing_tables]
|
64
|
+
raise ArgumentError,
|
65
|
+
"table(s) mentioned in `insert_order` which has no yml file to import: #{@insert_order - @table_names}"
|
66
|
+
end
|
58
67
|
|
59
|
-
|
60
|
-
|
61
|
-
raise ArgumentError, "table(s) mentioned in `insert_order` which has no yml file to import: #{@insert_order - @table_names}"
|
68
|
+
@label_to_id = {}
|
69
|
+
@alternate_imports = {}
|
62
70
|
end
|
63
71
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
iterator.new(ff).each do |tname, label, row|
|
108
|
-
conn.insert_fixture(row, tname)
|
109
|
-
@label_to_id[label] = conn.send(:last_inserted_id, tname) unless label == NO_LABEL
|
72
|
+
# Within a transaction, import all the fixtures into the database.
|
73
|
+
#
|
74
|
+
# The very similar method: ActiveRecord::FixtureSet.create_fixtures has the
|
75
|
+
# unfortunate side effect of truncating each table!!
|
76
|
+
#
|
77
|
+
# Therefore, we have reproduced the relevant sections here, without DELETEs,
|
78
|
+
# with calling {LiveFixtures::Import::Fixtures#each_table_row_with_label} instead of
|
79
|
+
# `AR::Fixtures#table_rows`, and using those labels to populate `@label_to_id`.
|
80
|
+
# @see https://github.com/rails/rails/blob/4-2-stable/activerecord/lib/active_record/fixtures.rb#L496
|
81
|
+
def import_all
|
82
|
+
connection = ActiveRecord::Base.connection
|
83
|
+
show_progress = @options[:show_progress]
|
84
|
+
|
85
|
+
# TODO: should be additive with alternate_imports so we can delete the fixture file
|
86
|
+
files_to_read = @table_names
|
87
|
+
|
88
|
+
return if files_to_read.empty?
|
89
|
+
|
90
|
+
connection.transaction(requires_new: true) do
|
91
|
+
files_to_read.each do |path|
|
92
|
+
table_name = path.tr '/', '_'
|
93
|
+
if (alternate = @alternate_imports[table_name])
|
94
|
+
time = Benchmark.ms do
|
95
|
+
alternate.call(@label_to_id)
|
96
|
+
end
|
97
|
+
puts format('Imported %s in %.0fms', table_name, time) if show_progress
|
98
|
+
else
|
99
|
+
class_name = @class_names[table_name.to_sym] || table_name.classify
|
100
|
+
|
101
|
+
ff = Fixtures.new(connection,
|
102
|
+
table_name,
|
103
|
+
class_name,
|
104
|
+
::File.join(@root_path, path),
|
105
|
+
@label_to_id,
|
106
|
+
skip_missing_refs: @options[:skip_missing_refs])
|
107
|
+
|
108
|
+
conn = ff.model_connection || connection
|
109
|
+
|
110
|
+
iterator = show_progress ? ProgressBarIterator : SimpleIterator
|
111
|
+
iterator.new(ff).each do |tname, label, row|
|
112
|
+
last_inserted_id = conn.insert(conn.send(:build_fixture_sql, Array.wrap(row), tname))
|
113
|
+
@label_to_id[label] = last_inserted_id unless label == NO_LABEL
|
114
|
+
end
|
110
115
|
end
|
111
116
|
end
|
112
117
|
end
|
113
118
|
end
|
114
|
-
end
|
115
119
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
120
|
+
# Override import of table using a callable object
|
121
|
+
# @param table_name [String] table to use callable instead of fixture file
|
122
|
+
# @param callable [Proc] Proc/lambda that will be called with @label_to_id
|
123
|
+
def override(table_name, callable)
|
124
|
+
@alternate_imports[table_name] = callable
|
125
|
+
self
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
# Here we go through each of the fixture YAML files to see what polymorphic
|
131
|
+
# dependencies exist for each of the models.
|
132
|
+
# We do this by inspecting the value of any field that ends with `_type`,
|
133
|
+
# for example `author_type`, `assignment_type`, etc.
|
134
|
+
# Becuase we can't know all the possible types of a polymorphic association
|
135
|
+
# we compute them from the YAML file contents.
|
136
|
+
# Returns a Hash[Class => Set[Class]]
|
137
|
+
def compute_polymorphic_associations
|
138
|
+
polymorphic_associations = Hash.new { |h, k| h[k] = Set.new }
|
123
139
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
yaml = YAML.load(file)
|
153
|
-
yaml.each do |key, object|
|
154
|
-
object.each do |field, value|
|
155
|
-
next unless field.ends_with?("_type")
|
156
|
-
|
157
|
-
begin
|
158
|
-
polymorphic_associations[class_name.constantize] << value.constantize
|
159
|
-
rescue NameError
|
160
|
-
# It might be the case that the `..._type` field doesn't actually
|
161
|
-
# refer to a type name, so we just ignore it.
|
140
|
+
ActiveRecord::Base.connection
|
141
|
+
files_to_read = @table_names
|
142
|
+
|
143
|
+
files_to_read.each do |path|
|
144
|
+
table_name = path.tr '/', '_'
|
145
|
+
class_name = @class_names[table_name.to_sym] || table_name.classify
|
146
|
+
|
147
|
+
# Here we use the yaml file and YAML.load instead of ActiveRecord::FixtureSet.new
|
148
|
+
# because it's faster and we can also check whether we actually need to
|
149
|
+
# load the file: only if it includes "_type" in it, otherwise there will be
|
150
|
+
# no polymorphic types in there.
|
151
|
+
|
152
|
+
filename = ::File.join(@root_path, "#{path}.yml")
|
153
|
+
file = File.read(filename)
|
154
|
+
next unless file =~ /_type/
|
155
|
+
|
156
|
+
yaml = YAML.load(file)
|
157
|
+
yaml.each_value do |object|
|
158
|
+
object.each do |field, value|
|
159
|
+
next unless field.ends_with?('_type')
|
160
|
+
|
161
|
+
begin
|
162
|
+
polymorphic_associations[class_name.constantize] << value.constantize
|
163
|
+
rescue NameError
|
164
|
+
# It might be the case that the `..._type` field doesn't actually
|
165
|
+
# refer to a type name, so we just ignore it.
|
166
|
+
end
|
162
167
|
end
|
163
168
|
end
|
164
169
|
end
|
165
|
-
end
|
166
170
|
|
167
|
-
|
168
|
-
end
|
169
|
-
|
170
|
-
class ProgressBarIterator
|
171
|
-
def initialize(ff)
|
172
|
-
@ff = ff
|
173
|
-
@bar = LiveFixtures.get_progress_bar(
|
174
|
-
total: ff.fixtures.size,
|
175
|
-
title: ff.model_class.name
|
176
|
-
)
|
171
|
+
polymorphic_associations
|
177
172
|
end
|
178
173
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
@bar
|
174
|
+
class ProgressBarIterator
|
175
|
+
def initialize(ff)
|
176
|
+
@ff = ff
|
177
|
+
@bar = LiveFixtures.get_progress_bar(
|
178
|
+
total: ff.fixtures.size,
|
179
|
+
title: ff.model_class.name
|
180
|
+
)
|
183
181
|
end
|
184
|
-
@bar.finish
|
185
|
-
end
|
186
|
-
end
|
187
182
|
|
188
|
-
|
189
|
-
|
190
|
-
|
183
|
+
def each
|
184
|
+
@ff.each_table_row_with_label do |*args|
|
185
|
+
yield(*args)
|
186
|
+
@bar.increment unless @bar.finished?
|
187
|
+
end
|
188
|
+
@bar.finish
|
189
|
+
end
|
191
190
|
end
|
192
191
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
192
|
+
class SimpleIterator
|
193
|
+
def initialize(ff)
|
194
|
+
@ff = ff
|
195
|
+
end
|
196
|
+
|
197
|
+
def each
|
198
|
+
puts @ff.model_class.name
|
199
|
+
@ff.each_table_row_with_label do |*args|
|
200
|
+
yield(*args)
|
201
|
+
end
|
197
202
|
end
|
198
203
|
end
|
199
204
|
end
|
data/lib/live_fixtures.rb
CHANGED
@@ -1,19 +1,22 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'live_fixtures/version'
|
4
|
+
require 'live_fixtures/import'
|
5
|
+
require 'live_fixtures/import/fixtures'
|
6
|
+
require 'live_fixtures/import/insertion_order_computer'
|
7
|
+
require 'live_fixtures/export'
|
8
|
+
require 'live_fixtures/export/fixture'
|
9
|
+
require 'ruby-progressbar'
|
10
|
+
require 'yaml'
|
9
11
|
|
10
12
|
module LiveFixtures
|
11
13
|
module_function
|
12
|
-
|
14
|
+
|
15
|
+
def get_progress_bar(total:, title:)
|
13
16
|
ProgressBar.create(
|
14
17
|
total: total,
|
15
18
|
title: title,
|
16
|
-
format:'%t: |%B| %P% %E',
|
19
|
+
format: '%t: |%B| %P% %E',
|
17
20
|
throttle_rate: 0.1
|
18
21
|
)
|
19
22
|
end
|