activerecord-model-spaces 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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