kintoneアプリ間のレコード同期処理を cli-kintone を使い行います。
同期する先のアプリにレコードが無い場合は追加を行います。
同期シナリオ
- レコードのバックアップ処理
- 定期的なバックアップ
環境
- macOS 10.13.6
- Ruby 2.5.0
- cli-kintone 0.9.4
前提条件
- 更新用のキーフィールドを用意する。(今回はフィールドコード:顧客コードを用意)
- 更新用のキーは数値、必須、重複無し、1から順に割り振られている。(1,2,3..,n)
- 処理は更新元のマスタアプリからバックアップ先のアプリへの一方向。
処理概要
更新元のマスタアプリ(MA)に新しく追加されたレコードをバックアップ先アプリ(BA)へ追加します。
- バックアップ先の最後に追加された顧客コードを取得(例:BA顧客コード→20)
- マスタからバックアップ先の顧客コード20より後のレコードを取得(例:MA顧客コード→21,22)
- 取得したマスタのレコードをバックアップ先に追加
全体コード
add_difference_records.rb
require 'minitest/autorun'
require 'rubygems'
require 'bundler/setup'
require 'dotenv/load'
require 'open3'
require 'csv'
require 'stringio'
require 'pp'
require 'net/http'
require 'uri'
require 'json'
require 'base64'
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']
# 1. バックアップ先の最後に追加された顧客コードを取得
def get_customer_code_by_last(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][0]
rescue => e
pp e
exit
end
else
return 0
end
end
# 2. バックアップ先とマスタの差分のレコードを取得
def fetch_records_by_master(domain, app_from, api_from, select_column, last_customer_code)
query = "\"顧客コード > #{last_customer_code} 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)
if (err != "")
pp err; pp status; exit
end
out
end
# 3. 取得した差分のレコードをバックアップ先に追加
def add_difference_records(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 setup
puts __method__
unless (get_customer_code_by_last(SUBDOMAIN, APP_TO, API_TO, "顧客コード") == "20")
test_setup
end
end
def test_1
puts __method__
assert_equal "20", get_customer_code_by_last(SUBDOMAIN, APP_TO, API_TO, "顧客コード")
end
def test_2
puts __method__
records = fetch_records_by_master(SUBDOMAIN, APP_FROM, API_FROM, SELECT_COLUMN, "20")
io_string = StringIO.new(records)
ary = io_string.readlines
assert_equal 3, ary.size
assert_equal "\"21\"", ary[1].split(',')[4]
assert_equal "22", CSV.parse(ary[2])[0][4]
end
def test_3
puts __method__
records = <<EOS
"住所","担当者名","部署名","FAX","顧客コード","顧客名","備考","郵便番号","TEL","メールアドレス"
"北海道","吉里","","","21","有限会社 吉里吉里","","123456","123-456-7890","hoge@test.com"
"北海道","ホワイト","","","22","ホワイトルーム","","123456","123-456-7890","hoge@test.com"
EOS
ret = add_difference_records(SUBDOMAIN, APP_TO, API_TO, records)
assert_equal false, ret !~ /SUCCESS/
end
end
def test_setup
puts __method__
url = "https://#{ENV['SUBDOMAIN']}.cybozu.com/k/v1/records.json"
uri = URI.parse(url)
api_token = ENV['API_TO']
app_id = ENV['APP_TO']
req_get = Net::HTTP::Get.new(uri.path)
req_get['X-Cybozu-API-Token'] = api_token
req_get['Content-Type'] = 'application/json'
fields = ["\$id", "顧客コード"]
query = %q(顧客コード > "20")
req_get.body = JSON.generate({"app" => app_id, "fields" => fields, "query" => query, "totalCount" => true})
ary_ids = []
Net::HTTP.start(uri.host, uri.port, :use_ssl => true) {|http|
res_get = http.request(req_get)
case res_get.code.to_i
when 200
pp JSON.parse(res_get.body)
ary_ids = JSON.parse(res_get.body)['records'].map { |rec| rec['$id']['value'] }
else
pp %Q(#{res_get.code} #{res_get.message})
pp JSON.parse(res_get.body)
end
}
req_delete = Net::HTTP::Delete.new(uri.path)
req_delete['X-Cybozu-API-Token'] = api_token
req_delete['Content-Type'] = 'application/json'
req_delete.body = JSON.generate(
{"app" => app_id,
"ids" => ary_ids}
)
Net::HTTP.start(uri.host, uri.port, :use_ssl => true) {|http|
res = http.request(req_delete)
case res.code.to_i
when 200
pp JSON.parse(res.body)
else
pp %Q(#{res.code} #{res.message})
pp JSON.parse(res.body)
end
}
end
処理説明
主な処理を説明します。
1. バックアップ先の最後に追加された顧客コードを取得
バックアップ先のアプリに対して、クエリで顧客コードの降順でソートした先頭1レコードを取得します。
顧客コードは重複無しの1,2,..nの順で付けている前提です。
query = '"order by 顧客コード desc limit 1"'
2. マスタからバックアップ先の顧客コード20より後のレコードを取得
バックアップ元のマスタアプリとバックアップ先の差分のレコードを取得します。
last_customer_codeには、1.で取得した数値が入ってきます。(例えば "20" など)
query = "\"顧客コード > #{last_customer_code} order by 顧客コード asc\""
3. 取得したマスタのレコードをバックアップ先に追加
--import オプションを付けて、標準出力からの入力を元にレコードを追加します。
cap_str = %Q(cli-kintone --import -d #{domain} -a #{app_to} -t #{api_to})
参考リンク
cli-kintone関連
- はじめようkintone コマンドライン
- クエリ指定
- kintone コマンドラインツールの使い方
- 定期実行でデータの同期を実現するスマートな方法 その1〜cli-kintone編〜
- https://github.com/kintone/cli-kintone
- https://developer.kintone.io/hc/en-us/articles/115002614853