rails_admin_import 0.1.9 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/{RELEASE_NOTES → CHANGELOG.md} +0 -0
- data/MIT-LICENSE +1 -1
- data/README.md +193 -50
- data/Rakefile +8 -32
- data/app/views/rails_admin/main/_results.html.haml +18 -0
- data/app/views/rails_admin/main/_section.html.haml +10 -0
- data/app/views/rails_admin/main/import.html.haml +72 -0
- data/config/locales/import.en.yml +39 -0
- data/lib/rails_admin_import/action.rb +45 -0
- data/lib/rails_admin_import/config/legacy_model.rb +42 -0
- data/lib/rails_admin_import/config/sections/import.rb +30 -0
- data/lib/rails_admin_import/config.rb +16 -37
- data/lib/rails_admin_import/formats/csv_importer.rb +75 -0
- data/lib/rails_admin_import/formats/dummy_importer.rb +16 -0
- data/lib/rails_admin_import/formats/file_importer.rb +45 -0
- data/lib/rails_admin_import/formats/json_importer.rb +31 -0
- data/lib/rails_admin_import/formats.rb +28 -0
- data/lib/rails_admin_import/import_logger.rb +7 -4
- data/lib/rails_admin_import/import_model.rb +94 -0
- data/lib/rails_admin_import/importer.rb +224 -0
- data/lib/rails_admin_import/rails_admin_plugin.rb +23 -0
- data/lib/rails_admin_import/version.rb +1 -1
- data/lib/rails_admin_import.rb +5 -39
- metadata +83 -13
- data/app/views/rails_admin/main/import.html.erb +0 -143
- data/lib/rails_admin_import/config/base.rb +0 -62
- data/lib/rails_admin_import/config/model.rb +0 -23
- data/lib/rails_admin_import/import.rb +0 -218
| @@ -0,0 +1,75 @@ | |
| 1 | 
            +
            require "csv"
         | 
| 2 | 
            +
            require "rchardet"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module RailsAdminImport
         | 
| 5 | 
            +
              module Formats
         | 
| 6 | 
            +
                class CSVImporter < FileImporter
         | 
| 7 | 
            +
                  Formats.register(:csv, self)
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  # Default is to downcase headers and add underscores to convert into attribute names
         | 
| 10 | 
            +
                  HEADER_CONVERTER = lambda do |header|
         | 
| 11 | 
            +
                    header.parameterize.underscore
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def initialize(import_model, params)
         | 
| 15 | 
            +
                    super
         | 
| 16 | 
            +
                    @encoding = params[:encoding]
         | 
| 17 | 
            +
                    @header_converter = RailsAdminImport.config.header_converter || HEADER_CONVERTER
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  # A method that yields a hash of attributes for each record to import
         | 
| 21 | 
            +
                  def each_record
         | 
| 22 | 
            +
                    CSV.foreach(filename, csv_options) do |row|
         | 
| 23 | 
            +
                      yield convert_to_attributes(row)
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  private
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def csv_options
         | 
| 30 | 
            +
                    {
         | 
| 31 | 
            +
                      headers: true,
         | 
| 32 | 
            +
                      header_converters: @header_converter
         | 
| 33 | 
            +
                    }.tap do |options|
         | 
| 34 | 
            +
                      add_encoding!(options)
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def add_encoding!(options)
         | 
| 39 | 
            +
                    from_encoding =
         | 
| 40 | 
            +
                      if !@encoding.blank?
         | 
| 41 | 
            +
                        @encoding
         | 
| 42 | 
            +
                      else
         | 
| 43 | 
            +
                        detect_encoding
         | 
| 44 | 
            +
                      end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    to_encoding = import_model.abstract_model.encoding
         | 
| 47 | 
            +
                    if from_encoding && from_encoding != to_encoding
         | 
| 48 | 
            +
                      options[:encoding] = "#{from_encoding}:#{to_encoding}"
         | 
| 49 | 
            +
                    end
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  def detect_encoding
         | 
| 53 | 
            +
                    charset = CharDet.detect File.read(filename)
         | 
| 54 | 
            +
                    if charset["confidence"] > 0.6
         | 
| 55 | 
            +
                      from_encoding = charset["encoding"]
         | 
