mainameiz
@mainameiz
Full-stack web-developer

Как безболезненно реализовать массовые действия с БД?

Как можно реализовать массовые insert/update записей в базу данных без дублирования всей логики валидации/коллбэков и т.д. в еще одном месте. Не хочется поддерживать два места в коде.

Или какие есть альтернативные решения данной проблемы?

UPD: проблема в том что делать 1000 insert'ов по отдельности медленнее чем сделать все одним запросом. Так вот я подумал как можно это реализовать с минимальными потерями. Есть мысль использовать Ruby Object Mapper (реализация паттерна data mapper) написать для него адаптер, который будет сохранять все в памяти, а потом строить из этого sql-запрос для массовой вставки/апдейта.
  • Вопрос задан
  • 322 просмотра
Решения вопроса 1
@vsuhachev
Есть activerecord-import, который делает примерно то что вы хотите сделать, только без Ruby Object Mapper.

Если у вас PG, то для него есть команда copy from csv, ее можно использовать как-то так:
task :ips, [:filename] => :environment do |_, args|
      filename = args[:filename] || Rails.root.join(*%w(tmp geoip cidr_optim.txt))
      file = File.new(filename, 'r', encoding: 'windows-1251')

      puts "Import #{filename}"

      csv = CSV.new(file,
                    col_sep: "\t",
                    quote_char: '$',
                    headers: [:range_begin, :range_end, :title, :country, :city_id]
      )

      GeoIp.transaction do

        connection = ActiveRecord::Base.connection_pool.checkout

        begin

          sql = <<-SQL
          COPY geo_ips(ip_range, country, city_id)
          FROM STDIN
          WITH CSV NULL '-'
          SQL

          pg_exec_with_stdin(connection, sql) do
            line = csv.shift
            if line
              line = CSV::Row.new(
                  [],
                  [
                      "[#{line[:range_begin]},#{line[:range_end].to_i + 1})",
                      line[:country],
                      line[:city_id]
                  ]
              )
            end
            line.try(:to_s)
          end

        ensure
          ActiveRecord::Base.connection_pool.checkin(connection)
        end

      end

    end
  end

def pg_exec_with_stdin(conn, sql)
  raw  = conn.raw_connection
  raw.exec(sql)

  while (line = yield)
    raw.put_copy_data line
  end

  raw.put_copy_end
  while raw.get_result do; end # very important to do this after a copy
end


UPD: добавил pg_exec_with_stdin
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
@kkrieger
Если правильно вас понял посмотрите на concerns в rails 4 позволяет вынести общую логику модели в отдельный модуль и подключать его в нужную модель, вот статья artemeff.com/2013/04/21/concerns-v-rails-4.html
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы