kintoneアプリ間のレコード同期処理を cli-kintone を使い行います。
同期シナリオ
- レコードのバックアップ処理
- 定期的なバックアップ
環境
- macOS 10.13.6
- Ruby 2.5.0
- cli-kintone 0.9.4
前提条件
- 更新用のキーフィールドを用意する。(今回はフィールドコード:顧客コードを用意)
- 更新用のキーは 数値、必須、重複無し 、1から順に割り振られている。(1,2,3..,n)
- 処理は更新元のマスタアプリからバックアップ先のアプリへの一方向。
- テーブルの更新は無い。
処理概要
バックアップ先のレコードの最終更新日付を取得して、更新元のマスタアプリ(MA)のレコードの最終更新日付と比較し
バックアップ先の最終更新日付以降に更新されたマスタアプリのレコードをバックアップ先アプリ(BA)へ更新します。
- バックアップ先のレコードの最終更新日付を取得(例:更新日時→"2019-10-07T13:22:00Z")
- バックアップ先の最終更新日付以降に更新された更新元のレコードを取得
- 更新用のデータに変換
- 取得したマスタのレコードをバックアップ先に更新
全体コード
require 'minitest/autorun'
require 'rubygems'
require 'bundler/setup'
require 'dotenv/load'
require 'open3'
require 'csv'
require 'stringio'
require 'date'
require 'pp'
SUBDOMAIN = ENV['SUBDOMAIN']
APP_FROM = ENV['APP_FROM']
API_FROM = ENV['API_FROM']
APP_TO = ENV['APP_TO']
API_TO = ENV['API_TO']
SELECT_COLUMN = ENV['SELECT_COLUMN']
UPDATE_COLUMN = ENV['UPDATE_COLUMN']
LAST_UPDATE_COLUMN = ENV['LAST_UPDATE_COLUMN']
# 1. バックアップ先のレコードの最終更新日時を取得
def fetch_lastupdate_by_backupapp(domain, app_to, api_to, select_column)
query = '"order by 更新日時 desc limit 1"'
cap_str = %Q(cli-kintone -d #{domain} -a #{app_to} -c '\"#{select_column}\"' -t #{api_to} -q #{query})
out, err, status = Open3.capture3(cap_str)
if (err != "")
pp err; pp status; exit
end
if (out.size > 0)
begin
lines = []
out.each_line { |line| lines << (line.chomp).parse_csv }
lines[1][1]
rescue => e
pp e
exit
end
else
return 0
end
end
# 2. バックアップ先の最終更新日時以降に更新された更新元のレコードを取得
def fetch_update_records_by_master(domain, app_from, api_from, select_column, datetime)
query = "\"更新日時 > \\\"#{datetime}\\\" order by 更新日時 asc\""
cap_str = %Q(cli-kintone -d #{domain} -a #{app_from} -c '\"#{select_column}\"' -t #{api_from} -q #{query})
out, err, status = Open3.capture3(cap_str)
puts out
if (err != "")
pp err; pp status; exit
end
out
end
# 3. 更新用のデータに変換
def convert_records_by_updatekey(update_column, records_string)
csv = CSV.new(records_string, headers: true)
csv_out = CSV.new(StringIO.new) << CSV.new(update_column).shift
csv.each {|row| csv_out << row}
csv_out.string
end
# 4. 取得したマスタのレコードをバックアップ先に更新
def update_as_backupapp(domain, app_to, api_to, records_string)
cap_str = %Q(cli-kintone --import -d #{domain} -a #{app_to} -t #{api_to})
csv_out = StringIO.new
csv_out << records_string
out, err, status = Open3.capture3(cap_str, :stdin_data => csv_out.string)
if (err != "")
pp err; pp status; exit
end
out
end
# UnitTest
class AddDifferenceRecordsTest < Minitest::Test
def test_1
puts __method__
records = fetch_lastupdate_by_backupapp(SUBDOMAIN, APP_TO, API_TO, LAST_UPDATE_COLUMN)
puts records.class
io_string = StringIO.new(records)
ary = io_string.readlines
assert_equal 1, ary.size
assert_equal DateTime.parse("2019-10-10T10:07:00Z").class, DateTime.parse(ary[0]).class
end
def test_2
puts __method__
records = fetch_update_records_by_master(SUBDOMAIN, APP_FROM, API_FROM, SELECT_COLUMN, "2019-10-07T13:22:00Z")
sio = StringIO.new(records)
ary = sio.readlines
assert_equal 5, ary.size
end
def test_3
puts __method__
records = <<EOS
"住所","担当者名","部署名","FAX","顧客コード","顧客名","備考","郵便番号","TEL","メールアドレス"
"岐阜県岐阜市××××","下山 達士","情報システム部","050-××××-××××","20","金都運総研","備考欄更新","5010001","090-××××-××××","shimoyama_tatsuhito@example.com"
"大阪府大阪市北区梅田××××","上野 裕太郎","経理部","050-××××-××××","14","有限会社亀山","","5300001","090-××××-××××","ueno_yuujirou@example.com"
EOS
str = convert_records_by_updatekey(UPDATE_COLUMN, records)
sio = StringIO.new(str)
header = sio.readline.chomp
assert_equal UPDATE_COLUMN, header
end
def test_4
puts __method__
records = <<EOS
"住所","担当者名","部署名","FAX","*顧客コード","顧客名","備考","郵便番号","TEL","メールアドレス"
"岐阜県岐阜市××××","下山 達士","情報システム部","050-××××-××××","20","金都運総研","備考欄更新","5010001","090-××××-××××","shimoyama_tatsuhito@example.com"
"大阪府大阪市北区梅田××××","上野 裕太郎","経理部","050-××××-××××","14","有限会社亀山","","5300001","090-××××-××××","ueno_yuujirou@example.com"
EOS
ret = update_as_backupapp(SUBDOMAIN, APP_TO, API_TO, records)
assert_equal false, ret !~ /SUCCESS/
end
end
処理説明
主な処理を説明します。
1. バックアップ先のレコードの最終更新日時を取得
バックアップ先のアプリに対して、クエリで更新日時の降順でソートした先頭1レコードを取得します。
今回は、最新の更新日時だけ取得できれば良いので、 order by 更新日時 desc
でレコードを日時の新しい順の並びで取得し、limit 1
でその先頭1レコードだけを取得しています。
「order by 句 を省略した場合は、レコードIDの降順(desc)で返されます」
ちなみに、order by 更新日時
でも良いです。デフォルトは desc なので省略ができます。
query = '"order by 更新日時 desc limit 1"'
2. マスタアプリから、バックアップ先の最終更新日時以降に更新されたレコードを取得
クエリ指定でマスタアプリのレコードを取得します。
日時の指定が少し読みにくいですが、クエリは下記のようになります。
"更新日時 > \"2019-10-07T13:22:00Z\" order by 更新日時 asc"
日付を囲むダブルクォーテーションをバックスラッシュでエスケイプします。
datetimeには 1.で取得したバックアップ先の最終更新日時がセットされます。
(例:"2019-10-11T10:15:00Z")
query = "\"更新日時 > \\\"#{datetime}\\\" order by 更新日時 asc\""
例えば、毎日決まった時刻に 「前日の更新分を定期取得する」 のなら、更新日時の条件は FROM_TODAY
関数を使って、引数に (-1, DAYS)
(1日前の指定) を使って
"更新日時 > FROM_TODAY(-1, DAYS) order by 更新日時 asc"
でも良いかも知れません。
3. 更新用のデータに変換
cli-kintone を使う最大のメリットはレコード更新にあるのでは無いかと個人的に思っています。REST API を使うと結構大変。
更新系は、レコードIDを使う方法と重複禁止フィールドをキーにする方法の2つがありますが、重複禁止フィールドをキーにする方法はcli-kintoneなら、読み込むCSVのヘッダーの項目に、アスタリスク(*)を付けるだけで行けます。
"住所","担当者名","部署名","FAX","*顧客コード","顧客名","備考","郵便番号","TEL","メールアドレス"
今回は、プログラムでアスタリスクを付けた更新用のヘッダーを用意して、2.で取得したデータのヘッダーと入れ替える処理をしています。
4. 取得したマスタのレコードをバックアップ先に更新
3.で作成した更新用のデータを --import
を指定して標準出力から読み込みます。
%Q(cli-kintone --import -d #{domain} -a #{app_to} -t #{api_to})
参考リンク
cli-kintone関連
- はじめようkintone コマンドライン
- 第3回 レコードの更新をしてみよう
- レコードの一括更新
- クエリ指定
- kintone コマンドラインツールの使い方
- 定期実行でデータの同期を実現するスマートな方法 その1〜cli-kintone編〜
- https://github.com/kintone/cli-kintone
- https://developer.kintone.io/hc/en-us/articles/115002614853
- kintoneコマンドラインツールでUPSERTしてみた