diff --git a/README.md b/README.md index 97d4cf3..b40e1ca 100644 --- a/README.md +++ b/README.md @@ -21,24 +21,40 @@ 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 options + + +* ``:find_by => :id`` + + Try find row by ID (or specified field) and update or create it. + +* ``: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: -``` -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 f15b4d1..28b6aeb 100644 --- a/app/models/csv_db.rb +++ b/app/models/csv_db.rb @@ -1,17 +1,68 @@ require 'csv' class CsvDb class << self - def convert_save(target_model, csv_data, &block) - csv_file = csv_data.read - 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) - end - end + 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 + + # @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_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_data, :headers => true, :header_converters => :symbol) do |row| + append_row(target_model, row, options, &block) + end + end + ensure + if options[:reset_pk_sequence] + target_model.connection.reset_pk_sequence! target_model.table_name + 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 + + 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 + else + 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 diff --git a/lib/active_admin_importable/dsl.rb b/lib/active_admin_importable/dsl.rb index ea79938..ec12c66 100644 --- a/lib/active_admin_importable/dsl.rb +++ b/lib/active_admin_importable/dsl.rb @@ -1,7 +1,8 @@ module ActiveAdminImportable module DSL - def active_admin_importable(&block) - action_item :only => :index do + def active_admin_importable(options = {}, &block) + + action_item :edit, :only => :index do link_to "Import #{active_admin_config.resource_name.to_s.pluralize}", :action => 'upload_csv' end @@ -10,8 +11,10 @@ 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) - redirect_to :action => :index, :notice => "#{active_admin_config.resource_name.to_s} imported successfully!" + 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 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