Method: PostgresCopy::ActsAsCopyTarget::CopyMethods#copy_from

Defined in:
lib/postgres-copy/acts_as_copy_target.rb

#copy_from(path_or_io, options = {}) ⇒ Object

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



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/postgres-copy/acts_as_copy_target.rb', line 82

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
                     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, get_file_mode('r', options[:encoding])) : 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] || 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
      bytes = 0
      begin
        while line = io.readpartial(10240)
          connection.raw_connection.put_copy_data line
          bytes += line.bytesize
        end
      rescue EOFError
      end
    else
      line_buffer = ''

      while line = io.gets do
        next if line.strip.size == 0

        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?(&:nil?)
              line_buffer = CSV.generate_line(row, col_sep: options[:delimiter])
            rescue CSV::MalformedCSVError
              next
            end
          end

          connection.raw_connection.put_copy_data(line_buffer)

          # Clear the buffer
          line_buffer = ''
        end
      end
    end
  end
end