From b18ed06b27d74dea2d7a63b0a5034a5490dfc937 Mon Sep 17 00:00:00 2001 From: ukolovda Date: Fri, 7 Jun 2013 23:53:31 +0400 Subject: [PATCH 01/11] Model.create should use role, defined in controller: controller do with_role :admin end --- app/models/csv_db.rb | 4 ++-- lib/active_admin_importable/dsl.rb | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/models/csv_db.rb b/app/models/csv_db.rb index f15b4d1..3969bc9 100644 --- a/app/models/csv_db.rb +++ b/app/models/csv_db.rb @@ -1,7 +1,7 @@ require 'csv' class CsvDb class << self - def convert_save(target_model, csv_data, &block) + def convert_save(target_model, csv_data, role = :default, &block) csv_file = csv_data.read CSV.parse(csv_file, :headers => true, header_converters: :symbol ) do |row| data = row.to_hash @@ -9,7 +9,7 @@ def convert_save(target_model, csv_data, &block) if (block_given?) block.call(target_model, data) else - target_model.create!(data) + target_model.create!(data, as: role) end end end diff --git a/lib/active_admin_importable/dsl.rb b/lib/active_admin_importable/dsl.rb index ea79938..e02d144 100644 --- a/lib/active_admin_importable/dsl.rb +++ b/lib/active_admin_importable/dsl.rb @@ -10,7 +10,8 @@ def active_admin_importable(&block) end collection_action :import_csv, :method => :post do - CsvDb.convert_save(active_admin_config.resource_class, params[:dump][:file], &block) + role = resources_configuration[:self][:role] || :default + CsvDb.convert_save(active_admin_config.resource_class, params[:dump][:file], role, &block) redirect_to :action => :index, :notice => "#{active_admin_config.resource_name.to_s} imported successfully!" end end From 2400b69e8b7b3c1a4489ca3413337187d70c6fa4 Mon Sep 17 00:00:00 2001 From: ukolovda Date: Tue, 10 Sep 2013 14:30:19 +0400 Subject: [PATCH 02/11] Make compatible with Ruby 1.8.7. --- app/models/csv_db.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/csv_db.rb b/app/models/csv_db.rb index 3969bc9..a44d768 100644 --- a/app/models/csv_db.rb +++ b/app/models/csv_db.rb @@ -3,13 +3,13 @@ class CsvDb class << self def convert_save(target_model, csv_data, role = :default, &block) csv_file = csv_data.read - CSV.parse(csv_file, :headers => true, header_converters: :symbol ) do |row| + CSV.parse(csv_file, :headers => true, :header_converters => :symbol ) do |row| data = row.to_hash if data.present? if (block_given?) block.call(target_model, data) else - target_model.create!(data, as: role) + target_model.create!(data, :as => role) end end end From 9cb12315a2b93d8e43a3cd1840038d2b2c64a363 Mon Sep 17 00:00:00 2001 From: ukolovda Date: Thu, 7 Nov 2013 16:36:25 +0400 Subject: [PATCH 03/11] Make compatible with Ruby 1.8.7. --- app/models/csv_db.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/csv_db.rb b/app/models/csv_db.rb index a44d768..40dc8f8 100644 --- a/app/models/csv_db.rb +++ b/app/models/csv_db.rb @@ -3,7 +3,8 @@ class CsvDb class << self def convert_save(target_model, csv_data, role = :default, &block) csv_file = csv_data.read - CSV.parse(csv_file, :headers => true, :header_converters => :symbol ) do |row| + parser_class = (RUBY_VERSION=='1.8.7') ? FasterCSV : CSV + parser_class.parse(csv_file, :headers => true, :header_converters => :symbol ) do |row| data = row.to_hash if data.present? if (block_given?) From fbb78dd400bf214db501e8f1024dace84e368773 Mon Sep 17 00:00:00 2001 From: ukolovda Date: Thu, 6 Aug 2015 14:51:59 +0400 Subject: [PATCH 04/11] Fix success flash notice on redirecting. --- lib/active_admin_importable/dsl.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/active_admin_importable/dsl.rb b/lib/active_admin_importable/dsl.rb index e02d144..cf5d042 100644 --- a/lib/active_admin_importable/dsl.rb +++ b/lib/active_admin_importable/dsl.rb @@ -12,7 +12,8 @@ def active_admin_importable(&block) collection_action :import_csv, :method => :post do role = resources_configuration[:self][:role] || :default CsvDb.convert_save(active_admin_config.resource_class, params[:dump][:file], role, &block) - redirect_to :action => :index, :notice => "#{active_admin_config.resource_name.to_s} imported successfully!" + flash[:notice] = "#{active_admin_config.resource_name.to_s} imported successfully!" + redirect_to :action => :index end end end From 189e2f9825b74f75517b9407f45d7fccb7534afb Mon Sep 17 00:00:00 2001 From: ukolovda Date: Wed, 12 Aug 2015 17:27:12 +0400 Subject: [PATCH 05/11] Make remove UTF-8 BOM if present. --- app/models/csv_db.rb | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/app/models/csv_db.rb b/app/models/csv_db.rb index 40dc8f8..0a96a8d 100644 --- a/app/models/csv_db.rb +++ b/app/models/csv_db.rb @@ -1,18 +1,32 @@ require 'csv' class CsvDb class << self + def char_code(c) + c.respond_to?(:ord) ? c.ord : c + end + + def has_bom(file_data) + char_code(file_data[0]) == 0xEF && + char_code(file_data[1]) == 0xBB && + char_code(file_data[2]) == 0xBF + end + + def remove_bom(file_data) + has_bom(file_data) ? file_data[3..-1] : file_data + end + def convert_save(target_model, csv_data, role = :default, &block) - csv_file = csv_data.read + csv_file = remove_bom(csv_data.read) parser_class = (RUBY_VERSION=='1.8.7') ? FasterCSV : CSV parser_class.parse(csv_file, :headers => true, :header_converters => :symbol ) do |row| data = row.to_hash if data.present? if (block_given?) - block.call(target_model, data) - else - target_model.create!(data, :as => role) - end - end + block.call(target_model, data) + else + target_model.create!(data, :as => role) + end + end end end end From 4156d5a428f7d35975790b0cd32b7646a8a0f2ff Mon Sep 17 00:00:00 2001 From: ukolovda Date: Thu, 13 Aug 2015 13:17:59 +0400 Subject: [PATCH 06/11] Append options: :role => :admin Role for create model :find_by => :id If specified, engine will try find object by this field and update it, or create if not found. --- app/models/csv_db.rb | 45 +++++++++++++++++++++++++----- lib/active_admin_importable/dsl.rb | 7 +++-- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/app/models/csv_db.rb b/app/models/csv_db.rb index 0a96a8d..7e18e53 100644 --- a/app/models/csv_db.rb +++ b/app/models/csv_db.rb @@ -15,19 +15,50 @@ def remove_bom(file_data) has_bom(file_data) ? file_data[3..-1] : file_data end - def convert_save(target_model, csv_data, role = :default, &block) + def convert_save(target_model, csv_data, options, &block) csv_file = remove_bom(csv_data.read) parser_class = (RUBY_VERSION=='1.8.7') ? FasterCSV : CSV - parser_class.parse(csv_file, :headers => true, :header_converters => :symbol ) do |row| - data = row.to_hash - if data.present? - if (block_given?) - block.call(target_model, data) + begin + target_model.transaction do + parser_class.parse(csv_file, :headers => true, :header_converters => :symbol ) do |row| + append_row(target_model, row, options, block) + end + end + ensure + if options[:reset_pk_sequence] + target_model.reset_pk_sequence! + end + end + end + + def append_row(target_model, row, options, block) + data = row.to_hash + if data.present? + if (block_given?) + block.call(target_model, data) + else + role = options[:role] || :default + if key_field = options[:find_by] + create_or_update! target_model, data, key_field else - target_model.create!(data, :as => role) + if role == :default + target_model.create!(data) + else + target_model.create!(data, :as => role) # Old version ActiveRecord + end end end end end + + def create_or_update!(target_model, values, key_field) + key_value = values[key_field] + scope = target_model.where(key_field => key_value) + if obj = scope.first + obj.update_attributes!(values) + else + scope.create!(values) + end + end end end \ No newline at end of file diff --git a/lib/active_admin_importable/dsl.rb b/lib/active_admin_importable/dsl.rb index cf5d042..7e2a2b4 100644 --- a/lib/active_admin_importable/dsl.rb +++ b/lib/active_admin_importable/dsl.rb @@ -1,6 +1,7 @@ module ActiveAdminImportable module DSL - def active_admin_importable(&block) + def active_admin_importable(options = {}, &block) + action_item :only => :index do link_to "Import #{active_admin_config.resource_name.to_s.pluralize}", :action => 'upload_csv' end @@ -10,8 +11,8 @@ def active_admin_importable(&block) end collection_action :import_csv, :method => :post do - role = resources_configuration[:self][:role] || :default - CsvDb.convert_save(active_admin_config.resource_class, params[:dump][:file], role, &block) + role = resources_configuration[:self][:role] + CsvDb.convert_save(active_admin_config.resource_class, params[:dump][:file], options.merge(:role=>role), &block) flash[:notice] = "#{active_admin_config.resource_name.to_s} imported successfully!" redirect_to :action => :index end From 34b380565f11499c7c239f539ace54cd81a31d7f Mon Sep 17 00:00:00 2001 From: ukolovda Date: Thu, 13 Aug 2015 13:44:47 +0400 Subject: [PATCH 07/11] Option :reset_pk_sequence => true will reset Primary Key sequence (tested on PostgreSQL). --- README.md | 34 +++++++++++++++++++++++----------- app/models/csv_db.rb | 2 +- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 97d4cf3..f754de7 100644 --- a/README.md +++ b/README.md @@ -21,24 +21,36 @@ Or install it yourself as: Add the following line into your active admin resource: - active_admin_importable + active_admin_importable The Import button should now appear. Click it and upload a CSV file with a header row corresponding to your model attributes. Press submit. Profit. +## Usefull options + + + active_admin_importable :reset_pk_sequence => true, :find_by => :id + + +* ``:find_by`` + + Will try find every row by ID (or specified field) and update or create it. + +* ``:reset_pk_sequence`` + + After process primary key sequence will by reset (tested on PostgreSQL). + ## Custom Import Behavior Need to do something special with the import? active_admin_importable accepts an optional block that will be called on each row, replacing the default functionality ( calling create! on the associated model). The associated model and a hash of the current row will get passed into the block. For example: -``` -ActiveAdmin.register Product do - active_admin_importable do |model, hash| - store = Store.find_by_name(hash[:store_name]) - hash[:store_id] = store.id - hash.delete(:store_name) - model.create!(hash) - end -end -``` + ActiveAdmin.register Product do + active_admin_importable do |model, hash| + store = Store.find_by_name(hash[:store_name]) + hash[:store_id] = store.id + hash.delete(:store_name) + model.create!(hash) + end + end ## Contributing diff --git a/app/models/csv_db.rb b/app/models/csv_db.rb index 7e18e53..ebc64a5 100644 --- a/app/models/csv_db.rb +++ b/app/models/csv_db.rb @@ -26,7 +26,7 @@ def convert_save(target_model, csv_data, options, &block) end ensure if options[:reset_pk_sequence] - target_model.reset_pk_sequence! + target_model.connection.reset_pk_sequence! target_model.table_name end end end From c087bc7e41a3ef84da15bfa7115ff35b61fb988e Mon Sep 17 00:00:00 2001 From: ukolovda Date: Thu, 13 Aug 2015 13:53:58 +0400 Subject: [PATCH 08/11] Fixed block yielding. --- app/models/csv_db.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/csv_db.rb b/app/models/csv_db.rb index ebc64a5..e5e7da1 100644 --- a/app/models/csv_db.rb +++ b/app/models/csv_db.rb @@ -21,7 +21,7 @@ def convert_save(target_model, csv_data, options, &block) begin target_model.transaction do parser_class.parse(csv_file, :headers => true, :header_converters => :symbol ) do |row| - append_row(target_model, row, options, block) + append_row(target_model, row, options, &block) end end ensure @@ -31,7 +31,7 @@ def convert_save(target_model, csv_data, options, &block) end end - def append_row(target_model, row, options, block) + def append_row(target_model, row, options, &block) data = row.to_hash if data.present? if (block_given?) From 1eb5f17a97cb237f72347ca712cf496e304a9e68 Mon Sep 17 00:00:00 2001 From: ukolovda Date: Thu, 13 Aug 2015 16:19:42 +0400 Subject: [PATCH 09/11] Append option :before_save => proc --- README.md | 12 ++++++++---- app/models/csv_db.rb | 3 +++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f754de7..b40e1ca 100644 --- a/README.md +++ b/README.md @@ -28,17 +28,21 @@ The Import button should now appear. Click it and upload a CSV file with a heade ## Usefull options - active_admin_importable :reset_pk_sequence => true, :find_by => :id + active_admin_importable options -* ``:find_by`` +* ``:find_by => :id`` - Will try find every row by ID (or specified field) and update or create it. + Try find row by ID (or specified field) and update or create it. -* ``:reset_pk_sequence`` +* ``:reset_pk_sequence => true`` After process primary key sequence will by reset (tested on PostgreSQL). +* ``:before_save => proc { |row| row[:salary] ||= 0 }`` + + Modify field values before write (and before find, if present). + ## Custom Import Behavior Need to do something special with the import? active_admin_importable accepts an optional block that will be called on each row, replacing the default functionality ( calling create! on the associated model). The associated model and a hash of the current row will get passed into the block. For example: diff --git a/app/models/csv_db.rb b/app/models/csv_db.rb index e5e7da1..4e8a091 100644 --- a/app/models/csv_db.rb +++ b/app/models/csv_db.rb @@ -37,6 +37,9 @@ def append_row(target_model, row, options, &block) if (block_given?) block.call(target_model, data) else + + options[:before_save].call(data) if options[:before_save] + role = options[:role] || :default if key_field = options[:find_by] create_or_update! target_model, data, key_field From b29b22c54fa5b88e66ceba70a927619e21fe2352 Mon Sep 17 00:00:00 2001 From: artkalm Date: Sun, 26 Feb 2017 18:08:24 +0300 Subject: [PATCH 10/11] Fix action_item deprecation warning --- lib/active_admin_importable/dsl.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_admin_importable/dsl.rb b/lib/active_admin_importable/dsl.rb index 7e2a2b4..ec12c66 100644 --- a/lib/active_admin_importable/dsl.rb +++ b/lib/active_admin_importable/dsl.rb @@ -2,7 +2,7 @@ module ActiveAdminImportable module DSL def active_admin_importable(options = {}, &block) - action_item :only => :index do + action_item :edit, :only => :index do link_to "Import #{active_admin_config.resource_name.to_s.pluralize}", :action => 'upload_csv' end From a9755721017dbe6c0d1ade38d75e28f24f23134b Mon Sep 17 00:00:00 2001 From: ukolovda Date: Wed, 10 Jul 2019 13:20:03 +0400 Subject: [PATCH 11/11] Force use UTF-8 for CSV data. --- app/models/csv_db.rb | 6 ++++-- lib/active_admin_importable/version.rb | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/models/csv_db.rb b/app/models/csv_db.rb index 4e8a091..28b6aeb 100644 --- a/app/models/csv_db.rb +++ b/app/models/csv_db.rb @@ -11,16 +11,18 @@ def has_bom(file_data) char_code(file_data[2]) == 0xBF end + # @return [String] def remove_bom(file_data) has_bom(file_data) ? file_data[3..-1] : file_data end def convert_save(target_model, csv_data, options, &block) - csv_file = remove_bom(csv_data.read) + csv_data = remove_bom(csv_data.read) + csv_data = csv_data.force_encoding('utf-8') if csv_data.respond_to?(:force_encoding) parser_class = (RUBY_VERSION=='1.8.7') ? FasterCSV : CSV begin target_model.transaction do - parser_class.parse(csv_file, :headers => true, :header_converters => :symbol ) do |row| + parser_class.parse(csv_data, :headers => true, :header_converters => :symbol) do |row| append_row(target_model, row, options, &block) end end diff --git a/lib/active_admin_importable/version.rb b/lib/active_admin_importable/version.rb index 1b77c53..51445d1 100644 --- a/lib/active_admin_importable/version.rb +++ b/lib/active_admin_importable/version.rb @@ -1,3 +1,3 @@ module ActiveAdminImportable - VERSION = "1.1.2" + VERSION = "1.1.3" end