From 41afec70f5c6b2431f045388b14a80d194329a20 Mon Sep 17 00:00:00 2001 From: Josemar Davi Luedke Date: Tue, 13 Aug 2013 15:53:03 -0500 Subject: [PATCH 01/77] Clean up deprecations for Rails 4 --- lib/postgres-copy/active_record.rb | 4 ++-- spec/pg_copy_from_binary_spec.rb | 4 ++-- spec/pg_copy_from_spec.rb | 28 ++++++++++++++-------------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/postgres-copy/active_record.rb b/lib/postgres-copy/active_record.rb index 142beb6..aaff61a 100644 --- a/lib/postgres-copy/active_record.rb +++ b/lib/postgres-copy/active_record.rb @@ -11,9 +11,9 @@ def self.pg_copy_to path = nil, options = {} if path raise "You have to choose between exporting to a file or receiving the lines inside a block" if block_given? - connection.execute "COPY (#{self.scoped.to_sql}) TO #{sanitize(path)} WITH #{options_string}" + connection.execute "COPY (#{self.all.to_sql}) TO #{sanitize(path)} WITH #{options_string}" else - connection.execute "COPY (#{self.scoped.to_sql}) TO STDOUT WITH #{options_string}" + connection.execute "COPY (#{self.all.to_sql}) TO STDOUT WITH #{options_string}" while line = connection.raw_connection.get_copy_data do yield(line) if block_given? end diff --git a/spec/pg_copy_from_binary_spec.rb b/spec/pg_copy_from_binary_spec.rb index 9af2886..22508fd 100644 --- a/spec/pg_copy_from_binary_spec.rb +++ b/spec/pg_copy_from_binary_spec.rb @@ -10,12 +10,12 @@ it "should import from file if path is passed without field_map" do TestModel.pg_copy_from File.expand_path('spec/fixtures/2_col_binary_data.dat'), :format => :binary, columns: [:id, :data] - TestModel.order(:id).all.map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'text'}] + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'text'}] end it "should import from file if columns are not specified" do TestModel.pg_copy_from File.expand_path('spec/fixtures/2_col_binary_data.dat'), :format => :binary - TestModel.order(:id).all.map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'text'}] + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'text'}] end end diff --git a/spec/pg_copy_from_spec.rb b/spec/pg_copy_from_spec.rb index 949c0f9..a6edec3 100644 --- a/spec/pg_copy_from_spec.rb +++ b/spec/pg_copy_from_spec.rb @@ -10,29 +10,29 @@ it "should import from file if path is passed without field_map" do TestModel.pg_copy_from File.expand_path('spec/fixtures/comma_with_header.csv') - TestModel.order(:id).all.map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end it "should import from IO without field_map" do TestModel.pg_copy_from File.open(File.expand_path('spec/fixtures/comma_with_header.csv'), 'r') - TestModel.order(:id).all.map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end it "should import with custom delimiter from path" do TestModel.pg_copy_from File.expand_path('spec/fixtures/semicolon_with_header.csv'), :delimiter => ';' - TestModel.order(:id).all.map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end it "should import with custom delimiter from IO" do TestModel.pg_copy_from File.open(File.expand_path('spec/fixtures/semicolon_with_header.csv'), 'r'), :delimiter => ';' - TestModel.order(:id).all.map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end it "should import and allow changes in block" do TestModel.pg_copy_from(File.open(File.expand_path('spec/fixtures/comma_with_header.csv'), 'r')) do |row| row[1] = 'changed this data' end - TestModel.order(:id).all.map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'changed this data'}] + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'changed this data'}] end it "should import 2 lines and allow changes in block" do @@ -45,37 +45,37 @@ it "should be able to copy from using custom set of columns" do TestModel.pg_copy_from(File.open(File.expand_path('spec/fixtures/tab_only_data.csv'), 'r'), :delimiter => "\t", :columns => ["data"]) - TestModel.order(:id).all.map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end it "default set of columns should be all table columns minus [id, created_at, updated_at]" do ExtraField.pg_copy_from(File.open(File.expand_path('spec/fixtures/comma_with_header.csv'), 'r')) - ExtraField.order(:id).all.map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1', 'created_at' => nil, 'updated_at' => nil}] + ExtraField.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1', 'created_at' => nil, 'updated_at' => nil}] end it "should not expect a header when :header is false" do TestModel.pg_copy_from(File.open(File.expand_path('spec/fixtures/comma_without_header.csv'), 'r'), :header => false, :columns => [:id,:data]) - TestModel.order(:id).all.map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end it "should use the table name given by :table" do ActiveRecord::Base.pg_copy_from(File.open(File.expand_path('spec/fixtures/comma_without_header.csv'), 'r'), :header => false, :columns => [:id,:data], :table => "test_models") - TestModel.order(:id).all.map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end it "should be able to map the header in the file to diferent column names" do TestModel.pg_copy_from(File.open(File.expand_path('spec/fixtures/tab_with_different_header.csv'), 'r'), :delimiter => "\t", :map => {'cod' => 'id', 'info' => 'data'}) - TestModel.order(:id).all.map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end it "should be able to map the header in the file to diferent column names with custom delimiter" do TestModel.pg_copy_from(File.open(File.expand_path('spec/fixtures/semicolon_with_different_header.csv'), 'r'), :delimiter => ';', :map => {'cod' => 'id', 'info' => 'data'}) - TestModel.order(:id).all.map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end it "should ignore empty lines" do TestModel.pg_copy_from(File.open(File.expand_path('spec/fixtures/tab_with_extra_line.csv'), 'r'), :delimiter => "\t") - TestModel.order(:id).all.map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end #we should implement this later @@ -83,12 +83,12 @@ #lambda do #TestModel.pg_copy_from(File.open(File.expand_path('spec/fixtures/tab_with_error.csv'), 'r')) #end.should raise_error - #TestModel.order(:id).all.map{|r| r.attributes}.should == [] + #TestModel.order(:id).map{|r| r.attributes}.should == [] #end it "should copy from even when table fields need identifier quoting" do ReservedWordModel.pg_copy_from File.expand_path('spec/fixtures/reserved_words.csv'), :delimiter => "\t" - ReservedWordModel.order(:id).all.map{|r| r.attributes}.should == [{"group"=>"group name", "id"=>1, "select"=>"test select"}] + ReservedWordModel.order(:id).map{|r| r.attributes}.should == [{"group"=>"group name", "id"=>1, "select"=>"test select"}] end end From 4487ed63dd955d6f485f3f9d759d2817fbd5b37b Mon Sep 17 00:00:00 2001 From: Josemar Davi Luedke Date: Wed, 14 Aug 2013 12:13:12 -0500 Subject: [PATCH 02/77] Change the gem dependencies for rails 4 --- Gemfile.lock | 115 ++++++++++++++++++++---------------------- postgres-copy.gemspec | 8 +-- 2 files changed, 60 insertions(+), 63 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index ac07f2e..37aa1a0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,84 +1,75 @@ PATH remote: . specs: - postgres-copy (0.6.0) - activerecord (>= 3.0.0) + postgres-copy (0.7.0) + activerecord (~> 4.0) pg - rails (>= 3.0.0) + rails (~> 4.0) responders GEM remote: https://rubygems.org/ specs: - actionmailer (3.2.13) - actionpack (= 3.2.13) + actionmailer (4.0.0) + actionpack (= 4.0.0) mail (~> 2.5.3) - actionpack (3.2.13) - activemodel (= 3.2.13) - activesupport (= 3.2.13) - builder (~> 3.0.0) + actionpack (4.0.0) + activesupport (= 4.0.0) + builder (~> 3.1.0) erubis (~> 2.7.0) - journey (~> 1.0.4) - rack (~> 1.4.5) - rack-cache (~> 1.2) - rack-test (~> 0.6.1) - sprockets (~> 2.2.1) - activemodel (3.2.13) - activesupport (= 3.2.13) - builder (~> 3.0.0) - activerecord (3.2.13) - activemodel (= 3.2.13) - activesupport (= 3.2.13) - arel (~> 3.0.2) - tzinfo (~> 0.3.29) - activeresource (3.2.13) - activemodel (= 3.2.13) - activesupport (= 3.2.13) - activesupport (3.2.13) - i18n (= 0.6.1) - multi_json (~> 1.0) - arel (3.0.2) - builder (3.0.4) + rack (~> 1.5.2) + rack-test (~> 0.6.2) + activemodel (4.0.0) + activesupport (= 4.0.0) + builder (~> 3.1.0) + activerecord (4.0.0) + activemodel (= 4.0.0) + activerecord-deprecated_finders (~> 1.0.2) + activesupport (= 4.0.0) + arel (~> 4.0.0) + activerecord-deprecated_finders (1.0.3) + activesupport (4.0.0) + i18n (~> 0.6, >= 0.6.4) + minitest (~> 4.2) + multi_json (~> 1.3) + thread_safe (~> 0.1) + tzinfo (~> 0.3.37) + arel (4.0.0) + atomic (1.1.13) + builder (3.1.4) diff-lcs (1.1.3) erubis (2.7.0) - hike (1.2.2) - i18n (0.6.1) - journey (1.0.4) + hike (1.2.3) + i18n (0.6.5) json (1.7.6) mail (2.5.4) mime-types (~> 1.16) treetop (~> 1.4.8) mime-types (1.23) - multi_json (1.7.3) - pg (0.15.1) + minitest (4.7.5) + multi_json (1.7.9) + pg (0.16.0) polyglot (0.3.3) - rack (1.4.5) - rack-cache (1.2) - rack (>= 0.4) - rack-ssl (1.3.3) - rack + rack (1.5.2) rack-test (0.6.2) rack (>= 1.0) - rails (3.2.13) - actionmailer (= 3.2.13) - actionpack (= 3.2.13) - activerecord (= 3.2.13) - activeresource (= 3.2.13) - activesupport (= 3.2.13) - bundler (~> 1.0) - railties (= 3.2.13) - railties (3.2.13) - actionpack (= 3.2.13) - activesupport (= 3.2.13) - rack-ssl (~> 1.3.2) + rails (4.0.0) + actionmailer (= 4.0.0) + actionpack (= 4.0.0) + activerecord (= 4.0.0) + activesupport (= 4.0.0) + bundler (>= 1.3.0, < 2.0) + railties (= 4.0.0) + sprockets-rails (~> 2.0.0) + railties (4.0.0) + actionpack (= 4.0.0) + activesupport (= 4.0.0) rake (>= 0.8.7) - rdoc (~> 3.4) - thor (>= 0.14.6, < 2.0) - rake (10.0.4) + thor (>= 0.18.1, < 2.0) + rake (10.1.0) rdoc (3.12) json (~> 1.4) - responders (0.9.3) - railties (~> 3.1) + responders (0.6.5) rspec (2.12.0) rspec-core (~> 2.12.0) rspec-expectations (~> 2.12.0) @@ -87,14 +78,20 @@ GEM rspec-expectations (2.12.1) diff-lcs (~> 1.1.3) rspec-mocks (2.12.2) - sprockets (2.2.2) + sprockets (2.10.0) hike (~> 1.2) multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) + sprockets-rails (2.0.0) + actionpack (>= 3.0) + activesupport (>= 3.0) + sprockets (~> 2.8) thor (0.18.1) + thread_safe (0.1.2) + atomic tilt (1.4.1) - treetop (1.4.12) + treetop (1.4.14) polyglot polyglot (>= 0.3.1) tzinfo (0.3.37) diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index f5aa6d2..3047d84 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -5,10 +5,10 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "postgres-copy" - s.version = "0.6.0" + s.version = "0.7.0" s.platform = Gem::Platform::RUBY - s.required_ruby_version = ">= 1.8.7" + s.required_ruby_version = ">= 1.9.3" s.authors = ["Diogo Biazus"] s.date = "2013-01-31" s.description = "Now you can use the super fast COPY for import/export data directly from your AR models." @@ -23,8 +23,8 @@ Gem::Specification.new do |s| s.summary = "Put COPY command functionality in ActiveRecord's model class" s.add_dependency "pg" - s.add_dependency "activerecord", '>= 3.0.0' - s.add_dependency "rails", '>= 3.0.0' + s.add_dependency "activerecord", '~> 4.0' + s.add_dependency "rails", '~> 4.0' s.add_dependency "responders" s.add_development_dependency "bundler" s.add_development_dependency "rdoc" From a463b20e4c8b4d29520a54f902606a0692564da7 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Wed, 21 Aug 2013 20:06:37 -0300 Subject: [PATCH 03/77] adding remark about rails versions --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 418b9b9..958c077 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,10 @@ Run the bundle command bundle +## Rails (or ActiveRecord) versions + +Rails 4 users should use the version 0.7 and onward, while if you use Rails 3.2 stick with the 0.6 versions. + ## Usage The gem will add two aditiontal class methods to ActiveRecord::Base: From de22dc2b90a4ea1c55fc5eb240119d96edefb22d Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Mon, 9 Sep 2013 15:51:05 -0300 Subject: [PATCH 04/77] moved class methods to a Concern and now they are added dynamicaly to classes that call acts_as_copy_target. [fix #10] --- lib/postgres-copy.rb | 18 +-- lib/postgres-copy/active_record.rb | 88 --------------- lib/postgres-copy/acts_as_copy_target.rb | 103 ++++++++++++++++++ ...inary_spec.rb => copy_from_binary_spec.rb} | 4 +- ...pg_copy_from_spec.rb => copy_from_spec.rb} | 30 ++--- ..._binary_spec.rb => copy_to_binary_spec.rb} | 6 +- spec/{pg_copy_to_spec.rb => copy_to_spec.rb} | 14 +-- spec/fixtures/extra_field.rb | 1 + spec/fixtures/reserved_word_model.rb | 1 + spec/fixtures/test_model.rb | 1 + spec/spec_helper.rb | 1 + 11 files changed, 140 insertions(+), 127 deletions(-) delete mode 100644 lib/postgres-copy/active_record.rb create mode 100644 lib/postgres-copy/acts_as_copy_target.rb rename spec/{pg_copy_from_binary_spec.rb => copy_from_binary_spec.rb} (71%) rename spec/{pg_copy_from_spec.rb => copy_from_spec.rb} (61%) rename spec/{pg_copy_to_binary_spec.rb => copy_to_binary_spec.rb} (81%) rename spec/{pg_copy_to_spec.rb => copy_to_spec.rb} (79%) diff --git a/lib/postgres-copy.rb b/lib/postgres-copy.rb index bb9dd70..5eaf533 100644 --- a/lib/postgres-copy.rb +++ b/lib/postgres-copy.rb @@ -1,16 +1,10 @@ require 'rubygems' -require 'active_record' -require 'postgres-copy/active_record' -require 'rails' +require 'active_support' -class PostgresCopy < Rails::Railtie +ActiveSupport.on_load :active_record do + require "postgres-copy/acts_as_copy_target" +end - initializer 'postgres-copy' do - ActiveSupport.on_load :active_record do - require "postgres-copy/active_record" - end - ActiveSupport.on_load :action_controller do - require "postgres-copy/csv_responder" - end - end +ActiveSupport.on_load :action_controller do + require "postgres-copy/csv_responder" end diff --git a/lib/postgres-copy/active_record.rb b/lib/postgres-copy/active_record.rb deleted file mode 100644 index aaff61a..0000000 --- a/lib/postgres-copy/active_record.rb +++ /dev/null @@ -1,88 +0,0 @@ -module ActiveRecord - class Base - # Copy data to a file passed as a string (the file path) or to lines that are passed to a block - def self.pg_copy_to path = nil, options = {} - options = {:delimiter => ",", :format => :csv, :header => true}.merge(options) - options_string = if options[:format] == :binary - "BINARY" - else - "DELIMITER '#{options[:delimiter]}' CSV #{options[:header] ? 'HEADER' : ''}" - end - - if path - raise "You have to choose between exporting to a file or receiving the lines inside a block" if block_given? - connection.execute "COPY (#{self.all.to_sql}) TO #{sanitize(path)} WITH #{options_string}" - else - connection.execute "COPY (#{self.all.to_sql}) TO STDOUT WITH #{options_string}" - while line = connection.raw_connection.get_copy_data do - yield(line) if block_given? - end - end - return self - end - - # Copy all data to a single string - def self.pg_copy_to_string options = {} - data = '' - self.pg_copy_to(nil, options){|l| data << l } - if options[:format] == :binary - data.force_encoding("ASCII-8BIT") - end - data - end - - # Copy data from a CSV that can be passed as a string (the file path) or as an IO object. - # * You can change the default delimiter passing delimiter: '' in the options hash - # * You can map fields from the file to different fields in the table using a map in the options hash - # * For further details on usage take a look at the README.md - def self.pg_copy_from path_or_io, options = {} - options = {:delimiter => ",", :format => :csv, :header => true}.merge(options) - options_string = if options[:format] == :binary - "BINARY" - else - "DELIMITER '#{options[:delimiter]}' CSV" - end - io = path_or_io.instance_of?(String) ? File.open(path_or_io, 'r') : path_or_io - - if options[:format] == :binary - columns_list = options[:columns] || [] - elsif options[:header] - line = io.gets - columns_list = options[:columns] || line.strip.split(options[:delimiter]) - else - columns_list = options[:columns] - end - - table = if options[:table] - connection.quote_table_name(options[:table]) - else - quoted_table_name - end - - columns_list = columns_list.map{|c| options[:map][c.to_s] } if options[:map] - columns_string = columns_list.size > 0 ? "(\"#{columns_list.join('","')}\")" : "" - connection.execute %{COPY #{table} #{columns_string} FROM STDIN #{options_string}} - if options[:format] == :binary - bytes = 0 - begin - while line = io.readpartial(10240) - connection.raw_connection.put_copy_data line - bytes += line.bytesize - end - rescue EOFError - end - else - while line = io.gets do - next if line.strip.size == 0 - if block_given? - row = line.strip.split(options[:delimiter]) - yield(row) - line = row.join(options[:delimiter]) + "\n" - end - connection.raw_connection.put_copy_data line - end - end - connection.raw_connection.put_copy_end - end - end -end diff --git a/lib/postgres-copy/acts_as_copy_target.rb b/lib/postgres-copy/acts_as_copy_target.rb new file mode 100644 index 0000000..b6295ec --- /dev/null +++ b/lib/postgres-copy/acts_as_copy_target.rb @@ -0,0 +1,103 @@ +module PostgresCopy + module ActsAsCopyTarget + extend ActiveSupport::Concern + + included do + end + + module CopyMethods + # Copy data to a file passed as a string (the file path) or to lines that are passed to a block + def copy_to path = nil, options = {} + options = {:delimiter => ",", :format => :csv, :header => true}.merge(options) + options_string = if options[:format] == :binary + "BINARY" + else + "DELIMITER '#{options[:delimiter]}' CSV #{options[:header] ? 'HEADER' : ''}" + end + + if path + raise "You have to choose between exporting to a file or receiving the lines inside a block" if block_given? + connection.execute "COPY (#{self.all.to_sql}) TO #{sanitize(path)} WITH #{options_string}" + else + connection.execute "COPY (#{self.all.to_sql}) TO STDOUT WITH #{options_string}" + while line = connection.raw_connection.get_copy_data do + yield(line) if block_given? + end + end + return self + end + + # Copy all data to a single string + def copy_to_string options = {} + data = '' + self.copy_to(nil, options){|l| data << l } + if options[:format] == :binary + data.force_encoding("ASCII-8BIT") + end + data + end + + # Copy data from a CSV that can be passed as a string (the file path) or as an IO object. + # * You can change the default delimiter passing delimiter: '' in the options hash + # * You can map fields from the file to different fields in the table using a map in the options hash + # * For further details on usage take a look at the README.md + def copy_from path_or_io, options = {} + options = {:delimiter => ",", :format => :csv, :header => true}.merge(options) + options_string = if options[:format] == :binary + "BINARY" + else + "DELIMITER '#{options[:delimiter]}' CSV" + end + io = path_or_io.instance_of?(String) ? File.open(path_or_io, 'r') : path_or_io + + if options[:format] == :binary + columns_list = options[:columns] || [] + elsif options[:header] + line = io.gets + columns_list = options[:columns] || line.strip.split(options[:delimiter]) + else + columns_list = options[:columns] + end + + table = if options[:table] + connection.quote_table_name(options[:table]) + else + quoted_table_name + end + + columns_list = columns_list.map{|c| options[:map][c.to_s] } if options[:map] + columns_string = columns_list.size > 0 ? "(\"#{columns_list.join('","')}\")" : "" + connection.execute %{COPY #{table} #{columns_string} FROM STDIN #{options_string}} + if options[:format] == :binary + bytes = 0 + begin + while line = io.readpartial(10240) + connection.raw_connection.put_copy_data line + bytes += line.bytesize + end + rescue EOFError + end + else + while line = io.gets do + next if line.strip.size == 0 + if block_given? + row = line.strip.split(options[:delimiter]) + yield(row) + line = row.join(options[:delimiter]) + "\n" + end + connection.raw_connection.put_copy_data line + end + end + connection.raw_connection.put_copy_end + end + end + + module ClassMethods + def acts_as_copy_target + extend PostgresCopy::ActsAsCopyTarget::CopyMethods + end + end + end +end + +ActiveRecord::Base.send :include, PostgresCopy::ActsAsCopyTarget diff --git a/spec/pg_copy_from_binary_spec.rb b/spec/copy_from_binary_spec.rb similarity index 71% rename from spec/pg_copy_from_binary_spec.rb rename to spec/copy_from_binary_spec.rb index 22508fd..d17bc65 100644 --- a/spec/pg_copy_from_binary_spec.rb +++ b/spec/copy_from_binary_spec.rb @@ -9,12 +9,12 @@ end it "should import from file if path is passed without field_map" do - TestModel.pg_copy_from File.expand_path('spec/fixtures/2_col_binary_data.dat'), :format => :binary, columns: [:id, :data] + TestModel.copy_from File.expand_path('spec/fixtures/2_col_binary_data.dat'), :format => :binary, columns: [:id, :data] TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'text'}] end it "should import from file if columns are not specified" do - TestModel.pg_copy_from File.expand_path('spec/fixtures/2_col_binary_data.dat'), :format => :binary + TestModel.copy_from File.expand_path('spec/fixtures/2_col_binary_data.dat'), :format => :binary TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'text'}] end diff --git a/spec/pg_copy_from_spec.rb b/spec/copy_from_spec.rb similarity index 61% rename from spec/pg_copy_from_spec.rb rename to spec/copy_from_spec.rb index a6edec3..f4c0dce 100644 --- a/spec/pg_copy_from_spec.rb +++ b/spec/copy_from_spec.rb @@ -9,34 +9,34 @@ end it "should import from file if path is passed without field_map" do - TestModel.pg_copy_from File.expand_path('spec/fixtures/comma_with_header.csv') + TestModel.copy_from File.expand_path('spec/fixtures/comma_with_header.csv') TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end it "should import from IO without field_map" do - TestModel.pg_copy_from File.open(File.expand_path('spec/fixtures/comma_with_header.csv'), 'r') + TestModel.copy_from File.open(File.expand_path('spec/fixtures/comma_with_header.csv'), 'r') TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end it "should import with custom delimiter from path" do - TestModel.pg_copy_from File.expand_path('spec/fixtures/semicolon_with_header.csv'), :delimiter => ';' + TestModel.copy_from File.expand_path('spec/fixtures/semicolon_with_header.csv'), :delimiter => ';' TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end it "should import with custom delimiter from IO" do - TestModel.pg_copy_from File.open(File.expand_path('spec/fixtures/semicolon_with_header.csv'), 'r'), :delimiter => ';' + TestModel.copy_from File.open(File.expand_path('spec/fixtures/semicolon_with_header.csv'), 'r'), :delimiter => ';' TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end it "should import and allow changes in block" do - TestModel.pg_copy_from(File.open(File.expand_path('spec/fixtures/comma_with_header.csv'), 'r')) do |row| + TestModel.copy_from(File.open(File.expand_path('spec/fixtures/comma_with_header.csv'), 'r')) do |row| row[1] = 'changed this data' end TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'changed this data'}] end it "should import 2 lines and allow changes in block" do - TestModel.pg_copy_from(File.open(File.expand_path('spec/fixtures/tab_with_two_lines.csv'), 'r'), :delimiter => "\t") do |row| + TestModel.copy_from(File.open(File.expand_path('spec/fixtures/tab_with_two_lines.csv'), 'r'), :delimiter => "\t") do |row| row[1] = 'changed this data' end TestModel.order(:id).first.attributes.should == {'id' => 1, 'data' => 'changed this data'} @@ -44,50 +44,50 @@ end it "should be able to copy from using custom set of columns" do - TestModel.pg_copy_from(File.open(File.expand_path('spec/fixtures/tab_only_data.csv'), 'r'), :delimiter => "\t", :columns => ["data"]) + TestModel.copy_from(File.open(File.expand_path('spec/fixtures/tab_only_data.csv'), 'r'), :delimiter => "\t", :columns => ["data"]) TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end it "default set of columns should be all table columns minus [id, created_at, updated_at]" do - ExtraField.pg_copy_from(File.open(File.expand_path('spec/fixtures/comma_with_header.csv'), 'r')) + ExtraField.copy_from(File.open(File.expand_path('spec/fixtures/comma_with_header.csv'), 'r')) ExtraField.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1', 'created_at' => nil, 'updated_at' => nil}] end it "should not expect a header when :header is false" do - TestModel.pg_copy_from(File.open(File.expand_path('spec/fixtures/comma_without_header.csv'), 'r'), :header => false, :columns => [:id,:data]) + TestModel.copy_from(File.open(File.expand_path('spec/fixtures/comma_without_header.csv'), 'r'), :header => false, :columns => [:id,:data]) TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end it "should use the table name given by :table" do - ActiveRecord::Base.pg_copy_from(File.open(File.expand_path('spec/fixtures/comma_without_header.csv'), 'r'), :header => false, :columns => [:id,:data], :table => "test_models") + ExtraField.copy_from(File.open(File.expand_path('spec/fixtures/comma_without_header.csv'), 'r'), :header => false, :columns => [:id,:data], :table => "test_models") TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end it "should be able to map the header in the file to diferent column names" do - TestModel.pg_copy_from(File.open(File.expand_path('spec/fixtures/tab_with_different_header.csv'), 'r'), :delimiter => "\t", :map => {'cod' => 'id', 'info' => 'data'}) + TestModel.copy_from(File.open(File.expand_path('spec/fixtures/tab_with_different_header.csv'), 'r'), :delimiter => "\t", :map => {'cod' => 'id', 'info' => 'data'}) TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end it "should be able to map the header in the file to diferent column names with custom delimiter" do - TestModel.pg_copy_from(File.open(File.expand_path('spec/fixtures/semicolon_with_different_header.csv'), 'r'), :delimiter => ';', :map => {'cod' => 'id', 'info' => 'data'}) + TestModel.copy_from(File.open(File.expand_path('spec/fixtures/semicolon_with_different_header.csv'), 'r'), :delimiter => ';', :map => {'cod' => 'id', 'info' => 'data'}) TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end it "should ignore empty lines" do - TestModel.pg_copy_from(File.open(File.expand_path('spec/fixtures/tab_with_extra_line.csv'), 'r'), :delimiter => "\t") + TestModel.copy_from(File.open(File.expand_path('spec/fixtures/tab_with_extra_line.csv'), 'r'), :delimiter => "\t") TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end #we should implement this later #it "should raise error in malformed files" do #lambda do - #TestModel.pg_copy_from(File.open(File.expand_path('spec/fixtures/tab_with_error.csv'), 'r')) + #TestModel.copy_from(File.open(File.expand_path('spec/fixtures/tab_with_error.csv'), 'r')) #end.should raise_error #TestModel.order(:id).map{|r| r.attributes}.should == [] #end it "should copy from even when table fields need identifier quoting" do - ReservedWordModel.pg_copy_from File.expand_path('spec/fixtures/reserved_words.csv'), :delimiter => "\t" + ReservedWordModel.copy_from File.expand_path('spec/fixtures/reserved_words.csv'), :delimiter => "\t" ReservedWordModel.order(:id).map{|r| r.attributes}.should == [{"group"=>"group name", "id"=>1, "select"=>"test select"}] end end diff --git a/spec/pg_copy_to_binary_spec.rb b/spec/copy_to_binary_spec.rb similarity index 81% rename from spec/pg_copy_to_binary_spec.rb rename to spec/copy_to_binary_spec.rb index 225d28f..4b11bf5 100644 --- a/spec/pg_copy_to_binary_spec.rb +++ b/spec/copy_to_binary_spec.rb @@ -11,18 +11,18 @@ describe "should allow binary output to string" do context "with only binary option" do - subject{ TestModel.pg_copy_to_string(:format => :binary) } + subject{ TestModel.copy_to_string(:format => :binary) } it{ should == File.open('spec/fixtures/2_col_binary_data.dat', 'r:ASCII-8BIT').read } end context "with custom select" do - subject{ TestModel.select("id, data").pg_copy_to_string(:format => :binary) } + subject{ TestModel.select("id, data").copy_to_string(:format => :binary) } it{ should == File.open('spec/fixtures/2_col_binary_data.dat', 'r:ASCII-8BIT').read } end end describe "should allow binary output to file" do it "should copy to disk if block is not given and a path is passed" do - TestModel.pg_copy_to '/tmp/export.dat', :format => :binary + TestModel.copy_to '/tmp/export.dat', :format => :binary str = File.open('/tmp/export.dat', 'r:ASCII-8BIT').read str.should == File.open('spec/fixtures/2_col_binary_data.dat', 'r:ASCII-8BIT').read diff --git a/spec/pg_copy_to_spec.rb b/spec/copy_to_spec.rb similarity index 79% rename from spec/pg_copy_to_spec.rb rename to spec/copy_to_spec.rb index 9715402..3972631 100644 --- a/spec/pg_copy_to_spec.rb +++ b/spec/copy_to_spec.rb @@ -9,29 +9,29 @@ TestModel.create :data => 'test data 1' end - describe ".pg_copy_to_string" do + describe ".copy_to_string" do context "with no options" do - subject{ TestModel.pg_copy_to_string } + subject{ TestModel.copy_to_string } it{ should == File.open('spec/fixtures/comma_with_header.csv', 'r').read } end context "with tab as delimiter" do - subject{ TestModel.pg_copy_to_string :delimiter => "\t" } + subject{ TestModel.copy_to_string :delimiter => "\t" } it{ should == File.open('spec/fixtures/tab_with_header.csv', 'r').read } end end - describe ".pg_copy_to" do + describe ".copy_to" do it "should copy and pass data to block if block is given and no path is passed" do File.open('spec/fixtures/comma_with_header.csv', 'r') do |f| - TestModel.pg_copy_to do |row| + TestModel.copy_to do |row| row.should == f.readline end end end it "should copy to disk if block is not given and a path is passed" do - TestModel.pg_copy_to '/tmp/export.csv' + TestModel.copy_to '/tmp/export.csv' File.open('spec/fixtures/comma_with_header.csv', 'r') do |fixture| File.open('/tmp/export.csv', 'r') do |result| result.read.should == fixture.read @@ -41,7 +41,7 @@ it "should raise exception if I pass a path and a block simultaneously" do lambda do - TestModel.pg_copy_to('/tmp/bogus_path') do |row| + TestModel.copy_to('/tmp/bogus_path') do |row| end end.should raise_error end diff --git a/spec/fixtures/extra_field.rb b/spec/fixtures/extra_field.rb index e297ab4..f9ce2fd 100644 --- a/spec/fixtures/extra_field.rb +++ b/spec/fixtures/extra_field.rb @@ -1,5 +1,6 @@ require 'postgres-copy' class ExtraField < ActiveRecord::Base + acts_as_copy_target end diff --git a/spec/fixtures/reserved_word_model.rb b/spec/fixtures/reserved_word_model.rb index 89a3992..d196dff 100644 --- a/spec/fixtures/reserved_word_model.rb +++ b/spec/fixtures/reserved_word_model.rb @@ -1,5 +1,6 @@ require 'postgres-copy' class ReservedWordModel < ActiveRecord::Base + acts_as_copy_target end diff --git a/spec/fixtures/test_model.rb b/spec/fixtures/test_model.rb index ef7d18b..a4b82fb 100644 --- a/spec/fixtures/test_model.rb +++ b/spec/fixtures/test_model.rb @@ -1,4 +1,5 @@ require 'postgres-copy' class TestModel < ActiveRecord::Base + acts_as_copy_target end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index da8956f..631e40d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,6 @@ $LOAD_PATH.unshift(File.dirname(__FILE__)) $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) +require 'active_record' require 'fixtures/test_model' require 'fixtures/extra_field' require 'fixtures/reserved_word_model' From 441516af3bca635b0ea8b366c1086fa50c6a8b7d Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Mon, 9 Sep 2013 16:02:45 -0300 Subject: [PATCH 05/77] updating README --- README.md | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 958c077..2dccf80 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,18 @@ Rails 4 users should use the version 0.7 and onward, while if you use Rails 3.2 ## Usage -The gem will add two aditiontal class methods to ActiveRecord::Base: +To enable the copy commands in an ActiveRecord model called User you should use: +```ruby +class User < ActiveRecord::Base + acts_as_copy_target +end +``` + +This will add the aditiontal class methods to your model: -* pg_copy_to -* pg_copy_to_string -* pg_copy_from +* copy_to +* copy_to_string +* copy_from ### Using pg_copy_to and pg_copy_to_string @@ -37,7 +44,7 @@ The first and most basic use case, let's copy the enteire content of a database Assuming we have a users table and a User AR model: ```ruby -User.pg_copy_to '/tmp/users.csv' +User.copy_to '/tmp/users.csv' ``` This will execute in the database the command: @@ -52,16 +59,16 @@ In this case you can pass a block and retrieve the generated lines and then writ ```ruby File.open('/tmp/users.csv', 'w') do |f| - User.pg_copy_to do |line| + User.copy_to do |line| f.write line end end ``` -Or, if you have enough memory, you can read all table contents to a string using .pg_copy_to_string +Or, if you have enough memory, you can read all table contents to a string using .copy_to_string ```ruby -puts User.pg_copy_to_string +puts User.copy_to_string ``` Another insteresting feature of pg_copy_to is that it uses the scoped relation, it means that you can use ARel @@ -69,7 +76,7 @@ operations to generate different CSV files according to your needs. Assuming we want to generate a file only with the names of users 1, 2 and 3: ```ruby -User.select("name").where(:id => [1,2,3]).pg_copy_to "/tmp/users.csv" +User.select("name").where(:id => [1,2,3]).copy_to "/tmp/users.csv" ``` Which will generate the following SQL command: @@ -81,7 +88,7 @@ COPY (SELECT name FROM "users" WHERE "users"."id" IN (1, 2, 3)) TO '/tmp/users.c The COPY command also supports exporting the data in binary format. ```ruby -User.select("name").where(:id => [1,2,3]).pg_copy_to "/tmp/users.dat", :format => :binary +User.select("name").where(:id => [1,2,3]).copy_to "/tmp/users.dat", :format => :binary ``` Which will generate the following SQL command: @@ -93,7 +100,7 @@ COPY (SELECT name FROM "users" WHERE "users"."id" IN (1, 2, 3)) TO '/tmp/users.d The copy_to_string method also supports this ```ruby -puts User.pg_copy_to_string(:format => :binary) +puts User.copy_to_string(:format => :binary) ``` @@ -106,21 +113,21 @@ Let's first copy from a file in the database server, assuming again that we have that we are in the Rails console: ```ruby -User.pg_copy_from "/tmp/users.csv" +User.copy_from "/tmp/users.csv" ``` This command will use the headers in the CSV file as fields of the target table, so beware to always have a header in the files you want to import. If the column names in the CSV header do not match the field names of the target table, you can pass a map in the options parameter. ```ruby -User.pg_copy_from "/tmp/users.csv", :map => {'name' => 'first_name'} +User.copy_from "/tmp/users.csv", :map => {'name' => 'first_name'} ``` In the above example the header name in the CSV file will be mapped to the field called first_name in the users table. You can also manipulate and modify the values of the file being imported before they enter into the database using a block: ```ruby -User.pg_copy_from "/tmp/users.csv" do |row| +User.copy_from "/tmp/users.csv" do |row| row[0] = "fixed string" end ``` @@ -132,7 +139,7 @@ For each iteration of the block row receives an array with the same order as the To copy a binary formatted data file or IO object you can specify the format as binary ```ruby -User.pg_copy_from "/tmp/users.dat", :format => :binary +User.copy_from "/tmp/users.dat", :format => :binary ``` NOTE: Columns must line up with the table unless you specify how they map to table columns. @@ -140,7 +147,7 @@ NOTE: Columns must line up with the table unless you specify how they map to tab To specify how the columns will map to the table you can specify the :columns option ```ruby -User.pg_copy_from "/tmp/users.dat", :format => :binary, :columns => [:id, :name] +User.copy_from "/tmp/users.dat", :format => :binary, :columns => [:id, :name] ``` Which will generate the following SQL command: From b2b57d4f850998fbf550de4b9f2897ca1c3a4ddf Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Mon, 9 Sep 2013 16:04:54 -0300 Subject: [PATCH 06/77] bumped minor version --- postgres-copy.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index 3047d84..c1ebb8a 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -5,7 +5,7 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "postgres-copy" - s.version = "0.7.0" + s.version = "0.8.0" s.platform = Gem::Platform::RUBY s.required_ruby_version = ">= 1.9.3" From 88d3919db187842e734c79e1ee6159cddceac331 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Mon, 9 Sep 2013 16:05:09 -0300 Subject: [PATCH 07/77] notes in README about versions --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2dccf80..3dd33e3 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,10 @@ Run the bundle command bundle -## Rails (or ActiveRecord) versions +## IMPORTANT note about recent versions -Rails 4 users should use the version 0.7 and onward, while if you use Rails 3.2 stick with the 0.6 versions. +* Rails 4 users should use the version 0.7 and onward, while if you use Rails 3.2 stick with the 0.6 versions. +* Since version 0.8 all methods lost the prefix pg_ and they should be included in models thourgh acts_as_copy_target. ## Usage From bfcf4d560078a624ab2d894206e255720001e13b Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Mon, 9 Sep 2013 16:06:40 -0300 Subject: [PATCH 08/77] fixing README --- Gemfile.lock | 6 +++--- README.md | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 37aa1a0..50b3366 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - postgres-copy (0.7.0) + postgres-copy (0.8.0) activerecord (~> 4.0) pg rails (~> 4.0) @@ -45,7 +45,7 @@ GEM mail (2.5.4) mime-types (~> 1.16) treetop (~> 1.4.8) - mime-types (1.23) + mime-types (1.25) minitest (4.7.5) multi_json (1.7.9) pg (0.16.0) @@ -91,7 +91,7 @@ GEM thread_safe (0.1.2) atomic tilt (1.4.1) - treetop (1.4.14) + treetop (1.4.15) polyglot polyglot (>= 0.3.1) tzinfo (0.3.37) diff --git a/README.md b/README.md index 3dd33e3..c77121c 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ This will add the aditiontal class methods to your model: * copy_to_string * copy_from -### Using pg_copy_to and pg_copy_to_string +### Using copy_to and copy_to_string You can go to the rails console and try some cool things first. The first and most basic use case, let's copy the enteire content of a database table to a CSV file on the database server disk. @@ -72,7 +72,7 @@ Or, if you have enough memory, you can read all table contents to a string using puts User.copy_to_string ``` -Another insteresting feature of pg_copy_to is that it uses the scoped relation, it means that you can use ARel +Another insteresting feature of copy_to is that it uses the scoped relation, it means that you can use ARel operations to generate different CSV files according to your needs. Assuming we want to generate a file only with the names of users 1, 2 and 3: @@ -106,9 +106,9 @@ puts User.copy_to_string(:format => :binary) -### Using pg_copy_from +### Using copy_from -Now, if you want to copy data from a CSV file into the database, you can use the pg_copy_from method. +Now, if you want to copy data from a CSV file into the database, you can use the copy_from method. It will allow you to copy data from an arbritary IO object or from a file in the database server (when you pass the path as string). Let's first copy from a file in the database server, assuming again that we have a users table and that we are in the Rails console: From 31df363a3f9aecd1c94057ba816640ceb566fe77 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Mon, 9 Sep 2013 16:14:07 -0300 Subject: [PATCH 09/77] fixed CsvResponder --- lib/postgres-copy/csv_responder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/postgres-copy/csv_responder.rb b/lib/postgres-copy/csv_responder.rb index a3e0321..36a856c 100644 --- a/lib/postgres-copy/csv_responder.rb +++ b/lib/postgres-copy/csv_responder.rb @@ -2,7 +2,7 @@ module Responders module CsvResponder def to_csv controller.response_body = Enumerator.new do |y| - controller.send(:end_of_association_chain).pg_copy_to do |line| + controller.send(:end_of_association_chain).copy_to do |line| y << line end end From a3f741b23ca56da7cefbee1efda0e02608b76840 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Tue, 17 Jun 2014 21:44:36 -0400 Subject: [PATCH 10/77] updated gemspec changing rails dependency to >= 4.0 --- Gemfile.lock | 126 ++++++++++++++++++++++-------------------- postgres-copy.gemspec | 4 +- 2 files changed, 67 insertions(+), 63 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 50b3366..4cb8ff0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,99 +2,103 @@ PATH remote: . specs: postgres-copy (0.8.0) - activerecord (~> 4.0) + activerecord (>= 4.0) pg - rails (~> 4.0) + rails (>= 4.0) responders GEM remote: https://rubygems.org/ specs: - actionmailer (4.0.0) - actionpack (= 4.0.0) - mail (~> 2.5.3) - actionpack (4.0.0) - activesupport (= 4.0.0) - builder (~> 3.1.0) - erubis (~> 2.7.0) + actionmailer (4.1.1) + actionpack (= 4.1.1) + actionview (= 4.1.1) + mail (~> 2.5.4) + actionpack (4.1.1) + actionview (= 4.1.1) + activesupport (= 4.1.1) rack (~> 1.5.2) rack-test (~> 0.6.2) - activemodel (4.0.0) - activesupport (= 4.0.0) - builder (~> 3.1.0) - activerecord (4.0.0) - activemodel (= 4.0.0) - activerecord-deprecated_finders (~> 1.0.2) - activesupport (= 4.0.0) - arel (~> 4.0.0) - activerecord-deprecated_finders (1.0.3) - activesupport (4.0.0) - i18n (~> 0.6, >= 0.6.4) - minitest (~> 4.2) - multi_json (~> 1.3) + actionview (4.1.1) + activesupport (= 4.1.1) + builder (~> 3.1) + erubis (~> 2.7.0) + activemodel (4.1.1) + activesupport (= 4.1.1) + builder (~> 3.1) + activerecord (4.1.1) + activemodel (= 4.1.1) + activesupport (= 4.1.1) + arel (~> 5.0.0) + activesupport (4.1.1) + i18n (~> 0.6, >= 0.6.9) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) thread_safe (~> 0.1) - tzinfo (~> 0.3.37) - arel (4.0.0) - atomic (1.1.13) - builder (3.1.4) - diff-lcs (1.1.3) + tzinfo (~> 1.1) + arel (5.0.1.20140414130214) + builder (3.2.2) + diff-lcs (1.2.5) erubis (2.7.0) hike (1.2.3) - i18n (0.6.5) - json (1.7.6) + i18n (0.6.9) + json (1.8.1) mail (2.5.4) mime-types (~> 1.16) treetop (~> 1.4.8) - mime-types (1.25) - minitest (4.7.5) - multi_json (1.7.9) - pg (0.16.0) - polyglot (0.3.3) + mime-types (1.25.1) + minitest (5.3.5) + multi_json (1.10.1) + pg (0.17.1) + polyglot (0.3.5) rack (1.5.2) rack-test (0.6.2) rack (>= 1.0) - rails (4.0.0) - actionmailer (= 4.0.0) - actionpack (= 4.0.0) - activerecord (= 4.0.0) - activesupport (= 4.0.0) + rails (4.1.1) + actionmailer (= 4.1.1) + actionpack (= 4.1.1) + actionview (= 4.1.1) + activemodel (= 4.1.1) + activerecord (= 4.1.1) + activesupport (= 4.1.1) bundler (>= 1.3.0, < 2.0) - railties (= 4.0.0) - sprockets-rails (~> 2.0.0) - railties (4.0.0) - actionpack (= 4.0.0) - activesupport (= 4.0.0) + railties (= 4.1.1) + sprockets-rails (~> 2.0) + railties (4.1.1) + actionpack (= 4.1.1) + activesupport (= 4.1.1) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rake (10.1.0) - rdoc (3.12) + rake (10.3.2) + rdoc (4.1.1) json (~> 1.4) - responders (0.6.5) - rspec (2.12.0) - rspec-core (~> 2.12.0) - rspec-expectations (~> 2.12.0) - rspec-mocks (~> 2.12.0) - rspec-core (2.12.2) - rspec-expectations (2.12.1) - diff-lcs (~> 1.1.3) - rspec-mocks (2.12.2) - sprockets (2.10.0) + responders (1.1.0) + railties (>= 3.2, < 5) + rspec (2.99.0) + rspec-core (~> 2.99.0) + rspec-expectations (~> 2.99.0) + rspec-mocks (~> 2.99.0) + rspec-core (2.99.0) + rspec-expectations (2.99.0) + diff-lcs (>= 1.1.3, < 2.0) + rspec-mocks (2.99.1) + sprockets (2.12.1) hike (~> 1.2) multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - sprockets-rails (2.0.0) + sprockets-rails (2.1.3) actionpack (>= 3.0) activesupport (>= 3.0) sprockets (~> 2.8) - thor (0.18.1) - thread_safe (0.1.2) - atomic + thor (0.19.1) + thread_safe (0.3.4) tilt (1.4.1) treetop (1.4.15) polyglot polyglot (>= 0.3.1) - tzinfo (0.3.37) + tzinfo (1.2.1) + thread_safe (~> 0.1) PLATFORMS ruby diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index c1ebb8a..6950aa8 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -23,8 +23,8 @@ Gem::Specification.new do |s| s.summary = "Put COPY command functionality in ActiveRecord's model class" s.add_dependency "pg" - s.add_dependency "activerecord", '~> 4.0' - s.add_dependency "rails", '~> 4.0' + s.add_dependency "activerecord", '>= 4.0' + s.add_dependency "rails", '>= 4.0' s.add_dependency "responders" s.add_development_dependency "bundler" s.add_development_dependency "rdoc" From 522541f1dd685093887c7872f4fe5f7da3f639b8 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Tue, 17 Jun 2014 21:46:55 -0400 Subject: [PATCH 11/77] bumped patch version number --- Gemfile.lock | 2 +- postgres-copy.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 4cb8ff0..e9a764e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - postgres-copy (0.8.0) + postgres-copy (0.8.1) activerecord (>= 4.0) pg rails (>= 4.0) diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index 6950aa8..1312fab 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -5,7 +5,7 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "postgres-copy" - s.version = "0.8.0" + s.version = "0.8.1" s.platform = Gem::Platform::RUBY s.required_ruby_version = ">= 1.9.3" From 37da2f17397912170193ff74734711f6df5fb7c0 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Tue, 17 Jun 2014 21:54:20 -0400 Subject: [PATCH 12/77] using new copy_data method from PG::Connection and added pg >= 0.17 in gemspec --- Gemfile.lock | 2 +- lib/postgres-copy/acts_as_copy_target.rb | 43 ++++++++++++------------ postgres-copy.gemspec | 2 +- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index e9a764e..5a35c3c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,7 +3,7 @@ PATH specs: postgres-copy (0.8.1) activerecord (>= 4.0) - pg + pg (>= 0.17) rails (>= 4.0) responders diff --git a/lib/postgres-copy/acts_as_copy_target.rb b/lib/postgres-copy/acts_as_copy_target.rb index b6295ec..d8ce608 100644 --- a/lib/postgres-copy/acts_as_copy_target.rb +++ b/lib/postgres-copy/acts_as_copy_target.rb @@ -19,9 +19,10 @@ def copy_to path = nil, options = {} raise "You have to choose between exporting to a file or receiving the lines inside a block" if block_given? connection.execute "COPY (#{self.all.to_sql}) TO #{sanitize(path)} WITH #{options_string}" else - connection.execute "COPY (#{self.all.to_sql}) TO STDOUT WITH #{options_string}" - while line = connection.raw_connection.get_copy_data do - yield(line) if block_given? + connection.raw_connection.copy_data "COPY (#{self.all.to_sql}) TO STDOUT WITH #{options_string}" do + while line = connection.raw_connection.get_copy_data do + yield(line) if block_given? + end end end return self @@ -67,28 +68,28 @@ def copy_from path_or_io, options = {} columns_list = columns_list.map{|c| options[:map][c.to_s] } if options[:map] columns_string = columns_list.size > 0 ? "(\"#{columns_list.join('","')}\")" : "" - connection.execute %{COPY #{table} #{columns_string} FROM STDIN #{options_string}} - if options[:format] == :binary - bytes = 0 - begin - while line = io.readpartial(10240) - connection.raw_connection.put_copy_data line - bytes += line.bytesize + connection.raw_connection.copy_data %{COPY #{table} #{columns_string} FROM STDIN #{options_string}} do + if options[:format] == :binary + bytes = 0 + begin + while line = io.readpartial(10240) + connection.raw_connection.put_copy_data line + bytes += line.bytesize + end + rescue EOFError end - rescue EOFError - end - else - while line = io.gets do - next if line.strip.size == 0 - if block_given? - row = line.strip.split(options[:delimiter]) - yield(row) - line = row.join(options[:delimiter]) + "\n" + else + while line = io.gets do + next if line.strip.size == 0 + if block_given? + row = line.strip.split(options[:delimiter]) + yield(row) + line = row.join(options[:delimiter]) + "\n" + end + connection.raw_connection.put_copy_data line end - connection.raw_connection.put_copy_data line end end - connection.raw_connection.put_copy_end end end diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index 1312fab..b709866 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |s| s.require_paths = ["lib"] s.summary = "Put COPY command functionality in ActiveRecord's model class" - s.add_dependency "pg" + s.add_dependency "pg", ">= 0.17" s.add_dependency "activerecord", '>= 4.0' s.add_dependency "rails", '>= 4.0' s.add_dependency "responders" From 018826ca2ee7a4cb40c626902acf9ceb809db304 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Tue, 17 Jun 2014 21:55:04 -0400 Subject: [PATCH 13/77] bumped minor version number --- Gemfile.lock | 2 +- postgres-copy.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5a35c3c..2b71261 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - postgres-copy (0.8.1) + postgres-copy (0.9.0) activerecord (>= 4.0) pg (>= 0.17) rails (>= 4.0) diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index b709866..66db882 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -5,7 +5,7 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "postgres-copy" - s.version = "0.8.1" + s.version = "0.9.0" s.platform = Gem::Platform::RUBY s.required_ruby_version = ">= 1.9.3" From af841ccee7cfe88c1e943d53dcc1d55a668c2bef Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Tue, 17 Jun 2014 22:01:13 -0400 Subject: [PATCH 14/77] adding codeclimate badge to readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c77121c..72e9fa1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# postgres-copy [![Build Status](https://travis-ci.org/diogob/postgres-copy.png?branch=master)](https://travis-ci.org/diogob/postgres-copy) +# postgres-copy [![Build Status](https://travis-ci.org/diogob/postgres-copy.png?branch=master)](https://travis-ci.org/diogob/postgres-copy) [![Code Climate](https://codeclimate.com/github/diogob/postgres-copy.png)](https://codeclimate.com/github/diogob/postgres-copy) This Gem will enable your AR models to use the PostgreSQL COPY command to import/export data in CSV format. If you need to tranfer data between a PostgreSQL database and CSV files, the PostgreSQL native CSV parser From 34408756dcaab722c5e47fec017474270c521655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Casta=C3=B1eda?= Date: Wed, 11 Feb 2015 16:26:17 -0600 Subject: [PATCH 15/77] FIX. CSV files are not loaded when they have empty values at the final of the rows and a block is used for modifying values --- .project | 12 +++++++++++ lib/postgres-copy/acts_as_copy_target.rb | 2 +- spec/copy_from_spec.rb | 21 +++++++++++++++++++ ...ma_with_header_empty_values_at_the_end.csv | 4 ++++ spec/fixtures/test_extended_model.rb | 5 +++++ spec/spec_helper.rb | 2 ++ 6 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 .project create mode 100644 spec/fixtures/comma_with_header_empty_values_at_the_end.csv create mode 100644 spec/fixtures/test_extended_model.rb diff --git a/.project b/.project new file mode 100644 index 0000000..b0bcfa0 --- /dev/null +++ b/.project @@ -0,0 +1,12 @@ + + + postgres-copy + + + + + + + com.aptana.ruby.core.rubynature + + diff --git a/lib/postgres-copy/acts_as_copy_target.rb b/lib/postgres-copy/acts_as_copy_target.rb index d8ce608..5e83d68 100644 --- a/lib/postgres-copy/acts_as_copy_target.rb +++ b/lib/postgres-copy/acts_as_copy_target.rb @@ -82,7 +82,7 @@ def copy_from path_or_io, options = {} while line = io.gets do next if line.strip.size == 0 if block_given? - row = line.strip.split(options[:delimiter]) + row = line.strip.split(options[:delimiter],-1) yield(row) line = row.join(options[:delimiter]) + "\n" end diff --git a/spec/copy_from_spec.rb b/spec/copy_from_spec.rb index f4c0dce..fb7a3f8 100644 --- a/spec/copy_from_spec.rb +++ b/spec/copy_from_spec.rb @@ -4,6 +4,7 @@ before(:each) do ActiveRecord::Base.connection.execute %{ TRUNCATE TABLE test_models; + TRUNCATE TABLE test_extended_models; SELECT setval('test_models_id_seq', 1, false); } end @@ -90,5 +91,25 @@ ReservedWordModel.copy_from File.expand_path('spec/fixtures/reserved_words.csv'), :delimiter => "\t" ReservedWordModel.order(:id).map{|r| r.attributes}.should == [{"group"=>"group name", "id"=>1, "select"=>"test select"}] end + + it "should import even last columns have empty values" do + TestExtendedModel.copy_from File.expand_path('spec/fixtures/comma_with_header_empty_values_at_the_end.csv') + TestExtendedModel.order(:id).map{|r| r.attributes}.should == + [{"id"=>1, "data"=>"test data 1", "more_data"=>nil, "other_data"=>nil, "final_data"=>nil}, + {"id"=>2, "data"=>"test data 2", "more_data"=>"9", "other_data"=>nil, "final_data"=>nil}, + {"id"=>3, "data"=>"test data 2", "more_data"=>"9", "other_data"=>nil, "final_data"=>"0"}] + end + + it "should import even last columns have empty values with block" do + TestExtendedModel.copy_from File.expand_path('spec/fixtures/comma_with_header_empty_values_at_the_end.csv') do |row| + row[4]="666" + end + TestExtendedModel.order(:id).map{|r| r.attributes}.should == + [{"id"=>1, "data"=>"test data 1", "more_data"=>nil, "other_data"=>nil, "final_data"=>"666"}, + {"id"=>2, "data"=>"test data 2", "more_data"=>"9", "other_data"=>nil, "final_data"=>"666"}, + {"id"=>3, "data"=>"test data 2", "more_data"=>"9", "other_data"=>nil, "final_data"=>"666"}] + end + + end diff --git a/spec/fixtures/comma_with_header_empty_values_at_the_end.csv b/spec/fixtures/comma_with_header_empty_values_at_the_end.csv new file mode 100644 index 0000000..b7f0bfc --- /dev/null +++ b/spec/fixtures/comma_with_header_empty_values_at_the_end.csv @@ -0,0 +1,4 @@ +id,data,more_data,other_data,final_data +1,test data 1,,, +2,test data 2,9,, +3,test data 2,9,,0 diff --git a/spec/fixtures/test_extended_model.rb b/spec/fixtures/test_extended_model.rb new file mode 100644 index 0000000..e5d218a --- /dev/null +++ b/spec/fixtures/test_extended_model.rb @@ -0,0 +1,5 @@ +require 'postgres-copy' + +class TestExtendedModel < ActiveRecord::Base + acts_as_copy_target +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 631e40d..52377c5 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,6 +2,7 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) require 'active_record' require 'fixtures/test_model' +require 'fixtures/test_extended_model' require 'fixtures/extra_field' require 'fixtures/reserved_word_model' require 'rspec' @@ -26,6 +27,7 @@ DROP TABLE IF EXISTS extra_fields; DROP TABLE IF EXISTS reserved_word_models; CREATE TABLE test_models (id serial PRIMARY KEY, data text); + CREATE TABLE test_extended_models (id serial PRIMARY KEY, data text, more_data text,other_data text,final_data text ); CREATE TABLE reserved_word_models (id serial PRIMARY KEY, "select" text, "group" text); CREATE TABLE extra_fields (id serial PRIMARY KEY, data text, created_at timestamp, updated_at timestamp); } From 9906761065d6bd9f3808c5f1c9ec22e01b8830d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Casta=C3=B1eda?= Date: Wed, 11 Feb 2015 16:30:28 -0600 Subject: [PATCH 16/77] Removing ignored files --- .gitignore | 1 + .project | 12 ------------ 2 files changed, 1 insertion(+), 12 deletions(-) delete mode 100644 .project diff --git a/.gitignore b/.gitignore index c1e0daf..7b3b2bf 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ rdoc pkg ## PROJECT::SPECIFIC +.project diff --git a/.project b/.project deleted file mode 100644 index b0bcfa0..0000000 --- a/.project +++ /dev/null @@ -1,12 +0,0 @@ - - - postgres-copy - - - - - - - com.aptana.ruby.core.rubynature - - From 87ed5217fef9e34d219ad4e7789cd5c6b6b76139 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Thu, 26 Mar 2015 19:47:25 -0400 Subject: [PATCH 17/77] Bumped fix version number --- Gemfile.lock | 130 ++++++++++++++++++++++++------------------ postgres-copy.gemspec | 2 +- 2 files changed, 76 insertions(+), 56 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 2b71261..bc3639f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - postgres-copy (0.9.0) + postgres-copy (0.9.1) activerecord (>= 4.0) pg (>= 0.17) rails (>= 4.0) @@ -10,70 +10,93 @@ PATH GEM remote: https://rubygems.org/ specs: - actionmailer (4.1.1) - actionpack (= 4.1.1) - actionview (= 4.1.1) - mail (~> 2.5.4) - actionpack (4.1.1) - actionview (= 4.1.1) - activesupport (= 4.1.1) - rack (~> 1.5.2) + actionmailer (4.2.1) + actionpack (= 4.2.1) + actionview (= 4.2.1) + activejob (= 4.2.1) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 1.0, >= 1.0.5) + actionpack (4.2.1) + actionview (= 4.2.1) + activesupport (= 4.2.1) + rack (~> 1.6) rack-test (~> 0.6.2) - actionview (4.1.1) - activesupport (= 4.1.1) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.1) + actionview (4.2.1) + activesupport (= 4.2.1) builder (~> 3.1) erubis (~> 2.7.0) - activemodel (4.1.1) - activesupport (= 4.1.1) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.1) + activejob (4.2.1) + activesupport (= 4.2.1) + globalid (>= 0.3.0) + activemodel (4.2.1) + activesupport (= 4.2.1) builder (~> 3.1) - activerecord (4.1.1) - activemodel (= 4.1.1) - activesupport (= 4.1.1) - arel (~> 5.0.0) - activesupport (4.1.1) - i18n (~> 0.6, >= 0.6.9) + activerecord (4.2.1) + activemodel (= 4.2.1) + activesupport (= 4.2.1) + arel (~> 6.0) + activesupport (4.2.1) + i18n (~> 0.7) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) - thread_safe (~> 0.1) + thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - arel (5.0.1.20140414130214) + arel (6.0.0) builder (3.2.2) diff-lcs (1.2.5) erubis (2.7.0) + globalid (0.3.3) + activesupport (>= 4.1.0) hike (1.2.3) - i18n (0.6.9) + i18n (0.7.0) json (1.8.1) - mail (2.5.4) - mime-types (~> 1.16) - treetop (~> 1.4.8) - mime-types (1.25.1) - minitest (5.3.5) - multi_json (1.10.1) - pg (0.17.1) - polyglot (0.3.5) - rack (1.5.2) - rack-test (0.6.2) + loofah (2.0.1) + nokogiri (>= 1.5.9) + mail (2.6.3) + mime-types (>= 1.16, < 3) + mime-types (2.4.3) + mini_portile (0.6.2) + minitest (5.5.1) + multi_json (1.11.0) + nokogiri (1.6.6.2) + mini_portile (~> 0.6.0) + pg (0.18.1) + rack (1.6.0) + rack-test (0.6.3) rack (>= 1.0) - rails (4.1.1) - actionmailer (= 4.1.1) - actionpack (= 4.1.1) - actionview (= 4.1.1) - activemodel (= 4.1.1) - activerecord (= 4.1.1) - activesupport (= 4.1.1) + rails (4.2.1) + actionmailer (= 4.2.1) + actionpack (= 4.2.1) + actionview (= 4.2.1) + activejob (= 4.2.1) + activemodel (= 4.2.1) + activerecord (= 4.2.1) + activesupport (= 4.2.1) bundler (>= 1.3.0, < 2.0) - railties (= 4.1.1) - sprockets-rails (~> 2.0) - railties (4.1.1) - actionpack (= 4.1.1) - activesupport (= 4.1.1) + railties (= 4.2.1) + sprockets-rails + rails-deprecated_sanitizer (1.0.3) + activesupport (>= 4.2.0.alpha) + rails-dom-testing (1.0.6) + activesupport (>= 4.2.0.beta, < 5.0) + nokogiri (~> 1.6.0) + rails-deprecated_sanitizer (>= 1.0.1) + rails-html-sanitizer (1.0.2) + loofah (~> 2.0) + railties (4.2.1) + actionpack (= 4.2.1) + activesupport (= 4.2.1) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rake (10.3.2) + rake (10.4.2) rdoc (4.1.1) json (~> 1.4) - responders (1.1.0) - railties (>= 3.2, < 5) + responders (2.1.0) + railties (>= 4.2.0, < 5) rspec (2.99.0) rspec-core (~> 2.99.0) rspec-expectations (~> 2.99.0) @@ -82,22 +105,19 @@ GEM rspec-expectations (2.99.0) diff-lcs (>= 1.1.3, < 2.0) rspec-mocks (2.99.1) - sprockets (2.12.1) + sprockets (2.12.3) hike (~> 1.2) multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - sprockets-rails (2.1.3) + sprockets-rails (2.2.4) actionpack (>= 3.0) activesupport (>= 3.0) - sprockets (~> 2.8) + sprockets (>= 2.8, < 4.0) thor (0.19.1) - thread_safe (0.3.4) + thread_safe (0.3.5) tilt (1.4.1) - treetop (1.4.15) - polyglot - polyglot (>= 0.3.1) - tzinfo (1.2.1) + tzinfo (1.2.2) thread_safe (~> 0.1) PLATFORMS diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index 66db882..b4459d4 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -5,7 +5,7 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "postgres-copy" - s.version = "0.9.0" + s.version = "0.9.1" s.platform = Gem::Platform::RUBY s.required_ruby_version = ">= 1.9.3" From 63c54ed97884a68b0cbca75ea2afa7f8a8d2feed Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Thu, 26 Mar 2015 19:48:57 -0400 Subject: [PATCH 18/77] Changes README shields to SVG --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 72e9fa1..9e1b007 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# postgres-copy [![Build Status](https://travis-ci.org/diogob/postgres-copy.png?branch=master)](https://travis-ci.org/diogob/postgres-copy) [![Code Climate](https://codeclimate.com/github/diogob/postgres-copy.png)](https://codeclimate.com/github/diogob/postgres-copy) +# postgres-copy [![Build Status](https://travis-ci.org/diogob/postgres-copy.svg?branch=master)](https://travis-ci.org/diogob/postgres-copy) [![Code Climate](https://codeclimate.com/github/diogob/postgres-copy.svg)](https://codeclimate.com/github/diogob/postgres-copy) This Gem will enable your AR models to use the PostgreSQL COPY command to import/export data in CSV format. If you need to tranfer data between a PostgreSQL database and CSV files, the PostgreSQL native CSV parser From 0979da1b1e72a6682e099352d3c603c190bea560 Mon Sep 17 00:00:00 2001 From: Nikita Shilnikov Date: Sun, 12 Apr 2015 17:08:26 +0300 Subject: [PATCH 19/77] Add quote option to #copy_from --- .gitignore | 3 +++ lib/postgres-copy/acts_as_copy_target.rb | 5 +++-- spec/copy_from_spec.rb | 7 +++++-- spec/fixtures/semicolon_with_quote.csv | 2 ++ spec/spec_helper.rb | 3 ++- 5 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 spec/fixtures/semicolon_with_quote.csv diff --git a/.gitignore b/.gitignore index 7b3b2bf..38df41b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,9 @@ tmtags ## VIM *.swp +## RubyMine +.idea + ## PROJECT::GENERAL coverage rdoc diff --git a/lib/postgres-copy/acts_as_copy_target.rb b/lib/postgres-copy/acts_as_copy_target.rb index 5e83d68..cdd3194 100644 --- a/lib/postgres-copy/acts_as_copy_target.rb +++ b/lib/postgres-copy/acts_as_copy_target.rb @@ -43,11 +43,12 @@ def copy_to_string options = {} # * You can map fields from the file to different fields in the table using a map in the options hash # * For further details on usage take a look at the README.md def copy_from path_or_io, options = {} - options = {:delimiter => ",", :format => :csv, :header => true}.merge(options) + options = {:delimiter => ",", :format => :csv, :header => true, :quote => '"'}.merge(options) options_string = if options[:format] == :binary "BINARY" else - "DELIMITER '#{options[:delimiter]}' CSV" + quote = options[:quote] == "'" ? "''" : options[:quote] + "DELIMITER '#{options[:delimiter]}' QUOTE '#{quote}' CSV" end io = path_or_io.instance_of?(String) ? File.open(path_or_io, 'r') : path_or_io diff --git a/spec/copy_from_spec.rb b/spec/copy_from_spec.rb index fb7a3f8..c8ea1c0 100644 --- a/spec/copy_from_spec.rb +++ b/spec/copy_from_spec.rb @@ -110,6 +110,9 @@ {"id"=>3, "data"=>"test data 2", "more_data"=>"9", "other_data"=>nil, "final_data"=>"666"}] end - -end + it "should import lines with single quotes" do + TestModel.copy_from(File.open(File.expand_path('spec/fixtures/semicolon_with_quote.csv'), 'r'), :delimiter => ";", :quote => "'") + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test "data" 1'}] + end +end diff --git a/spec/fixtures/semicolon_with_quote.csv b/spec/fixtures/semicolon_with_quote.csv new file mode 100644 index 0000000..bee48e5 --- /dev/null +++ b/spec/fixtures/semicolon_with_quote.csv @@ -0,0 +1,2 @@ +id;data +1;'test "data" 1' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 52377c5..d3e4a45 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -24,8 +24,9 @@ ActiveRecord::Base.connection.execute %{ SET client_min_messages TO warning; DROP TABLE IF EXISTS test_models; - DROP TABLE IF EXISTS extra_fields; + DROP TABLE IF EXISTS test_extended_models; DROP TABLE IF EXISTS reserved_word_models; + DROP TABLE IF EXISTS extra_fields; CREATE TABLE test_models (id serial PRIMARY KEY, data text); CREATE TABLE test_extended_models (id serial PRIMARY KEY, data text, more_data text,other_data text,final_data text ); CREATE TABLE reserved_word_models (id serial PRIMARY KEY, "select" text, "group" text); From e976c84ca6fc8cbb57ff598719bc2ea50df64092 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Sun, 12 Apr 2015 23:19:33 -0400 Subject: [PATCH 20/77] Bumps fix version number --- Gemfile.lock | 2 +- postgres-copy.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index bc3639f..14cb267 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - postgres-copy (0.9.1) + postgres-copy (0.9.2) activerecord (>= 4.0) pg (>= 0.17) rails (>= 4.0) diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index b4459d4..4da18c5 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -5,7 +5,7 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "postgres-copy" - s.version = "0.9.1" + s.version = "0.9.2" s.platform = Gem::Platform::RUBY s.required_ruby_version = ">= 1.9.3" From 6f501acd763e1a2f5c6d783ce61ac0b2744635ee Mon Sep 17 00:00:00 2001 From: Leigh Halliday Date: Fri, 8 May 2015 13:33:45 -0400 Subject: [PATCH 21/77] Removing the date from the gemspec file. --- postgres-copy.gemspec | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index 4da18c5..d838e25 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -4,23 +4,21 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| - s.name = "postgres-copy" - s.version = "0.9.2" - - s.platform = Gem::Platform::RUBY - s.required_ruby_version = ">= 1.9.3" - s.authors = ["Diogo Biazus"] - s.date = "2013-01-31" - s.description = "Now you can use the super fast COPY for import/export data directly from your AR models." - s.email = "diogob@gmail.com" - git_files = `git ls-files`.split("\n") rescue '' - s.files = git_files - s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") - s.executables = [] - s.require_paths = %w(lib) - s.homepage = "http://github.com/diogob/postgres-copy" - s.require_paths = ["lib"] - s.summary = "Put COPY command functionality in ActiveRecord's model class" + s.name = "postgres-copy" + s.version = "0.9.2" + s.platform = Gem::Platform::RUBY + s.required_ruby_version = ">= 1.9.3" + s.authors = ["Diogo Biazus"] + s.description = "Now you can use the super fast COPY for import/export data directly from your AR models." + s.email = "diogob@gmail.com" + git_files = `git ls-files`.split("\n") rescue '' + s.files = git_files + s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + s.executables = [] + s.require_paths = %w(lib) + s.homepage = "http://github.com/diogob/postgres-copy" + s.require_paths = ["lib"] + s.summary = "Put COPY command functionality in ActiveRecord's model class" s.add_dependency "pg", ">= 0.17" s.add_dependency "activerecord", '>= 4.0' From 536cabd7024a503dfea468e28e7bf2d96b1cca50 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Mon, 29 Jun 2015 21:55:20 -0400 Subject: [PATCH 22/77] Bumps fix version path --- Gemfile.lock | 86 ++++++++++++++++++++----------------------- postgres-copy.gemspec | 2 +- 2 files changed, 41 insertions(+), 47 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 14cb267..e364e9f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - postgres-copy (0.9.2) + postgres-copy (0.9.3) activerecord (>= 4.0) pg (>= 0.17) rails (>= 4.0) @@ -10,36 +10,36 @@ PATH GEM remote: https://rubygems.org/ specs: - actionmailer (4.2.1) - actionpack (= 4.2.1) - actionview (= 4.2.1) - activejob (= 4.2.1) + actionmailer (4.2.3) + actionpack (= 4.2.3) + actionview (= 4.2.3) + activejob (= 4.2.3) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.1) - actionview (= 4.2.1) - activesupport (= 4.2.1) + actionpack (4.2.3) + actionview (= 4.2.3) + activesupport (= 4.2.3) rack (~> 1.6) rack-test (~> 0.6.2) rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.1) - actionview (4.2.1) - activesupport (= 4.2.1) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (4.2.3) + activesupport (= 4.2.3) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.1) - activejob (4.2.1) - activesupport (= 4.2.1) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + activejob (4.2.3) + activesupport (= 4.2.3) globalid (>= 0.3.0) - activemodel (4.2.1) - activesupport (= 4.2.1) + activemodel (4.2.3) + activesupport (= 4.2.3) builder (~> 3.1) - activerecord (4.2.1) - activemodel (= 4.2.1) - activesupport (= 4.2.1) + activerecord (4.2.3) + activemodel (= 4.2.3) + activesupport (= 4.2.3) arel (~> 6.0) - activesupport (4.2.1) + activesupport (4.2.3) i18n (~> 0.7) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) @@ -49,35 +49,33 @@ GEM builder (3.2.2) diff-lcs (1.2.5) erubis (2.7.0) - globalid (0.3.3) + globalid (0.3.5) activesupport (>= 4.1.0) - hike (1.2.3) i18n (0.7.0) json (1.8.1) - loofah (2.0.1) + loofah (2.0.2) nokogiri (>= 1.5.9) mail (2.6.3) mime-types (>= 1.16, < 3) - mime-types (2.4.3) + mime-types (2.6.1) mini_portile (0.6.2) - minitest (5.5.1) - multi_json (1.11.0) + minitest (5.7.0) nokogiri (1.6.6.2) mini_portile (~> 0.6.0) - pg (0.18.1) - rack (1.6.0) + pg (0.18.2) + rack (1.6.4) rack-test (0.6.3) rack (>= 1.0) - rails (4.2.1) - actionmailer (= 4.2.1) - actionpack (= 4.2.1) - actionview (= 4.2.1) - activejob (= 4.2.1) - activemodel (= 4.2.1) - activerecord (= 4.2.1) - activesupport (= 4.2.1) + rails (4.2.3) + actionmailer (= 4.2.3) + actionpack (= 4.2.3) + actionview (= 4.2.3) + activejob (= 4.2.3) + activemodel (= 4.2.3) + activerecord (= 4.2.3) + activesupport (= 4.2.3) bundler (>= 1.3.0, < 2.0) - railties (= 4.2.1) + railties (= 4.2.3) sprockets-rails rails-deprecated_sanitizer (1.0.3) activesupport (>= 4.2.0.alpha) @@ -87,9 +85,9 @@ GEM rails-deprecated_sanitizer (>= 1.0.1) rails-html-sanitizer (1.0.2) loofah (~> 2.0) - railties (4.2.1) - actionpack (= 4.2.1) - activesupport (= 4.2.1) + railties (4.2.3) + actionpack (= 4.2.3) + activesupport (= 4.2.3) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rake (10.4.2) @@ -105,18 +103,14 @@ GEM rspec-expectations (2.99.0) diff-lcs (>= 1.1.3, < 2.0) rspec-mocks (2.99.1) - sprockets (2.12.3) - hike (~> 1.2) - multi_json (~> 1.0) + sprockets (3.2.0) rack (~> 1.0) - tilt (~> 1.1, != 1.3.0) - sprockets-rails (2.2.4) + sprockets-rails (2.3.2) actionpack (>= 3.0) activesupport (>= 3.0) sprockets (>= 2.8, < 4.0) thor (0.19.1) thread_safe (0.3.5) - tilt (1.4.1) tzinfo (1.2.2) thread_safe (~> 0.1) diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index d838e25..7388bd1 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -5,7 +5,7 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "postgres-copy" - s.version = "0.9.2" + s.version = "0.9.3" s.platform = Gem::Platform::RUBY s.required_ruby_version = ">= 1.9.3" s.authors = ["Diogo Biazus"] From 392037dcd7ee0004d4520ef2046ee3293610df94 Mon Sep 17 00:00:00 2001 From: Dave Steinberg Date: Wed, 30 Sep 2015 20:35:14 +0000 Subject: [PATCH 23/77] skip over all-nil records like we do for blank lines --- lib/postgres-copy/acts_as_copy_target.rb | 1 + spec/copy_from_spec.rb | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/lib/postgres-copy/acts_as_copy_target.rb b/lib/postgres-copy/acts_as_copy_target.rb index cdd3194..58aca9e 100644 --- a/lib/postgres-copy/acts_as_copy_target.rb +++ b/lib/postgres-copy/acts_as_copy_target.rb @@ -85,6 +85,7 @@ def copy_from path_or_io, options = {} if block_given? row = line.strip.split(options[:delimiter],-1) yield(row) + next if row.all?{|f| f.nil? } line = row.join(options[:delimiter]) + "\n" end connection.raw_connection.put_copy_data line diff --git a/spec/copy_from_spec.rb b/spec/copy_from_spec.rb index c8ea1c0..1c0e919 100644 --- a/spec/copy_from_spec.rb +++ b/spec/copy_from_spec.rb @@ -79,6 +79,15 @@ TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end + it "should ignore all-nil rows" do + lambda do + TestModel.copy_from(File.open(File.expand_path('spec/fixtures/tab_with_error.csv'), 'r'), :delimiter => "\t") do |row| + 0.upto(row.length) {|idx| row[idx] = nil} + end + end.should_not raise_error + TestModel.order(:id).map{|r| r.attributes}.should == [] + end + #we should implement this later #it "should raise error in malformed files" do #lambda do From 7731ae46399562788ac4c53c58f3a2222beb5fa6 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Wed, 30 Sep 2015 18:26:30 -0400 Subject: [PATCH 24/77] Bumps major version --- Gemfile.lock | 80 +++++++++++++++++++++---------------------- postgres-copy.gemspec | 2 +- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index e364e9f..d8c16d5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - postgres-copy (0.9.3) + postgres-copy (1.0.0) activerecord (>= 4.0) pg (>= 0.17) rails (>= 4.0) @@ -10,84 +10,84 @@ PATH GEM remote: https://rubygems.org/ specs: - actionmailer (4.2.3) - actionpack (= 4.2.3) - actionview (= 4.2.3) - activejob (= 4.2.3) + actionmailer (4.2.4) + actionpack (= 4.2.4) + actionview (= 4.2.4) + activejob (= 4.2.4) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.3) - actionview (= 4.2.3) - activesupport (= 4.2.3) + actionpack (4.2.4) + actionview (= 4.2.4) + activesupport (= 4.2.4) rack (~> 1.6) rack-test (~> 0.6.2) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (4.2.3) - activesupport (= 4.2.3) + actionview (4.2.4) + activesupport (= 4.2.4) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - activejob (4.2.3) - activesupport (= 4.2.3) + activejob (4.2.4) + activesupport (= 4.2.4) globalid (>= 0.3.0) - activemodel (4.2.3) - activesupport (= 4.2.3) + activemodel (4.2.4) + activesupport (= 4.2.4) builder (~> 3.1) - activerecord (4.2.3) - activemodel (= 4.2.3) - activesupport (= 4.2.3) + activerecord (4.2.4) + activemodel (= 4.2.4) + activesupport (= 4.2.4) arel (~> 6.0) - activesupport (4.2.3) + activesupport (4.2.4) i18n (~> 0.7) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - arel (6.0.0) + arel (6.0.3) builder (3.2.2) diff-lcs (1.2.5) erubis (2.7.0) - globalid (0.3.5) + globalid (0.3.6) activesupport (>= 4.1.0) i18n (0.7.0) json (1.8.1) - loofah (2.0.2) + loofah (2.0.3) nokogiri (>= 1.5.9) mail (2.6.3) mime-types (>= 1.16, < 3) - mime-types (2.6.1) + mime-types (2.6.2) mini_portile (0.6.2) - minitest (5.7.0) + minitest (5.8.1) nokogiri (1.6.6.2) mini_portile (~> 0.6.0) - pg (0.18.2) + pg (0.18.3) rack (1.6.4) rack-test (0.6.3) rack (>= 1.0) - rails (4.2.3) - actionmailer (= 4.2.3) - actionpack (= 4.2.3) - actionview (= 4.2.3) - activejob (= 4.2.3) - activemodel (= 4.2.3) - activerecord (= 4.2.3) - activesupport (= 4.2.3) + rails (4.2.4) + actionmailer (= 4.2.4) + actionpack (= 4.2.4) + actionview (= 4.2.4) + activejob (= 4.2.4) + activemodel (= 4.2.4) + activerecord (= 4.2.4) + activesupport (= 4.2.4) bundler (>= 1.3.0, < 2.0) - railties (= 4.2.3) + railties (= 4.2.4) sprockets-rails rails-deprecated_sanitizer (1.0.3) activesupport (>= 4.2.0.alpha) - rails-dom-testing (1.0.6) + rails-dom-testing (1.0.7) activesupport (>= 4.2.0.beta, < 5.0) nokogiri (~> 1.6.0) rails-deprecated_sanitizer (>= 1.0.1) rails-html-sanitizer (1.0.2) loofah (~> 2.0) - railties (4.2.3) - actionpack (= 4.2.3) - activesupport (= 4.2.3) + railties (4.2.4) + actionpack (= 4.2.4) + activesupport (= 4.2.4) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rake (10.4.2) @@ -103,9 +103,9 @@ GEM rspec-expectations (2.99.0) diff-lcs (>= 1.1.3, < 2.0) rspec-mocks (2.99.1) - sprockets (3.2.0) - rack (~> 1.0) - sprockets-rails (2.3.2) + sprockets (3.3.5) + rack (> 1, < 3) + sprockets-rails (2.3.3) actionpack (>= 3.0) activesupport (>= 3.0) sprockets (>= 2.8, < 4.0) diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index 7388bd1..505f8d0 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -5,7 +5,7 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "postgres-copy" - s.version = "0.9.3" + s.version = "1.0.0" s.platform = Gem::Platform::RUBY s.required_ruby_version = ">= 1.9.3" s.authors = ["Diogo Biazus"] From 05cdbe72ab8c47bacdd9ae872219a8799fec3831 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Wed, 17 Aug 2016 22:14:43 -0400 Subject: [PATCH 25/77] Update Gemfile.lock --- Gemfile.lock | 155 ++++++++++++++++++++++++++++----------------------- 1 file changed, 85 insertions(+), 70 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index d8c16d5..297c119 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,109 +10,121 @@ PATH GEM remote: https://rubygems.org/ specs: - actionmailer (4.2.4) - actionpack (= 4.2.4) - actionview (= 4.2.4) - activejob (= 4.2.4) + actioncable (5.0.0.1) + actionpack (= 5.0.0.1) + nio4r (~> 1.2) + websocket-driver (~> 0.6.1) + actionmailer (5.0.0.1) + actionpack (= 5.0.0.1) + actionview (= 5.0.0.1) + activejob (= 5.0.0.1) mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.4) - actionview (= 4.2.4) - activesupport (= 4.2.4) - rack (~> 1.6) - rack-test (~> 0.6.2) - rails-dom-testing (~> 1.0, >= 1.0.5) + rails-dom-testing (~> 2.0) + actionpack (5.0.0.1) + actionview (= 5.0.0.1) + activesupport (= 5.0.0.1) + rack (~> 2.0) + rack-test (~> 0.6.3) + rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (4.2.4) - activesupport (= 4.2.4) + actionview (5.0.0.1) + activesupport (= 5.0.0.1) builder (~> 3.1) erubis (~> 2.7.0) - rails-dom-testing (~> 1.0, >= 1.0.5) + rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - activejob (4.2.4) - activesupport (= 4.2.4) - globalid (>= 0.3.0) - activemodel (4.2.4) - activesupport (= 4.2.4) - builder (~> 3.1) - activerecord (4.2.4) - activemodel (= 4.2.4) - activesupport (= 4.2.4) - arel (~> 6.0) - activesupport (4.2.4) + activejob (5.0.0.1) + activesupport (= 5.0.0.1) + globalid (>= 0.3.6) + activemodel (5.0.0.1) + activesupport (= 5.0.0.1) + activerecord (5.0.0.1) + activemodel (= 5.0.0.1) + activesupport (= 5.0.0.1) + arel (~> 7.0) + activesupport (5.0.0.1) + concurrent-ruby (~> 1.0, >= 1.0.2) i18n (~> 0.7) - json (~> 1.7, >= 1.7.7) minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - arel (6.0.3) + arel (7.1.1) builder (3.2.2) + concurrent-ruby (1.0.2) diff-lcs (1.2.5) erubis (2.7.0) - globalid (0.3.6) + globalid (0.3.7) activesupport (>= 4.1.0) i18n (0.7.0) - json (1.8.1) + json (1.8.3) loofah (2.0.3) nokogiri (>= 1.5.9) - mail (2.6.3) - mime-types (>= 1.16, < 3) - mime-types (2.6.2) - mini_portile (0.6.2) - minitest (5.8.1) - nokogiri (1.6.6.2) - mini_portile (~> 0.6.0) - pg (0.18.3) - rack (1.6.4) + mail (2.6.4) + mime-types (>= 1.16, < 4) + method_source (0.8.2) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) + mini_portile2 (2.1.0) + minitest (5.9.0) + nio4r (1.2.1) + nokogiri (1.6.8) + mini_portile2 (~> 2.1.0) + pkg-config (~> 1.1.7) + pg (0.18.4) + pkg-config (1.1.7) + rack (2.0.1) rack-test (0.6.3) rack (>= 1.0) - rails (4.2.4) - actionmailer (= 4.2.4) - actionpack (= 4.2.4) - actionview (= 4.2.4) - activejob (= 4.2.4) - activemodel (= 4.2.4) - activerecord (= 4.2.4) - activesupport (= 4.2.4) + rails (5.0.0.1) + actioncable (= 5.0.0.1) + actionmailer (= 5.0.0.1) + actionpack (= 5.0.0.1) + actionview (= 5.0.0.1) + activejob (= 5.0.0.1) + activemodel (= 5.0.0.1) + activerecord (= 5.0.0.1) + activesupport (= 5.0.0.1) bundler (>= 1.3.0, < 2.0) - railties (= 4.2.4) - sprockets-rails - rails-deprecated_sanitizer (1.0.3) - activesupport (>= 4.2.0.alpha) - rails-dom-testing (1.0.7) - activesupport (>= 4.2.0.beta, < 5.0) + railties (= 5.0.0.1) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.1) + activesupport (>= 4.2.0, < 6.0) nokogiri (~> 1.6.0) - rails-deprecated_sanitizer (>= 1.0.1) - rails-html-sanitizer (1.0.2) + rails-html-sanitizer (1.0.3) loofah (~> 2.0) - railties (4.2.4) - actionpack (= 4.2.4) - activesupport (= 4.2.4) + railties (5.0.0.1) + actionpack (= 5.0.0.1) + activesupport (= 5.0.0.1) + method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rake (10.4.2) - rdoc (4.1.1) + rake (11.2.2) + rdoc (4.2.2) json (~> 1.4) - responders (2.1.0) - railties (>= 4.2.0, < 5) + responders (2.3.0) + railties (>= 4.2.0, < 5.1) rspec (2.99.0) rspec-core (~> 2.99.0) rspec-expectations (~> 2.99.0) rspec-mocks (~> 2.99.0) - rspec-core (2.99.0) - rspec-expectations (2.99.0) + rspec-core (2.99.2) + rspec-expectations (2.99.2) diff-lcs (>= 1.1.3, < 2.0) - rspec-mocks (2.99.1) - sprockets (3.3.5) + rspec-mocks (2.99.4) + sprockets (3.7.0) + concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (2.3.3) - actionpack (>= 3.0) - activesupport (>= 3.0) - sprockets (>= 2.8, < 4.0) + sprockets-rails (3.1.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) thor (0.19.1) thread_safe (0.3.5) tzinfo (1.2.2) thread_safe (~> 0.1) + websocket-driver (0.6.4) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.2) PLATFORMS ruby @@ -122,3 +134,6 @@ DEPENDENCIES postgres-copy! rdoc rspec (~> 2.12) + +BUNDLED WITH + 1.12.5 From 4eef053c62c8e32d983a081095cd680e040cf60c Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Wed, 17 Aug 2016 23:44:14 -0400 Subject: [PATCH 26/77] Add test case for issue #35 --- .travis.yml | 2 +- spec/copy_from_spec.rb | 5 +++++ spec/fixtures/comma_inside_field.csv | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/comma_inside_field.csv diff --git a/.travis.yml b/.travis.yml index f86adb8..f7739cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: ruby rvm: - - 1.9.3 + - 2.3.1 branches: only: diff --git a/spec/copy_from_spec.rb b/spec/copy_from_spec.rb index 1c0e919..a740700 100644 --- a/spec/copy_from_spec.rb +++ b/spec/copy_from_spec.rb @@ -124,4 +124,9 @@ TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test "data" 1'}] end + it "should import lines with commas inside fields with default options" do + TestModel.copy_from(File.open(File.expand_path('spec/fixtures/comma_inside_field.csv'), 'r')) + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test, again'}] + end + end diff --git a/spec/fixtures/comma_inside_field.csv b/spec/fixtures/comma_inside_field.csv new file mode 100644 index 0000000..650a0e1 --- /dev/null +++ b/spec/fixtures/comma_inside_field.csv @@ -0,0 +1,2 @@ +id,data +1,"test, again" From 1342a0552293ccac11b61de8fc7b1c0d08e29cba Mon Sep 17 00:00:00 2001 From: "T. Matsu" Date: Wed, 14 Dec 2016 11:36:31 +0900 Subject: [PATCH 27/77] Support NULL option parmeter (#36) * support null option parmeter * add test for copy_from null option --- README.md | 7 +++++++ lib/postgres-copy/acts_as_copy_target.rb | 3 ++- spec/copy_from_spec.rb | 9 +++++++++ spec/fixtures/special_null_with_header.csv | 2 ++ 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/special_null_with_header.csv diff --git a/README.md b/README.md index 9e1b007..2a7eb9a 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,13 @@ The above extample will always change the value of the first column to "fixed st For each iteration of the block row receives an array with the same order as the columns in the CSV file. +To specify NULL value you can pass the null option parameter. + +```ruby +User.copy_from "/tmp/users.csv", :null => 'null' +``` + + To copy a binary formatted data file or IO object you can specify the format as binary ```ruby diff --git a/lib/postgres-copy/acts_as_copy_target.rb b/lib/postgres-copy/acts_as_copy_target.rb index 58aca9e..22327a2 100644 --- a/lib/postgres-copy/acts_as_copy_target.rb +++ b/lib/postgres-copy/acts_as_copy_target.rb @@ -48,7 +48,8 @@ def copy_from path_or_io, options = {} "BINARY" else quote = options[:quote] == "'" ? "''" : options[:quote] - "DELIMITER '#{options[:delimiter]}' QUOTE '#{quote}' CSV" + null = options.key?(:null) ? "NULL '#{options[:null]}'" : '' + "DELIMITER '#{options[:delimiter]}' QUOTE '#{quote}' #{null} CSV" end io = path_or_io.instance_of?(String) ? File.open(path_or_io, 'r') : path_or_io diff --git a/spec/copy_from_spec.rb b/spec/copy_from_spec.rb index a740700..3dcb127 100644 --- a/spec/copy_from_spec.rb +++ b/spec/copy_from_spec.rb @@ -129,4 +129,13 @@ TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test, again'}] end + it "should import with custom null expression from path" do + TestModel.copy_from File.expand_path('spec/fixtures/special_null_with_header.csv'), :null => 'NULL' + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => nil}] + end + + it "should import with custom null expression from IO" do + TestModel.copy_from File.open(File.expand_path('spec/fixtures/special_null_with_header.csv'), 'r'), :null => 'NULL' + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => nil}] + end end diff --git a/spec/fixtures/special_null_with_header.csv b/spec/fixtures/special_null_with_header.csv new file mode 100644 index 0000000..ab6d294 --- /dev/null +++ b/spec/fixtures/special_null_with_header.csv @@ -0,0 +1,2 @@ +id,data +1,NULL \ No newline at end of file From 7d92d51c965c9e81f7a3c5e6ac50a701efd7d627 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Tue, 13 Dec 2016 21:42:59 -0500 Subject: [PATCH 28/77] Update minor version and Gemfile.lock --- Gemfile.lock | 19 +++++++++---------- postgres-copy.gemspec | 3 ++- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 297c119..32da5a6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - postgres-copy (1.0.0) + postgres-copy (1.1.0) activerecord (>= 4.0) pg (>= 0.17) rails (>= 4.0) @@ -47,7 +47,7 @@ GEM i18n (~> 0.7) minitest (~> 5.1) tzinfo (~> 1.1) - arel (7.1.1) + arel (7.1.4) builder (3.2.2) concurrent-ruby (1.0.2) diff-lcs (1.2.5) @@ -65,13 +65,11 @@ GEM mime-types-data (~> 3.2015) mime-types-data (3.2016.0521) mini_portile2 (2.1.0) - minitest (5.9.0) + minitest (5.10.1) nio4r (1.2.1) - nokogiri (1.6.8) + nokogiri (1.6.8.1) mini_portile2 (~> 2.1.0) - pkg-config (~> 1.1.7) - pg (0.18.4) - pkg-config (1.1.7) + pg (0.19.0) rack (2.0.1) rack-test (0.6.3) rack (>= 1.0) @@ -114,11 +112,11 @@ GEM sprockets (3.7.0) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.1.1) + sprockets-rails (3.2.0) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - thor (0.19.1) + thor (0.19.4) thread_safe (0.3.5) tzinfo (1.2.2) thread_safe (~> 0.1) @@ -132,8 +130,9 @@ PLATFORMS DEPENDENCIES bundler postgres-copy! + rake (~> 11.2.2) rdoc rspec (~> 2.12) BUNDLED WITH - 1.12.5 + 1.13.6 diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index 505f8d0..09ec87b 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -5,7 +5,7 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "postgres-copy" - s.version = "1.0.0" + s.version = "1.1.0" s.platform = Gem::Platform::RUBY s.required_ruby_version = ">= 1.9.3" s.authors = ["Diogo Biazus"] @@ -27,5 +27,6 @@ Gem::Specification.new do |s| s.add_development_dependency "bundler" s.add_development_dependency "rdoc" s.add_development_dependency "rspec", "~> 2.12" + s.add_development_dependency "rake", "~> 11.2.2" end From 5f1a84d6755fa6cb8b44f57ebe2548553001873e Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Wed, 22 Feb 2017 23:11:20 -0500 Subject: [PATCH 29/77] Remove rails dependency [fix #32] (#40) --- Gemfile.lock | 94 +++++++++++-------------------------------- postgres-copy.gemspec | 1 - 2 files changed, 24 insertions(+), 71 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 32da5a6..b69afed 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,101 +4,65 @@ PATH postgres-copy (1.1.0) activerecord (>= 4.0) pg (>= 0.17) - rails (>= 4.0) responders GEM remote: https://rubygems.org/ specs: - actioncable (5.0.0.1) - actionpack (= 5.0.0.1) - nio4r (~> 1.2) - websocket-driver (~> 0.6.1) - actionmailer (5.0.0.1) - actionpack (= 5.0.0.1) - actionview (= 5.0.0.1) - activejob (= 5.0.0.1) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 2.0) - actionpack (5.0.0.1) - actionview (= 5.0.0.1) - activesupport (= 5.0.0.1) + actionpack (5.0.1) + actionview (= 5.0.1) + activesupport (= 5.0.1) rack (~> 2.0) rack-test (~> 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.0.0.1) - activesupport (= 5.0.0.1) + actionview (5.0.1) + activesupport (= 5.0.1) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - activejob (5.0.0.1) - activesupport (= 5.0.0.1) - globalid (>= 0.3.6) - activemodel (5.0.0.1) - activesupport (= 5.0.0.1) - activerecord (5.0.0.1) - activemodel (= 5.0.0.1) - activesupport (= 5.0.0.1) + activemodel (5.0.1) + activesupport (= 5.0.1) + activerecord (5.0.1) + activemodel (= 5.0.1) + activesupport (= 5.0.1) arel (~> 7.0) - activesupport (5.0.0.1) + activesupport (5.0.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (~> 0.7) minitest (~> 5.1) tzinfo (~> 1.1) arel (7.1.4) - builder (3.2.2) - concurrent-ruby (1.0.2) - diff-lcs (1.2.5) + builder (3.2.3) + concurrent-ruby (1.0.4) + diff-lcs (1.3) erubis (2.7.0) - globalid (0.3.7) - activesupport (>= 4.1.0) - i18n (0.7.0) - json (1.8.3) + i18n (0.8.1) loofah (2.0.3) nokogiri (>= 1.5.9) - mail (2.6.4) - mime-types (>= 1.16, < 4) method_source (0.8.2) - mime-types (3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2016.0521) mini_portile2 (2.1.0) minitest (5.10.1) - nio4r (1.2.1) - nokogiri (1.6.8.1) + nokogiri (1.7.0.1) mini_portile2 (~> 2.1.0) pg (0.19.0) rack (2.0.1) rack-test (0.6.3) rack (>= 1.0) - rails (5.0.0.1) - actioncable (= 5.0.0.1) - actionmailer (= 5.0.0.1) - actionpack (= 5.0.0.1) - actionview (= 5.0.0.1) - activejob (= 5.0.0.1) - activemodel (= 5.0.0.1) - activerecord (= 5.0.0.1) - activesupport (= 5.0.0.1) - bundler (>= 1.3.0, < 2.0) - railties (= 5.0.0.1) - sprockets-rails (>= 2.0.0) - rails-dom-testing (2.0.1) + rails-dom-testing (2.0.2) activesupport (>= 4.2.0, < 6.0) - nokogiri (~> 1.6.0) + nokogiri (~> 1.6) rails-html-sanitizer (1.0.3) loofah (~> 2.0) - railties (5.0.0.1) - actionpack (= 5.0.0.1) - activesupport (= 5.0.0.1) + railties (5.0.1) + actionpack (= 5.0.1) + activesupport (= 5.0.1) method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rake (11.2.2) - rdoc (4.2.2) - json (~> 1.4) + rdoc (5.0.0) responders (2.3.0) railties (>= 4.2.0, < 5.1) rspec (2.99.0) @@ -109,20 +73,10 @@ GEM rspec-expectations (2.99.2) diff-lcs (>= 1.1.3, < 2.0) rspec-mocks (2.99.4) - sprockets (3.7.0) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.2.0) - actionpack (>= 4.0) - activesupport (>= 4.0) - sprockets (>= 3.0.0) thor (0.19.4) - thread_safe (0.3.5) + thread_safe (0.3.6) tzinfo (1.2.2) thread_safe (~> 0.1) - websocket-driver (0.6.4) - websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.2) PLATFORMS ruby @@ -135,4 +89,4 @@ DEPENDENCIES rspec (~> 2.12) BUNDLED WITH - 1.13.6 + 1.14.3 diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index 09ec87b..613e7d3 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -22,7 +22,6 @@ Gem::Specification.new do |s| s.add_dependency "pg", ">= 0.17" s.add_dependency "activerecord", '>= 4.0' - s.add_dependency "rails", '>= 4.0' s.add_dependency "responders" s.add_development_dependency "bundler" s.add_development_dependency "rdoc" From c3713bbfe777ffdcd47603855c0de190fc399dba Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Wed, 22 Feb 2017 23:13:25 -0500 Subject: [PATCH 30/77] Bump patch version number for version without unnecessary rails dependency. --- Gemfile.lock | 2 +- postgres-copy.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index b69afed..86e3f91 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - postgres-copy (1.1.0) + postgres-copy (1.1.1) activerecord (>= 4.0) pg (>= 0.17) responders diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index 613e7d3..a210d98 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -5,7 +5,7 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "postgres-copy" - s.version = "1.1.0" + s.version = "1.1.1" s.platform = Gem::Platform::RUBY s.required_ruby_version = ">= 1.9.3" s.authors = ["Diogo Biazus"] From 92b4f88e3310038c794213e2e8219e242a45a003 Mon Sep 17 00:00:00 2001 From: Milan Date: Wed, 8 Mar 2017 16:50:41 +0100 Subject: [PATCH 31/77] Ensure valid row values when fields have commas and block is given (#41) * Add failing spec for given block when fields have commas When there are commas inside fields and block is given there is a problem with `row`. Line is split by delimiter, and when delimiter is a comma, `row` has more values than there are columns. This test demonstrates that case. * Use CSV lib to parse and join csv lines when passing values to given block When there are commas inside fields and block is given there is a problem with `row`. Line is split by delimiter, and when delimiter is a comma, `row` has more values than there are columns. Using CSV lib to parse the line avoids splitting just by delimiter. Line is split with regards to CSV format. When `yield` finishes, line is again generated by CSV lib `generate_line` to ensure the line is a valid CSV line. * Make use of auto-cleanup block. --- lib/postgres-copy/acts_as_copy_target.rb | 6 ++++-- spec/copy_from_spec.rb | 12 ++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/postgres-copy/acts_as_copy_target.rb b/lib/postgres-copy/acts_as_copy_target.rb index 22327a2..e21004a 100644 --- a/lib/postgres-copy/acts_as_copy_target.rb +++ b/lib/postgres-copy/acts_as_copy_target.rb @@ -1,3 +1,5 @@ +require 'csv' + module PostgresCopy module ActsAsCopyTarget extend ActiveSupport::Concern @@ -84,10 +86,10 @@ def copy_from path_or_io, options = {} while line = io.gets do next if line.strip.size == 0 if block_given? - row = line.strip.split(options[:delimiter],-1) + row = CSV.parse_line(line.strip, {:col_sep => options[:delimiter]}) yield(row) next if row.all?{|f| f.nil? } - line = row.join(options[:delimiter]) + "\n" + line = CSV.generate_line(row, {:col_sep => options[:delimiter]}) end connection.raw_connection.put_copy_data line end diff --git a/spec/copy_from_spec.rb b/spec/copy_from_spec.rb index 3dcb127..d51ad31 100644 --- a/spec/copy_from_spec.rb +++ b/spec/copy_from_spec.rb @@ -129,6 +129,18 @@ TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test, again'}] end + it "should import lines with commas inside fields with block given" do + File.open(File.expand_path('spec/fixtures/comma_inside_field.csv'), 'r') do |file| + TestModel.copy_from(file) do |row| + # since our CSV line look like this: {1,"test, again"} we expect only two elements withing row + row.size.should == 2 + row[0].should == '1' + row[1].should == 'test, again' + end + end + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test, again'}] + end + it "should import with custom null expression from path" do TestModel.copy_from File.expand_path('spec/fixtures/special_null_with_header.csv'), :null => 'NULL' TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => nil}] From 4f79ee776aaf9323ffdfc6b195bf2d1090ac73a1 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Wed, 8 Mar 2017 19:01:43 -0500 Subject: [PATCH 32/77] Bump patch version number --- Gemfile.lock | 2 +- postgres-copy.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 86e3f91..fce4c4d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - postgres-copy (1.1.1) + postgres-copy (1.1.2) activerecord (>= 4.0) pg (>= 0.17) responders diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index a210d98..679feb5 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -5,7 +5,7 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "postgres-copy" - s.version = "1.1.1" + s.version = "1.1.2" s.platform = Gem::Platform::RUBY s.required_ruby_version = ">= 1.9.3" s.authors = ["Diogo Biazus"] From 4eff3382eb6bb46dbf46ce98eaf72d9e1decf728 Mon Sep 17 00:00:00 2001 From: Daniel Heath Date: Wed, 21 Jun 2017 11:48:27 +1000 Subject: [PATCH 33/77] Add copy_to_enumerator with buffering support (#44) --- lib/postgres-copy/acts_as_copy_target.rb | 26 +++++++++++++++ spec/copy_to_spec.rb | 32 ++++++++++++++++++- spec/fixtures/comma_with_header_and_scope.csv | 4 +++ spec/fixtures/comma_with_header_multi.csv | 5 +++ spec/fixtures/tab_with_header_multi.csv | 5 +++ 5 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/comma_with_header_and_scope.csv create mode 100644 spec/fixtures/comma_with_header_multi.csv create mode 100644 spec/fixtures/tab_with_header_multi.csv diff --git a/lib/postgres-copy/acts_as_copy_target.rb b/lib/postgres-copy/acts_as_copy_target.rb index e21004a..4fa3b88 100644 --- a/lib/postgres-copy/acts_as_copy_target.rb +++ b/lib/postgres-copy/acts_as_copy_target.rb @@ -30,6 +30,32 @@ def copy_to path = nil, options = {} return self end + # Create an enumerator with each line from the CSV. + # Note that using this directly in a controller response + # will perform very poorly as each line will get put + # into its own chunk. Joining every (eg) 100 rows together + # is much, much faster. + def copy_to_enumerator(options={}) + buffer_lines = options.delete(:buffer_lines) + # Somehow, self loses its scope once inside the Enumerator + scope = self.current_scope || self + result = Enumerator.new do |y| + scope.copy_to(nil, options) do |line| + y << line + end + end + + if buffer_lines.to_i > 0 + Enumerator.new do |y| + result.each_slice(buffer_lines.to_i) do |slice| + y << slice.join + end + end + else + result + end + end + # Copy all data to a single string def copy_to_string options = {} data = '' diff --git a/spec/copy_to_spec.rb b/spec/copy_to_spec.rb index 3972631..db82893 100644 --- a/spec/copy_to_spec.rb +++ b/spec/copy_to_spec.rb @@ -1,7 +1,7 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper') describe "COPY TO" do - before(:all) do + before(:each) do ActiveRecord::Base.connection.execute %{ TRUNCATE TABLE test_models; SELECT setval('test_models_id_seq', 1, false); @@ -21,6 +21,36 @@ end end + describe ".copy_to_enumerator" do + before(:each) do + TestModel.create :data => 'test data 2' + TestModel.create :data => 'test data 3' + TestModel.create :data => 'test data 4' + end + + context "with no options" do + subject{ TestModel.copy_to_enumerator.to_a } + it{ should == File.open('spec/fixtures/comma_with_header_multi.csv', 'r').read.lines } + end + + context "with tab as delimiter" do + subject{ TestModel.copy_to_enumerator(:delimiter => "\t").to_a } + it{ should == File.open('spec/fixtures/tab_with_header_multi.csv', 'r').read.lines } + end + + context "with many records" do + context "enumerating in batches" do + subject{ TestModel.copy_to_enumerator(:buffer => 2).to_a } + it{ should == File.open('spec/fixtures/comma_with_header_multi.csv', 'r').read.lines } + end + + context "excluding some records via a scope" do + subject{ TestModel.where("data not like '%3'").copy_to_enumerator.to_a } + it{ should == File.open('spec/fixtures/comma_with_header_and_scope.csv', 'r').read.lines } + end + end + end + describe ".copy_to" do it "should copy and pass data to block if block is given and no path is passed" do File.open('spec/fixtures/comma_with_header.csv', 'r') do |f| diff --git a/spec/fixtures/comma_with_header_and_scope.csv b/spec/fixtures/comma_with_header_and_scope.csv new file mode 100644 index 0000000..563ccb0 --- /dev/null +++ b/spec/fixtures/comma_with_header_and_scope.csv @@ -0,0 +1,4 @@ +id,data +1,test data 1 +2,test data 2 +4,test data 4 diff --git a/spec/fixtures/comma_with_header_multi.csv b/spec/fixtures/comma_with_header_multi.csv new file mode 100644 index 0000000..5b680f3 --- /dev/null +++ b/spec/fixtures/comma_with_header_multi.csv @@ -0,0 +1,5 @@ +id,data +1,test data 1 +2,test data 2 +3,test data 3 +4,test data 4 diff --git a/spec/fixtures/tab_with_header_multi.csv b/spec/fixtures/tab_with_header_multi.csv new file mode 100644 index 0000000..ffe9af4 --- /dev/null +++ b/spec/fixtures/tab_with_header_multi.csv @@ -0,0 +1,5 @@ +id data +1 test data 1 +2 test data 2 +3 test data 3 +4 test data 4 From 6c2f9c3a61330232a534c77f9ae4140a875fb173 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Tue, 20 Jun 2017 22:06:47 -0400 Subject: [PATCH 34/77] Add version contraint to activerecord < 5.1 (there was a method rename in sanitize). --- Gemfile.lock | 65 ++++++++++++++++++++++--------------------- postgres-copy.gemspec | 4 +-- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index fce4c4d..5e59eb4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,70 +1,71 @@ PATH remote: . specs: - postgres-copy (1.1.2) - activerecord (>= 4.0) + postgres-copy (1.2.0) + activerecord (>= 4.0, < 5.1) pg (>= 0.17) responders GEM remote: https://rubygems.org/ specs: - actionpack (5.0.1) - actionview (= 5.0.1) - activesupport (= 5.0.1) + actionpack (5.0.3) + actionview (= 5.0.3) + activesupport (= 5.0.3) rack (~> 2.0) rack-test (~> 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.0.1) - activesupport (= 5.0.1) + actionview (5.0.3) + activesupport (= 5.0.3) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - activemodel (5.0.1) - activesupport (= 5.0.1) - activerecord (5.0.1) - activemodel (= 5.0.1) - activesupport (= 5.0.1) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activemodel (5.0.3) + activesupport (= 5.0.3) + activerecord (5.0.3) + activemodel (= 5.0.3) + activesupport (= 5.0.3) arel (~> 7.0) - activesupport (5.0.1) + activesupport (5.0.3) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (~> 0.7) minitest (~> 5.1) tzinfo (~> 1.1) arel (7.1.4) builder (3.2.3) - concurrent-ruby (1.0.4) + concurrent-ruby (1.0.5) diff-lcs (1.3) erubis (2.7.0) - i18n (0.8.1) + i18n (0.8.4) loofah (2.0.3) nokogiri (>= 1.5.9) method_source (0.8.2) - mini_portile2 (2.1.0) - minitest (5.10.1) - nokogiri (1.7.0.1) - mini_portile2 (~> 2.1.0) - pg (0.19.0) - rack (2.0.1) + mini_portile2 (2.2.0) + minitest (5.10.2) + nokogiri (1.8.0) + mini_portile2 (~> 2.2.0) + pg (0.21.0) + rack (2.0.3) rack-test (0.6.3) rack (>= 1.0) - rails-dom-testing (2.0.2) - activesupport (>= 4.2.0, < 6.0) - nokogiri (~> 1.6) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) rails-html-sanitizer (1.0.3) loofah (~> 2.0) - railties (5.0.1) - actionpack (= 5.0.1) - activesupport (= 5.0.1) + railties (5.0.3) + actionpack (= 5.0.3) + activesupport (= 5.0.3) method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rake (11.2.2) rdoc (5.0.0) - responders (2.3.0) - railties (>= 4.2.0, < 5.1) + responders (2.4.0) + actionpack (>= 4.2.0, < 5.3) + railties (>= 4.2.0, < 5.3) rspec (2.99.0) rspec-core (~> 2.99.0) rspec-expectations (~> 2.99.0) @@ -75,7 +76,7 @@ GEM rspec-mocks (2.99.4) thor (0.19.4) thread_safe (0.3.6) - tzinfo (1.2.2) + tzinfo (1.2.3) thread_safe (~> 0.1) PLATFORMS @@ -89,4 +90,4 @@ DEPENDENCIES rspec (~> 2.12) BUNDLED WITH - 1.14.3 + 1.14.6 diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index 679feb5..cc8a1e3 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -5,7 +5,7 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "postgres-copy" - s.version = "1.1.2" + s.version = "1.2.0" s.platform = Gem::Platform::RUBY s.required_ruby_version = ">= 1.9.3" s.authors = ["Diogo Biazus"] @@ -21,7 +21,7 @@ Gem::Specification.new do |s| s.summary = "Put COPY command functionality in ActiveRecord's model class" s.add_dependency "pg", ">= 0.17" - s.add_dependency "activerecord", '>= 4.0' + s.add_dependency "activerecord", '>= 4.0', '< 5.1' s.add_dependency "responders" s.add_development_dependency "bundler" s.add_development_dependency "rdoc" From e1360b33f4025e9e606173f27d7d2fe70a78e0a6 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Tue, 20 Jun 2017 22:16:17 -0400 Subject: [PATCH 35/77] Fix spec for buffered enumerator. --- spec/copy_to_spec.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/copy_to_spec.rb b/spec/copy_to_spec.rb index db82893..2a11021 100644 --- a/spec/copy_to_spec.rb +++ b/spec/copy_to_spec.rb @@ -40,8 +40,12 @@ context "with many records" do context "enumerating in batches" do - subject{ TestModel.copy_to_enumerator(:buffer => 2).to_a } - it{ should == File.open('spec/fixtures/comma_with_header_multi.csv', 'r').read.lines } + subject{ TestModel.copy_to_enumerator(:buffer_lines => 2).to_a } + it do + expected = [] + File.open('spec/fixtures/comma_with_header_multi.csv', 'r').read.lines.each_slice(2){|s| expected << s.join } + should == expected + end end context "excluding some records via a scope" do From 3c6ce74404c08eb0301985fe35e2e74e5a9293a1 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Tue, 20 Jun 2017 22:16:30 -0400 Subject: [PATCH 36/77] Add new copy_to_enumerator to README. --- README.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2a7eb9a..7fad3ad 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ If you need to tranfer data between a PostgreSQL database and CSV files, the Pos will give you a greater performance than using the ruby CSV+INSERT commands. I have not found time to make accurate benchmarks, but in the use scenario where I have developed the gem I have had a four-fold performance gain. -This gem was written having the Rails framework in mind, I think it could work only with active-record, +This gem was written having the Rails framework in mind, I think it could work only with active-record, but I will assume in this README that you are using Rails. ## Install @@ -34,8 +34,9 @@ end This will add the aditiontal class methods to your model: -* copy_to +* copy_to * copy_to_string +* copy_to_enumerator * copy_from ### Using copy_to and copy_to_string @@ -66,13 +67,23 @@ File.open('/tmp/users.csv', 'w') do |f| end ``` +Instead of yielding each line, you could return an enumerator with all users: +```ruby +enumerator = User.copy_to_enumerator +``` + +And for better performance when rendering the result of the enumerator, you can return an enumerator with blocks of 100 lines joined: +```ruby +enumerator = User.copy_to_enumerator(:buffer_lines => 100) +``` + Or, if you have enough memory, you can read all table contents to a string using .copy_to_string ```ruby puts User.copy_to_string ``` -Another insteresting feature of copy_to is that it uses the scoped relation, it means that you can use ARel +Another insteresting feature of copy_to is that it uses the scoped relation, it means that you can use ARel operations to generate different CSV files according to your needs. Assuming we want to generate a file only with the names of users 1, 2 and 3: From c3626381417d85ae6dc57c4560a07cdb88b1c96f Mon Sep 17 00:00:00 2001 From: Christopher Styles Date: Thu, 7 Sep 2017 09:57:21 -0700 Subject: [PATCH 37/77] Fix minor typos in README This update corrects spelling on a few typos in the README. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7fad3ad..a8373c9 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ class User < ActiveRecord::Base end ``` -This will add the aditiontal class methods to your model: +This will add the additional class methods to your model: * copy_to * copy_to_string @@ -42,7 +42,7 @@ This will add the aditiontal class methods to your model: ### Using copy_to and copy_to_string You can go to the rails console and try some cool things first. -The first and most basic use case, let's copy the enteire content of a database table to a CSV file on the database server disk. +The first and most basic use case, let's copy the entire content of a database table to a CSV file on the database server disk. Assuming we have a users table and a User AR model: ```ruby @@ -83,7 +83,7 @@ Or, if you have enough memory, you can read all table contents to a string using puts User.copy_to_string ``` -Another insteresting feature of copy_to is that it uses the scoped relation, it means that you can use ARel +Another interesting feature of copy_to is that it uses the scoped relation, it means that you can use ARel operations to generate different CSV files according to your needs. Assuming we want to generate a file only with the names of users 1, 2 and 3: @@ -144,7 +144,7 @@ User.copy_from "/tmp/users.csv" do |row| end ``` -The above extample will always change the value of the first column to "fixed string" before storing it into the database. +The above example will always change the value of the first column to "fixed string" before storing it into the database. For each iteration of the block row receives an array with the same order as the columns in the CSV file. From 3938bfcf5fc1732c4753170fe55de7c57162db32 Mon Sep 17 00:00:00 2001 From: Martin Gonzalez Date: Thu, 31 Aug 2017 12:06:37 -0300 Subject: [PATCH 38/77] Fix for ActiveRecord 5.1, break backwards compat --- Gemfile.lock | 40 ++++++++++++------------ lib/postgres-copy/acts_as_copy_target.rb | 2 +- postgres-copy.gemspec | 4 +-- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5e59eb4..f69daee 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,43 +1,43 @@ PATH remote: . specs: - postgres-copy (1.2.0) - activerecord (>= 4.0, < 5.1) + postgres-copy (1.3.0) + activerecord (>= 5.1) pg (>= 0.17) responders GEM remote: https://rubygems.org/ specs: - actionpack (5.0.3) - actionview (= 5.0.3) - activesupport (= 5.0.3) + actionpack (5.1.1) + actionview (= 5.1.1) + activesupport (= 5.1.1) rack (~> 2.0) rack-test (~> 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.0.3) - activesupport (= 5.0.3) + actionview (5.1.1) + activesupport (= 5.1.1) builder (~> 3.1) - erubis (~> 2.7.0) + erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - activemodel (5.0.3) - activesupport (= 5.0.3) - activerecord (5.0.3) - activemodel (= 5.0.3) - activesupport (= 5.0.3) - arel (~> 7.0) - activesupport (5.0.3) + activemodel (5.1.1) + activesupport (= 5.1.1) + activerecord (5.1.1) + activemodel (= 5.1.1) + activesupport (= 5.1.1) + arel (~> 8.0) + activesupport (5.1.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (~> 0.7) minitest (~> 5.1) tzinfo (~> 1.1) - arel (7.1.4) + arel (8.0.0) builder (3.2.3) concurrent-ruby (1.0.5) diff-lcs (1.3) - erubis (2.7.0) + erubi (1.6.0) i18n (0.8.4) loofah (2.0.3) nokogiri (>= 1.5.9) @@ -55,9 +55,9 @@ GEM nokogiri (>= 1.6) rails-html-sanitizer (1.0.3) loofah (~> 2.0) - railties (5.0.3) - actionpack (= 5.0.3) - activesupport (= 5.0.3) + railties (5.1.1) + actionpack (= 5.1.1) + activesupport (= 5.1.1) method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) diff --git a/lib/postgres-copy/acts_as_copy_target.rb b/lib/postgres-copy/acts_as_copy_target.rb index 4fa3b88..c1320b1 100644 --- a/lib/postgres-copy/acts_as_copy_target.rb +++ b/lib/postgres-copy/acts_as_copy_target.rb @@ -19,7 +19,7 @@ def copy_to path = nil, options = {} if path raise "You have to choose between exporting to a file or receiving the lines inside a block" if block_given? - connection.execute "COPY (#{self.all.to_sql}) TO #{sanitize(path)} WITH #{options_string}" + connection.execute "COPY (#{self.all.to_sql}) TO '#{sanitize_sql(path)}' WITH #{options_string}" else connection.raw_connection.copy_data "COPY (#{self.all.to_sql}) TO STDOUT WITH #{options_string}" do while line = connection.raw_connection.get_copy_data do diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index cc8a1e3..e039b62 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -5,7 +5,7 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "postgres-copy" - s.version = "1.2.0" + s.version = "1.3.0" s.platform = Gem::Platform::RUBY s.required_ruby_version = ">= 1.9.3" s.authors = ["Diogo Biazus"] @@ -21,7 +21,7 @@ Gem::Specification.new do |s| s.summary = "Put COPY command functionality in ActiveRecord's model class" s.add_dependency "pg", ">= 0.17" - s.add_dependency "activerecord", '>= 4.0', '< 5.1' + s.add_dependency "activerecord", '>= 5.1' s.add_dependency "responders" s.add_development_dependency "bundler" s.add_development_dependency "rdoc" From a296838bd2832f2a7095a60059936f6f3336ae11 Mon Sep 17 00:00:00 2001 From: Abdullah Barrak Date: Sun, 28 Jan 2018 12:07:10 +0300 Subject: [PATCH 39/77] add support of providing raw sql query instead of context relation --- .gitignore | 4 ++++ lib/postgres-copy/acts_as_copy_target.rb | 5 +++-- spec/copy_to_spec.rb | 8 +++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 38df41b..ce3cd4a 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,7 @@ pkg ## PROJECT::SPECIFIC .project + +# RVM files +.ruby-version +.ruby-gemset diff --git a/lib/postgres-copy/acts_as_copy_target.rb b/lib/postgres-copy/acts_as_copy_target.rb index c1320b1..fc75d58 100644 --- a/lib/postgres-copy/acts_as_copy_target.rb +++ b/lib/postgres-copy/acts_as_copy_target.rb @@ -16,12 +16,13 @@ def copy_to path = nil, options = {} else "DELIMITER '#{options[:delimiter]}' CSV #{options[:header] ? 'HEADER' : ''}" end + options_query = options.delete(:query) || self.all.to_sql if path raise "You have to choose between exporting to a file or receiving the lines inside a block" if block_given? - connection.execute "COPY (#{self.all.to_sql}) TO '#{sanitize_sql(path)}' WITH #{options_string}" + connection.execute "COPY (#{options_query}) TO '#{sanitize_sql(path)}' WITH #{options_string}" else - connection.raw_connection.copy_data "COPY (#{self.all.to_sql}) TO STDOUT WITH #{options_string}" do + connection.raw_connection.copy_data "COPY (#{options_query}) TO STDOUT WITH #{options_string}" do while line = connection.raw_connection.get_copy_data do yield(line) if block_given? end diff --git a/spec/copy_to_spec.rb b/spec/copy_to_spec.rb index 2a11021..a3ebfb9 100644 --- a/spec/copy_to_spec.rb +++ b/spec/copy_to_spec.rb @@ -5,7 +5,7 @@ ActiveRecord::Base.connection.execute %{ TRUNCATE TABLE test_models; SELECT setval('test_models_id_seq', 1, false); -} + } TestModel.create :data => 'test data 1' end @@ -79,5 +79,11 @@ end end.should raise_error end + + it "accepts custom sql query to run instead on the current relation" do + TestModel.copy_to '/tmp/export.csv', query: 'SELECT count(*) as "Total" FROM test_models' + content = File.open('/tmp/export.csv', 'r').read + expect(content).to eq("Total\n1\n") + end end end From 3a5cd81f77b9ff976c6381d6e405b6e94f4a4161 Mon Sep 17 00:00:00 2001 From: Abdullah Barrak Date: Sun, 28 Jan 2018 12:13:02 +0300 Subject: [PATCH 40/77] docs: add documentations for new query option --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index a8373c9..1090996 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,18 @@ Which will generate the following SQL command: COPY (SELECT name FROM "users" WHERE "users"."id" IN (1, 2, 3)) TO '/tmp/users.csv' WITH DELIMITER ',' CSV HEADER ``` +Alternatively, you can supply customized raw SQL query to copy_to instead of scoped relation: + +```ruby +User.copy_to("/tmp/users.csv", query: 'SELECT count(*) as Total FROM users') +``` + +Which will generate the following SQL command: + +```sql +COPY (SELECT count(*) as Total FROM users) TO '/tmp/users.csv' WITH DELIMITER ',' CSV HEADER +``` + The COPY command also supports exporting the data in binary format. ```ruby From 0106a4433dfa0fce4e3ad613ce76d963f23ce352 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Sun, 28 Jan 2018 18:24:05 -0500 Subject: [PATCH 41/77] Bumps minor version --- Gemfile.lock | 50 +++++++++++++++++++++---------------------- postgres-copy.gemspec | 2 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index f69daee..777856e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - postgres-copy (1.3.0) + postgres-copy (1.4.0) activerecord (>= 5.1) pg (>= 0.17) responders @@ -9,26 +9,26 @@ PATH GEM remote: https://rubygems.org/ specs: - actionpack (5.1.1) - actionview (= 5.1.1) - activesupport (= 5.1.1) + actionpack (5.1.4) + actionview (= 5.1.4) + activesupport (= 5.1.4) rack (~> 2.0) - rack-test (~> 0.6.3) + rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.1.1) - activesupport (= 5.1.1) + actionview (5.1.4) + activesupport (= 5.1.4) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - activemodel (5.1.1) - activesupport (= 5.1.1) - activerecord (5.1.1) - activemodel (= 5.1.1) - activesupport (= 5.1.1) + activemodel (5.1.4) + activesupport (= 5.1.4) + activerecord (5.1.4) + activemodel (= 5.1.4) + activesupport (= 5.1.4) arel (~> 8.0) - activesupport (5.1.1) + activesupport (5.1.4) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (~> 0.7) minitest (~> 5.1) @@ -37,27 +37,27 @@ GEM builder (3.2.3) concurrent-ruby (1.0.5) diff-lcs (1.3) - erubi (1.6.0) - i18n (0.8.4) + erubi (1.6.1) + i18n (0.8.6) loofah (2.0.3) nokogiri (>= 1.5.9) method_source (0.8.2) - mini_portile2 (2.2.0) - minitest (5.10.2) - nokogiri (1.8.0) - mini_portile2 (~> 2.2.0) + mini_portile2 (2.3.0) + minitest (5.10.3) + nokogiri (1.8.1) + mini_portile2 (~> 2.3.0) pg (0.21.0) rack (2.0.3) - rack-test (0.6.3) - rack (>= 1.0) + rack-test (0.7.0) + rack (>= 1.0, < 3) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) rails-html-sanitizer (1.0.3) loofah (~> 2.0) - railties (5.1.1) - actionpack (= 5.1.1) - activesupport (= 5.1.1) + railties (5.1.4) + actionpack (= 5.1.4) + activesupport (= 5.1.4) method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) @@ -74,7 +74,7 @@ GEM rspec-expectations (2.99.2) diff-lcs (>= 1.1.3, < 2.0) rspec-mocks (2.99.4) - thor (0.19.4) + thor (0.20.0) thread_safe (0.3.6) tzinfo (1.2.3) thread_safe (~> 0.1) diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index e039b62..f347de7 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -5,7 +5,7 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "postgres-copy" - s.version = "1.3.0" + s.version = "1.4.0" s.platform = Gem::Platform::RUBY s.required_ruby_version = ">= 1.9.3" s.authors = ["Diogo Biazus"] From 056b20604edc65270e6d76d8f6368a4bddc641c6 Mon Sep 17 00:00:00 2001 From: Conrad Chu Date: Wed, 23 Jan 2019 15:00:35 -0800 Subject: [PATCH 42/77] Adds support values with carriage returns or empty strings --- lib/postgres-copy/acts_as_copy_target.rb | 33 ++++++++++++++----- spec/copy_from_spec.rb | 18 +++++++--- spec/fixtures/comma_with_carriage_returns.csv | 3 ++ spec/fixtures/comma_with_empty_string.csv | 2 ++ 4 files changed, 44 insertions(+), 12 deletions(-) create mode 100644 spec/fixtures/comma_with_carriage_returns.csv create mode 100644 spec/fixtures/comma_with_empty_string.csv diff --git a/lib/postgres-copy/acts_as_copy_target.rb b/lib/postgres-copy/acts_as_copy_target.rb index fc75d58..4914f31 100644 --- a/lib/postgres-copy/acts_as_copy_target.rb +++ b/lib/postgres-copy/acts_as_copy_target.rb @@ -77,8 +77,9 @@ def copy_from path_or_io, options = {} "BINARY" else quote = options[:quote] == "'" ? "''" : options[:quote] - null = options.key?(:null) ? "NULL '#{options[:null]}'" : '' - "DELIMITER '#{options[:delimiter]}' QUOTE '#{quote}' #{null} CSV" + null = options.key?(:null) ? "NULL '#{options[:null]}'" : nil + force_null = options.key?(:force_null) ? "FORCE_NULL(#{options[:force_null].join(',')})" : nil + "WITH (" + ["DELIMITER '#{options[:delimiter]}'", "QUOTE '#{quote}'", null, force_null, "FORMAT CSV"].compact.join(', ') + ")" end io = path_or_io.instance_of?(String) ? File.open(path_or_io, 'r') : path_or_io @@ -110,15 +111,31 @@ def copy_from path_or_io, options = {} rescue EOFError end else + line_buffer = '' + while line = io.gets do next if line.strip.size == 0 - if block_given? - row = CSV.parse_line(line.strip, {:col_sep => options[:delimiter]}) - yield(row) - next if row.all?{|f| f.nil? } - line = CSV.generate_line(row, {:col_sep => options[:delimiter]}) + + line_buffer += line + + # If line is incomplete, get the next line until it terminates + if line_buffer =~ /\n$/ || line_buffer =~ /\Z/ + if block_given? + begin + row = CSV.parse_line(line_buffer.strip, {:col_sep => options[:delimiter]}) + yield(row) + next if row.all?{|f| f.nil? } + line_buffer = CSV.generate_line(row, {:col_sep => options[:delimiter]}) + rescue CSV::MalformedCSVError => e + next + end + end + + connection.raw_connection.put_copy_data(line_buffer) + + # Clear the buffer + line_buffer = '' end - connection.raw_connection.put_copy_data line end end end diff --git a/spec/copy_from_spec.rb b/spec/copy_from_spec.rb index d51ad31..4811cda 100644 --- a/spec/copy_from_spec.rb +++ b/spec/copy_from_spec.rb @@ -100,20 +100,20 @@ ReservedWordModel.copy_from File.expand_path('spec/fixtures/reserved_words.csv'), :delimiter => "\t" ReservedWordModel.order(:id).map{|r| r.attributes}.should == [{"group"=>"group name", "id"=>1, "select"=>"test select"}] end - + it "should import even last columns have empty values" do TestExtendedModel.copy_from File.expand_path('spec/fixtures/comma_with_header_empty_values_at_the_end.csv') - TestExtendedModel.order(:id).map{|r| r.attributes}.should == + TestExtendedModel.order(:id).map{|r| r.attributes}.should == [{"id"=>1, "data"=>"test data 1", "more_data"=>nil, "other_data"=>nil, "final_data"=>nil}, {"id"=>2, "data"=>"test data 2", "more_data"=>"9", "other_data"=>nil, "final_data"=>nil}, {"id"=>3, "data"=>"test data 2", "more_data"=>"9", "other_data"=>nil, "final_data"=>"0"}] end - + it "should import even last columns have empty values with block" do TestExtendedModel.copy_from File.expand_path('spec/fixtures/comma_with_header_empty_values_at_the_end.csv') do |row| row[4]="666" end - TestExtendedModel.order(:id).map{|r| r.attributes}.should == + TestExtendedModel.order(:id).map{|r| r.attributes}.should == [{"id"=>1, "data"=>"test data 1", "more_data"=>nil, "other_data"=>nil, "final_data"=>"666"}, {"id"=>2, "data"=>"test data 2", "more_data"=>"9", "other_data"=>nil, "final_data"=>"666"}, {"id"=>3, "data"=>"test data 2", "more_data"=>"9", "other_data"=>nil, "final_data"=>"666"}] @@ -150,4 +150,14 @@ TestModel.copy_from File.open(File.expand_path('spec/fixtures/special_null_with_header.csv'), 'r'), :null => 'NULL' TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => nil}] end + + it "should import with a carriage return in the value" do + TestModel.copy_from File.expand_path('spec/fixtures/comma_with_carriage_returns.csv') + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => "test\ndata 1"}] + end + + it "should import custom force null expressions from path" do + TestModel.copy_from File.expand_path('spec/fixtures/comma_with_empty_string.csv'), :null => '', :force_null => [:data] + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => nil}] + end end diff --git a/spec/fixtures/comma_with_carriage_returns.csv b/spec/fixtures/comma_with_carriage_returns.csv new file mode 100644 index 0000000..c76c15a --- /dev/null +++ b/spec/fixtures/comma_with_carriage_returns.csv @@ -0,0 +1,3 @@ +id,data +1,"test +data 1" \ No newline at end of file diff --git a/spec/fixtures/comma_with_empty_string.csv b/spec/fixtures/comma_with_empty_string.csv new file mode 100644 index 0000000..8e02be6 --- /dev/null +++ b/spec/fixtures/comma_with_empty_string.csv @@ -0,0 +1,2 @@ +id,data +1,"" From 5400d01a1890763abb04f7d0186539197a2ce709 Mon Sep 17 00:00:00 2001 From: Conrad Chu Date: Wed, 23 Jan 2019 15:07:42 -0800 Subject: [PATCH 43/77] Updates README with force_null option --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1090996..4b83ba4 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,8 @@ This will execute in the database the command: COPY (SELECT "users".* FROM "users" ) TO '/tmp/users.csv' WITH DELIMITER ',' CSV HEADER ``` -Remark that the file will be created in the database server disk. -But what if you want to write the lines in a file on the server that is running Rails, instead of the database? +Remark that the file will be created in the database server disk. +But what if you want to write the lines in a file on the server that is running Rails, instead of the database? In this case you can pass a block and retrieve the generated lines and then write them to a file: ```ruby @@ -166,6 +166,12 @@ To specify NULL value you can pass the null option parameter. User.copy_from "/tmp/users.csv", :null => 'null' ``` +Match the specified columns' values against the null string, even if it has been quoted, and if a match is found set the value to NULL (Postgres 9.4+ only). + +```ruby +User.copy_from "/tmp/users.csv", :null => '', :force_null => [:name, :city] +``` + To copy a binary formatted data file or IO object you can specify the format as binary From db5c3225ed45027f8be93581abfcce9a4e9cf571 Mon Sep 17 00:00:00 2001 From: Conrad Chu Date: Mon, 28 Jan 2019 18:13:26 -0800 Subject: [PATCH 44/77] Configures Postgres 9.4 minimum requirement --- .travis.yml | 6 ++++++ lib/postgres-copy/acts_as_copy_target.rb | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f7739cf..4810844 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,12 @@ language: ruby rvm: - 2.3.1 +services: + - postgresql + +addons: + postgresql: '9.4' + branches: only: - master diff --git a/lib/postgres-copy/acts_as_copy_target.rb b/lib/postgres-copy/acts_as_copy_target.rb index 4914f31..d37b334 100644 --- a/lib/postgres-copy/acts_as_copy_target.rb +++ b/lib/postgres-copy/acts_as_copy_target.rb @@ -126,7 +126,7 @@ def copy_from path_or_io, options = {} yield(row) next if row.all?{|f| f.nil? } line_buffer = CSV.generate_line(row, {:col_sep => options[:delimiter]}) - rescue CSV::MalformedCSVError => e + rescue CSV::MalformedCSVError next end end From b398af7fc50aebc18b1acfde559a2a8e9a0f25ca Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Tue, 29 Jan 2019 21:44:12 -0500 Subject: [PATCH 45/77] Bump patch version number --- VERSION | 1 - postgres-copy.gemspec | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 VERSION diff --git a/VERSION b/VERSION deleted file mode 100644 index ad83b1b..0000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.5.6 \ No newline at end of file diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index f347de7..5aaceb2 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -5,7 +5,7 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "postgres-copy" - s.version = "1.4.0" + s.version = "1.4.1" s.platform = Gem::Platform::RUBY s.required_ruby_version = ">= 1.9.3" s.authors = ["Diogo Biazus"] From a7718d3fcfc2ed1458034972711d42aa09f18ca9 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Tue, 29 Jan 2019 21:46:16 -0500 Subject: [PATCH 46/77] Update dependencies --- Gemfile.lock | 81 +++++++++++++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 777856e..524ac57 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - postgres-copy (1.4.0) + postgres-copy (1.4.1) activerecord (>= 5.1) pg (>= 0.17) responders @@ -9,63 +9,66 @@ PATH GEM remote: https://rubygems.org/ specs: - actionpack (5.1.4) - actionview (= 5.1.4) - activesupport (= 5.1.4) + actionpack (5.2.2) + actionview (= 5.2.2) + activesupport (= 5.2.2) rack (~> 2.0) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.1.4) - activesupport (= 5.1.4) + actionview (5.2.2) + activesupport (= 5.2.2) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - activemodel (5.1.4) - activesupport (= 5.1.4) - activerecord (5.1.4) - activemodel (= 5.1.4) - activesupport (= 5.1.4) - arel (~> 8.0) - activesupport (5.1.4) + activemodel (5.2.2) + activesupport (= 5.2.2) + activerecord (5.2.2) + activemodel (= 5.2.2) + activesupport (= 5.2.2) + arel (>= 9.0) + activesupport (5.2.2) concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (~> 0.7) + i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) - arel (8.0.0) + arel (9.0.0) builder (3.2.3) - concurrent-ruby (1.0.5) + concurrent-ruby (1.1.4) + crass (1.0.4) diff-lcs (1.3) - erubi (1.6.1) - i18n (0.8.6) - loofah (2.0.3) + erubi (1.8.0) + i18n (1.5.3) + concurrent-ruby (~> 1.0) + loofah (2.2.3) + crass (~> 1.0.2) nokogiri (>= 1.5.9) - method_source (0.8.2) - mini_portile2 (2.3.0) - minitest (5.10.3) - nokogiri (1.8.1) - mini_portile2 (~> 2.3.0) - pg (0.21.0) - rack (2.0.3) - rack-test (0.7.0) + method_source (0.9.2) + mini_portile2 (2.4.0) + minitest (5.11.3) + nokogiri (1.10.1) + mini_portile2 (~> 2.4.0) + pg (1.1.4) + rack (2.0.6) + rack-test (1.1.0) rack (>= 1.0, < 3) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.0.3) - loofah (~> 2.0) - railties (5.1.4) - actionpack (= 5.1.4) - activesupport (= 5.1.4) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + railties (5.2.2) + actionpack (= 5.2.2) + activesupport (= 5.2.2) method_source rake (>= 0.8.7) - thor (>= 0.18.1, < 2.0) + thor (>= 0.19.0, < 2.0) rake (11.2.2) rdoc (5.0.0) - responders (2.4.0) - actionpack (>= 4.2.0, < 5.3) - railties (>= 4.2.0, < 5.3) + responders (2.4.1) + actionpack (>= 4.2.0, < 6.0) + railties (>= 4.2.0, < 6.0) rspec (2.99.0) rspec-core (~> 2.99.0) rspec-expectations (~> 2.99.0) @@ -74,9 +77,9 @@ GEM rspec-expectations (2.99.2) diff-lcs (>= 1.1.3, < 2.0) rspec-mocks (2.99.4) - thor (0.20.0) + thor (0.20.3) thread_safe (0.3.6) - tzinfo (1.2.3) + tzinfo (1.2.5) thread_safe (~> 0.1) PLATFORMS @@ -90,4 +93,4 @@ DEPENDENCIES rspec (~> 2.12) BUNDLED WITH - 1.14.6 + 1.16.3 From e2dedc48a9f21644d9f20849fae6a98f7dd92a4f Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Sat, 18 Jul 2020 12:36:20 -0400 Subject: [PATCH 47/77] Create ruby.yml --- .github/workflows/ruby.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/ruby.yml diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml new file mode 100644 index 0000000..0abf017 --- /dev/null +++ b/.github/workflows/ruby.yml @@ -0,0 +1,32 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake +# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby + +name: Ruby + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + test: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Ruby + # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby, + # change this to (see https://github.com/ruby/setup-ruby#versioning): + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7 + - name: Install dependencies + run: bundle install + - name: Run tests + run: bundle exec rake From d4c4fcd26d424c950fa5c0c31c2b9e327a5a6c51 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Sat, 18 Jul 2020 12:42:36 -0400 Subject: [PATCH 48/77] Update gems for test environment so we ensure we can still run with recent versions --- Gemfile.lock | 82 ++++++++++++++++++++++++++-------------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 524ac57..52c07b8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -9,66 +9,65 @@ PATH GEM remote: https://rubygems.org/ specs: - actionpack (5.2.2) - actionview (= 5.2.2) - activesupport (= 5.2.2) - rack (~> 2.0) + actionpack (6.0.3.2) + actionview (= 6.0.3.2) + activesupport (= 6.0.3.2) + rack (~> 2.0, >= 2.0.8) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.2) - activesupport (= 5.2.2) + rails-html-sanitizer (~> 1.0, >= 1.2.0) + actionview (6.0.3.2) + activesupport (= 6.0.3.2) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.3) - activemodel (5.2.2) - activesupport (= 5.2.2) - activerecord (5.2.2) - activemodel (= 5.2.2) - activesupport (= 5.2.2) - arel (>= 9.0) - activesupport (5.2.2) + rails-html-sanitizer (~> 1.1, >= 1.2.0) + activemodel (6.0.3.2) + activesupport (= 6.0.3.2) + activerecord (6.0.3.2) + activemodel (= 6.0.3.2) + activesupport (= 6.0.3.2) + activesupport (6.0.3.2) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) - arel (9.0.0) - builder (3.2.3) - concurrent-ruby (1.1.4) - crass (1.0.4) - diff-lcs (1.3) - erubi (1.8.0) - i18n (1.5.3) + zeitwerk (~> 2.2, >= 2.2.2) + builder (3.2.4) + concurrent-ruby (1.1.6) + crass (1.0.6) + diff-lcs (1.4.4) + erubi (1.9.0) + i18n (1.8.3) concurrent-ruby (~> 1.0) - loofah (2.2.3) + loofah (2.6.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) - method_source (0.9.2) + method_source (1.0.0) mini_portile2 (2.4.0) - minitest (5.11.3) - nokogiri (1.10.1) + minitest (5.14.1) + nokogiri (1.10.10) mini_portile2 (~> 2.4.0) - pg (1.1.4) - rack (2.0.6) + pg (1.2.3) + rack (2.2.3) rack-test (1.1.0) rack (>= 1.0, < 3) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.0.4) - loofah (~> 2.2, >= 2.2.2) - railties (5.2.2) - actionpack (= 5.2.2) - activesupport (= 5.2.2) + rails-html-sanitizer (1.3.0) + loofah (~> 2.3) + railties (6.0.3.2) + actionpack (= 6.0.3.2) + activesupport (= 6.0.3.2) method_source rake (>= 0.8.7) - thor (>= 0.19.0, < 2.0) + thor (>= 0.20.3, < 2.0) rake (11.2.2) - rdoc (5.0.0) - responders (2.4.1) - actionpack (>= 4.2.0, < 6.0) - railties (>= 4.2.0, < 6.0) + rdoc (6.2.1) + responders (3.0.1) + actionpack (>= 5.0) + railties (>= 5.0) rspec (2.99.0) rspec-core (~> 2.99.0) rspec-expectations (~> 2.99.0) @@ -77,10 +76,11 @@ GEM rspec-expectations (2.99.2) diff-lcs (>= 1.1.3, < 2.0) rspec-mocks (2.99.4) - thor (0.20.3) + thor (1.0.1) thread_safe (0.3.6) - tzinfo (1.2.5) + tzinfo (1.2.7) thread_safe (~> 0.1) + zeitwerk (2.4.0) PLATFORMS ruby @@ -93,4 +93,4 @@ DEPENDENCIES rspec (~> 2.12) BUNDLED WITH - 1.16.3 + 2.1.2 From d7733eb825774a8770a22a229f1447607f3e4987 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Sat, 18 Jul 2020 12:42:55 -0400 Subject: [PATCH 49/77] Try setting up postgresql service on github action --- .github/workflows/ruby.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 0abf017..4cf68a0 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -15,6 +15,20 @@ on: jobs: test: + services: + # Label used to access the service container + postgres: + # Docker Hub image + image: postgres + # Provide the password for postgres + env: + POSTGRES_PASSWORD: postgres + # Set health checks to wait until postgres has started + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 runs-on: ubuntu-latest From fc19ebc007c2fc05323cb2ccfff818b355e16665 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Sat, 18 Jul 2020 12:47:25 -0400 Subject: [PATCH 50/77] Map postgresql port to localhost so the tests run Add badge for github action --- .github/workflows/ruby.yml | 14 ++++++++------ README.md | 4 +++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 4cf68a0..2b57f34 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -23,12 +23,14 @@ jobs: # Provide the password for postgres env: POSTGRES_PASSWORD: postgres - # Set health checks to wait until postgres has started - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 + ports: + - 5432:5432 + # Set health checks to wait until postgres has started + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 runs-on: ubuntu-latest diff --git a/README.md b/README.md index 4b83ba4..cbf0a87 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# postgres-copy [![Build Status](https://travis-ci.org/diogob/postgres-copy.svg?branch=master)](https://travis-ci.org/diogob/postgres-copy) [![Code Climate](https://codeclimate.com/github/diogob/postgres-copy.svg)](https://codeclimate.com/github/diogob/postgres-copy) +# postgres-copy + +![Ruby](https://github.com/diogob/postgres-copy/workflows/Ruby/badge.svg) This Gem will enable your AR models to use the PostgreSQL COPY command to import/export data in CSV format. If you need to tranfer data between a PostgreSQL database and CSV files, the PostgreSQL native CSV parser From 74cb54b6714849113402c814b075bed95924dd69 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Sat, 18 Jul 2020 13:23:03 -0400 Subject: [PATCH 51/77] Remove some specs that depended on filesystem. These specs need to be rewritten anyways. --- spec/copy_to_binary_spec.rb | 11 ----------- spec/copy_to_spec.rb | 16 ++++------------ 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/spec/copy_to_binary_spec.rb b/spec/copy_to_binary_spec.rb index 4b11bf5..cd1d058 100644 --- a/spec/copy_to_binary_spec.rb +++ b/spec/copy_to_binary_spec.rb @@ -19,15 +19,4 @@ it{ should == File.open('spec/fixtures/2_col_binary_data.dat', 'r:ASCII-8BIT').read } end end - - describe "should allow binary output to file" do - it "should copy to disk if block is not given and a path is passed" do - TestModel.copy_to '/tmp/export.dat', :format => :binary - str = File.open('/tmp/export.dat', 'r:ASCII-8BIT').read - - str.should == File.open('spec/fixtures/2_col_binary_data.dat', 'r:ASCII-8BIT').read - - end - end - end diff --git a/spec/copy_to_spec.rb b/spec/copy_to_spec.rb index a3ebfb9..be8516f 100644 --- a/spec/copy_to_spec.rb +++ b/spec/copy_to_spec.rb @@ -64,15 +64,6 @@ end end - it "should copy to disk if block is not given and a path is passed" do - TestModel.copy_to '/tmp/export.csv' - File.open('spec/fixtures/comma_with_header.csv', 'r') do |fixture| - File.open('/tmp/export.csv', 'r') do |result| - result.read.should == fixture.read - end - end - end - it "should raise exception if I pass a path and a block simultaneously" do lambda do TestModel.copy_to('/tmp/bogus_path') do |row| @@ -81,9 +72,10 @@ end it "accepts custom sql query to run instead on the current relation" do - TestModel.copy_to '/tmp/export.csv', query: 'SELECT count(*) as "Total" FROM test_models' - content = File.open('/tmp/export.csv', 'r').read - expect(content).to eq("Total\n1\n") + TestModel.copy_to(nil, query: 'SELECT count(*) as "Total" FROM test_models') do |row| + expect(row).to eq("Total\n") + break + end end end end From 08dc4685f684471603afc32a5587c94c496de505 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Sat, 18 Jul 2020 13:26:24 -0400 Subject: [PATCH 52/77] Remove travis configuration --- .travis.yml | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4810844..0000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: ruby -rvm: - - 2.3.1 - -services: - - postgresql - -addons: - postgresql: '9.4' - -branches: - only: - - master From fd20ea450e4d7e0a7fd4e35a4796e3a8eb5733dd Mon Sep 17 00:00:00 2001 From: Daisuke Nashiro Date: Thu, 28 May 2020 22:38:47 +0900 Subject: [PATCH 53/77] Add support for tsv --- README.md | 5 +++++ lib/postgres-copy/acts_as_copy_target.rb | 4 +++- spec/copy_from_spec.rb | 13 +++++++++++++ spec/fixtures/tab_with_header.tsv | 2 ++ spec/fixtures/tab_with_two_lines.tsv | 3 +++ 5 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/tab_with_header.tsv create mode 100644 spec/fixtures/tab_with_two_lines.tsv diff --git a/README.md b/README.md index cbf0a87..cbd228a 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,11 @@ Match the specified columns' values against the null string, even if it has been User.copy_from "/tmp/users.csv", :null => '', :force_null => [:name, :city] ``` +To copy from tsv file , you can set format `:tsv` + +```ruby +User.copy_from "/tmp/users.tsv", :format => :tsv +``` To copy a binary formatted data file or IO object you can specify the format as binary diff --git a/lib/postgres-copy/acts_as_copy_target.rb b/lib/postgres-copy/acts_as_copy_target.rb index d37b334..1c66d55 100644 --- a/lib/postgres-copy/acts_as_copy_target.rb +++ b/lib/postgres-copy/acts_as_copy_target.rb @@ -73,13 +73,15 @@ def copy_to_string options = {} # * For further details on usage take a look at the README.md def copy_from path_or_io, options = {} options = {:delimiter => ",", :format => :csv, :header => true, :quote => '"'}.merge(options) + options[:delimiter] = "\t" if options[:format] == :tsv options_string = if options[:format] == :binary "BINARY" else quote = options[:quote] == "'" ? "''" : options[:quote] null = options.key?(:null) ? "NULL '#{options[:null]}'" : nil force_null = options.key?(:force_null) ? "FORCE_NULL(#{options[:force_null].join(',')})" : nil - "WITH (" + ["DELIMITER '#{options[:delimiter]}'", "QUOTE '#{quote}'", null, force_null, "FORMAT CSV"].compact.join(', ') + ")" + delimiter = options[:format] == :tsv ? "E'\t'" : "'#{options[:delimiter]}'" + "WITH (" + ["DELIMITER #{delimiter}", "QUOTE '#{quote}'", null, force_null, "FORMAT CSV"].compact.join(', ') + ")" end io = path_or_io.instance_of?(String) ? File.open(path_or_io, 'r') : path_or_io diff --git a/spec/copy_from_spec.rb b/spec/copy_from_spec.rb index 4811cda..b455808 100644 --- a/spec/copy_from_spec.rb +++ b/spec/copy_from_spec.rb @@ -160,4 +160,17 @@ TestModel.copy_from File.expand_path('spec/fixtures/comma_with_empty_string.csv'), :null => '', :force_null => [:data] TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => nil}] end + + it "should import tsv from path" do + TestModel.copy_from File.expand_path('spec/fixtures/tab_with_header.tsv'), :format => :tsv + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] + end + + it "should import 2 lines from tsv and allow changes in block" do + TestModel.copy_from(File.open(File.expand_path('spec/fixtures/tab_with_two_lines.tsv'), 'r'), :format => :tsv) do |row| + row[1] = 'changed this data' + end + TestModel.order(:id).first.attributes.should == {'id' => 1, 'data' => 'changed this data'} + TestModel.count.should == 2 + end end diff --git a/spec/fixtures/tab_with_header.tsv b/spec/fixtures/tab_with_header.tsv new file mode 100644 index 0000000..d522d21 --- /dev/null +++ b/spec/fixtures/tab_with_header.tsv @@ -0,0 +1,2 @@ +id data +1 test data 1 diff --git a/spec/fixtures/tab_with_two_lines.tsv b/spec/fixtures/tab_with_two_lines.tsv new file mode 100644 index 0000000..aaac52c --- /dev/null +++ b/spec/fixtures/tab_with_two_lines.tsv @@ -0,0 +1,3 @@ +id data +1 test data 1 +2 test data 2 From d4e83c0a10d899119b50a449764d3862970d18cb Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Sat, 18 Jul 2020 13:40:42 -0400 Subject: [PATCH 54/77] Bump minor version number since we added TSV format --- Gemfile.lock | 2 +- postgres-copy.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 52c07b8..61d2c53 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - postgres-copy (1.4.1) + postgres-copy (1.5.0) activerecord (>= 5.1) pg (>= 0.17) responders diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index 5aaceb2..da73436 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -5,7 +5,7 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "postgres-copy" - s.version = "1.4.1" + s.version = "1.5.0" s.platform = Gem::Platform::RUBY s.required_ruby_version = ">= 1.9.3" s.authors = ["Diogo Biazus"] From 91e56ba03374488c715d5435a10223eccc532054 Mon Sep 17 00:00:00 2001 From: Matthew Bechtel Date: Sun, 24 Oct 2021 10:16:01 -0700 Subject: [PATCH 55/77] Allow for reading csv filse with atypical encoding --- lib/postgres-copy/acts_as_copy_target.rb | 10 +++++++++- spec/copy_from_spec.rb | 8 +++++++- spec/fixtures/comma_with_bom.csv | 2 ++ 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 spec/fixtures/comma_with_bom.csv diff --git a/lib/postgres-copy/acts_as_copy_target.rb b/lib/postgres-copy/acts_as_copy_target.rb index 1c66d55..9c5de9f 100644 --- a/lib/postgres-copy/acts_as_copy_target.rb +++ b/lib/postgres-copy/acts_as_copy_target.rb @@ -1,5 +1,13 @@ require 'csv' +def get_file_mode mode, encoding = nil + if encoding + "#{mode}:#{encoding}" + else + mode + end +end + module PostgresCopy module ActsAsCopyTarget extend ActiveSupport::Concern @@ -83,7 +91,7 @@ def copy_from path_or_io, options = {} delimiter = options[:format] == :tsv ? "E'\t'" : "'#{options[:delimiter]}'" "WITH (" + ["DELIMITER #{delimiter}", "QUOTE '#{quote}'", null, force_null, "FORMAT CSV"].compact.join(', ') + ")" end - io = path_or_io.instance_of?(String) ? File.open(path_or_io, 'r') : path_or_io + io = path_or_io.instance_of?(String) ? File.open(path_or_io, get_file_mode('r', options[:encoding])) : path_or_io if options[:format] == :binary columns_list = options[:columns] || [] diff --git a/spec/copy_from_spec.rb b/spec/copy_from_spec.rb index b455808..6798eb2 100644 --- a/spec/copy_from_spec.rb +++ b/spec/copy_from_spec.rb @@ -173,4 +173,10 @@ TestModel.order(:id).first.attributes.should == {'id' => 1, 'data' => 'changed this data'} TestModel.count.should == 2 end -end + + it "should import csv headers with BOM when provided encoding option" do + TestModel.copy_from File.expand_path("spec/fixtures/comma_with_bom.csv"), :encoding => "bom|utf-8" + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] + end + +end \ No newline at end of file diff --git a/spec/fixtures/comma_with_bom.csv b/spec/fixtures/comma_with_bom.csv new file mode 100644 index 0000000..5172bfc --- /dev/null +++ b/spec/fixtures/comma_with_bom.csv @@ -0,0 +1,2 @@ +id,data +1,"test data 1" \ No newline at end of file From 099cef9cf111bc3b5dde3be04332c98d9d92d06d Mon Sep 17 00:00:00 2001 From: Matthew Bechtel Date: Sun, 24 Oct 2021 10:36:21 -0700 Subject: [PATCH 56/77] Adding instructions in readme; adding newlines at ends of files --- README.md | 7 +++++++ spec/copy_from_spec.rb | 2 +- spec/fixtures/comma_with_bom.csv | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cbd228a..4240659 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,13 @@ Which will generate the following SQL command: COPY users (id, name) FROM '/tmp/users.dat' WITH BINARY ``` +To specify the encoding with which to read the file, set the :encoding option. +This is useful for removing byte order marks when matching column headers. + +```ruby +User.copy_from "/tmp/users_with_byte_order_mark.csv", :encoding => 'bom|utf-8' +``` + ### Using the CSV Responder If you want to make the result of a COPY command available to download this gem provides a CSV responder that, in conjunction with [inherited_resources](https://github.com/josevalim/inherited_resources), is a very powerfull tool. BTW, do not try to use the responder without inherited_resources. diff --git a/spec/copy_from_spec.rb b/spec/copy_from_spec.rb index 6798eb2..d7b0c95 100644 --- a/spec/copy_from_spec.rb +++ b/spec/copy_from_spec.rb @@ -179,4 +179,4 @@ TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end -end \ No newline at end of file +end diff --git a/spec/fixtures/comma_with_bom.csv b/spec/fixtures/comma_with_bom.csv index 5172bfc..df635db 100644 --- a/spec/fixtures/comma_with_bom.csv +++ b/spec/fixtures/comma_with_bom.csv @@ -1,2 +1,2 @@ id,data -1,"test data 1" \ No newline at end of file +1,"test data 1" From 42902b776cb8640fd4d2e617d47a9ea7770a0eaf Mon Sep 17 00:00:00 2001 From: Clark Brown Date: Sun, 6 Feb 2022 08:38:05 -0700 Subject: [PATCH 57/77] bump minitest from 5.14.1 to 5.15.0 --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 61d2c53..02b6c87 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -45,7 +45,7 @@ GEM nokogiri (>= 1.5.9) method_source (1.0.0) mini_portile2 (2.4.0) - minitest (5.14.1) + minitest (5.15.0) nokogiri (1.10.10) mini_portile2 (~> 2.4.0) pg (1.2.3) @@ -93,4 +93,4 @@ DEPENDENCIES rspec (~> 2.12) BUNDLED WITH - 2.1.2 + 2.2.22 From 3812c1eb305015d52c7f79987142eb24288502a4 Mon Sep 17 00:00:00 2001 From: Clark Brown Date: Sun, 6 Feb 2022 08:40:14 -0700 Subject: [PATCH 58/77] pass kwargs without hash to support ruby 3.x --- lib/postgres-copy/acts_as_copy_target.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/postgres-copy/acts_as_copy_target.rb b/lib/postgres-copy/acts_as_copy_target.rb index 9c5de9f..41a574d 100644 --- a/lib/postgres-copy/acts_as_copy_target.rb +++ b/lib/postgres-copy/acts_as_copy_target.rb @@ -18,7 +18,7 @@ module ActsAsCopyTarget module CopyMethods # Copy data to a file passed as a string (the file path) or to lines that are passed to a block def copy_to path = nil, options = {} - options = {:delimiter => ",", :format => :csv, :header => true}.merge(options) + options = { delimiter: ",", format: :csv, header: true }.merge(options) options_string = if options[:format] == :binary "BINARY" else @@ -80,7 +80,7 @@ def copy_to_string options = {} # * You can map fields from the file to different fields in the table using a map in the options hash # * For further details on usage take a look at the README.md def copy_from path_or_io, options = {} - options = {:delimiter => ",", :format => :csv, :header => true, :quote => '"'}.merge(options) + options = { delimiter: ",", format: :csv, header: true, quote: '"' }.merge(options) options[:delimiter] = "\t" if options[:format] == :tsv options_string = if options[:format] == :binary "BINARY" @@ -132,10 +132,10 @@ def copy_from path_or_io, options = {} if line_buffer =~ /\n$/ || line_buffer =~ /\Z/ if block_given? begin - row = CSV.parse_line(line_buffer.strip, {:col_sep => options[:delimiter]}) + row = CSV.parse_line(line_buffer.strip, col_sep: options[:delimiter]) yield(row) - next if row.all?{|f| f.nil? } - line_buffer = CSV.generate_line(row, {:col_sep => options[:delimiter]}) + next if row.all?(&:nil?) + line_buffer = CSV.generate_line(row, col_sep: options[:delimiter]) rescue CSV::MalformedCSVError next end From e881ce0ae7667299518c6fd69e05e9a8c1a45fc4 Mon Sep 17 00:00:00 2001 From: Clark Brown Date: Sun, 6 Feb 2022 08:47:06 -0700 Subject: [PATCH 59/77] run tests against all maintainted versions of ruby in CI --- .github/workflows/ruby.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 2b57f34..59986c2 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -33,6 +33,10 @@ jobs: --health-retries 5 runs-on: ubuntu-latest + name: test (ruby v${{ matrix.ruby }}) + strategy: + matrix: + ruby: ["2.6", "2.7", "3.0", "3.1"] steps: - uses: actions/checkout@v2 @@ -41,7 +45,8 @@ jobs: # change this to (see https://github.com/ruby/setup-ruby#versioning): uses: ruby/setup-ruby@v1 with: - ruby-version: 2.7 + ruby-version: ${{ matrix.ruby }} + bundler-cache: true - name: Install dependencies run: bundle install - name: Run tests From fc5718a72a35f78571182761baecacedd83cde73 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Mon, 7 Feb 2022 15:09:41 -0500 Subject: [PATCH 60/77] Update nokogiri trying to fix CI install for ruby 3.1 --- Gemfile.lock | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 02b6c87..5960f34 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -44,11 +44,13 @@ GEM crass (~> 1.0.2) nokogiri (>= 1.5.9) method_source (1.0.0) - mini_portile2 (2.4.0) + mini_portile2 (2.7.1) minitest (5.15.0) - nokogiri (1.10.10) - mini_portile2 (~> 2.4.0) + nokogiri (1.13.1) + mini_portile2 (~> 2.7.0) + racc (~> 1.4) pg (1.2.3) + racc (1.6.0) rack (2.2.3) rack-test (1.1.0) rack (>= 1.0, < 3) From afa08b60d9ad3442a47a563c9e27ac14bc5ee3d0 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Mon, 7 Feb 2022 15:16:55 -0500 Subject: [PATCH 61/77] This outdated gem was preventing us from upgrading other gems such as activerecord --- Gemfile.lock | 73 ++++++++----------------------------------- postgres-copy.gemspec | 1 - 2 files changed, 13 insertions(+), 61 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5960f34..ab19a9f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,72 +4,28 @@ PATH postgres-copy (1.5.0) activerecord (>= 5.1) pg (>= 0.17) - responders GEM remote: https://rubygems.org/ specs: - actionpack (6.0.3.2) - actionview (= 6.0.3.2) - activesupport (= 6.0.3.2) - rack (~> 2.0, >= 2.0.8) - rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) - actionview (6.0.3.2) - activesupport (= 6.0.3.2) - builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - activemodel (6.0.3.2) - activesupport (= 6.0.3.2) - activerecord (6.0.3.2) - activemodel (= 6.0.3.2) - activesupport (= 6.0.3.2) - activesupport (6.0.3.2) + activemodel (7.0.1) + activesupport (= 7.0.1) + activerecord (7.0.1) + activemodel (= 7.0.1) + activesupport (= 7.0.1) + activesupport (7.0.1) concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) - builder (3.2.4) - concurrent-ruby (1.1.6) - crass (1.0.6) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + concurrent-ruby (1.1.9) diff-lcs (1.4.4) - erubi (1.9.0) - i18n (1.8.3) + i18n (1.9.1) concurrent-ruby (~> 1.0) - loofah (2.6.0) - crass (~> 1.0.2) - nokogiri (>= 1.5.9) - method_source (1.0.0) - mini_portile2 (2.7.1) minitest (5.15.0) - nokogiri (1.13.1) - mini_portile2 (~> 2.7.0) - racc (~> 1.4) pg (1.2.3) - racc (1.6.0) - rack (2.2.3) - rack-test (1.1.0) - rack (>= 1.0, < 3) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) - nokogiri (>= 1.6) - rails-html-sanitizer (1.3.0) - loofah (~> 2.3) - railties (6.0.3.2) - actionpack (= 6.0.3.2) - activesupport (= 6.0.3.2) - method_source - rake (>= 0.8.7) - thor (>= 0.20.3, < 2.0) rake (11.2.2) rdoc (6.2.1) - responders (3.0.1) - actionpack (>= 5.0) - railties (>= 5.0) rspec (2.99.0) rspec-core (~> 2.99.0) rspec-expectations (~> 2.99.0) @@ -78,11 +34,8 @@ GEM rspec-expectations (2.99.2) diff-lcs (>= 1.1.3, < 2.0) rspec-mocks (2.99.4) - thor (1.0.1) - thread_safe (0.3.6) - tzinfo (1.2.7) - thread_safe (~> 0.1) - zeitwerk (2.4.0) + tzinfo (2.0.4) + concurrent-ruby (~> 1.0) PLATFORMS ruby diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index da73436..899adf9 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -22,7 +22,6 @@ Gem::Specification.new do |s| s.add_dependency "pg", ">= 0.17" s.add_dependency "activerecord", '>= 5.1' - s.add_dependency "responders" s.add_development_dependency "bundler" s.add_development_dependency "rdoc" s.add_development_dependency "rspec", "~> 2.12" From 66254b5e98331cb4b95b90d47f11a4cf7328cb09 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Mon, 7 Feb 2022 15:20:30 -0500 Subject: [PATCH 62/77] Since activerecord 7 dropped support for ruby 2.6 we are also dropping from CI --- .github/workflows/ruby.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 59986c2..9ec83eb 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -36,7 +36,7 @@ jobs: name: test (ruby v${{ matrix.ruby }}) strategy: matrix: - ruby: ["2.6", "2.7", "3.0", "3.1"] + ruby: ["2.7", "3.0", "3.1"] steps: - uses: actions/checkout@v2 From fed8aab833b51c23e839ac47daa7ae9420db6f35 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Mon, 7 Feb 2022 15:25:26 -0500 Subject: [PATCH 63/77] Bump gem version to 1.6.0 --- Gemfile.lock | 2 +- postgres-copy.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index ab19a9f..c3b4a17 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - postgres-copy (1.5.0) + postgres-copy (1.6.0) activerecord (>= 5.1) pg (>= 0.17) diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index 899adf9..3b2041b 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -5,7 +5,7 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "postgres-copy" - s.version = "1.5.0" + s.version = "1.6.0" s.platform = Gem::Platform::RUBY s.required_ruby_version = ">= 1.9.3" s.authors = ["Diogo Biazus"] From ae905061e9098be12944419fad61ff46a96fa7c5 Mon Sep 17 00:00:00 2001 From: Aguilar <8@santego.com> Date: Tue, 4 Oct 2022 17:45:05 +0200 Subject: [PATCH 64/77] Update acts_as_copy_target.rb Fix issue where adding a column mapping erase others column names. --- lib/postgres-copy/acts_as_copy_target.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/postgres-copy/acts_as_copy_target.rb b/lib/postgres-copy/acts_as_copy_target.rb index 41a574d..64e2e68 100644 --- a/lib/postgres-copy/acts_as_copy_target.rb +++ b/lib/postgres-copy/acts_as_copy_target.rb @@ -108,7 +108,7 @@ def copy_from path_or_io, options = {} quoted_table_name end - columns_list = columns_list.map{|c| options[:map][c.to_s] } if options[:map] + columns_list = columns_list.map{|c| options[:map][c.to_s] || c.to_s } if options[:map] columns_string = columns_list.size > 0 ? "(\"#{columns_list.join('","')}\")" : "" connection.raw_connection.copy_data %{COPY #{table} #{columns_string} FROM STDIN #{options_string}} do if options[:format] == :binary From 0804b4444202a4d4d39048c728f3567670bb1cea Mon Sep 17 00:00:00 2001 From: Pierre <8@santego.com> Date: Thu, 6 Oct 2022 08:38:01 +0200 Subject: [PATCH 65/77] Test case for headers mapping fix. --- spec/copy_from_spec.rb | 6 ++++++ spec/fixtures/comma_with_header_to_map.csv | 2 ++ 2 files changed, 8 insertions(+) create mode 100644 spec/fixtures/comma_with_header_to_map.csv diff --git a/spec/copy_from_spec.rb b/spec/copy_from_spec.rb index d7b0c95..c3c357e 100644 --- a/spec/copy_from_spec.rb +++ b/spec/copy_from_spec.rb @@ -14,6 +14,12 @@ TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] end + it "should import from file if path is with a field_map" do + TestModel.copy_from File.expand_path('spec/fixtures/comma_with_header_to_map.csv'), map: {'ref' => 'id'} + TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] + end + + it "should import from IO without field_map" do TestModel.copy_from File.open(File.expand_path('spec/fixtures/comma_with_header.csv'), 'r') TestModel.order(:id).map{|r| r.attributes}.should == [{'id' => 1, 'data' => 'test data 1'}] diff --git a/spec/fixtures/comma_with_header_to_map.csv b/spec/fixtures/comma_with_header_to_map.csv new file mode 100644 index 0000000..b85c91c --- /dev/null +++ b/spec/fixtures/comma_with_header_to_map.csv @@ -0,0 +1,2 @@ +ref,data +1,test data 1 From e828a49709b4038c77487910959b3a9c853cffd2 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Thu, 6 Oct 2022 10:06:28 -0400 Subject: [PATCH 66/77] Update dependencies --- Gemfile.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index c3b4a17..02bc0ff 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,29 +1,29 @@ PATH remote: . specs: - postgres-copy (1.6.0) + postgres-copy (1.6.1) activerecord (>= 5.1) pg (>= 0.17) GEM remote: https://rubygems.org/ specs: - activemodel (7.0.1) - activesupport (= 7.0.1) - activerecord (7.0.1) - activemodel (= 7.0.1) - activesupport (= 7.0.1) - activesupport (7.0.1) + activemodel (7.0.4) + activesupport (= 7.0.4) + activerecord (7.0.4) + activemodel (= 7.0.4) + activesupport (= 7.0.4) + activesupport (7.0.4) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - concurrent-ruby (1.1.9) + concurrent-ruby (1.1.10) diff-lcs (1.4.4) - i18n (1.9.1) + i18n (1.12.0) concurrent-ruby (~> 1.0) - minitest (5.15.0) - pg (1.2.3) + minitest (5.16.3) + pg (1.4.3) rake (11.2.2) rdoc (6.2.1) rspec (2.99.0) @@ -34,7 +34,7 @@ GEM rspec-expectations (2.99.2) diff-lcs (>= 1.1.3, < 2.0) rspec-mocks (2.99.4) - tzinfo (2.0.4) + tzinfo (2.0.5) concurrent-ruby (~> 1.0) PLATFORMS From 73ec5d331205ba8c4c987de63c33d7f5854a4b60 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Thu, 6 Oct 2022 10:06:40 -0400 Subject: [PATCH 67/77] Bump patch version number --- postgres-copy.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index 3b2041b..1d27017 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -5,7 +5,7 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "postgres-copy" - s.version = "1.6.0" + s.version = "1.6.1" s.platform = Gem::Platform::RUBY s.required_ruby_version = ">= 1.9.3" s.authors = ["Diogo Biazus"] From 8917452a49f581667b401d9cc793d5b8a8c06f2a Mon Sep 17 00:00:00 2001 From: Daniel Luna Date: Fri, 11 Nov 2022 20:51:07 -0300 Subject: [PATCH 68/77] Temp table convenience method --- README.md | 15 ++++++++++++++- lib/postgres-copy/with_temp_table.rb | 18 ++++++++++++++++++ spec/postgres-copy/with_temp_table_spec.rb | 17 +++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 lib/postgres-copy/with_temp_table.rb create mode 100644 spec/postgres-copy/with_temp_table_spec.rb diff --git a/README.md b/README.md index 4240659..f94122a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# postgres-copy +# postgres-copy ![Ruby](https://github.com/diogob/postgres-copy/workflows/Ruby/badge.svg) @@ -207,6 +207,19 @@ This is useful for removing byte order marks when matching column headers. User.copy_from "/tmp/users_with_byte_order_mark.csv", :encoding => 'bom|utf-8' ``` +### Using PostgresCopy::WithTempTable.generate + +Based on [nfedyashev](https://github.com/nfedyashev)'s [comment](https://github.com/diogob/postgres-copy/issues/51): + +```ruby + PostgresCopy::WithTempTable.generate do |t| + columns.each do |column_name| + t.string column_name.to_sym + end + end +``` + +This auto-generates an id column, but the temp table creation is configurable. ### Using the CSV Responder If you want to make the result of a COPY command available to download this gem provides a CSV responder that, in conjunction with [inherited_resources](https://github.com/josevalim/inherited_resources), is a very powerfull tool. BTW, do not try to use the responder without inherited_resources. diff --git a/lib/postgres-copy/with_temp_table.rb b/lib/postgres-copy/with_temp_table.rb new file mode 100644 index 0000000..c571294 --- /dev/null +++ b/lib/postgres-copy/with_temp_table.rb @@ -0,0 +1,18 @@ +module PostgresCopy + module WithTempTable + def self.generate(connection = ActiveRecord::Base.connection, base_klass: + ActiveRecord::Base, temp_table_name: nil, create_table_opts: {id: :bigint}) + raise "You have to pass a table schema definition block!" unless block_given? + table_name = temp_table_name || "temp_table_#{SecureRandom.hex}" + + connection.create_table table_name, temporary: true, **create_table_opts do |t| + yield t + end + + klass = Class.new(base_klass) do + acts_as_copy_target + self.table_name = table_name + end + end + end +end diff --git a/spec/postgres-copy/with_temp_table_spec.rb b/spec/postgres-copy/with_temp_table_spec.rb new file mode 100644 index 0000000..e7cad09 --- /dev/null +++ b/spec/postgres-copy/with_temp_table_spec.rb @@ -0,0 +1,17 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') +require 'postgres-copy/with_temp_table' + +describe '.generate' do + subject(:generate) { + PostgresCopy::WithTempTable.generate do |t| + t.string :data + end + } + + it { + generate.copy_from 'spec/fixtures/comma_with_header.csv' + data = generate.all.first + expect(data.id).to eq(1) + expect(data.data).to eq('test data 1') + } +end From 9b7ac59a1c1a0b1cd746ada0b91b1fbed3fa47eb Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Fri, 18 Nov 2022 15:07:37 -0500 Subject: [PATCH 69/77] Bump minor version number --- Gemfile.lock | 4 ++-- postgres-copy.gemspec | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 02bc0ff..09df52e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - postgres-copy (1.6.1) + postgres-copy (1.7.0) activerecord (>= 5.1) pg (>= 0.17) @@ -23,7 +23,7 @@ GEM i18n (1.12.0) concurrent-ruby (~> 1.0) minitest (5.16.3) - pg (1.4.3) + pg (1.4.5) rake (11.2.2) rdoc (6.2.1) rspec (2.99.0) diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index 1d27017..713ad71 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -5,7 +5,7 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "postgres-copy" - s.version = "1.6.1" + s.version = "1.7.0" s.platform = Gem::Platform::RUBY s.required_ruby_version = ">= 1.9.3" s.authors = ["Diogo Biazus"] From 8e421f1c943b5078901e707a8943b4337617eab8 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Mon, 9 Jan 2023 09:35:31 -0500 Subject: [PATCH 70/77] Update rdoc dependency to avoid security vulnerability --- Gemfile.lock | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 09df52e..25bdc40 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -19,13 +19,16 @@ GEM minitest (>= 5.1) tzinfo (~> 2.0) concurrent-ruby (1.1.10) - diff-lcs (1.4.4) + diff-lcs (1.5.0) i18n (1.12.0) concurrent-ruby (~> 1.0) - minitest (5.16.3) + minitest (5.17.0) pg (1.4.5) + psych (5.0.1) + stringio rake (11.2.2) - rdoc (6.2.1) + rdoc (6.5.0) + psych (>= 4.0.0) rspec (2.99.0) rspec-core (~> 2.99.0) rspec-expectations (~> 2.99.0) @@ -34,6 +37,7 @@ GEM rspec-expectations (2.99.2) diff-lcs (>= 1.1.3, < 2.0) rspec-mocks (2.99.4) + stringio (3.0.4) tzinfo (2.0.5) concurrent-ruby (~> 1.0) From 70056b9aae31c1ea06a3664fa9f0c57d05562b96 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Mon, 9 Jan 2023 09:41:36 -0500 Subject: [PATCH 71/77] Update rspec and rake so we fix security vulnerability in rake < 12.3.3 --- Gemfile.lock | 27 ++++++++++++++++----------- postgres-copy.gemspec | 4 ++-- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 25bdc40..80c94e3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -26,17 +26,22 @@ GEM pg (1.4.5) psych (5.0.1) stringio - rake (11.2.2) + rake (12.3.3) rdoc (6.5.0) psych (>= 4.0.0) - rspec (2.99.0) - rspec-core (~> 2.99.0) - rspec-expectations (~> 2.99.0) - rspec-mocks (~> 2.99.0) - rspec-core (2.99.2) - rspec-expectations (2.99.2) - diff-lcs (>= 1.1.3, < 2.0) - rspec-mocks (2.99.4) + rspec (3.12.0) + rspec-core (~> 3.12.0) + rspec-expectations (~> 3.12.0) + rspec-mocks (~> 3.12.0) + rspec-core (3.12.0) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-mocks (3.12.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-support (3.12.0) stringio (3.0.4) tzinfo (2.0.5) concurrent-ruby (~> 1.0) @@ -47,9 +52,9 @@ PLATFORMS DEPENDENCIES bundler postgres-copy! - rake (~> 11.2.2) + rake (~> 12.3.3) rdoc - rspec (~> 2.12) + rspec (~> 3.0) BUNDLED WITH 2.2.22 diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index 713ad71..d46e049 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -24,7 +24,7 @@ Gem::Specification.new do |s| s.add_dependency "activerecord", '>= 5.1' s.add_development_dependency "bundler" s.add_development_dependency "rdoc" - s.add_development_dependency "rspec", "~> 2.12" - s.add_development_dependency "rake", "~> 11.2.2" + s.add_development_dependency "rspec", "~> 3.0" + s.add_development_dependency "rake", "~> 12.3.3" end From dc64939655d127d5b69f5de78cff66db66ae15e1 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Mon, 9 Jan 2023 09:46:50 -0500 Subject: [PATCH 72/77] Bump patch number --- Gemfile.lock | 2 +- postgres-copy.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 80c94e3..5387b6b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - postgres-copy (1.7.0) + postgres-copy (1.7.1) activerecord (>= 5.1) pg (>= 0.17) diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index d46e049..3e7281d 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -5,7 +5,7 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "postgres-copy" - s.version = "1.7.0" + s.version = "1.7.1" s.platform = Gem::Platform::RUBY s.required_ruby_version = ">= 1.9.3" s.authors = ["Diogo Biazus"] From 64701b36698dcdbc7ac62cb664b755a3c2819941 Mon Sep 17 00:00:00 2001 From: Don Sisco Date: Wed, 28 Aug 2024 11:31:47 -0400 Subject: [PATCH 73/77] Ruby 3.4 dependency gemspec change ``` csv.rb was loaded from the standard library, but will no longer be part of the default gems since Ruby 3.4.0. Add csv to your Gemfile or gemspec. Also contact author of postgres-copy-1.7.1 to add csv into its gemspec. ``` --- postgres-copy.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index 3e7281d..cd347a3 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -22,6 +22,7 @@ Gem::Specification.new do |s| s.add_dependency "pg", ">= 0.17" s.add_dependency "activerecord", '>= 5.1' + s.add_dependency "csv" s.add_development_dependency "bundler" s.add_development_dependency "rdoc" s.add_development_dependency "rspec", "~> 3.0" From 71baec56706e7f72d8ecd7397bb5e4dd40f244de Mon Sep 17 00:00:00 2001 From: Donald Sisco Date: Mon, 2 Sep 2024 14:53:41 -0400 Subject: [PATCH 74/77] Check in Gemfile.lock --- Gemfile.lock | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Gemfile.lock b/Gemfile.lock index 5387b6b..3f81d38 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,6 +3,7 @@ PATH specs: postgres-copy (1.7.1) activerecord (>= 5.1) + csv pg (>= 0.17) GEM @@ -19,6 +20,7 @@ GEM minitest (>= 5.1) tzinfo (~> 2.0) concurrent-ruby (1.1.10) + csv (3.3.0) diff-lcs (1.5.0) i18n (1.12.0) concurrent-ruby (~> 1.0) From 13eaab515499b578fb4542ede0d1865b675b3059 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Mon, 2 Sep 2024 17:29:56 -0400 Subject: [PATCH 75/77] Update Gemfile.lock for development --- Gemfile.lock | 70 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 3f81d38..6abf8b9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -9,43 +9,57 @@ PATH GEM remote: https://rubygems.org/ specs: - activemodel (7.0.4) - activesupport (= 7.0.4) - activerecord (7.0.4) - activemodel (= 7.0.4) - activesupport (= 7.0.4) - activesupport (7.0.4) - concurrent-ruby (~> 1.0, >= 1.0.2) + activemodel (7.2.1) + activesupport (= 7.2.1) + activerecord (7.2.1) + activemodel (= 7.2.1) + activesupport (= 7.2.1) + timeout (>= 0.4.0) + activesupport (7.2.1) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) + logger (>= 1.4.2) minitest (>= 5.1) - tzinfo (~> 2.0) - concurrent-ruby (1.1.10) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + base64 (0.2.0) + bigdecimal (3.1.8) + concurrent-ruby (1.3.4) + connection_pool (2.4.1) csv (3.3.0) - diff-lcs (1.5.0) - i18n (1.12.0) + diff-lcs (1.5.1) + drb (2.2.1) + i18n (1.14.5) concurrent-ruby (~> 1.0) - minitest (5.17.0) - pg (1.4.5) - psych (5.0.1) + logger (1.6.1) + minitest (5.25.1) + pg (1.5.7) + psych (5.1.2) stringio rake (12.3.3) - rdoc (6.5.0) + rdoc (6.7.0) psych (>= 4.0.0) - rspec (3.12.0) - rspec-core (~> 3.12.0) - rspec-expectations (~> 3.12.0) - rspec-mocks (~> 3.12.0) - rspec-core (3.12.0) - rspec-support (~> 3.12.0) - rspec-expectations (3.12.2) + rspec (3.13.0) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.0) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.2) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.12.0) - rspec-mocks (3.12.2) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.12.0) - rspec-support (3.12.0) - stringio (3.0.4) - tzinfo (2.0.5) + rspec-support (~> 3.13.0) + rspec-support (3.13.1) + securerandom (0.3.1) + stringio (3.1.1) + timeout (0.4.1) + tzinfo (2.0.6) concurrent-ruby (~> 1.0) PLATFORMS From 4b0bd306e1a13dd1687d10cfe63d8d981ed9a3ed Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Mon, 2 Sep 2024 17:32:38 -0400 Subject: [PATCH 76/77] Bump patch version --- Gemfile.lock | 2 +- postgres-copy.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 6abf8b9..7eff25c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - postgres-copy (1.7.1) + postgres-copy (1.7.2) activerecord (>= 5.1) csv pg (>= 0.17) diff --git a/postgres-copy.gemspec b/postgres-copy.gemspec index cd347a3..08b320e 100644 --- a/postgres-copy.gemspec +++ b/postgres-copy.gemspec @@ -5,7 +5,7 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "postgres-copy" - s.version = "1.7.1" + s.version = "1.7.2" s.platform = Gem::Platform::RUBY s.required_ruby_version = ">= 1.9.3" s.authors = ["Diogo Biazus"] From 57ea2c4626489010a55c81b872c165f98d14b105 Mon Sep 17 00:00:00 2001 From: Diogo Biazus Date: Mon, 2 Sep 2024 17:36:13 -0400 Subject: [PATCH 77/77] Update ruby versions used for CI --- .github/workflows/ruby.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 9ec83eb..f5f990a 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -36,7 +36,7 @@ jobs: name: test (ruby v${{ matrix.ruby }}) strategy: matrix: - ruby: ["2.7", "3.0", "3.1"] + ruby: ["3.2", "3.3", "3.4"] steps: - uses: actions/checkout@v2