require File.expand_path(File.dirname(__FILE__) + '/spec_helper') describe "pg_upsert from file with CSV format" do before do DateTime.stub_chain(:now, :utc).and_return (DateTime.parse("2012-01-01").utc.round(4)) @time_now = Time.now.round(4) allow(Time).to receive(:now).and_return(@time_now) end def timestamp DateTime.now.utc end it "should import from file if path is passed without field_map" do PostgresUpsert.write TestModel, File.expand_path('spec/fixtures/comma_with_header.csv') expect( TestModel.first.attributes ).to include('data' => 'test data 1', 'created_at' => timestamp, 'updated_at' => timestamp) end it "correctly handles delimiters in content" do PostgresUpsert.write TestModel, File.expand_path('spec/fixtures/comma_with_header_and_comma_values.csv') expect( TestModel.first.attributes ).to include('data' => 'test, the data 1', 'created_at' => timestamp, 'updated_at' => timestamp) end it "throws error if csv is malformed" do expect{ PostgresUpsert.write TestModel, File.expand_path('spec/fixtures/comma_with_header_and_unquoted_comma.csv') }.to raise_error end it "throws error if source data contains duplicate pk" do expect{ PostgresUpsert.write TestModel, File.expand_path('spec/fixtures/comma_with_header_duplicate.csv') }.to raise_error(/duplicate key/) end it "should import from IO without field_map" do PostgresUpsert.write TestModel, File.open(File.expand_path('spec/fixtures/comma_with_header.csv'), 'r') expect( TestModel.first.attributes ).to include('data' => 'test data 1', 'created_at' => timestamp, 'updated_at' => timestamp) end it "should import with custom delimiter from path" do PostgresUpsert.write TestModel, File.expand_path('spec/fixtures/semicolon_with_header.csv'), :delimiter => ';' expect( TestModel.first.attributes ).to include('data' => 'test data 1', 'created_at' => timestamp, 'updated_at' => timestamp) end it "should import with custom delimiter from IO" do PostgresUpsert.write TestModel, File.open(File.expand_path('spec/fixtures/semicolon_with_header.csv'), 'r'), :delimiter => ';' expect( TestModel.first.attributes ).to include('data' => 'test data 1', 'created_at' => timestamp, 'updated_at' => timestamp) end it "should not expect a header when :header is false" do PostgresUpsert.write(TestModel, File.open(File.expand_path('spec/fixtures/comma_without_header.csv'), 'r'), :header => false, :columns => [:id,:data]) expect( TestModel.first.attributes ).to include('data' => 'test data 1', 'created_at' => timestamp, 'updated_at' => timestamp) end it "should be able to map the header in the file to diferent column names" do PostgresUpsert.write(TestModel, File.open(File.expand_path('spec/fixtures/tab_with_different_header.csv'), 'r'), :delimiter => "\t", :map => {'cod' => 'id', 'info' => 'data'}) expect( TestModel.first.attributes ).to include('data' => 'test data 1', 'created_at' => timestamp, 'updated_at' => timestamp) end it "should be able to map the header in the file to diferent column names with custom delimiter" do PostgresUpsert.write(TestModel, File.open(File.expand_path('spec/fixtures/semicolon_with_different_header.csv'), 'r'), :delimiter => ';', :map => {'cod' => 'id', 'info' => 'data'}) expect( TestModel.first.attributes ).to include('data' => 'test data 1', 'created_at' => timestamp, 'updated_at' => timestamp) end it "should ignore empty lines" do PostgresUpsert.write(TestModel, File.open(File.expand_path('spec/fixtures/tab_with_extra_line.csv'), 'r'), :delimiter => "\t") expect( TestModel.first.attributes ).to include('data' => 'test data 1', 'created_at' => timestamp, 'updated_at' => timestamp) end it "should not create timestamps when the model does not include them" do PostgresUpsert.write ReservedWordModel, File.expand_path('spec/fixtures/reserved_words.csv'), :delimiter => "\t" expect( ReservedWordModel.first.attributes ).to eq("group"=>"group name", "id"=>1, "select"=>"test select") end context "upserting data to handle inserts and creates" do let(:original_created_at) {5.days.ago.utc} before(:each) do TestModel.create(id: 1, data: "From the before time, in the long long ago", :created_at => original_created_at) end it "should not violate primary key constraint" do expect{ PostgresUpsert.write TestModel, File.expand_path('spec/fixtures/comma_with_header.csv') }.to_not raise_error end it "should upsert (update existing records and insert new records)" do PostgresUpsert.write TestModel, File.expand_path('spec/fixtures/tab_with_two_lines.csv'), :delimiter => "\t" expect( TestModel.find(1).attributes ).to eq("id"=>1, "data"=>"test data 1", "created_at" => original_created_at, "updated_at" => timestamp) expect( TestModel.find(2).attributes ).to eq("id"=>2, "data"=>"test data 2", "created_at" => timestamp, "updated_at" => timestamp) end it "should return updated and inserted results" do result = PostgresUpsert.write TestModel, File.expand_path('spec/fixtures/tab_with_two_lines.csv'), :delimiter => "\t" expect( result.updated ).to eq(1) expect( result.inserted ).to eq(1) end it "should require columns option if no header" do expect{ PostgresUpsert.write TestModel, File.expand_path('spec/fixtures/comma_without_header.csv'), :header => false }.to raise_error("Either the :columns option or :header => true are required") end it "should clean up the temp table after completion" do PostgresUpsert.write TestModel, File.expand_path('spec/fixtures/tab_with_two_lines.csv'), :delimiter => "\t" ActiveRecord::Base.connection.tables.should_not include("test_models_temp") end it "should gracefully drop the temp table if it already exists" do ActiveRecord::Base.connection.execute "CREATE TEMP TABLE test_models_temp (LIKE test_models);" PostgresUpsert.write TestModel, File.expand_path('spec/fixtures/tab_with_two_lines.csv'), :delimiter => "\t" expect( TestModel.find(1).attributes ).to eq("id"=>1, "data"=>"test data 1", "created_at" => original_created_at, "updated_at" => timestamp) expect( TestModel.find(2).attributes ).to eq("id"=>2, "data"=>"test data 2", "created_at" => timestamp, "updated_at" => timestamp) end it "should be able to copy using custom set of columns" do ThreeColumn.create(id: 1, data: "old stuff", extra: "neva change!", created_at: original_created_at) PostgresUpsert.write(ThreeColumn, File.open(File.expand_path('spec/fixtures/tab_only_data.csv'), 'r'), :delimiter => "\t", :columns => ["id", "data"]) expect( ThreeColumn.first.attributes ).to eq('id' => 1, 'data' => 'test data 1', 'extra' => "neva change!", 'created_at' => original_created_at, 'updated_at' => timestamp) end end context 'overriding the comparison column' do it 'updates records based the match column option if its passed in' do three_col = ThreeColumn.create(id: 1, data: "old stuff", extra: "neva change!") file = File.open(File.expand_path('spec/fixtures/no_id.csv'), 'r') PostgresUpsert.write(ThreeColumn, file, :unique_key => "data") expect( three_col.reload.extra ).to eq("ABC: Always Be Changing.") end it 'inserts records if the passed match column doesnt exist' do file = File.open(File.expand_path('spec/fixtures/no_id.csv'), 'r') PostgresUpsert.write(ThreeColumn, file, :unique_key => "data") expect( ThreeColumn.last.attributes ).to include("data" => "old stuff", "extra" => "ABC: Always Be Changing.") end it 'allows key column to be a string or symbol' do file = File.open(File.expand_path('spec/fixtures/no_id.csv'), 'r') PostgresUpsert.write(ThreeColumn, file, :header => true, :unique_key => :data) expect( ThreeColumn.last.attributes ).to include("data" => "old stuff", "extra" => "ABC: Always Be Changing.") end it 'raises an error if the expected key column is not in data' do file = File.open(File.expand_path('spec/fixtures/no_id.csv'), 'r') expect{ PostgresUpsert.write(ThreeColumn, file, :header => true) }.to raise_error (/Expected column 'id'/) end end context 'update only' do let(:original_created_at) {5.days.ago.utc} before(:each) do TestModel.create(id: 1, data: "From the before time, in the long long ago", :created_at => original_created_at) end it 'will only update and not insert if insert_only flag is passed.' do PostgresUpsert.write TestModel, File.expand_path('spec/fixtures/tab_with_two_lines.csv'), :delimiter => "\t", :update_only => true expect( TestModel.find(1).attributes ).to eq("id"=>1, "data"=>"test data 1", "created_at" => original_created_at , "updated_at" => timestamp) expect{ TestModel.find(2) }.to raise_error(ActiveRecord::RecordNotFound) end it 'will return the number of updated rows' do a = PostgresUpsert.write TestModel, File.expand_path('spec/fixtures/tab_with_two_lines.csv'), :delimiter => "\t", :update_only => true expect( a.updated ).to eq(1) expect( a.inserted ).to eq(0) end end context 'using table_name' do it "should import from file if path is passed without field_map" do PostgresUpsert.write TestModel.table_name, File.expand_path('spec/fixtures/comma_with_header.csv') expect( TestModel.first.attributes ).to include('data' => 'test data 1', 'created_at' => timestamp, 'updated_at' => timestamp) end it "should still report results" do TestModel.create(data: "test data 1") result = PostgresUpsert.write TestModel.table_name, File.expand_path('spec/fixtures/tab_with_two_lines.csv'), :delimiter => "\t" expect( result.updated ).to eq(1) expect( result.inserted ).to eq(1) end end end