LoginSignup
2
2

More than 5 years have passed since last update.

全モデルで使えるシンプルなデータアップデート機能を実装する

Last updated at Posted at 2017-08-26

概要

  • 前回、activerecord-importを利用して、全モデルで使えるシンプルなインポート機能を実装する方法を紹介したので、今回は既存データのアップデートする機能の実装を紹介する。
  • activerecord-importの記事では、 重複をチェックするカラムは primary or unique key が設定されているカラムに限定されているが、使いにくいので、それ以外のカラムでも使える実装をした。
  • postgresql、mysqlいずれでもできるが、今回は postgresql の例のみ示す。

環境

  • Ruby 2.3.1
  • Rails 5.1.2
  • postgresql 9.6.2 ( 9.5+ が必須)

方法

activerecord-importを導入

前回参照

ApplicationRecordクラスに実装

前回同様ApplicationRecordクラスに smple_update クラスを実装する。

app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base

  def self.simple_update(path: '', col_sep: ',', per_num: 5000, conflict_target: :id)
    # pathはファイル名
    # col_sepは区切り文字列
    # per_numはactiverecord-importで何件ごとにsqlを発行するかの値
    # conflict_targetは重複をチェックするカラム

    sources = {}.with_indifferent_access
    columns = []
    # csvデータを読み込む
    CSV.foreach(path, headers: true, col_sep: col_sep) do |row|
      key = row.field(conflict_target.to_s)
      sources[key] = get_import_params(row)
      columns = sources[key].keys if $. == 2
    end

    targets = where(conflict_target => sources.keys)

    list = targets.map do |target|
      target.attributes.merge(sources[target.send(conflict_target).to_s])
    end

    import list,
           on_duplicate_key_update: {conflict_target: [:id], columns: columns},
           batch_size: per_num
  end

private
  def self.get_import_params(row)
    headers = row.headers & self.column_names # 存在するカラム名のみに限定する
    headers.each_with_object({}) do |c, h|
      h[c.to_sym] = row.field(c)
    end
  end
end

ちなみに、aはarray、rはrow, hはhash、cはcolの略のつもり。

使い方

モデル

  • 例えば、idとnameとcolorカラムを持つFruitモデルを想定します。
app/models/fruit.rb
class Fruit < ApplicationRecord
end

csvデータ

  • アップデートするカラムと重複を判断するカラムを含むcsvデータを用意します。
  • アップデートしたいカラムには 必ずカラム名と同じ名称 をつけてください。なお、存在しないカラム名はアップデート対象から自動的に除外されます。(この例ではkansou)
  • 重複を判断するカラムを元にアップデートする行を検索して、アップデートするカラムのデータでデータを更新します。

インポートするデータをdbフォルダ直下に用意します。

db/input.csv
name,color,kansou
apple,red,oisii
banana,yellow,amai  
  • 1行目はヘッダーで、2行目以降がデータです。
  • アップデートしたいカラムは color
  • 重複を判断するカラムは name

インポート

例えば、rails consoleから下記の様にコマンドをうてばnameカラムに基づいて重複するデータのみが、対応するcolorカラムのデータでアップデートされます。(kansouカラムは無視されます)

> Fruit.simple_update(path: 'db/input.csv', col_sep: ',', conflict_target: :name)
  • 区切り文字をタブ区切りにしたい場合は col_sep: "\t" を指定してください。

その他

  • mysqlで行いたい場合は、activerecord-importの記事 On Duplicate Key Update を参考にして修正してください。
  • 重複をチェックするカラム(conflict_target)が primary or unique key が設定されているカラムならば、もっとコードはシンプルにできます。試してないけど、以下の感じ。(get_import_paramasは上のコードと同じです。)
  def self.simple_update(path: '', col_sep: ',', per_num: 5000, conflict_target: :id)
    list = []
    columns = []
    CSV.foreach(path, headers: true, col_sep: col_sep) do |row|
      import_params = get_import_params(row)
      list.push(import_params)
      columns = import_params.keys if $. == 2
    end

    import list,
           on_duplicate_key_update: {conflict_target: [conflict_target], columns: columns},
           batch_size: per_num
  end

以上

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2