Skip to content

Instantly share code, notes, and snippets.

@sue445
Last active August 29, 2015 14:10
Show Gist options
  • Save sue445/41d190b63ae1a2a404ce to your computer and use it in GitHub Desktop.
Save sue445/41d190b63ae1a2a404ce to your computer and use it in GitHub Desktop.
date partitioning module for rails4
# created_atでRANGEパーティショニングされたmodelの共通module
#
# @example
# class CreatedAtRangePartitionModel < ActiveRecord::Base
# include ActsAsMysqlPartition::CreatedAtRangePartitionModule
#
# end
module CreatedAtRangePartitionModule
extend ActiveSupport::Concern
included do
# created_atが所属するパーティションで絞る。(念の為前後1日ほどバッファを持たせる)
scope :near_partition, -> created_at { where(created_at: created_at - 1.day ... created_at + 1.day) }
end
# パーティションを絞りつつupdateする
# @param [Hash] new_attributes
def update_with_partition(new_attributes = {})
# saveやupdateだとWHEREにidしかつかずに全パーティション検索するためcreated_atを入れてパーティションを絞る
# UPDATEする時にcreated_atの範囲指定だとパーティション全体にロックがかかるため範囲指定は使わない
unique_keys = { id: self.id, created_at: self.created_at }
# turntableが使われてる時は絞り込みの条件にshard_keyも必須
if self.class.respond_to?(:turntable_shard_key)
turntable_shard_key = self.class.turntable_shard_key
# Railsはカラム名をStringで保持しているけどいつ仕様が変わるか分からないのでStringとSymbol両方考慮しておく
original_attributes = attributes.with_indifferent_access
unique_keys[turntable_shard_key] = original_attributes[turntable_shard_key]
end
self.class.where(unique_keys).update_all(new_attributes.merge(updated_at: Time.current))
end
# update_with_partitionの前にバリデーションを検証する
# @param [Hash] attributes
# @raise [ActiveRecord::RecordInvalid]
def update_with_partition!(attributes = {})
# バリデーションの検証用に別オブジェクトを作って代入する
output = self.clone
output.attributes = attributes
raise ActiveRecord::RecordInvalid.new(output) unless output.valid?
update_with_partition(attributes)
end
# UPDATE文のWHERE句にidとcreated_atを含めてパーティションを絞りつつsaveする
def save_with_partition
run_callbacks :save do
__save_with_partition
end
end
# UPDATE文のWHERE句にidとcreated_atを含めてパーティションを絞りつつsave!する
def save_with_partition!
run_callbacks :save do
raise ActiveRecord::RecordInvalid.new(self) unless self.valid?
__save_with_partition
end
end
private
def __save_with_partition
# 更新されたattributesだけ抽出してUPDATEに渡す
target_attributes = attributes.select{ |k, v| changed.include?(k) }
update_with_partition(target_attributes)
changed_attributes.clear
end
end
describe CreatedAtRangePartitionModule do
shared_examples :save_and_update_with_partition do
let(:before_price) { 30000 }
let(:after_price) { 45000 }
let(:invalid_price) { -1 }
# updated_atを更新させるためわざと古い日付にする
let(:before_updated_at) { 1.day.ago }
describe "#update_with_partition" do
subject do
model.update_with_partition(attributes)
model.reload
end
let(:attributes) do
{
price: after_price,
}
end
it{ expect{ subject }.to change{ model.price }.to(after_price) }
it{ expect{ subject }.to change{ model.updated_at } }
end
describe "#update_with_partition!" do
subject do
model.update_with_partition!(attributes)
model.reload
end
let(:attributes) do
{
price: invalid_price,
}
end
it{ expect{ subject }.to raise_error ActiveRecord::RecordInvalid }
end
describe "#save_with_partition" do
subject do
model.price = after_price
model.save_with_partition
model.reload
end
it{ expect{ subject }.to change{ model.price }.to(after_price) }
it{ expect{ subject }.to change{ model.updated_at } }
it "save後は変更なしの状態になってること" do
subject
expect(model).not_to be_changed
end
end
describe "#save_with_partition!" do
subject do
model.price = invalid_price
model.save_with_partition!
model.reload
end
it{ expect{ subject }.to raise_error ActiveRecord::RecordInvalid }
end
end
context "without turntable" do
it_behaves_like :save_and_update_with_partition do
let!(:model) do
# created_atでRANGEパーティショニングされててバリデーションがあればなんでもいい
CreatedAtRangePartitionModel.create!(
price: before_price,
updated_at: before_updated_at,
)
end
end
end
context "with turntable" do
let(:user_id){ 445 }
before do
skip "turntableはrails4以降でしか動かないのでテストもしない" unless rails4?
end
it_behaves_like :save_and_update_with_partition do
let!(:model) do
# created_atでRANGEパーティショニングされててバリデーションがあればなんでもいい
TurntableModel.create!(
price: before_price,
updated_at: before_updated_at,
user_id: user_id,
)
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment