-
Notifications
You must be signed in to change notification settings - Fork 19
Ability to specify the model to import to. #19
Comments
Interesting suggestion. We never had the situation of more than one model with exactly the same structure. This is currently not possible, but I don't see any problem in adapting the library to receive the model class as a parameter to the |
However, now that I think about it, there's a workaround. You can create a base class DataImporter < ActiveImporter::Base
# column definitions here
end
class CustomerImporter < DataImporter
end
class ProspectImporter < DataImporter
end
class LeadImporter < DataImporter
end Right now I'm not entirely sure that this will work, but I guess it will. At most, you'll need to include the Please try this and let me know how it went. |
I hit on the same workaround with subclassing. I even called my base class It works fine this way. Actually the way I'm using it is the base class defines all the common I think it would possible to pass in the columns in the params and iterate through them in the base class but I haven't tried that yet. After your last comment I did try leaving out the |
Well, if the column definitions vary from importer to importer, then in my opinion it makes no sense to try to achieve defining it all in a single importer, and attempt to pass the column definitions to the |
You're right, that's what I've ended up with: common row handlers in a base class and column definitions in subclasses. As you can see the base class holds the majority of the code so it's definitely worth doing this way. Given that I could even omit the class CustomerImporter < DataImporter
imports Customer
column 'First', :first_name
column 'Last', :last_name
column 'Email', :email
skip_rows_if { row['First'].blank? && row['Last'].blank? && row['Email'].blank? }
end class DataImporter < ActiveImporter::Base
attr_reader :rows_processed, :rows_imported, :rows_skipped, :errors, :summary
on :import_started do
@rows_processed = @rows_imported = @rows_skipped = 0
@errors = @summary = ''
end
on :row_processed do
@rows_processed += 1
end
on :row_success do
@rows_imported += 1
end
on :row_error do |exception|
@errors += "Error on row #{row_index} - #{exception.message}\n"
end
on :row_skipped do
@rows_skipped += 1
end
on :import_finished do
@summary += "#{@rows_processed} row(s) processed\n"
@summary += "#{@rows_imported} row(s) imported successfully\n"
@summary += "#{@rows_skipped} blank row(s) skipped\n" if @rows_skipped > 0
@summary += @errors
# delete_s3_temp_file
send_notification(@summary)
end
on :import_failed do |exception|
send_notification("Fatal error while importing data: #{exception.message}")
end
private
def send_notification(message)
puts message
end
end |
After having a look at your code, allow me to make some suggestions. First, the base importer class already keeps some counters, and it's my fault for not having documented these in any way, so it's understandable that you tried to replicate them. Inside the code blocks, you can access the importer properties Also, earlier today I implemented the inheritable skip block definition, so you can already use this feature and define a single skip block in your base importer class. By the way, if you want to skip blank rows, you could define the condition without having to know the names of the columns, like show below: class DataImporter < ActiveImporter::Base
skip_rows_if do
row.values.all?(&:blank?)
end
end This way it will work with whatever column definitions you have on any given subclass. But even in the case where you would need to have some parametrization, you could achieve it by passing custom parameters to the importer. These can be passed to the class CustomerImporter < ActiveImporter::Base
# required_columns parameter wrapped in a attr reader for convenience
# and for providing a default value if the parameter was not passed
def required_columns
params[:required_columns] || []
end
skip_rows_if do
required_columns.each do |column_name|
return true if row[column_name].blank?
end
return false
end
end
# Pass the parameter at the moment of invocation
CustomerImporter.import('file.xls', params: {
required_columns: ['First name', 'Last name'],
}) And finally, if you would like to define these dynamic lists of required columns on each subclass, instead of having to repeat them on each invocation to class DataImporter < ActiveRecord::Importer
def required_columns
[]
end
skip_rows_if do
required_columns.each do |column_name|
return true if row[column_name].blank?
end
return false
end
end
class CustomerImporter < DataImporter
def required_columns
['First name', 'Last name']
end
end
class InvoiceImporter < DataImporter
def required_columns
['Order number', 'Client name']
end
end I haven't tested any of these, but I'm pretty sure they'll work, now that |
By the way, would you care to close this issue if you consider it solved? |
Thanks for the tips. It's great to know that there are counters already defined. When you document them you should also mention Also I like the idea of defining |
My app user must be able to import data into three different models, say Customer, Prospect and Lead. At the moment I have a
MODEL_importer.rb
class for each type, containing mostly the same code. The main difference is that they have differentimports
calls at the top of the file.I've tried passing in the model name to import to within the
params
hash but it doesn't work. Maybe theimports
call is run before the hash is processed?Is there any way for me to reduce duplication and have one
data_importer.rb
class and pass things like class name, columns, etc in the params hash?The text was updated successfully, but these errors were encountered: