Last active
August 29, 2015 14:10
-
-
Save sue445/41d190b63ae1a2a404ce to your computer and use it in GitHub Desktop.
date partitioning module for rails4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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