class BatchApi
def initialize(app)
@app = app
end
def call(env)
if env['PATH_INFO'] == '/api/batch'
process_batch(env)
else
@app.call(env)
end
end
def process_batch(env)
responses = []
request = ActionDispatch::Request.new(env.deep_dup)
ActiveRecord::Base.transaction do
request.parameters[:requests].each do |override|
# 子供は別のtransationを使いたいですので、requires_new: trueをつけます。
ActiveRecord::Base.transaction(requires_new: true) do
response = process_request(env.deep_dup, override)
responses.push(response)
# 子供は何かエラーは発生した時に、子供のデータをロルバックする
raise ActiveRecord::Rollback unless success?([response])
end
end
# レスポンスが失敗の場合、全部ロールバックします。
raise ActiveRecord::Rollback unless success?(responses)
end
render_response(responses)
end
def process_request(env, override)
path, query = override['url'].split('?')
env['REQUEST_METHOD'] = override['method']
env['PATH_INFO'] = path
env['QUERY_STRING'] = query
env['FROM_BATCH_API'] = true
# env['rack.input'] = StringIO.new(override['body'].to_json)
# 2020/11/12アップデート: to_jsonは&がある場合、変気宇されちゃったのでたまにエラー発生した、代わりにJSON::generate または JSON::dump 使うべき
env['rack.input'] = StringIO.new(JSON::generate(override['body']))
status, headers, body = @app.call(env)
{ status: status, headers: headers, body: status == 200 ? JSON.parse(body&.body) : body }
end
def render_response(responses)
hash = {}
hash[:l] = responses
success_response = responses&.find { |response| response[:status] == 200 }
headers = success_response ? success_response[:headers] : responses[0][:headers]
hash_json = hash.to_json
# レスポンスの長さを更新します。
headers = headers.merge('Content-Length' => hash_json.bytesize)
[200, headers, [hash_json]]
end
def success?(responses)
responses&.each do |response|
return false if response[:status] != 200
end
true
end
end