| 56 | 
            +
                      from_encoding = "UTF-8" if from_encoding == "ascii"
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
                    from_encoding
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  def convert_to_attributes(row)
         | 
| 62 | 
            +
                    row.each_with_object({}) do |(field, value), record|
         | 
| 63 | 
            +
                      break if field.nil?
         | 
| 64 | 
            +
                      field = field.to_sym
         | 
| 65 | 
            +
                      if import_model.has_multiple_values?(field)
         | 
| 66 | 
            +
                        field = import_model.pluralize_field(field)
         | 
| 67 | 
            +
                        (record[field] ||= []) << value
         | 
| 68 | 
            +
                      else
         | 
| 69 | 
            +
                        record[field] = value
         | 
| 70 | 
            +
                      end
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
              end
         | 
| 75 | 
            +
            end
         | 
| @@ -0,0 +1,45 @@ | |
| 1 | 
            +
            module RailsAdminImport
         | 
| 2 | 
            +
              module Formats
         | 
| 3 | 
            +
                class FileImporter
         | 
| 4 | 
            +
                  def initialize(import_model, params)
         | 
| 5 | 
            +
                    if params.has_key?(:file)
         | 
| 6 | 
            +
                      @filename = params[:file].tempfile
         | 
| 7 | 
            +
                    end
         | 
| 8 | 
            +
                    @import_model = import_model
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  attr_reader :filename, :error, :import_model
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def valid?
         | 
| 14 | 
            +
                    if filename.nil?
         | 
| 15 | 
            +
                      @error = I18n.t("admin.import.missing_file")
         | 
| 16 | 
            +
                      false
         | 
| 17 | 
            +
                    else
         | 
| 18 | 
            +
                      true
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def each(&block)
         | 
| 23 | 
            +
                    return enum_for(:each) unless block_given?
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    if RailsAdminImport.config.logging && filename
         | 
| 26 | 
            +
                      copy_uploaded_file_to_log_dir
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    each_record(&block)
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  private
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  def each_record
         | 
| 35 | 
            +
                    raise "Implement each_record in subclasses"
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def copy_uploaded_file_to_log_dir
         | 
| 39 | 
            +
                    copy_filename = "#{Time.now.strftime("%Y-%m-%d-%H-%M-%S")}-import.csv"
         | 
| 40 | 
            +
                    copy_path = File.join(Rails.root, "log", "import", copy_filename)
         | 
| 41 | 
            +
                    FileUtils.copy(filename, copy_path)
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
            end
         | 
| @@ -0,0 +1,31 @@ | |
| 1 | 
            +
            module RailsAdminImport
         | 
| 2 | 
            +
              module Formats
         | 
| 3 | 
            +
                class JSONImporter < FileImporter
         | 
| 4 | 
            +
                  Formats.register(:json, self)
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  # A method that yields a hash of attributes for each record to import
         | 
| 7 | 
            +
                  def each_record
         | 
| 8 | 
            +
                    File.open(filename) do |file|
         | 
| 9 | 
            +
                      data = JSON.load(file)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                      if data.is_a? Hash
         | 
| 12 | 
            +
                        # Load array from root key
         | 
| 13 | 
            +
                        data = data[root_key]
         | 
| 14 | 
            +
                      end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                      if !data.is_a? Array
         | 
| 17 | 
            +
                        raise ArgumentError, I18n.t("admin.import.invalid_json", root_key: root_key)
         | 
| 18 | 
            +
                      end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                      data.each do |record|
         | 
| 21 | 
            +
                        yield record.symbolize_keys
         | 
| 22 | 
            +
                      end
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def root_key
         | 
| 27 | 
            +
                    import_model.model.model_name.element.pluralize
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
            end
         | 
| @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            module RailsAdminImport
         | 
| 2 | 
            +
              module Formats
         | 
| 3 | 
            +
                class << self
         | 
| 4 | 
            +
                  def register(format, klass)
         | 
| 5 | 
            +
                    @registry[format.to_s] = klass
         | 
| 6 | 
            +
                  end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def for(format, *args)
         | 
| 9 | 
            +
                    @registry.fetch(format.to_s, DummyImporter).new(*args)
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def all
         | 
