A bit of context before going to the solution...
I was trying to call a bulk insertion of some objects in the database in a Rails app hosted in Heroku, taking data from an user uploaded CSV file and processing it via
find_or_create_by for each row, updating all the attributes when necessary and finally persisting the data in the database.
As the data rows grew in number (over 5k entries usually) the timeout limit of 30 seconds imposed by Heroku wasn't enough. So I had to setup the delayed_job gem with the HireFire gem for a cheaper experience in Heroku for the bulk process.
So I did it this way.
# controllers/the_model_controller.rb TheModel.delay.import_from_csv params[:file] # The offending line redirect_to root_url, notice: 'Data inserted successfully' # models/the_model.rb class TheModel def self.import_from_csv(file) CSV.foreach(file_path, headers: true) do |row| temp_stuff = row.to_hash the_model = TheModel.find_or_create_by(:attribute1 => temp_stuff['attribute1']) # ... fill with data the_model.save! end end end
With the delayed_job ruby gem I was always getting
Routing Error undefined method `import_from_csv' for class `TheModel'
Of course, it is right. You need to have an already existing (and persisted) object for delayed_job to work this way.
The workaround I found is pretty simple actually, though it is not very well documented IMO. You just have to create a singleton version of your class and mark the asynchronous methods with
Above example, fixed:
# controllers/the_model_controller.rb TheModel.import_from_csv params[:file] # Without 'delay' now redirect_to root_url, notice: 'Data inserted successfully' # models/the_model.rb class TheModel class << self def from_csv(file_path) CSV.foreach(file_path, headers: true) do |row| temp_stuff = row.to_hash the_model = TheModel.find_or_create_by(:attribute1 => temp_stuff['attribute1']) # ... fill with data the_model.save! end end handle_asynchronously :from_csv end # My importer as a class method def self.import_from_csv(file) TheModel.from_csv file.path end end
And now it works nicely. Also, only one insertion to the Delayed::Job queue database.