activerecord-model-spaces 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.rspec +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +56 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +98 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/activerecord-model-spaces.gemspec +87 -0
- data/lib/active_record/model_spaces.rb +89 -0
- data/lib/active_record/model_spaces/context.rb +184 -0
- data/lib/active_record/model_spaces/model_space.rb +121 -0
- data/lib/active_record/model_spaces/persistor.rb +72 -0
- data/lib/active_record/model_spaces/registry.rb +195 -0
- data/lib/active_record/model_spaces/table_manager.rb +111 -0
- data/lib/active_record/model_spaces/table_names.rb +51 -0
- data/lib/active_record/model_spaces/util.rb +55 -0
- data/spec/active_record/model_spaces/context_spec.rb +479 -0
- data/spec/active_record/model_spaces/model_space_spec.rb +292 -0
- data/spec/active_record/model_spaces/persistor_spec.rb +101 -0
- data/spec/active_record/model_spaces/registry_spec.rb +522 -0
- data/spec/active_record/model_spaces/table_manager_spec.rb +186 -0
- data/spec/active_record/model_spaces/table_names_spec.rb +90 -0
- data/spec/active_record/model_spaces/util_spec.rb +106 -0
- data/spec/active_record/model_spaces_spec.rb +116 -0
- data/spec/spec_helper.rb +18 -0
- metadata +205 -0
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'active_record/model_spaces/util'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ModelSpaces
|
5
|
+
|
6
|
+
|
7
|
+
# manages the creation and destruction of tables, and the bulk handling of data in those tables
|
8
|
+
class TableManager
|
9
|
+
include Util
|
10
|
+
|
11
|
+
attr_reader :model
|
12
|
+
attr_reader :connection
|
13
|
+
|
14
|
+
def initialize(model)
|
15
|
+
@model = model_from_name(model)
|
16
|
+
@connection = @model.connection
|
17
|
+
end
|
18
|
+
|
19
|
+
# create a new table with the same schema as the base_table, but a different name
|
20
|
+
def create_table(base_table_name, table_name)
|
21
|
+
if table_name != base_table_name && !connection.table_exists?(table_name)
|
22
|
+
get_table_schema_copier(connection).copy_table_schema(connection, base_table_name, table_name)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# drop a table
|
27
|
+
def drop_table(table_name)
|
28
|
+
connection.execute("drop table #{table_name}") if connection.table_exists?(table_name)
|
29
|
+
end
|
30
|
+
|
31
|
+
# drop and recreate a table
|
32
|
+
def recreate_table(base_table_name, table_name)
|
33
|
+
if table_name != base_table_name
|
34
|
+
drop_table(table_name)
|
35
|
+
create_table(base_table_name, table_name)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# truncate a table
|
40
|
+
def truncate_table(table_name)
|
41
|
+
connection.execute("truncate table #{table_name}")
|
42
|
+
end
|
43
|
+
|
44
|
+
# copy all data from one table to another
|
45
|
+
def copy_table(from, to)
|
46
|
+
connection.execute("insert into #{to} select * from #{from}") if from != to
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
TABLE_SCHEMA_COPIERS = {}
|
52
|
+
|
53
|
+
def get_table_schema_copier(connection)
|
54
|
+
adapter_name = connection.adapter_name
|
55
|
+
|
56
|
+
if !TABLE_SCHEMA_COPIERS[adapter_name]
|
57
|
+
klassname = "ActiveRecord::ModelSpaces::#{adapter_name}TableSchemaCopier"
|
58
|
+
klass = class_from_classname(klassname)
|
59
|
+
if klass
|
60
|
+
TABLE_SCHEMA_COPIERS[adapter_name] = klass
|
61
|
+
else
|
62
|
+
TABLE_SCHEMA_COPIERS[adapter_name] = DefaultTableSchemaCopier
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
TABLE_SCHEMA_COPIERS[adapter_name]
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
module MySQLTableSchemaCopier
|
72
|
+
module_function
|
73
|
+
|
74
|
+
def copy_table_schema(connection, from_table_name, to_table_name)
|
75
|
+
from_table_schema = table_schema(connection, from_table_name)
|
76
|
+
to_table_schema = change_table_name(from_table_name, to_table_name, from_table_schema)
|
77
|
+
connection.execute(to_table_schema)
|
78
|
+
end
|
79
|
+
|
80
|
+
def table_schema(connection, table_name)
|
81
|
+
connection.select_rows("SHOW CREATE TABLE `#{table_name}`").last.last
|
82
|
+
end
|
83
|
+
|
84
|
+
def change_table_name(from_table_name, to_table_name, schema)
|
85
|
+
schema.gsub(/CREATE TABLE `#{from_table_name}`/, "CREATE TABLE `#{to_table_name}`")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
module DefaultTableSchemaCopier
|
90
|
+
module_function
|
91
|
+
|
92
|
+
def copy_table_schema(connection, from_table_name, to_table_name)
|
93
|
+
from_table_schema = table_schema(connection, from_table_name)
|
94
|
+
to_table_schema = change_table_name(from_table_name, to_table_name, from_table_schema)
|
95
|
+
connection.instance_eval(to_table_schema)
|
96
|
+
end
|
97
|
+
|
98
|
+
# retrieve a schema.rb fragment pertaining to the table called table_name. uses a private Rails API
|
99
|
+
def table_schema(connection, table_name)
|
100
|
+
ActiveRecord::SchemaDumper.send(:new, connection).send(:table, table_name, StringIO.new).string
|
101
|
+
end
|
102
|
+
|
103
|
+
# change the table_name in a schema.rb fragment
|
104
|
+
def change_table_name(base_table_name, table_name, schema)
|
105
|
+
schema.
|
106
|
+
gsub(/create_table \"#{base_table_name}\"/, "create_table \"#{table_name}\"").
|
107
|
+
gsub(/add_index \"#{base_table_name}\"/, "add_index \"#{table_name}\"")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_record/model_spaces/util'
|
3
|
+
|
4
|
+
module ActiveRecord
|
5
|
+
module ModelSpaces
|
6
|
+
module TableNames
|
7
|
+
|
8
|
+
class << self
|
9
|
+
include Util
|
10
|
+
end
|
11
|
+
|
12
|
+
module_function
|
13
|
+
|
14
|
+
def base_table_name(model)
|
15
|
+
name_from_model(model).
|
16
|
+
instance_eval{ ActiveSupport::Inflector.demodulize(self)}.
|
17
|
+
instance_eval{ ActiveSupport::Inflector.underscore(self)}.
|
18
|
+
instance_eval{ ActiveSupport::Inflector.pluralize(self)}
|
19
|
+
end
|
20
|
+
|
21
|
+
def model_space_table_name(model_space_name, model_space_key, base_table_name)
|
22
|
+
if (!model_space_name || model_space_name.to_s.empty?) &&
|
23
|
+
(model_space_key && !model_space_key.to_s.empty?)
|
24
|
+
raise "model_space_key cannot be non-empty if model_space_name is empty"
|
25
|
+
end
|
26
|
+
|
27
|
+
[ ("#{model_space_name}__" if model_space_name && !model_space_name.to_s.empty?),
|
28
|
+
("#{model_space_key}__" if model_space_key && !model_space_key.to_s.empty?),
|
29
|
+
base_table_name].compact.join
|
30
|
+
end
|
31
|
+
|
32
|
+
def table_name(model_space_name, model_space_key, model, history_versions, v)
|
33
|
+
[model_space_table_name(model_space_name, model_space_key, model),
|
34
|
+
("__#{v}" if v && v>0)].compact.join
|
35
|
+
end
|
36
|
+
|
37
|
+
def next_version(history_versions, v)
|
38
|
+
version(history_versions, (v || 0)+1)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
module_function
|
44
|
+
|
45
|
+
def version(history_versions, v)
|
46
|
+
(v || 0) % ((history_versions || 0) + 1)
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ModelSpaces
|
3
|
+
|
4
|
+
module Util
|
5
|
+
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def name_from_model(model)
|
9
|
+
model.to_s
|
10
|
+
end
|
11
|
+
|
12
|
+
def model_from_name(key)
|
13
|
+
if key.is_a? String
|
14
|
+
Kernel.eval(key)
|
15
|
+
else
|
16
|
+
key
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def require_for_classname(classname)
|
21
|
+
begin
|
22
|
+
Kernel.require ActiveSupport::Inflector.underscore(classname)
|
23
|
+
Kernel.eval(classname)
|
24
|
+
rescue Exception=>e
|
25
|
+
false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def class_for_classname(classname)
|
30
|
+
begin
|
31
|
+
model_from_name(classname)
|
32
|
+
rescue
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def class_from_classname(classname)
|
38
|
+
class_for_classname(classname) || require_for_classname(classname)
|
39
|
+
end
|
40
|
+
|
41
|
+
# returns all model superclasses upto but not including ActiveRecord::Base
|
42
|
+
def all_model_superclasses(klass)
|
43
|
+
superclasses = klass.ancestors.grep(Class).sort.take_while{|k| k < ActiveRecord::Base}
|
44
|
+
end
|
45
|
+
|
46
|
+
def is_active_record_model?(klass)
|
47
|
+
superclasses = klass.ancestors.grep(Class).sort.take_while{|k| k <= ActiveRecord::Base}
|
48
|
+
superclasses.length > 1 &&
|
49
|
+
superclasses.include?(ActiveRecord::Base)
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,479 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
require 'active_record/model_spaces/context'
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module ModelSpaces
|
7
|
+
|
8
|
+
describe Context do
|
9
|
+
|
10
|
+
def create_model(name, superklass=ActiveRecord::Base)
|
11
|
+
m = Class.new(superklass)
|
12
|
+
m.stub(:to_s).and_return(name)
|
13
|
+
m.stub(:inspect).and_return(name)
|
14
|
+
m
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
describe "initialize" do
|
19
|
+
|
20
|
+
it "should initialize with a model_space, model_space_key and persistor and create necessary tables" do
|
21
|
+
im = create_model('Items')
|
22
|
+
um = create_model('Users')
|
23
|
+
|
24
|
+
ms = ModelSpace.new(:foo_space)
|
25
|
+
ms.register_model(im)
|
26
|
+
ms.register_model(um)
|
27
|
+
|
28
|
+
Util.stub(:model_from_name).with("Items").and_return(im)
|
29
|
+
Util.stub(:model_from_name).with("Users").and_return(um)
|
30
|
+
|
31
|
+
itm = double('items-table-manager')
|
32
|
+
utm = double('users-table-manager')
|
33
|
+
TableManager.should_receive(:new).with(im).and_return(itm)
|
34
|
+
TableManager.should_receive(:new).with(um).and_return(utm)
|
35
|
+
|
36
|
+
itm.should_receive(:create_table).with("items", "foo_space__one__items__1")
|
37
|
+
utm.should_receive(:create_table).with("users", "foo_space__one__users__2")
|
38
|
+
|
39
|
+
p = double('persistor')
|
40
|
+
v = {"Items"=>1, "Users"=>2}
|
41
|
+
p.should_receive(:read_model_space_model_versions).with(:foo_space, :one).and_return(v)
|
42
|
+
|
43
|
+
c = Context.new(ms, 'one', p)
|
44
|
+
|
45
|
+
c.model_space.should == ms
|
46
|
+
c.model_space_key.should == :one
|
47
|
+
c.persistor.should == p
|
48
|
+
c.current_model_versions.should == {"Items"=>1, "Users"=>2}
|
49
|
+
c.working_model_versions.should == {}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def create_context(attrs = {})
|
54
|
+
ms = attrs[:model_space] || double('model-space')
|
55
|
+
|
56
|
+
if attrs[:model_space]
|
57
|
+
ms_name = attrs[:model_space].name
|
58
|
+
else
|
59
|
+
ms_name = double('model-space-name')
|
60
|
+
ms.stub(:name).and_return(ms_name)
|
61
|
+
end
|
62
|
+
|
63
|
+
if attrs[:model_space_key]
|
64
|
+
msk = attrs[:model_space_key].to_sym
|
65
|
+
else
|
66
|
+
msk = double('model-space-key')
|
67
|
+
msk.stub(:to_sym).and_return(msk)
|
68
|
+
end
|
69
|
+
p = attrs[:persistor] || double('persistor')
|
70
|
+
|
71
|
+
cmv = attrs[:current_model_versions] || double('current-model-versions')
|
72
|
+
p.should_receive(:read_model_space_model_versions).with(ms_name, msk).and_return(cmv)
|
73
|
+
|
74
|
+
Context.any_instance.stub(:create_tables) # don't need this here
|
75
|
+
Context.new(ms, msk, p)
|
76
|
+
end
|
77
|
+
|
78
|
+
def create_context_with_one_model(attrs={})
|
79
|
+
im = create_model('Item')
|
80
|
+
ms = ModelSpace.new(:foo)
|
81
|
+
ms.register_model(im, :history_versions=>2, :base_table_name=>"some_items")
|
82
|
+
ctx = create_context(attrs.merge(:model_space=>ms, :current_model_versions=>{"Item"=>1}))
|
83
|
+
[ctx, im]
|
84
|
+
end
|
85
|
+
|
86
|
+
def create_context_with_parent_child_models(attrs={})
|
87
|
+
p = create_model('Parent')
|
88
|
+
c = create_model('Child', p)
|
89
|
+
ms = ModelSpace.new(:foo)
|
90
|
+
ms.register_model(p, :history_versions=>2)
|
91
|
+
ctx = create_context(attrs.merge(:model_space=>ms, :current_model_versions=>{"Parent"=>1}))
|
92
|
+
[ctx, p, c]
|
93
|
+
end
|
94
|
+
|
95
|
+
def create_context_with_two_models(attrs={})
|
96
|
+
im = create_model('Item')
|
97
|
+
um = create_model('User')
|
98
|
+
ms = ModelSpace.new(:foo)
|
99
|
+
ms.register_model(im, :history_versions=>2)
|
100
|
+
ms.register_model(um, :history_versions=>1)
|
101
|
+
ctx = create_context(attrs.merge(:model_space=>ms, :current_model_versions=>{"Item"=>1}))
|
102
|
+
[ctx, im, um]
|
103
|
+
end
|
104
|
+
|
105
|
+
def create_context_with_three_models(attrs={})
|
106
|
+
im = create_model('Item')
|
107
|
+
um = create_model('User')
|
108
|
+
om = create_model('Other')
|
109
|
+
ms = ModelSpace.new(:foo)
|
110
|
+
ms.register_model(im, :history_versions=>2)
|
111
|
+
ms.register_model(um, :history_versions=>1)
|
112
|
+
ms.register_model(om, :history_versions=>0)
|
113
|
+
ctx = create_context(attrs.merge(:model_space=>ms, :current_model_versions=>{"Item"=>1}))
|
114
|
+
[ctx, im, um, om]
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "drop_tables" do
|
118
|
+
it "should drop all tables for all models associated with a given model_space and model_space_key" do
|
119
|
+
ctx, im, um, om = create_context_with_three_models
|
120
|
+
ms = ctx.model_space
|
121
|
+
|
122
|
+
Util.stub(:model_from_name).with("Item").and_return(im)
|
123
|
+
Util.stub(:model_from_name).with("User").and_return(um)
|
124
|
+
Util.stub(:model_from_name).with("Other").and_return(om)
|
125
|
+
|
126
|
+
imtm = double('ItemTableManager')
|
127
|
+
TableManager.should_receive(:new).with(im).and_return(imtm)
|
128
|
+
umtm = double("UserTableManager")
|
129
|
+
TableManager.should_receive(:new).with(um).and_return(umtm)
|
130
|
+
omtm = double('OtherTableManager')
|
131
|
+
TableManager.should_receive(:new).with(om).and_return(omtm)
|
132
|
+
|
133
|
+
imtm.should_receive(:drop_table).with("foo__one__items")
|
134
|
+
imtm.should_receive(:drop_table).with("foo__one__items__1")
|
135
|
+
imtm.should_receive(:drop_table).with("foo__one__items__2")
|
136
|
+
|
137
|
+
umtm.should_receive(:drop_table).with("foo__one__users")
|
138
|
+
umtm.should_receive(:drop_table).with("foo__one__users__1")
|
139
|
+
|
140
|
+
omtm.should_receive(:drop_table).with("foo__one__others")
|
141
|
+
|
142
|
+
|
143
|
+
Context.drop_tables(ms, :one)
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe "base_table_name" do
|
149
|
+
it "should ask the ModelSpace for the base_table_name" do
|
150
|
+
ctx, m = create_context_with_one_model
|
151
|
+
ctx.model_space.should_receive(:base_table_name).with(m)
|
152
|
+
ctx.base_table_name(m)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
describe "table_name" do
|
157
|
+
it "should return the current_model_version based table_name if present" do
|
158
|
+
ctx, im = create_context_with_one_model(:model_space_key=>"one")
|
159
|
+
TableNames.should_receive(:table_name).with(:foo, :one, "some_items", 2, 1)
|
160
|
+
ctx.table_name(im)
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should return the working_model_version based table_name if present" do
|
164
|
+
ctx,im = create_context_with_one_model(:model_space_key=>"one")
|
165
|
+
ctx.send(:set_working_model_version, im, 2)
|
166
|
+
TableNames.should_receive(:table_name).with(:foo, :one, "some_items", 2, 2)
|
167
|
+
ctx.table_name(im)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
describe "hoovered_table_name" do
|
172
|
+
it "should return the table_name for the model with version 0" do
|
173
|
+
ctx,im = create_context_with_one_model(:model_space_key=>"one")
|
174
|
+
TableNames.should_receive(:table_name).with(:foo, :one, "some_items", 2, 0)
|
175
|
+
ctx.hoovered_table_name(im)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe "current_table_name" do
|
180
|
+
it "should return the current_model_version based table_name" do
|
181
|
+
ctx,im = create_context_with_one_model(:model_space_key=>"one")
|
182
|
+
ctx.should_receive(:table_name_from_model_version).with(im, 1)
|
183
|
+
ctx.current_table_name(im)
|
184
|
+
|
185
|
+
ctx.rspec_reset
|
186
|
+
ctx.send(:set_working_model_version, im, 2)
|
187
|
+
ctx.should_receive(:table_name_from_model_version).with(im,1)
|
188
|
+
ctx.current_table_name(im)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
describe "next_table_name" do
|
193
|
+
it "should return the next version based table_name" do
|
194
|
+
ctx,im = create_context_with_one_model(:model_space_key=>"one")
|
195
|
+
ctx.should_receive(:table_name_from_model_version).with(im, 2)
|
196
|
+
ctx.next_table_name(im)
|
197
|
+
|
198
|
+
ctx.rspec_reset
|
199
|
+
ctx.send(:set_working_model_version, im, 2)
|
200
|
+
ctx.should_receive(:table_name_from_model_version).with(im,2)
|
201
|
+
ctx.next_table_name(im)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
describe "working_table_name" do
|
206
|
+
it "should return the next version based table name or nil if no working version has been registered " do
|
207
|
+
ctx,im = create_context_with_one_model(:model_space_key=>"one")
|
208
|
+
ctx.working_table_name(im).should == nil
|
209
|
+
|
210
|
+
ctx.rspec_reset
|
211
|
+
ctx.send(:set_working_model_version, im, 2)
|
212
|
+
ctx.should_receive(:table_name_from_model_version).with(im,2)
|
213
|
+
ctx.next_table_name(im)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
describe "new_version" do
|
218
|
+
it "should just call the block if the model already has a working version" do
|
219
|
+
ctx,im = create_context_with_one_model(:model_space_key=>"one")
|
220
|
+
ctx.send(:set_working_model_version, im, 2)
|
221
|
+
ctx.send(:get_current_model_version, im).should == 1
|
222
|
+
ctx.send(:get_working_model_version, im).should == 2
|
223
|
+
|
224
|
+
TableManager.should_not_receive(:new)
|
225
|
+
|
226
|
+
r = ctx.new_version(im){ :result }
|
227
|
+
r.should == :result
|
228
|
+
end
|
229
|
+
|
230
|
+
it "should truncate the table and call the block if the model has no history versions and !copy_old_version" do
|
231
|
+
ctx, im, um, om = create_context_with_three_models(:model_space_key=>"one")
|
232
|
+
|
233
|
+
tm = double('table-manager')
|
234
|
+
TableManager.stub(:new).and_return(tm)
|
235
|
+
tm.should_receive(:truncate_table).with("foo__one__others")
|
236
|
+
|
237
|
+
r = ctx.new_version(om){ :result }
|
238
|
+
r.should == :result
|
239
|
+
end
|
240
|
+
|
241
|
+
it "should just call the block if the model has no history versions and copy_old_version" do
|
242
|
+
ctx, im, um, om = create_context_with_three_models(:model_space_key=>"one")
|
243
|
+
|
244
|
+
tm = double('table-manager')
|
245
|
+
TableManager.stub(:new).and_return(tm)
|
246
|
+
|
247
|
+
r = ctx.new_version(om, true){ :result }
|
248
|
+
r.should == :result
|
249
|
+
end
|
250
|
+
|
251
|
+
it "should recreate the next_version table, set the working version and call the block if !copy_old_version" do
|
252
|
+
ctx, im, um, om = create_context_with_three_models(:model_space_key=>"one")
|
253
|
+
|
254
|
+
imtm = double('im-table-manager')
|
255
|
+
TableManager.stub(:new).with(im).and_return(imtm)
|
256
|
+
imtm.should_receive(:recreate_table).with('items', 'foo__one__items__2')
|
257
|
+
|
258
|
+
umtm = double('um-table-manager')
|
259
|
+
TableManager.stub(:new).with(um).and_return(umtm)
|
260
|
+
umtm.should_receive(:recreate_table).with('users', 'foo__one__users__1')
|
261
|
+
|
262
|
+
omtm = double('om-table-manager')
|
263
|
+
TableManager.stub(:new).with(om).and_return(omtm)
|
264
|
+
omtm.should_receive(:truncate_table).with('foo__one__others')
|
265
|
+
|
266
|
+
ctx.table_name(im).should == 'foo__one__items__1'
|
267
|
+
ctx.new_version(im){:result}.should == :result
|
268
|
+
ctx.send(:get_working_model_version, im).should == 2
|
269
|
+
ctx.table_name(im).should == 'foo__one__items__2'
|
270
|
+
|
271
|
+
ctx.table_name(um).should == 'foo__one__users'
|
272
|
+
ctx.new_version(um){:um_result}.should == :um_result
|
273
|
+
ctx.send(:get_working_model_version, um).should == 1
|
274
|
+
ctx.table_name(um).should == 'foo__one__users__1'
|
275
|
+
|
276
|
+
ctx.table_name(om).should == 'foo__one__others'
|
277
|
+
ctx.new_version(om){:om_result}.should == :om_result
|
278
|
+
ctx.send(:get_working_model_version, om).should == 0
|
279
|
+
ctx.table_name(om).should == 'foo__one__others'
|
280
|
+
end
|
281
|
+
|
282
|
+
it "should recreate the next_version table, set the working version, copy the previous version data and call the block if copy_old_version" do
|
283
|
+
ctx, im, um, om = create_context_with_three_models(:model_space_key=>"one")
|
284
|
+
|
285
|
+
imtm = double('im-table-manager')
|
286
|
+
TableManager.stub(:new).with(im).and_return(imtm)
|
287
|
+
imtm.should_receive(:recreate_table).with('items', 'foo__one__items__2')
|
288
|
+
imtm.should_receive(:copy_table).with('foo__one__items__1', 'foo__one__items__2')
|
289
|
+
|
290
|
+
umtm = double('um-table-manager')
|
291
|
+
TableManager.stub(:new).with(um).and_return(umtm)
|
292
|
+
umtm.should_receive(:recreate_table).with('users', 'foo__one__users__1')
|
293
|
+
umtm.should_receive(:copy_table).with('foo__one__users', 'foo__one__users__1')
|
294
|
+
|
295
|
+
omtm = double('om-table-manager')
|
296
|
+
TableManager.stub(:new).with(om).and_return(omtm)
|
297
|
+
omtm.should_not_receive(:truncate_table)
|
298
|
+
|
299
|
+
ctx.table_name(im).should == 'foo__one__items__1'
|
300
|
+
ctx.new_version(im, true){:result}.should == :result
|
301
|
+
ctx.send(:get_working_model_version, im).should == 2
|
302
|
+
ctx.table_name(im).should == 'foo__one__items__2'
|
303
|
+
|
304
|
+
ctx.table_name(um).should == 'foo__one__users'
|
305
|
+
ctx.new_version(um, true){:um_result}.should == :um_result
|
306
|
+
ctx.send(:get_working_model_version, um).should == 1
|
307
|
+
ctx.table_name(um).should == 'foo__one__users__1'
|
308
|
+
|
309
|
+
ctx.table_name(om).should == 'foo__one__others'
|
310
|
+
ctx.new_version(om, true){:om_result}.should == :om_result
|
311
|
+
ctx.send(:get_working_model_version, om).should == 0
|
312
|
+
ctx.table_name(om).should == 'foo__one__others'
|
313
|
+
end
|
314
|
+
|
315
|
+
it "should remove the working version if the supplied block borks" do
|
316
|
+
ctx, im, um, om = create_context_with_three_models(:model_space_key=>"one")
|
317
|
+
|
318
|
+
imtm = double('im-table-manager')
|
319
|
+
TableManager.stub(:new).with(im).and_return(imtm)
|
320
|
+
imtm.should_receive(:recreate_table).with('items', 'foo__one__items__2')
|
321
|
+
|
322
|
+
ctx.table_name(im).should == 'foo__one__items__1'
|
323
|
+
expect{
|
324
|
+
ctx.new_version(im){raise "blah"}
|
325
|
+
}.to raise_error /blah/
|
326
|
+
ctx.table_name(im).should == 'foo__one__items__1'
|
327
|
+
|
328
|
+
end
|
329
|
+
|
330
|
+
it "should bork if a block is not supplied" do
|
331
|
+
ctx, im = create_context_with_one_model(:model_space_key=>"one")
|
332
|
+
|
333
|
+
expect {
|
334
|
+
ctx.new_version(im)
|
335
|
+
}.to raise_error /a block must be supplied/
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
describe "hoover" do
|
340
|
+
it "should bork if there are any working versions" do
|
341
|
+
ctx, im = create_context_with_one_model(:model_space_key=>"one")
|
342
|
+
ctx.send(:set_working_model_version, im, 2)
|
343
|
+
|
344
|
+
expect {
|
345
|
+
ctx.hoover
|
346
|
+
}.to raise_error /can\'t hoover with active working versions/
|
347
|
+
end
|
348
|
+
|
349
|
+
it "should copy models to their base table, drop history tables and re-read model-versions" do
|
350
|
+
ctx, im, um, om = create_context_with_three_models(:model_space_key=>"one")
|
351
|
+
|
352
|
+
Util.stub(:model_from_name).with("Item").and_return(im)
|
353
|
+
Util.stub(:model_from_name).with("User").and_return(um)
|
354
|
+
Util.stub(:model_from_name).with("Other").and_return(om)
|
355
|
+
|
356
|
+
imtm = double('im-table-manager')
|
357
|
+
TableManager.stub(:new).with(im).and_return(imtm)
|
358
|
+
TableManager.stub(:new).with("Item").and_return(imtm)
|
359
|
+
imtm.should_receive(:recreate_table).with('items', 'foo__one__items')
|
360
|
+
imtm.should_receive(:copy_table).with('foo__one__items__1', 'foo__one__items')
|
361
|
+
imtm.should_receive(:drop_table).with('foo__one__items__1')
|
362
|
+
imtm.should_receive(:drop_table).with('foo__one__items__2')
|
363
|
+
|
364
|
+
umtm = double('um-table-manager')
|
365
|
+
TableManager.stub(:new).with(um).and_return(umtm)
|
366
|
+
TableManager.stub(:new).with("User").and_return(umtm)
|
367
|
+
umtm.should_receive(:drop_table).with('foo__one__users__1')
|
368
|
+
|
369
|
+
omtm = double('om-table-manager')
|
370
|
+
TableManager.stub(:new).with(om).and_return(omtm)
|
371
|
+
TableManager.stub(:new).with("Other").and_return(omtm)
|
372
|
+
|
373
|
+
ctx.persistor.should_receive(:update_model_space_model_versions).with("Item"=>0, "User"=>0, "Other"=>0)
|
374
|
+
ctx.should_receive(:read_versions)
|
375
|
+
|
376
|
+
|
377
|
+
ctx.hoover
|
378
|
+
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
describe "updated_version" do
|
383
|
+
it "should call new_version with the copy_old_version flat set to true returning the result of the block" do
|
384
|
+
ctx, im = create_context_with_one_model(:model_space_key=>"one")
|
385
|
+
|
386
|
+
ctx.should_receive(:new_version).and_return do |model, copy_old_version, &block|
|
387
|
+
model.should == im
|
388
|
+
copy_old_version.should == true
|
389
|
+
block.call
|
390
|
+
end
|
391
|
+
|
392
|
+
ctx.updated_version(im) { :result }.should == :result
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
describe "commit" do
|
397
|
+
it "should call the persistor with the merge of the current and working model versions" do
|
398
|
+
ctx, im, um = create_context_with_two_models(:model_space_key=>"one")
|
399
|
+
ctx.send(:set_working_model_version, um, 1)
|
400
|
+
|
401
|
+
ctx.persistor.should_receive(:update_model_space_model_versions).with(ctx.model_space.name, :one, {"Item"=>1, "User"=>1})
|
402
|
+
|
403
|
+
ctx.commit
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
describe "table_name_from_model_version" do
|
408
|
+
it "should call the TableNames.table_name method" do
|
409
|
+
ctx, im = create_context_with_one_model(:model_space_key=>"one")
|
410
|
+
|
411
|
+
v = double('version')
|
412
|
+
TableNames.should_receive(:table_name).with(:foo, :one, "some_items", 2, v)
|
413
|
+
ctx.send(:table_name_from_model_version, im, v)
|
414
|
+
|
415
|
+
TableNames.rspec_reset
|
416
|
+
ctx.send(:table_name_from_model_version, im, 3).should == "foo__one__some_items__3"
|
417
|
+
ctx.send(:table_name_from_model_version, im, 0).should == "foo__one__some_items"
|
418
|
+
ctx.send(:table_name_from_model_version, im, nil).should == "foo__one__some_items"
|
419
|
+
end
|
420
|
+
|
421
|
+
it "should call the TableNames.table_name method with registered superclass values" do
|
422
|
+
ctx, p, c = create_context_with_parent_child_models(:model_space_key=>"one")
|
423
|
+
v = double('version')
|
424
|
+
TableNames.should_receive(:table_name).with(:foo, :one, "parents", 2, v)
|
425
|
+
ctx.send(:table_name_from_model_version, c, v)
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
describe "get_current_model_version & set_working_model_version & get_working_model_version & delete_working_model_version" do
|
430
|
+
it "should set and get the working model version" do
|
431
|
+
ctx, im = create_context_with_one_model(:model_space_key=>"one")
|
432
|
+
ctx.send(:get_current_model_version, im).should == 1
|
433
|
+
|
434
|
+
ctx.send(:get_working_model_version, im).should == nil
|
435
|
+
ctx.send(:set_working_model_version, im, 2)
|
436
|
+
ctx.send(:get_working_model_version, im).should == 2
|
437
|
+
ctx.send(:delete_working_model_version, im)
|
438
|
+
ctx.send(:get_working_model_version, im).should == nil
|
439
|
+
end
|
440
|
+
|
441
|
+
it "should set and get working model versions using the registered superclass" do
|
442
|
+
ctx, p, c = create_context_with_parent_child_models(:model_space_key=>"one")
|
443
|
+
|
444
|
+
ctx.send(:get_current_model_version, p).should == 1
|
445
|
+
ctx.send(:get_current_model_version, c).should == 1
|
446
|
+
|
447
|
+
ctx.send(:get_working_model_version, p).should == nil
|
448
|
+
ctx.send(:get_working_model_version, c).should == nil
|
449
|
+
|
450
|
+
ctx.send(:set_working_model_version, c, 2)
|
451
|
+
|
452
|
+
ctx.send(:get_working_model_version, p).should == 2
|
453
|
+
ctx.send(:get_working_model_version, c).should == 2
|
454
|
+
|
455
|
+
ctx.send(:delete_working_model_version, c)
|
456
|
+
|
457
|
+
ctx.send(:get_working_model_version, p).should == nil
|
458
|
+
ctx.send(:get_working_model_version, c).should == nil
|
459
|
+
end
|
460
|
+
|
461
|
+
it "should bork if called with an unregistered model" do
|
462
|
+
ctx, im = create_context_with_one_model(:model_space_key=>"one")
|
463
|
+
|
464
|
+
rm = create_model('Random')
|
465
|
+
|
466
|
+
expect {
|
467
|
+
ctx.send(:get_current_model_version, rm)
|
468
|
+
}.to raise_error /not registered with ModelSpace/
|
469
|
+
|
470
|
+
expect {
|
471
|
+
ctx.send(:get_working_model_version, rm)
|
472
|
+
}.to raise_error /not registered with ModelSpace/
|
473
|
+
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
end
|
478
|
+
end
|
479
|
+
end
|