| 13 | 
            +
                    @registry.keys
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def reset
         | 
| 17 | 
            +
                    @registry = {}
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                reset
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
            end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            require "rails_admin_import/formats/dummy_importer"
         | 
| 26 | 
            +
            require "rails_admin_import/formats/file_importer"
         | 
| 27 | 
            +
            require "rails_admin_import/formats/csv_importer"
         | 
| 28 | 
            +
            require "rails_admin_import/formats/json_importer"
         | 
| @@ -2,13 +2,16 @@ module RailsAdminImport | |
| 2 2 | 
             
              class ImportLogger
         | 
| 3 3 | 
             
                attr_reader :logger
         | 
| 4 4 |  | 
| 5 | 
            -
                def initialize(log_file_name =  | 
| 6 | 
            -
                   | 
| 5 | 
            +
                def initialize(log_file_name = "rails_admin_import.log")
         | 
| 6 | 
            +
                  if RailsAdminImport.config.logging
         | 
| 7 | 
            +
                    @logger = Logger.new(File.join(Rails.root, "log", log_file_name))
         | 
| 8 | 
            +
                  end
         | 
| 7 9 | 
             
                end
         | 
| 8 10 |  | 
| 9 11 | 
             
                def info(message)
         | 
| 10 | 
            -
                   | 
| 12 | 
            +
                  if RailsAdminImport.config.logging
         | 
| 13 | 
            +
                    @logger.info message
         | 
| 14 | 
            +
                  end
         | 
| 11 15 | 
             
                end
         | 
| 12 | 
            -
             
         | 
| 13 16 | 
             
              end
         | 
| 14 17 | 
             
            end
         | 
| @@ -0,0 +1,94 @@ | |
| 1 | 
            +
            module RailsAdminImport
         | 
| 2 | 
            +
              class AssociationNotFound < StandardError
         | 
| 3 | 
            +
              end
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              class ImportModel
         | 
| 6 | 
            +
                def initialize(abstract_model)
         | 
| 7 | 
            +
                  @abstract_model = abstract_model
         | 
| 8 | 
            +
                  @config = abstract_model.config.import
         | 
| 9 | 
            +
                  @model = abstract_model.model
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                attr_reader :abstract_model, :model, :config
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def display_name
         | 
| 15 | 
            +
                  abstract_model.config.label
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def label_for_model(object)
         | 
| 19 | 
            +
                  object.public_send(label_method)
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def label_method
         | 
| 23 | 
            +
                  @label_method ||= abstract_model.config.object_label_method
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def importable_fields(model_config = config)
         | 
| 27 | 
            +
                  @importable_fields ||= {}
         | 
| 28 | 
            +
                  @importable_fields[model_config] ||= model_config.visible_fields.reject do |f|
         | 
| 29 | 
            +
                    # Exclude id, created_at and updated_at
         | 
| 30 | 
            +
                    model_config.default_excluded_fields.include? f.name
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def model_fields(model_config = config)
         | 
| 35 | 
            +
                  @model_fields ||= {}
         | 
| 36 | 
            +
                  @model_fields[model_config] ||= importable_fields(model_config).select { |f|
         | 
| 37 | 
            +
                    !f.association? || f.association.polymorphic?
         | 
| 38 | 
            +
                  }
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def association_fields
         | 
| 42 | 
            +
                  @association_fields ||= importable_fields.select { |f|
         | 
| 43 | 
            +
                    f.association? && !f.association.polymorphic?
         | 
| 44 | 
            +
                  }
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def belongs_to_fields
         | 
| 48 | 
            +
                  @belongs_to_fields ||= association_fields.select { |f|
         | 
| 49 | 
            +
                    !f.multiple?
         | 
| 50 | 
            +
                  }
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                def many_fields
         | 
| 54 | 
            +
                  @many_fields ||= association_fields.select {
         | 
| 55 | 
            +
                    |f| f.multiple?
         | 
| 56 | 
            +
                  }
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                def associated_object(field, mapping_field, value)
         | 
| 60 | 
            +
                  klass = association_class(field)
         | 
| 61 | 
            +
                  klass.where(mapping_field => value).first or
         | 
| 62 | 
            +
                    raise AssociationNotFound, "#{klass}.#{mapping_field} = #{value}"
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                def association_class(field)
         | 
| 66 | 
            +
                  field.association.klass
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                def associated_config(field)
         | 
| 70 | 
            +
                  field.associated_model_config.import
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                def associated_model_fields(field)
         | 
| 74 | 
            +
                  @associated_fields ||= {}
         | 
| 75 | 
            +
                  @associated_fields[field] ||= associated_config(field).visible_fields.select { |f|
         | 
| 76 | 
            +
                    !f.association?
         | 
| 77 | 
            +
                  }
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                def has_multiple_values?(field_name)
         | 
| 81 | 
            +
                  plural_name = pluralize_field(field_name)
         | 
| 82 | 
            +
                  many_fields.any? { |field| field.name == field_name || field.name == plural_name }
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                def pluralize_field(field_name)
         | 
| 86 | 
            +
                  @plural_fields ||= many_fields.map(&:name).each_with_object({}) { |name, h|
         | 
| 87 | 
            +
                    h[name.to_s.singularize.to_sym] = name
         | 
| 88 | 
            +
                  }
         | 
| 89 | 
            +
                  @plural_fields[field_name] || field_name
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
              end
         | 
| 92 | 
            +
            end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
             | 
| @@ -0,0 +1,224 @@ | |
| 1 | 
            +
            require "rails_admin_import/import_logger"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module RailsAdminImport
         | 
| 4 | 
            +
              class Importer
         | 
| 5 | 
            +
                def initialize(import_model, params)
         | 
| 6 | 
            +
                  @import_model = import_model
         | 
| 7 | 
            +
                  @params = params
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                attr_reader :import_model, :params
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                class UpdateLookupError < StandardError
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def import(records)
         | 
| 16 | 
            +
                  begin
         | 
| 17 | 
            +
                    init_results
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    # TODO: re-implement file size check
         | 
| 20 | 
            +
                    # if file_check.readlines.size > RailsAdminImport.config.line_item_limit
         | 
| 21 | 
            +
                    #   return results = { :success => [], :error => ["Please limit upload file to #{RailsAdminImport.config.line_item_limit} line items."] }
         | 
| 22 | 
            +
                    # end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    with_transaction do
         | 
| 25 | 
            +
                      records.each do |record|
         | 
| 26 | 
            +
                        import_record(record)
         | 
| 27 | 
            +
                      end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                      rollback_if_error
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
                  rescue Exception => e
         | 
| 32 | 
            +
                    report_general_error("#{e} (#{e.backtrace.first})")
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  format_results
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                private
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def init_results
         | 
| 41 | 
            +
                  @results = { :success => [], :error => [] }
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def with_transaction(&block)
         | 
| 45 | 
            +
                  if RailsAdminImport.config.rollback_on_error &&
         | 
| 46 | 
            +
                    defined?(ActiveRecord)
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    ActiveRecord::Base.transaction &block
         | 
| 49 | 
            +
                  else
         | 
| 50 | 
            +
                    block.call
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                def rollback_if_error
         | 
| 55 | 
            +
                  if RailsAdminImport.config.rollback_on_error &&
         | 
| 56 | 
            +
                    defined?(ActiveRecord) &&
         | 
| 57 | 
            +
                    !results[:error].empty?
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    results[:success] = []
         | 
| 60 | 
            +
                    raise ActiveRecord::Rollback
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                def import_record(record)
         | 
| 65 | 
            +
                  if update_lookup && !record.has_key?(update_lookup)
         | 
| 66 | 
            +
                    raise UpdateLookupError, I18n.t("admin.import.missing_update_lookup")
         | 
| 67 | 
            +
                  end 
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  object = find_or_create_object(record, update_lookup)
         | 
| 70 | 
            +
                  action = object.new_record? ? :create : :update
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  begin
         | 
| 73 | 
            +
                    import_belongs_to_data(object, record)
         | 
| 74 | 
            +
                    import_has_many_data(object, record)
         | 
| 75 | 
            +
                  rescue AssociationNotFound => e
         | 
| 76 | 
            +
                    error = I18n.t("admin.import.association_not_found", :error => e.to_s)
         | 
| 77 | 
            +
                    report_error(object, action, error)
         | 
| 78 | 
            +
                    return
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  perform_model_callback(object, :before_import_save, record)
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                  if object.save
         | 
| 84 | 
            +
                    report_success(object, action)
         | 
| 85 | 
            +
                    perform_model_callback(object, :after_import_save, record)
         | 
| 86 | 
            +
                  else
         | 
| 87 | 
            +
                    report_error(object, action, object.errors.full_messages.join(", "))
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
                end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                def update_lookup
         | 
| 92 | 
            +
                  @update_lookup ||= if params[:update_if_exists] == "1"
         | 
| 93 | 
            +
                                       params[:update_lookup].to_sym
         | 
| 94 | 
            +
                                     end
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                attr_reader :results
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                def logger
         | 
| 100 | 
            +
                  @logger ||= ImportLogger.new
         | 
| 101 | 
            +
                end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                def report_success(object, action)
         | 
| 104 | 
            +
                  object_label = import_model.label_for_model(object)
         | 
| 105 | 
            +
                  message = I18n.t("admin.import.import_success.#{action}",
         | 
| 106 | 
            +
                                   :name => object_label)
         | 
| 107 | 
            +
                  logger.info "#{Time.now}: #{message}"
         | 
| 108 | 
            +
                  results[:success] << message
         | 
| 109 | 
            +
                end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                def report_error(object, action, error)
         | 
| 112 | 
            +
                  object_label = import_model.label_for_model(object)
         | 
| 113 | 
            +
                  message = I18n.t("admin.import.import_error.#{action}",
         | 
| 114 | 
            +
                                   :name => object_label,
         | 
| 115 | 
            +
                                   :error => error)
         | 
| 116 | 
            +
                  logger.info "#{Time.now}: #{message}"
         | 
| 117 | 
            +
                  results[:error] << message
         | 
| 118 | 
            +
                end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                def report_general_error(error)
         | 
| 121 | 
            +
                  message = I18n.t("admin.import.import_error.general", :error => error)
         | 
| 122 | 
            +
                  logger.info "#{Time.now}: #{message}"
         | 
| 123 | 
            +
                  results[:error] << message
         | 
| 124 | 
            +
                end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                def format_results
         | 
| 127 | 
            +
                  imported = results[:success]
         | 
| 128 | 
            +
                  not_imported = results[:error]
         | 
| 129 | 
            +
                  unless imported.empty?
         | 
| 130 | 
            +
                    results[:success_message] = format_result_message("successful", imported)
         | 
| 131 | 
            +
                  end
         | 
| 132 | 
            +
                  unless not_imported.empty?
         | 
| 133 | 
            +
                    results[:error_message] = format_result_message("error", not_imported)
         | 
| 134 | 
            +
                  end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                  results
         | 
| 137 | 
            +
                end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                def format_result_message(type, array)
         | 
| 140 | 
            +
                  result_count = "#{array.size} #{import_model.display_name.pluralize(array.size)}"
         | 
| 141 | 
            +
                  I18n.t("admin.flash.#{type}",
         | 
| 142 | 
            +
                         name: result_count,
         | 
| 143 | 
            +
                           action: I18n.t("admin.actions.import.done"))
         | 
| 144 | 
            +
                end
         | 
| 145 | 
            +
                
         | 
| 146 | 
            +
                def perform_model_callback(object, method_name, record)
         | 
| 147 | 
            +
                  if object.respond_to?(method_name)
         | 
| 148 | 
            +
                    # Compatibility: Old import hook took 2 arguments.
         | 
| 149 | 
            +
                    # Warn and call with a blank hash as 2nd argument.
         | 
| 150 | 
            +
                    if object.method(method_name).arity == 2
         | 
| 151 | 
            +
                      report_old_import_hook(method_name)
         | 
| 152 | 
            +
                      object.send(method_name, record, {})
         | 
| 153 | 
            +
                    else
         | 
| 154 | 
            +
                      object.send(method_name, record)
         | 
| 155 | 
            +
                    end
         | 
| 156 | 
            +
                  end
         | 
| 157 | 
            +
                end
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                def report_old_import_hook(method_name)
         | 
| 160 | 
            +
                  unless @old_import_hook_reported
         | 
| 161 | 
            +
                    error = I18n.t("admin.import.import_error.old_import_hook",
         | 
| 162 | 
            +
                                   model: import_model.display_name,
         | 
| 163 | 
            +
                                   method: method_name)
         | 
| 164 | 
            +
                    report_general_error(error)
         | 
| 165 | 
            +
                    @old_import_hook_reported = true
         | 
| 166 | 
            +
                  end
         | 
| 167 | 
            +
                end
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                def find_or_create_object(record, update)
         | 
| 170 | 
            +
                  field_names = import_model.model_fields.map(&:name)
         | 
| 171 | 
            +
                  new_attrs = record.select do |field_name, value|
         | 
| 172 | 
            +
                    field_names.include?(field_name) && !value.blank?
         | 
| 173 | 
            +
                  end
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                  model = import_model.model
         | 
| 176 | 
            +
                  object = if update.present?
         | 
| 177 | 
            +
                             model.find_by(update => record[update])
         | 
| 178 | 
            +
                           end 
         | 
| 179 | 
            +
             | 
| 180 | 
            +
                  if object.nil?
         | 
| 181 | 
            +
                    object = model.new(new_attrs)
         | 
| 182 | 
            +
                  else
         | 
| 183 | 
            +
                    object.attributes = new_attrs.except(update.to_sym)
         | 
| 184 | 
            +
                  end
         | 
| 185 | 
            +
                  object
         | 
| 186 | 
            +
                end
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                def import_belongs_to_data(object, record)
         | 
| 189 | 
            +
                  import_model.belongs_to_fields.each do |field|
         | 
| 190 | 
            +
                    mapping_key = params[:associations][field.name]
         | 
| 191 | 
            +
                    value = extract_mapping(record[field.name], mapping_key)
         | 
| 192 | 
            +
             | 
| 193 | 
            +
                    if !value.blank?
         | 
| 194 | 
            +
                      object.send "#{field.name}=", import_model.associated_object(field, mapping_key, value)
         | 
| 195 | 
            +
                    end
         | 
| 196 | 
            +
                  end
         | 
| 197 | 
            +
                end
         | 
| 198 | 
            +
             | 
| 199 | 
            +
                def import_has_many_data(object, record)
         | 
| 200 | 
            +
                  import_model.many_fields.each do |field|
         | 
| 201 | 
            +
                    if record.has_key? field.name
         | 
| 202 | 
            +
                      mapping_key = params[:associations][field.name]
         | 
| 203 | 
            +
                      values = record[field.name].reject { |value| value.blank? }.map { |value|
         | 
| 204 | 
            +
                        extract_mapping(value, mapping_key)
         | 
| 205 | 
            +
                      }
         | 
| 206 | 
            +
             | 
| 207 | 
            +
                      if !values.empty?
         | 
| 208 | 
            +
                        associated = values.map { |value| import_model.associated_object(field, mapping_key, value) }
         | 
| 209 | 
            +
                        object.send "#{field.name}=", associated
         | 
| 210 | 
            +
                      end
         | 
| 211 | 
            +
                    end
         | 
| 212 | 
            +
                  end
         | 
| 213 | 
            +
                end
         | 
| 214 | 
            +
             | 
| 215 | 
            +
                def extract_mapping(value, mapping_key)
         | 
| 216 | 
            +
                  if value.is_a? Hash
         | 
| 217 | 
            +
                    value[mapping_key]
         | 
| 218 | 
            +
                  else
         | 
| 219 | 
            +
                    value
         | 
| 220 | 
            +
                  end
         | 
| 221 | 
            +
                end
         | 
| 222 | 
            +
              end
         | 
| 223 | 
            +
            end
         | 
| 224 | 
            +
             | 
| @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            # Load the Rails Admin gem if not already done
         | 
| 2 | 
            +
            require "rails_admin"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            # Add the Import action
         | 
| 5 | 
            +
            require "rails_admin_import/action"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            # Add the import configuration section for models
         | 
| 8 | 
            +
            require "rails_admin_import/config/sections/import"
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            # Register the configuration adapter for Rails Admin
         | 
| 11 | 
            +
            # to allow configure_with(:import)
         | 
| 12 | 
            +
            module RailsAdminImport
         | 
| 13 | 
            +
              module Extension
         | 
| 14 | 
            +
                class ConfigurationAdapter < SimpleDelegator
         | 
| 15 | 
            +
                  def initialize
         | 
| 16 | 
            +
                    super RailsAdminImport::Config
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
            end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            RailsAdmin.add_extension :import, RailsAdminImport::Extension,
         | 
| 23 | 
            +
              configuration: true
         | 
    
        data/lib/rails_admin_import.rb
    CHANGED
    
    | @@ -1,12 +1,15 @@ | |
| 1 1 | 
             
            require "rails_admin_import/engine"
         | 
| 2 | 
            -
            require "rails_admin_import/ | 
| 2 | 
            +
            require "rails_admin_import/import_model"
         | 
| 3 | 
            +
            require "rails_admin_import/formats"
         | 
| 4 | 
            +
            require "rails_admin_import/importer"
         | 
| 3 5 | 
             
            require "rails_admin_import/config"
         | 
| 6 | 
            +
            require "rails_admin_import/rails_admin_plugin"
         | 
| 4 7 |  | 
| 5 8 | 
             
            module RailsAdminImport
         | 
| 6 9 | 
             
              def self.config(entity = nil, &block)
         | 
| 7 10 | 
             
                if entity
         | 
| 8 11 | 
             
                  RailsAdminImport::Config.model(entity, &block)
         | 
| 9 | 
            -
                elsif block_given? && ENV[ | 
| 12 | 
            +
                elsif block_given? && ENV["SKIP_RAILS_ADMIN_INITIALIZER"] != "true"
         | 
| 10 13 | 
             
                  block.call(RailsAdminImport::Config)
         | 
| 11 14 | 
             
                else
         | 
| 12 15 | 
             
                  RailsAdminImport::Config
         | 
| @@ -18,40 +21,3 @@ module RailsAdminImport | |
| 18 21 | 
             
              end
         | 
| 19 22 | 
             
            end
         | 
| 20 23 |  | 
| 21 | 
            -
            require 'rails_admin/config/actions'
         | 
| 22 | 
            -
             | 
| 23 | 
            -
            module RailsAdmin
         | 
| 24 | 
            -
              module Config
         | 
| 25 | 
            -
                module Actions
         | 
| 26 | 
            -
                  class Import < Base
         | 
| 27 | 
            -
                    RailsAdmin::Config::Actions.register(self)
         | 
| 28 | 
            -
                    
         | 
| 29 | 
            -
                    register_instance_option :collection do
         | 
| 30 | 
            -
                      true
         | 
| 31 | 
            -
                    end
         | 
| 32 | 
            -
             | 
| 33 | 
            -
                    register_instance_option :http_methods do
         | 
| 34 | 
            -
                      [:get, :post]
         | 
| 35 | 
            -
                    end
         | 
| 36 | 
            -
             | 
| 37 | 
            -
                    register_instance_option :controller do
         | 
| 38 | 
            -
                      Proc.new do
         | 
| 39 | 
            -
                        @response = {}
         | 
| 40 | 
            -
             | 
| 41 | 
            -
                        if request.post?
         | 
| 42 | 
            -
                          results = @abstract_model.model.run_import(params)
         | 
| 43 | 
            -
                          @response[:notice] = results[:success].join("<br />").html_safe if results[:success].any?
         | 
| 44 | 
            -
                          @response[:error] = results[:error].join("<br />").html_safe if results[:error].any?
         | 
| 45 | 
            -
                        end
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                        render :action => @action.template_name
         | 
| 48 | 
            -
                      end
         | 
| 49 | 
            -
                    end
         | 
| 50 | 
            -
             | 
| 51 | 
            -
                    register_instance_option :link_icon do
         | 
| 52 | 
            -
                      'icon-folder-open'
         | 
| 53 | 
            -
                    end
         | 
| 54 | 
            -
                  end
         | 
| 55 | 
            -
                end
         | 
| 56 | 
            -
              end
         | 
| 57 | 
            -
            end
         |