# RowBoat API This is really more of a summary of what you can do with `RowBoat::Base` since you subclass it to do everything :) ## Contents - [Basic Usage](#basic-usage) - [`.import`](#import) - [`initialize`](#initialize) - [`import`](#import-1) - [`import_into`](#import_into) - [`csv_source`](#csv_source) - [`column_mapping`](#column_mapping) - [`preprocess_row`](#preprocess_row) - [`preprocess_rows`](#preprocess_rows) - [`options`](#options) - [`handle_failed_row`](#handle_failed_row) - [`handle_failed_rows`](#handle_failed_rows) - [`value_converters`](#value_converters) ## Basic Usage Just subclass `RowBoat::Base` and define the [`import_into`](#import_into) and [`column_mapping`](#column_mapping) methods to get started (They're the only methods that you're required to implement). ```ruby class ImportProduct < RowBoat::Base def import_into Product end def column_mapping { downcased_csv_column_header: :model_attribute_name, another_downcased_csv_column_header: :another_model_attribute_name } end end ``` ## `.import` ### Description Imports database records form the given CSV-like object. The CSV-like object can be anything that can be passed to [`SmarterCSV.process`](https://github.com/tilo/smarter_csv#documentation) (string paths to files, files, tempfiles, instances of StringIO, etc). It returns a hash containing - `:invalid_records` - an array of all records that failed to import since they were invalid. If you've configured the `:validate` option to be `false` it will be an empty array. - `:total_inserted` - the total number of records inserted into the database. - `:inserted_ids` - an array of all of the ids of records inserted into the database. If you want to pass additional information to help import CSVs, *don't override this method*. It just passes through to [`initialize`](#initialize) so override that :) ### Example #### Basic Use ```ruby class ImportProduct < RowBoat::Base # required configuration omitted for brevity end ImportProduct.import("path/to/my.csv") ``` #### Advanced Use ```ruby class ImportProduct < RowBoat::Base # required configuration omitted for brevity def intitialize(csv_source, my_options) super(csv_source) @my_options = my_options end end ImportProduct.import("path/to/my.csv", foo: "bar") ``` ## `initialize` ### Description Makes a new instance with the given CSV-like object. See [`.import`](#import) for more details around when and how to override this method. ## `import` ### Description The instance method that actually parses and imports the CSV. Generally, you wouldn't call this directly and would instead call [`.import`](#import). ## `import_into` ### Description It is required that you override this method to return whatever ActiveRecord class you want your CSV imported into. ### Example #### Basic Use ```ruby class ImportProduct < RowBoat::Base # other required configuration omitted for brevity def import_into Product end end ``` #### Advanced Use ```ruby class ImportProduct < RowBoat::Base # other required configuration omitted for brevity def import_into if csv_source.is_a?(String) && csv_source.match(/category/i) ProductCategory else Product end end end ImportProduct.import("path/to/category.csv") ImportProduct.import("path/to/product.csv") ``` ## `csv_source` ### Description Whatever you originally passed in as the CSV source. ### Example ```ruby class ImportProduct < RowBoat::Base # other required configuration omitted for brevity def import_into # `csv_source` is available in any of our instance methods if csv_source.is_a?(String) && csv_source.match(/category/i) ProductCategory else Product end end end ImportProduct.import("path/to/category.csv") ImportProduct.import("path/to/product.csv") ``` ## `column_mapping` ### Description It is required that you override this method with either a hash that maps columns in your CSV to their preferred names or an array of your preferred column names. By default when using a hash - CSV column names are downcased symbols of what they look like in the CSV. - CSV columns that are not mapped are ignored when processing the CSV. If you're familiar with [SmarterCSV](https://github.com/tilo/smarter_csv#documentation), this method essentially defines your `:key_mapping` with the `:remove_unmapped_keys` setting set to `true` when provided with a hash. When given an array it is the `:user_provided_headers` option. You can change these defaults by overriding the [`options`](#options) method. ### Example ```ruby class ImportProduct < RowBoat::Base # other required configuration omitted for brevity def column_mapping { prdct_nm: :name, "price/cost_amnt": :price_in_cents } end end # or... class ImportProduct < RowBoat::Base # other required configuration omitted for brevity def column_mapping [:name, :price_in_cents] end end ``` ## `preprocess_row` ### Description Implement this method if you need to do some work on the row before the record is inserted/updated. If you return `nil` from this method, the row will be skipped in the import. You also have access to `row_number` here. If the work you intend to do with the row only requires changing one attribute, it is recommended that you override [`value_converters`](#value_converters) instead of this. ### Example ```ruby class ImportProduct < RowBoat::Base # required configuration omitted for brevity def preprocess_row(row) { position: row_number }.merge(row) end # or... def preprocess_row(row) if row[:name] && row[:price] row else nil end end end ``` ## `preprocess_rows` ### Description Override this method if you need to do something with a chunk of rows (the chunk size is determined by the `:chunk_size` option in the [`options`](#options) method). If you need to filter particular rows, it's better to just implement [`preprocess_row`](#preprocess_row) and return `nil` for the rows you want to ignore. ### Example ```ruby class ImportProduct < RowBoat::Base # required configuration omitted for brevity def preprocess_rows(rows) if skip_batch?(rows) super([]) else super end end def skip_batch?(rows) # decide whether or not to skip the batch end end ``` ## `options` ### Description Implement this to configure RowBoat, [SmarterCSV](https://github.com/tilo/smarter_csv), and [activerecord-import](https://github.com/zdennis/activerecord-import). Except for `:wrap_in_transaction`, all options pass through to SmarterCSV and activerecord-import. `:wrap_in_transaction` simply tells RowBoat whether or not you want your whole import wrapped in a database transaction. Whatever you define in this method will be merged into the defaults: - `:chunk_size` - `500` - `:key_mapping` - `column_mapping` - `:recursive` - `true` - `:remove_unmapped_keys` - `true` - `:validate` - `true` - `:value_converters` - `csv_value_converters` - `:wrap_in_transaction` - `true` Don't provide `value_converters` or `key_mapping` options here. Implement the [`value_converters`](#value_converters) and [`column_mapping`](#column_mapping) respectively. ### Example ```ruby class ImportProduct < RowBoat::Base # required configuration omitted for brevity def options { chunk_size: 1000, validate: false, wrap_in_transaction: false } end end ``` ## `handle_failed_row` ### Description Implement this to do some work with a row that has failed to import. It's important to note that - This happens after the import has completed. - The given row is an instance of whatever class was returned by [`import_into`](#import_into). These records are also available in the return value of [`.import`](#import). ### Example ```ruby class ImportProduct < RowBoat::Base # required configuration omitted for brevity def handle_failed_row(row) puts row.errors.full_messages.join(", ") end end ``` ## `handle_failed_rows` ### Description Override this method to do some work will all of the rows that failed to import. ### Example ```ruby class ImportProduct < RowBoat::Base # required configuration omitted for brevity def handle_failed_rows(rows) puts "Failed to import #{rows.size} rows :(" super end end ``` ## `value_converters` ### Description Implement to specify how to translate values from the CSV into whatever sorts of objects you need. Simply return a hash that has the mapped column name (ie, what you mapped it to in the [`column_mapping`](#column_mapping) method) as a key pointing to either - a method name as a symbol - a proc or lambda - an object that implements `convert` Regardless of which one you choose, it takes a value and returns a converted value. This is essentially a sugared up version of `:value_converters` option in [SmarterCSV](https://github.com/tilo/smarter_csv#documentation). ### Example ```ruby class ImportProduct < RowBoat::Base # required configuration omitted for brevity def value_converters { sell_by: :convert_date, name: -> (value) { value.titlelize }, price: proc { |value| value.to_i }, description: DescriptionConverter } end def convert_date(value) Date.parse(value) rescue nil end end module DescriptionConverter def self.convert(value) value.present? ? value : "default description :(" end end ```