lib/active_record/model_spaces/context.rb
require 'active_record/model_spaces/table_names'
require 'active_record/model_spaces/table_manager'
require 'active_record/model_spaces/util'
module ActiveRecord
module ModelSpaces
# holds the current and working tables for a ModelSpace
class Context
include Util
attr_reader :model_space
attr_reader :model_space_key
attr_reader :persistor
attr_reader :current_model_versions
attr_reader :working_model_versions
def initialize(model_space, model_space_key, persistor)
@model_space = model_space
@model_space_key = model_space_key.to_sym
@persistor = persistor
read_versions
create_tables
end
def read_versions
@current_model_versions = persistor.read_model_space_model_versions(model_space.name, model_space_key)
@working_model_versions = {}
end
def create_tables
model_space.registered_model_keys.map do |model_name|
m = Util.model_from_name(model_name)
tm = TableManager.new(m)
tm.create_table(base_table_name(m), current_table_name(m))
end
end
def self.drop_tables(model_space, model_space_key)
model_space.registered_model_keys.map do |model_name|
m = Util.model_from_name(model_name)
tm = TableManager.new(m)
all_table_names = (0..model_space.history_versions(m)).map do |v|
TableNames.table_name(model_space.name, model_space_key, model_space.base_table_name(m), model_space.history_versions(m), v)
end
all_table_names.each do |table_name|
tm.drop_table(table_name)
end
end
end
# implements the Model.table_name method
def table_name(model)
version = get_working_model_version(model) || get_current_model_version(model)
table_name_from_model_version(model, version)
end
# base table name
def base_table_name(model)
model_space.base_table_name(model)
end
# table_name for version 0
def hoovered_table_name(model)
table_name_from_model_version(model, 0)
end
# current table_name, seen by everyone outside of this context
def current_table_name(model)
table_name_from_model_version(model, get_current_model_version(model))
end
# table_name which would be seen by this context in or after a new_version/updated_version. always returns a name
def next_table_name(model)
current_version = get_current_model_version(model)
next_version = TableNames.next_version(model_space.history_versions(model), current_version)
table_name_from_model_version(model, next_version)
end
# table_name of working table, seen by this context in or after new_version/updated_version. null if no new_version/updated_version has been issued/completed
def working_table_name(model)
table_name_from_model_version(model, get_working_model_version(model)) if get_working_model_version(model)
end
def new_version(model, copy_old_version=false, &block)
raise "new_version: a block must be supplied" if !block
if get_working_model_version(model)
block.call # nothing to do
else
current_version = get_current_model_version(model)
next_version = TableNames.next_version(model_space.history_versions(model), current_version)
tm = TableManager.new(model)
ok = false
begin
btn = base_table_name(model)
ctn = current_table_name(model)
ntn = next_table_name(model)
if next_version != current_version
tm.recreate_table(btn, ntn)
tm.copy_table(ctn, ntn) if copy_old_version
else # no history
tm.truncate_table(ntn) if !copy_old_version
end
set_working_model_version(model, next_version)
r = block.call
ok = true
r
ensure
delete_working_model_version(model) if !ok
end
end
end
def updated_version(model, &block)
new_version(model, true, &block)
end
# copy all data to the base model-space tables and drop all history tables
def hoover
raise "can't hoover with active working versions: #{working_model_versions.keys.inspect}" if !working_model_versions.empty?
model_names = model_space.registered_model_keys
new_versions = Hash[ model_names.map do |model_name|
m = Util.model_from_name(model_name)
base_name = base_table_name(m)
current_name = current_table_name(m)
hoovered_name = hoovered_table_name(m)
tm = TableManager.new(m)
# copy to hoovered table
if current_name != hoovered_name
tm.recreate_table(base_name, hoovered_name)
tm.copy_table(current_name, hoovered_name)
end
# drop history tables
(1..model_space.history_versions(m)).map do |v|
htn = table_name_from_model_version(m, v)
tm.drop_table(htn)
end
[model_name, 0]
end ]
persistor.update_model_space_model_versions(new_versions)
read_versions
end
def commit
persistor.update_model_space_model_versions(model_space.name, model_space_key, current_model_versions.merge(working_model_versions))
end
private
def table_name_from_model_version(model, version)
TableNames.table_name(model_space.name, model_space_key, model_space.base_table_name(model), model_space.history_versions(model), version)
end
def get_current_model_version(model)
raise "#{model}: not registered with ModelSpace: #{model_space.name}" if !model_space.is_registered?(model)
self.current_model_versions[model_space.registered_model_name(model)] || 0
end
def get_working_model_version(model)
raise "#{model}: not registered with ModelSpace: #{model_space.name}" if !model_space.is_registered?(model)
self.working_model_versions[model_space.registered_model_name(model)]
end
def set_working_model_version(model, version)
self.working_model_versions[model_space.registered_model_name(model)] = version
end
def delete_working_model_version(model)
self.working_model_versions.delete(model_space.registered_model_name(model))
end
end
end
end