From 3c9d9b2e76ce26da4297ef26a3efdb8acfcbec62 Mon Sep 17 00:00:00 2001 From: Philippe Creux Date: Fri, 29 Nov 2019 10:19:57 -0800 Subject: [PATCH] `to` supports anything that responds to `call` (#72) --- README.md | 22 +++++++++++++++++++++- lib/csv_importer/row.rb | 15 ++++++++------- spec/csv_importer_spec.rb | 12 ++++++++---- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 8558ee2..9e9937c 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,27 @@ Like very advanced stuff? We grant you access to the [`column`](https://github.c end ``` +Note that `to:` accepts anything that responds to call and take 1, 2 or +3 arguments. + +```ruby +class ImportUserCSV + include CSVImporter + + model User + + column :birth_date, to: DateTransformer + column :renewal_date, to: DateTransformer + column :next_renewal_at, to: ->(value) { Time.at(value.to_i) } +end + +class DateTransformer + def self.call(date) + Date.strptime(date, '%m/%d/%y') + end +end +``` + Now, what if the user does not provide the email column? It's not worth running the import, we should just reject the CSV file right away. That's easy: @@ -206,7 +227,6 @@ import.report.status # => :invalid_header import.report.message # => "The following columns are required: 'email'" ``` - ### Update or Create You often want to find-and-update-or-create when importing a CSV file. diff --git a/lib/csv_importer/row.rb b/lib/csv_importer/row.rb index fe85a97..785437a 100644 --- a/lib/csv_importer/row.rb +++ b/lib/csv_importer/row.rb @@ -52,18 +52,19 @@ def set_attributes(model) # Set the attribute using the column_definition and the csv_value def set_attribute(model, column, csv_value) column_definition = column.definition - if column_definition.to && column_definition.to.is_a?(Proc) - to_proc = column_definition.to + transformer = column_definition.to + if transformer.respond_to?(:call) + arity = transformer.is_a?(Proc) ? transformer.arity : transformer.method(:call).arity - case to_proc.arity + case arity when 1 # to: ->(email) { email.downcase } - model.public_send("#{column_definition.name}=", to_proc.call(csv_value)) + model.public_send("#{column_definition.name}=", transformer.call(csv_value)) when 2 # to: ->(published, post) { post.published_at = Time.now if published == "true" } - to_proc.call(csv_value, model) + transformer.call(csv_value, model) when 3 # to: ->(field_value, post, column) { post.hash_field[column.name] = field_value } - to_proc.call(csv_value, model, column) + transformer.call(csv_value, model, column) else - raise ArgumentError, "`to` proc can only have 1, 2 or 3 arguments" + raise ArgumentError, "arity: #{transformer.arity.inspect} - `to` can only have 1, 2 or 3 arguments" end else attribute = column_definition.attribute diff --git a/spec/csv_importer_spec.rb b/spec/csv_importer_spec.rb index d21f84a..fad5a1b 100644 --- a/spec/csv_importer_spec.rb +++ b/spec/csv_importer_spec.rb @@ -61,14 +61,18 @@ def self.store class ImportUserCSV include CSVImporter + class ConfirmedProcessor + def self.call(confirmed, model) + model.confirmed_at = confirmed == "true" ? Time.new(2012) : nil + end + end + model User column :email, required: true, as: /email/i, to: ->(email) { email.downcase } column :f_name, as: :first_name, required: true column :last_name, to: :l_name - column :confirmed, to: ->(confirmed, model) do - model.confirmed_at = confirmed == "true" ? Time.new(2012) : nil - end + column :confirmed, to: ConfirmedProcessor column :extra, as: /extra/i, to: ->(value, model, column) do model.custom_fields[column.name] = value end @@ -495,7 +499,7 @@ class ImportUserCSVByFirstName import = ImportUserCSV.new(content: csv_content).run! expect(import).to_not be_success - expect(import.message).to eq "Unclosed quoted field on line 3." + expect(import.message).to include "Unclosed quoted field" end it "matches columns via regexp" do