Problem
I have about a hundred Rake tasks that are creating Rails models for me from a legacy datafile. Each task shares some setup and teardown code; all that sets them apart is the code in the middle. For example:
task countries: setup do
rows = rows_from_file("system/countries", 2)
bar = RakeProgressbar.new(rows.length)
info "» Importing countries"
rows.each do |row|
Country.new(
alpha2: row[0],
name: row[1]
).save!
bar.inc
end
bar.finished
end
task telephone_number_types: setup do
rows = rows_from_file("system/telephone_number_types", 1)
bar = RakeProgressbar.new(rows.length)
info "» Importing telephone number types"
rows.each do |row|
TelephoneNumberType.new(
name: row[0]
).save!
bar.inc
end
bar.finished
end
task occupations: setup do
rows = rows_from_file("system/occupations", 3)
bar = RakeProgressbar.new(rows.length)
info "» Importing occupations"
rows.each do |row|
Occupation.new(
id: row[1],
name: row[2]
).save!
bar.inc
end
bar.finished
end
How could I extract the progress bar stuff so I don’t keep repeating it? I had thought of writing some generic method that takes the array of rows
and some code block (which would contain the Model.new(attrs).save! code) and wraps that stuff with the progress bar stuff, but I can’t quite make it work.
Solution
I’d say you’re on the right track with you idea to extract a method.
While I can’t fully replicate your code and its context, I think something like this should work:
task countries: setup do |task_name|
rows = rows_from_file("system/#{task_name.to_s}", 2)
process_rows(task_name, rows) do |row|
{ alpha2: row[0], name: row[1] }
end
end
# ...
def import_rows(task_name, rows, &block)
klass = task_name.to_s.classify
bar = RakeProgressbar.new(rows.length)
info "» Importing #{task_name.to_s.humanize}"
rows.each do |row|
attrs = block.call(row)
klass.new(attrs).save!
bar.inc
end
bar.finished
end
But it’s going pretty far in its assumptions – it’s basing a lot off of the task name alone.
A somewhat less fragile approach might be this where the correct class is explicitly passed to the helper method and the file name isn’t guessed from the task name
task countries: setup do
rows = rows_from_file("system/countries", 2)
process_rows(Country, rows) do |row|
{ alpha2: row[0], name: row[1] }
end
end
# ...
def import_rows(klass, rows, &block)
bar = RakeProgressbar.new(rows.length)
info "» Importing #{klass.to_s.pluralize.humanize}"
rows.each do |row|
attrs = block.call(row)
klass.new(attrs).save!
bar.inc
end
bar.finished
end