diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..1092131 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,46 @@ +AllCops: + TargetRubyVersion: 2.3 + Exclude: + - 'db/**/*' + - 'spec/fixtures/**/*' + - 'vendor/**/*' + - 'bin/*' + +Style/IndentationWidth: + Width: 2 + +Style/Documentation: + Enabled: false + +Style/Encoding: + Enabled: false + +Style/MultilineOperationIndentation: + EnforcedStyle: indented + +Style/FirstParameterIndentation: + EnforcedStyle: special_for_inner_method_call_in_parentheses + +Metrics/AbcSize: + Max: 25 + +Metrics/LineLength: + Max: 120 + +Metrics/MethodLength: + Max: 40 + +Metrics/ClassLength: + Max: 250 + +Metrics/ModuleLength: + Max: 250 + +Metrics/CyclomaticComplexity: + Max: 8 + +Metrics/PerceivedComplexity: + Max: 8 + +Rails: + Enabled: true diff --git a/Gemfile b/Gemfile index a5e53ed..786c251 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,4 @@ +# frozen_string_literal: true source 'https://rubygems.org' # Specify your gem's dependencies in active_admin_importable.gemspec @@ -7,7 +8,9 @@ group :test do gem 'sprockets-rails', '2.3.3' gem 'rails', "#{ENV['RAILS'] || default_rails_version}" gem 'rspec-rails' - gem 'activeadmin', github: 'activeadmin' , ref: 'd787029e5523be2eb2ed99816eb0cecca2b72862' + gem 'activeadmin', + github: 'activeadmin', + ref: 'd787029e5523be2eb2ed99816eb0cecca2b72862' gem 'coveralls', require: false # Test coverage website. Go to https://coveralls.io gem 'sass-rails' gem 'sqlite3' @@ -21,4 +24,5 @@ group :test do else gem 'mime-types', '< 3.0.0' end + gem 'mime-types', (RUBY_VERSION >= '2.0' ? '~> 3.0' : '~> 2.99') end diff --git a/Rakefile b/Rakefile index 74ea1a4..95475db 100644 --- a/Rakefile +++ b/Rakefile @@ -1,7 +1,8 @@ -require "bundler" +# frozen_string_literal: true +require 'bundler' require 'rake' Bundler.setup Bundler::GemHelper.install_tasks # Import all our rake tasks -FileList['tasks/**/*.rake'].each { |task| import task } \ No newline at end of file +FileList['tasks/**/*.rake'].each { |task| import task } diff --git a/active_admin_import.gemspec b/active_admin_import.gemspec index 2a79578..c8d5456 100644 --- a/active_admin_import.gemspec +++ b/active_admin_import.gemspec @@ -1,25 +1,24 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: true require File.expand_path('../lib/active_admin_import/version', __FILE__) Gem::Specification.new do |gem| - gem.authors = ["Igor Fedoronchuk"] - gem.email = ["fedoronchuk@gmail.com"] - gem.description = "The most efficient way to import for Active Admin" - gem.summary = "ActiveAdmin import based on activerecord-import gem." - gem.homepage = "http://github.com/Fivell/active_admin_import" + gem.authors = ['Igor Fedoronchuk'] + gem.email = ['fedoronchuk@gmail.com'] + gem.description = 'The most efficient way to import for Active Admin' + gem.summary = 'ActiveAdmin import based on activerecord-import gem.' + gem.homepage = 'http://github.com/Fivell/active_admin_import' gem.license = 'MIT' - gem.files = `git ls-files`.split($\) + gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR) gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) } gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) - gem.name = "active_admin_import" - gem.require_paths = ["lib"] + gem.name = 'active_admin_import' + gem.require_paths = ['lib'] gem.version = ActiveAdminImport::VERSION - gem.add_runtime_dependency 'activerecord-import', '~> 0.13.0' gem.add_runtime_dependency 'rchardet', '~> 1.6' gem.add_runtime_dependency 'rubyzip', '~> 1.2' - gem.add_dependency "rails", ">= 4.0" - + gem.add_dependency 'rails', '>= 4.0' end diff --git a/lib/active_admin_import.rb b/lib/active_admin_import.rb index ce0dcb4..40adf2b 100644 --- a/lib/active_admin_import.rb +++ b/lib/active_admin_import.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true require 'activerecord-import' require 'active_admin' require 'active_admin_import/version' @@ -9,5 +10,3 @@ require 'active_admin_import/model' require 'active_admin_import/authorization' ::ActiveAdmin::DSL.send(:include, ActiveAdminImport::DSL) - - diff --git a/lib/active_admin_import/authorization.rb b/lib/active_admin_import/authorization.rb index 73527b5..09779db 100644 --- a/lib/active_admin_import/authorization.rb +++ b/lib/active_admin_import/authorization.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true module ActiveAdminImport # Default Authorization permission for ActiveAdminImport module Authorization diff --git a/lib/active_admin_import/dsl.rb b/lib/active_admin_import/dsl.rb index b8b761b..e65ec11 100644 --- a/lib/active_admin_import/dsl.rb +++ b/lib/active_admin_import/dsl.rb @@ -1,48 +1,55 @@ +# frozen_string_literal: true module ActiveAdminImport + # Declares import functionality + # + # Options + # +back+:: resource action to redirect after processing + # +csv_options+:: hash to override default CSV options + # +batch_size+:: integer value of max record count inserted by 1 query/transaction + # +batch_transaction+:: bool (false by default), if transaction is used when batch importing + # and works when :validate is set to true + # +before_import+:: proc for before import action, hook called with importer object + # +after_import+:: proc for after import action, hook called with importer object + # +before_batch_import+:: proc for before each batch action, called with importer object + # +after_batch_import+:: proc for after each batch action, called with importer object + # +validate+:: true|false, means perform validations or not + # +on_duplicate_key_update+:: an Array or Hash, tells activerecord-import + # to use MySQL's ON DUPLICATE KEY UPDATE ability. + # +timestamps+:: true|false, tells activerecord-import to not add timestamps (if false) + # even if record timestamps is disabled in ActiveRecord::Base + # +ignore+:: true|false, tells activerecord-import to use MySQL's INSERT IGNORE ability + # +template+:: custom template rendering + # +template_object+:: object passing to view + # +resource_class+:: resource class name, override to import to another table (default config.resource_class) + # +resource_label+:: resource label value (default config.resource_label) + # +plural_resource_label+:: pluralized resource label value (default config.plural_resource_label) + # module DSL - - - # Declares import functionality - # - # Options - # +back+:: resource action to redirect after processing - # +csv_options+:: hash to override default CSV options - # +batch_size+:: integer value of max record count inserted by 1 query/transaction - # +batch_transaction+:: bool (false by default), if transaction is used when batch importing and works when :validate is set to true - # +before_import+:: proc for before import action, hook called with importer object - # +after_import+:: proc for after import action, hook called with importer object - # +before_batch_import+:: proc for before each batch action, called with importer object - # +after_batch_import+:: proc for after each batch action, called with importer object - # +validate+:: true|false, means perform validations or not - # +on_duplicate_key_update+:: an Array or Hash, tells activerecord-import to use MySQL's ON DUPLICATE KEY UPDATE ability. - # +timestamps+:: true|false, tells activerecord-import to not add timestamps (if false) even if record timestamps is disabled in ActiveRecord::Base - # +ignore+:: true|false, tells activerecord-import to use MySQL's INSERT IGNORE ability - # +template+:: custom template rendering - # +template_object+:: object passing to view - # +resource_class+:: resource class name, override to import to another table (default config.resource_class) - # +resource_label+:: resource label value (default config.resource_label) - # +plural_resource_label+:: pluralized resource label value (default config.plural_resource_label) - # - - - DEFAULT_RESULT_PROC = ->(result, options) do - + DEFAULT_RESULT_PROC = lambda do |result, options| model_name = options[:resource_label].downcase plural_model_name = options[:plural_resource_label].downcase if result.empty? flash[:warning] = I18n.t('active_admin_import.file_empty_error') else - if result.has_failed? - flash[:error] = I18n.t('active_admin_import.failed', count: result.failed.count, model: model_name, plural_model: plural_model_name, message: result.failed_message(limit: 5)) + if result.failed? + flash[:error] = I18n.t( + 'active_admin_import.failed', + count: result.failed.count, + model: model_name, + plural_model: plural_model_name, + message: result.failed_message(limit: 5)) return if options[:batch_transaction] end - if result.has_imported? - flash[:notice] = I18n.t('active_admin_import.imported', count: result.imported_qty, model: model_name, plural_model: plural_model_name) + if result.imported? + flash[:notice] = I18n.t( + 'active_admin_import.imported', + count: result.imported_qty, + model: model_name, + plural_model: plural_model_name) end end end - - + # rubocop:disable Metrics/AbcSize def active_admin_import(options = {}, &block) options.assert_valid_keys(*Options::VALID_OPTIONS) @@ -57,7 +64,10 @@ def active_admin_import(options = {}, &block) action_item :import, only: :index do if authorized?(ActiveAdminImport::Auth::IMPORT, active_admin_config.resource_class) - link_to(I18n.t('active_admin_import.import_model', plural_model: options[:plural_resource_label]), action: :import) + link_to( + I18n.t('active_admin_import.import_model', plural_model: options[:plural_resource_label]), + action: :import + ) end end @@ -66,14 +76,18 @@ def active_admin_import(options = {}, &block) @active_admin_import_model = options[:template_object] @active_admin_import_model.assign_attributes(params[params_key].try(:deep_symbolize_keys) || {}) - #go back to form + # go back to form return render template: options[:template] unless @active_admin_import_model.valid? - @importer = Importer.new(options[:resource_class], @active_admin_import_model, options) + @importer = Importer.new( + options[:resource_class], + @active_admin_import_model, + options + ) begin result = @importer.import if block_given? - instance_eval &block + instance_eval(&block) else instance_exec result, options, &DEFAULT_RESULT_PROC end @@ -83,7 +97,7 @@ def active_admin_import(options = {}, &block) end redirect_to options[:back] end - + # rubocop:enable Metrics/AbcSize end end end diff --git a/lib/active_admin_import/engine.rb b/lib/active_admin_import/engine.rb index 2d7bfb3..e646ec1 100644 --- a/lib/active_admin_import/engine.rb +++ b/lib/active_admin_import/engine.rb @@ -1,9 +1,8 @@ +# frozen_string_literal: true require 'rails' module ActiveAdminImport class Engine < ::Rails::Engine - config.mount_at = '/' - end -end \ No newline at end of file +end diff --git a/lib/active_admin_import/import_result.rb b/lib/active_admin_import/import_result.rb index db86308..c58e485 100644 --- a/lib/active_admin_import/import_result.rb +++ b/lib/active_admin_import/import_result.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true module ActiveAdminImport class ImportResult attr_reader :failed, :total @@ -16,11 +17,11 @@ def imported_qty total - failed.count end - def has_imported? + def imported? imported_qty > 0 end - def has_failed? + def failed? @failed.any? end @@ -30,10 +31,10 @@ def empty? def failed_message(options = {}) limit = options.fetch(:limit, failed.count) - failed.first(limit).map{|record| + failed.first(limit).map do |record| errors = record.errors - (errors.full_messages.zip errors.keys.map{|k| record.send k}).map{|ms| ms.join(' - ')}.join(', ') - }.join(" ; ") + (errors.full_messages.zip errors.keys.map { |k| record.send k }).map { |ms| ms.join(' - ') }.join(', ') + end.join(' ; ') end end end diff --git a/lib/active_admin_import/importer.rb b/lib/active_admin_import/importer.rb index 881ca7f..0561785 100644 --- a/lib/active_admin_import/importer.rb +++ b/lib/active_admin_import/importer.rb @@ -1,23 +1,23 @@ +# frozen_string_literal: true require 'csv' module ActiveAdminImport class Importer - - attr_reader :resource, :options, :result, :model + attr_reader :resource, :options, :result, :model, :headers_array attr_accessor :csv_lines, :headers OPTIONS = [ - :validate, - :on_duplicate_key_update, - :ignore, - :timestamps, - :before_import, - :after_import, - :before_batch_import, - :after_batch_import, - :headers_rewrites, - :batch_size, - :batch_transaction, - :csv_options + :validate, + :on_duplicate_key_update, + :ignore, + :timestamps, + :before_import, + :after_import, + :before_batch_import, + :after_batch_import, + :headers_rewrites, + :batch_size, + :batch_transaction, + :csv_options ].freeze def initialize(resource, model, options) @@ -48,14 +48,20 @@ def import end def import_options - @import_options ||= options.slice(:validate, :on_duplicate_key_update, :ignore, :timestamps, :batch_transaction) + @import_options ||= options.slice( + :validate, + :on_duplicate_key_update, + :ignore, + :timestamps, + :batch_transaction + ) end def batch_replace(header_key, options) index = header_index(header_key) csv_lines.map! do |line| from = line[index] - line[index] = options[from] if options.has_key?(from) + line[index] = options[from] if options.key?(from) line end end @@ -71,10 +77,12 @@ def header_index(header_key) protected def process_file - lines, batch_size = [], options[:batch_size].to_i + lines = [] + batch_size = options[:batch_size].to_i File.open(file.path) do |f| # capture headers if not exist - prepare_headers { CSV.parse(f.readline, @csv_options).first } + @headers_array = CSV.parse(f.readline, @csv_options).first + prepare_headers { @headers_array } f.each_line do |line| lines << line if line.present? if lines.size == batch_size || f.eof? @@ -87,12 +95,20 @@ def process_file end def prepare_headers - headers = self.headers.present? ? self.headers.map(&:to_s) : yield - @headers = Hash[headers.zip(headers.map { |el| el.underscore.gsub(/\s+/, '_') })].with_indifferent_access - @headers.merge!(options[:headers_rewrites].symbolize_keys.slice(*@headers.symbolize_keys.keys)) + @headers = headers.present? ? headers.map(&:to_s) : yield + @headers = construct_hash(@headers) + @headers.merge!(options[:headers_rewrites] + .symbolize_keys + .slice(*@headers.symbolize_keys.keys)) @headers end + def construct_hash + Hash[ + headers.zip(headers.map { |el| el.underscore.gsub(/\s+/, '_') }) + ].with_indifferent_access + end + def run_callback(name) options[name].call(self) if options[name].is_a?(Proc) end @@ -109,7 +125,7 @@ def batch_import end def assign_options(options) - @options = {batch_size: 1000, validate: true}.merge(options.slice(*OPTIONS)) + @options = { batch_size: 1000, validate: true }.merge(options.slice(*OPTIONS)) detect_csv_options end @@ -120,6 +136,5 @@ def detect_csv_options options[:csv_options] || {} end.reject { |_, value| value.blank? } end - end end diff --git a/lib/active_admin_import/model.rb b/lib/active_admin_import/model.rb index 34ae7c0..042a8f4 100644 --- a/lib/active_admin_import/model.rb +++ b/lib/active_admin_import/model.rb @@ -1,17 +1,17 @@ # encoding: utf-8 +# frozen_string_literal: true require 'rchardet' module ActiveAdminImport class Model - include ActiveModel::Model include ActiveModel::Validations include ActiveModel::Validations::Callbacks validates :file, presence: { - message: ->(*_){ I18n.t("active_admin_import.no_file_error") } - }, unless: ->(me){ me.new_record? } + message: ->(*_) { I18n.t('active_admin_import.no_file_error') } + }, unless: ->(me) { me.new_record? } validate :correct_content_type, if: ->(me) { me.file.present? } validate :file_contents_present, if: ->(me) { me.file.present? } @@ -21,7 +21,7 @@ class Model attr_reader :attributes - def initialize(args={}) + def initialize(args = {}) @new_record = true @attributes = {} assign_attributes(default_attributes.merge(args), true) @@ -44,21 +44,21 @@ def default_attributes allow_archive: true, csv_headers: [], file: nil, - force_encoding: "UTF-8", - hint: "" + force_encoding: 'UTF-8', + hint: '' } end def allow_archive? - !!attributes[:allow_archive] + attributes[:allow_archive] end def new_record? - !!@new_record + @new_record end def force_encoding? - !!attributes[:force_encoding] + attributes[:force_encoding] end def persisted? @@ -69,7 +69,7 @@ def archive? file_type == 'application/zip' end - alias :to_hash :attributes + alias to_hash attributes protected @@ -91,33 +91,35 @@ def encode_file def unzip_file Zip::File.open(file_path) do |zip_file| self.file = Tempfile.new('active-admin-import-unzipped') - data = zip_file.entries.select { |f| f.file? }.first.get_input_stream.read - self.file << data - self.file.close + data = zip_file.entries.select(&:file?).first.get_input_stream.read + file << data + file.close end end def csv_allowed_types [ - 'text/csv', - 'text/x-csv', - 'text/x-comma-separated-values', - 'text/comma-separated-values', - 'application/csv', - 'application/vnd.ms-excel', - 'application/vnd.msexcel', - + 'text/csv', + 'text/x-csv', + 'text/x-comma-separated-values', + 'text/comma-separated-values', + 'application/csv', + 'application/vnd.ms-excel', + 'application/vnd.msexcel' + ] end def correct_content_type - unless file.blank? || file.is_a?(Tempfile) - errors.add(:file, I18n.t('active_admin_import.file_format_error')) unless csv_allowed_types.include? file_type - end + return unless file.blank? || + file.is_a?(Tempfile) || + (csv_allowed_types.include? file_type) + errors.add(:file, I18n.t('active_admin_import.file_format_error')) end def file_contents_present - errors.add(:file, I18n.t('active_admin_import.file_empty_error')) if File.zero?(file_path) + return if File.zero?(file_path) + errors.add(:file, I18n.t('active_admin_import.file_empty_error')) end def file_type @@ -128,10 +130,8 @@ def file_type end end - protected - def define_methods_for(attr_name) - #generate methods for instance object by attributes + # generate methods for instance object by attributes singleton_class.class_eval do define_set_method(attr_name) define_get_method(attr_name) @@ -145,7 +145,7 @@ def encode(data) invalid: :replace, undef: :replace, universal_newline: true ) begin - data.sub("\xEF\xBB\xBF", '') # bom + data.sub("\xEF\xBB\xBF", '') # bom rescue StandardError => _ data end @@ -170,15 +170,14 @@ def content_encode(data) class <["has already been taken"]} - Author.create(name: nil, last_name: 'Doe') # {:name=>["can't be blank"], :last_name=>["has already been taken"]} + # {:last_name=>["has already been taken"]} + Author.create(name: 'Jim', last_name: 'Doe'), + # {:name=>["can't be blank"], :last_name=>["has already been taken"]} + Author.create(name: nil, last_name: 'Doe') ] end - it "should work without any failed instances" do - expect(import_result.failed_message).to eq("") + it 'should work without any failed instances' do + expect(import_result.failed_message).to eq('') end - it "should work" do + it 'should work' do import_result.add(@result, 4) - expect(import_result.failed_message).to eq("Last name has already been taken - Doe ; Name can't be blank - , Last name has already been taken - Doe") + expect(import_result.failed_message) + .to eq( + "Last name has already been taken - Doe ; Name can't be blank - , Last name has already been taken - Doe" + ) end - it "should work on limit param" do + it 'should work on limit param' do import_result.add(@result, 4) - expect(import_result.failed_message(limit: 1)).to eq("Last name has already been taken - Doe") + expect(import_result.failed_message(limit: 1)).to eq('Last name has already been taken - Doe') end end end diff --git a/spec/import_spec.rb b/spec/import_spec.rb index bc2d249..a53e36d 100644 --- a/spec/import_spec.rb +++ b/spec/import_spec.rb @@ -1,7 +1,7 @@ +# frozen_string_literal: true require 'spec_helper' describe 'import', type: :feature do - shared_examples 'successful inserts' do |encoding, csv_file_name| let(:options) do attributes = { force_encoding: encoding } @@ -12,8 +12,8 @@ upload_file!(csv_file_name) end - it "should import file with many records" do - expect(page).to have_content "Successfully imported 2 authors" + it 'should import file with many records' do + expect(page).to have_content 'Successfully imported 2 authors' expect(Author.count).to eq(2) Author.all.each do |author| expect(author).to be_valid @@ -24,124 +24,127 @@ end def with_zipped_csv(name, &block) - zip_file = File.expand_path("./spec/fixtures/files/#{name}.zip") begin Zip::File.open(zip_file, Zip::File::CREATE) do |z| z.add "#{name}.csv", File.expand_path("./spec/fixtures/files/#{name}.csv") end - instance_eval &block + instance_eval(&block) ensure - File.delete zip_file rescue nil + begin + File.delete zip_file + rescue + nil + end end end - def upload_file!(name, ext='csv') + def upload_file!(name, ext = 'csv') attach_file('active_admin_import_model_file', File.expand_path("./spec/fixtures/files/#{name}.#{ext}")) find_button('Import').click end - context "posts index" do + context 'posts index' do before do - Author.create!(name: "John", last_name: "Doe") - Author.create!(name: "Jane", last_name: "Roe") + Author.create!(name: 'John', last_name: 'Doe') + Author.create!(name: 'Jane', last_name: 'Roe') end - context "for csv for particular author" do + context 'for csv for particular author' do let(:author) { Author.take } shared_examples 'successful inserts for author' do - it "should use predefined author_id" do + it 'should use predefined author_id' do expect(Post.where(author_id: author.id).count).to eq(Post.count) end - it "should be imported" do + it 'should be imported' do expect(Post.count).to eq(2) - expect(page).to have_content "Successfully imported 2 posts" + expect(page).to have_content 'Successfully imported 2 posts' end end - context "no headers" do + context 'no headers' do before do add_post_resource(template_object: ActiveAdminImport::Model.new(author_id: author.id, csv_headers: [:title, :body, :author_id]), validate: true, - before_batch_import: ->(importer) do + before_batch_import: lambda do |importer| importer.csv_lines.map! { |row| row << importer.model.author_id } end - ) + ) - visit "/admin/posts/import" + visit '/admin/posts/import' upload_file!(:posts_for_author_no_headers) end include_examples 'successful inserts for author' end - context "with headers" do + context 'with headers' do before do add_post_resource(template_object: ActiveAdminImport::Model.new(author_id: author.id), validate: true, - before_batch_import: ->(importer) do + before_batch_import: lambda do |importer| importer.csv_lines.map! { |row| row << importer.model.author_id } - importer.headers.merge!({ :'Author Id' => :author_id }) + importer.headers.merge!('Author Id': :author_id) end - ) + ) - visit "/admin/posts/import" + visit '/admin/posts/import' upload_file!(:posts_for_author) end include_examples 'successful inserts for author' end end - context "for csv with author name" do + context 'for csv with author name' do before do add_post_resource( - validate: true, - template_object: ActiveAdminImport::Model.new, - headers_rewrites: { :'Author Name' => :author_id }, - before_batch_import: ->(importer) do - authors_names = importer.values_at(:author_id) - # replacing author name with author id - authors = Author.where(name: authors_names).pluck(:name, :id) - #{"Jane" => 2, "John" => 1} - options = Hash[*authors.flatten] - importer.batch_replace(:author_id, options) - end + validate: true, + template_object: ActiveAdminImport::Model.new, + headers_rewrites: { 'Author Name': :author_id }, + before_batch_import: lambda do |importer| + authors_names = importer.values_at(:author_id) + # replacing author name with author id + authors = Author.where(name: authors_names).pluck(:name, :id) + # {"Jane" => 2, "John" => 1} + options = Hash[*authors.flatten] + importer.batch_replace(:author_id, options) + end ) - visit "/admin/posts/import" + visit '/admin/posts/import' upload_file!(:posts) end - it "should resolve author_id by author name" do + it 'should resolve author_id by author name' do Post.all.each do |post| expect(Author.where(id: post.author.id)).to be_present end end - it "should be imported" do + it 'should be imported' do expect(Post.count).to eq(2) - expect(page).to have_content "Successfully imported 2 posts" + expect(page).to have_content 'Successfully imported 2 posts' end end end - context "authors index" do + context 'authors index' do before do add_author_resource end - it "should navigate to import page" do - #todo: removing this causes undefined method `ransack' for # + it 'should navigate to import page' do + # TODO: removing this causes undefined method `ransack' for # allow_any_instance_of(Admin::AuthorsController).to receive(:find_collection).and_return(Author.all) visit '/admin/authors' find_link('Import Authors').click - expect(current_path).to eq("/admin/authors/import") + expect(current_path).to eq('/admin/authors/import') end end - context "with custom block" do + context 'with custom block' do before do add_author_resource({}) do flash[:notice] = 'some custom message' @@ -149,44 +152,42 @@ def upload_file!(name, ext='csv') visit '/admin/authors/import' end - it "should display notice from custom block" do + it 'should display notice from custom block' do upload_file!(:author) - expect(page).to have_content "some custom message" + expect(page).to have_content 'some custom message' end - end - context "authors already exist" do + context 'authors already exist' do before do - Author.create!(id: 1, name: "Jane", last_name: "Roe") - Author.create!(id: 2, name: "John", last_name: "Doe") + Author.create!(id: 1, name: 'Jane', last_name: 'Roe') + Author.create!(id: 2, name: 'John', last_name: 'Doe') end - context "having authors with the same Id" do + context 'having authors with the same Id' do before do add_author_resource( - before_batch_import: ->(importer) do - Author.where(id: importer.values_at("id")).delete_all - end + before_batch_import: lambda do |importer| + Author.where(id: importer.values_at('id')).delete_all + end ) - visit "/admin/authors/import" + visit '/admin/authors/import' upload_file!(:authors_with_ids) end - it "should replace authors" do - expect(page).to have_content "Successfully imported 2 authors" + it 'should replace authors' do + expect(page).to have_content 'Successfully imported 2 authors' expect(Author.count).to eq(2) end - it "should replace authors by id" do - expect(Author.find(1).name).to eq("John") - expect(Author.find(2).name).to eq("Jane") + it 'should replace authors by id' do + expect(Author.find(1).name).to eq('John') + expect(Author.find(2).name).to eq('Jane') end end end - context "with valid options" do - + context 'with valid options' do let(:options) { {} } before do @@ -194,32 +195,31 @@ def upload_file!(name, ext='csv') visit '/admin/authors/import' end - it "has valid form" do + it 'has valid form' do form = find('#new_active_admin_import_model') - expect(form['action']).to eq("/admin/authors/do_import") - expect(form['enctype']).to eq("multipart/form-data") - file_input = form.find("input#active_admin_import_model_file") - expect(file_input[:type]).to eq("file") + expect(form['action']).to eq('/admin/authors/do_import') + expect(form['enctype']).to eq('multipart/form-data') + file_input = form.find('input#active_admin_import_model_file') + expect(file_input[:type]).to eq('file') expect(file_input.value).to be_blank - submit_input = form.find("#active_admin_import_model_submit_action input") - expect(submit_input[:value]).to eq("Import") - expect(submit_input[:type]).to eq("submit") + submit_input = form.find('#active_admin_import_model_submit_action input') + expect(submit_input[:value]).to eq('Import') + expect(submit_input[:type]).to eq('submit') end - context "with hint defined" do + context 'with hint defined' do let(:options) do - { template_object: ActiveAdminImport::Model.new(hint: "hint") } + { template_object: ActiveAdminImport::Model.new(hint: 'hint') } end - it "renders hint at upload page" do + it 'renders hint at upload page' do expect(page).to have_content options[:template_object].hint end end - context "when importing file" do - + context 'when importing file' do [:empty, :only_headers].each do |file| context "when #{file} file" do - it "should render warning" do + it 'should render warning' do upload_file!(file) expect(page).to have_content I18n.t('active_admin_import.file_empty_error') expect(Author.count).to eq(0) @@ -227,142 +227,142 @@ def upload_file!(name, ext='csv') end end - context "when no file" do - it "should render error" do + context 'when no file' do + it 'should render error' do find_button('Import').click expect(Author.count).to eq(0) expect(page).to have_content I18n.t('active_admin_import.no_file_error') end end - context "auto detect encoding" do + context 'auto detect encoding' do include_examples 'successful inserts', :auto, :authors_win1251_win_endline end - context "Win1251" do + context 'Win1251' do include_examples 'successful inserts', 'windows-1251', :authors_win1251_win_endline end - context "BOM" do - it "should import file with many records" do + context 'BOM' do + it 'should import file with many records' do upload_file!(:authors_bom) - expect(page).to have_content "Successfully imported 2 authors" + expect(page).to have_content 'Successfully imported 2 authors' expect(Author.count).to eq(2) end end - context "with headers" do - it "should import file with many records" do + context 'with headers' do + it 'should import file with many records' do upload_file!(:authors) - expect(page).to have_content "Successfully imported 2 authors" + expect(page).to have_content 'Successfully imported 2 authors' expect(Author.count).to eq(2) end - it "should import file with 1 record" do + it 'should import file with 1 record' do upload_file!(:author) - expect(page).to have_content "Successfully imported 1 author" + expect(page).to have_content 'Successfully imported 1 author' expect(Author.count).to eq(1) end end - context "without headers" do - context "with known csv headers" do + context 'without headers' do + context 'with known csv headers' do let(:options) do attributes = { csv_headers: ['Name', 'Last name', 'Birthday'] } { template_object: ActiveAdminImport::Model.new(attributes) } end - it "should import file" do + it 'should import file' do upload_file!(:authors_no_headers) - expect(page).to have_content "Successfully imported 2 authors" + expect(page).to have_content 'Successfully imported 2 authors' expect(Author.count).to eq(2) end end - context "with unknown csv headers" do - it "should render error" do + context 'with unknown csv headers' do + it 'should render error' do upload_file!(:authors_no_headers) - expect(page).to have_content "Error:" + expect(page).to have_content 'Error:' expect(Author.count).to eq(0) end end end - context "with invalid data insert on DB constraint" do + context 'with invalid data insert on DB constraint' do # :name field has an uniq index - it "should render error" do + it 'should render error' do upload_file!(:authors_invalid_db) - expect(page).to have_content "Error:" + expect(page).to have_content 'Error:' expect(Author.count).to eq(0) end end - context "with invalid data insert on model validation" do + context 'with invalid data insert on model validation' do let(:options) { { validate: true } } before do - Author.create!(name: "John", last_name: "Doe") + Author.create!(name: 'John', last_name: 'Doe') end - it "should render both successful and failed message" do + it 'should render both successful and failed message' do upload_file!(:authors_invalid_model) - expect(page).to have_content "Failed to import 1 author" - expect(page).to have_content "Successfully imported 1 author" + expect(page).to have_content 'Failed to import 1 author' + expect(page).to have_content 'Successfully imported 1 author' expect(Author.count).to eq(2) end - context "use batch_transaction to make transaction work on model validation" do + context 'use batch_transaction to make transaction work on model validation' do let(:options) { { validate: true, batch_transaction: true } } - it "should render only the failed message" do + it 'should render only the failed message' do upload_file!(:authors_invalid_model) - expect(page).to have_content "Failed to import 1 author" - expect(page).to_not have_content "Successfully imported" + expect(page).to have_content 'Failed to import 1 author' + expect(page).to_not have_content 'Successfully imported' expect(Author.count).to eq(1) end end end - context "with invalid records" do - context "with validation" do - it "should render error" do + context 'with invalid records' do + context 'with validation' do + it 'should render error' do upload_file!(:author_invalid) - expect(page).to have_content "Failed to import 1 author" + expect(page).to have_content 'Failed to import 1 author' expect(Author.count).to eq(0) end end - context "without validation" do + context 'without validation' do let(:options) { { validate: false } } - it "should render error" do + it 'should render error' do upload_file!(:author_invalid) - expect(page).to have_content "Successfully imported 1 author" + expect(page).to have_content 'Successfully imported 1 author' expect(Author.count).to eq(1) end end end - context "when zipped" do - context "when allowed" do - it "should import file" do + context 'when zipped' do + context 'when allowed' do + it 'should import file' do with_zipped_csv(:authors) do upload_file!(:authors, :zip) - expect(page).to have_content "Successfully imported 2 authors" + expect(page).to have_content 'Successfully imported 2 authors' expect(Author.count).to eq(2) end end end - context "when not allowed" do + context 'when not allowed' do let(:options) do attributes = { allow_archive: false } { template_object: ActiveAdminImport::Model.new(attributes) } end - it "should render error" do + it 'should render error' do with_zipped_csv(:authors) do upload_file!(:authors, :zip) expect(page).to have_content I18n.t('active_admin_import.file_format_error') @@ -372,43 +372,43 @@ def upload_file!(name, ext='csv') end end - context "with different header attribute names" do + context 'with different header attribute names' do let(:options) do - { headers_rewrites: { :'Second name' => :last_name } } + { headers_rewrites: { 'Second name': :last_name } } end - it "should import file" do + it 'should import file' do upload_file!(:author_broken_header) - expect(page).to have_content "Successfully imported 1 author" + expect(page).to have_content 'Successfully imported 1 author' expect(Author.count).to eq(1) end end - context "with semicolons separator" do + context 'with semicolons separator' do let(:options) do - attributes = { csv_options: { col_sep: ";" } } + attributes = { csv_options: { col_sep: ';' } } { template_object: ActiveAdminImport::Model.new(attributes) } end - it "should import file" do + it 'should import file' do upload_file!(:authors_with_semicolons) - expect(page).to have_content "Successfully imported 2 authors" + expect(page).to have_content 'Successfully imported 2 authors' expect(Author.count).to eq(2) end end end - context "with callback procs options" do + context 'with callback procs options' do let(:options) do { - before_import: ->(_) { true }, - after_import: ->(_) { true }, - before_batch_import: ->(_) { true }, - after_batch_import: ->(_) { true } + before_import: ->(_) { true }, + after_import: ->(_) { true }, + before_batch_import: ->(_) { true }, + after_batch_import: ->(_) { true } } end - it "should call each callback" do + it 'should call each callback' do expect(options[:before_import]).to receive(:call).with(kind_of(ActiveAdminImport::Importer)) expect(options[:after_import]).to receive(:call).with(kind_of(ActiveAdminImport::Importer)) expect(options[:before_batch_import]).to receive(:call).with(kind_of(ActiveAdminImport::Importer)) @@ -417,16 +417,13 @@ def upload_file!(name, ext='csv') expect(Author.count).to eq(2) end end - end - context "with invalid options" do + context 'with invalid options' do let(:options) { { invalid_option: :invalid_value } } - it "should raise TypeError" do + it 'should raise TypeError' do expect { add_author_resource(options) }.to raise_error(ArgumentError) end - end - end diff --git a/spec/model_spec.rb b/spec/model_spec.rb index 51b8580..55598f3 100644 --- a/spec/model_spec.rb +++ b/spec/model_spec.rb @@ -1,5 +1,6 @@ +# frozen_string_literal: true require 'spec_helper' require 'active_model_lint' -describe ActiveAdminImport::Model do - it_behaves_like "ActiveModel" -end \ No newline at end of file +describe ActiveAdminImport::Model do + it_behaves_like 'ActiveModel' +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3485fb9..aded259 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true require 'coveralls' Coveralls.wear! @@ -5,7 +6,7 @@ $LOAD_PATH << File.expand_path('../support', __FILE__) ENV['BUNDLE_GEMFILE'] = File.expand_path('../../Gemfile', __FILE__) -require "bundler" +require 'bundler' Bundler.setup ENV['RAILS_ENV'] = 'test' @@ -14,15 +15,13 @@ ENV['RAILS'] = Rails.version ENV['RAILS_ROOT'] = File.expand_path("../rails/rails-#{ENV['RAILS']}", __FILE__) # Create the test app if it doesn't exists -unless File.exists?(ENV['RAILS_ROOT']) - system 'rake setup' -end +system 'rake setup' unless File.exist?(ENV['RAILS_ROOT']) require 'active_model' # require ActiveRecord to ensure that Ransack loads correctly require 'active_record' require 'active_admin' -ActiveAdmin.application.load_paths = [ENV['RAILS_ROOT'] + "/app/admin"] +ActiveAdmin.application.load_paths = [ENV['RAILS_ROOT'] + '/app/admin'] require ENV['RAILS_ROOT'] + '/config/environment.rb' # Disabling authentication in specs so that we don't have to worry about # it allover the place @@ -36,13 +35,10 @@ require 'capybara/poltergeist' Capybara.register_driver :poltergeist do |app| - Capybara::Poltergeist::Driver.new(app, { - js_errors: true, - timeout: 80, - debug: true, - :phantomjs_options => ['--debug=no', '--load-images=no'] - - }) + Capybara::Poltergeist::Driver.new(app, js_errors: true, + timeout: 80, + debug: true, + phantomjs_options: ['--debug=no', '--load-images=no']) end Capybara.javascript_driver = :poltergeist @@ -62,4 +58,3 @@ DatabaseCleaner.clean end end - diff --git a/spec/support/active_model_lint.rb b/spec/support/active_model_lint.rb index 68838c7..9611abd 100644 --- a/spec/support/active_model_lint.rb +++ b/spec/support/active_model_lint.rb @@ -1,9 +1,10 @@ -shared_examples_for "ActiveModel" do +# frozen_string_literal: true +shared_examples_for 'ActiveModel' do include ActiveModel::Lint::Tests # to_s is to support ruby-1.9 - ActiveModel::Lint::Tests.public_instance_methods.map{|m| m.to_s}.grep(/^test/).each do |m| - example m.gsub('_',' ') do + ActiveModel::Lint::Tests.public_instance_methods.map(&:to_s).grep(/^test/).each do |m| + example m.tr('_', ' ') do send m end end @@ -11,4 +12,4 @@ def model subject end -end \ No newline at end of file +end diff --git a/spec/support/admin.rb b/spec/support/admin.rb index 2460bac..03fb0bd 100644 --- a/spec/support/admin.rb +++ b/spec/support/admin.rb @@ -1,20 +1,16 @@ +# frozen_string_literal: true def add_author_resource(options = {}, &block) - ActiveAdmin.register Author do - config.filters = false - active_admin_import(options, &block) + config.filters = false + active_admin_import(options, &block) end Rails.application.reload_routes! - end - def add_post_resource(options = {}, &block) - ActiveAdmin.register Post do - config.filters = false - active_admin_import(options, &block) + config.filters = false + active_admin_import(options, &block) end Rails.application.reload_routes! - end diff --git a/spec/support/rails_template.rb b/spec/support/rails_template.rb index 23cc218..2f999b3 100644 --- a/spec/support/rails_template.rb +++ b/spec/support/rails_template.rb @@ -1,28 +1,46 @@ +# frozen_string_literal: true # Rails template to build the sample app for specs generate :model, 'author name:string{10}:uniq last_name:string birthday:date' generate :model, 'post title:string:uniq body:text author:references' -#Add validation -inject_into_file "app/models/author.rb", " validates_presence_of :name\n validates_uniqueness_of :last_name\n", after: "Base\n" -inject_into_file "app/models/post.rb", " validates_presence_of :author\n", after: ":author\n" +# Add validation +inject_into_file( + 'app/models/author.rb', + " validates_presence_of :name\n validates_uniqueness_of :last_name\n", + after: "Base\n" +) +inject_into_file( + 'app/models/post.rb', + " validates_presence_of :author\n", + after: ":author\n" +) # Configure default_url_options in test environment -inject_into_file "config/environments/test.rb", " config.action_mailer.default_url_options = { :host => 'example.com' }\n", after: "config.cache_classes = true\n" +inject_into_file( + 'config/environments/test.rb', + " config.action_mailer.default_url_options = { :host => 'example.com' }\n", + after: "config.cache_classes = true\n" +) # Add our local Active Admin to the load path -inject_into_file "config/environment.rb", "\n$LOAD_PATH.unshift('#{File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib'))}')\nrequire \"active_admin\"\n", after: "require File.expand_path('../application', __FILE__)" +load_path = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib')) +inject_into_file( + 'config/environment.rb', + "\n$LOAD_PATH.unshift('#{load_path}')\nrequire \"active_admin\"\n", + after: "require File.expand_path('../application', __FILE__)" +) -run "rm Gemfile" +run 'rm Gemfile' $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) generate :'active_admin:install --skip-users' generate :'formtastic:install' -run "rm -r test" -run "rm -r spec" +run 'rm -r test' +run 'rm -r spec' route "root :to => 'admin/dashboard#index'" -rake "db:migrate" +rake 'db:migrate' diff --git a/tasks/test.rake b/tasks/test.rake index 5769ad5..81823ce 100644 --- a/tasks/test.rake +++ b/tasks/test.rake @@ -1,6 +1,9 @@ -desc "Creates a test rails app for the specs to run against" +# frozen_string_literal: true +# rubocop:disable Metrics/LineLength +desc 'Creates a test rails app for the specs to run against' task :setup do require 'rails/version' - system("mkdir spec/rails") unless File.exists?("spec/rails") + system('mkdir spec/rails') unless File.exist?('spec/rails') system "bundle exec rails new spec/rails/rails-#{Rails::VERSION::STRING} -m spec/support/rails_template.rb --skip-spring" -end \ No newline at end of file +end +# rubocop:enable Metrics/LineLength