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.
@@ -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