1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
|
require_relative "spec_helper"
describe "optimistic_locking plugin" do
before do
@c = Class.new(Sequel::Model(:people)) do
end
h = {1=>{:id=>1, :name=>'John', :lock_version=>2}}
lv = @lv = "lock_version".dup
@c.dataset = @c.dataset.with_numrows(proc do |sql|
case sql
when /UPDATE people SET (name|#{lv}) = ('Jim'|'Bob'|\d+), (?:name|#{lv}) = ('Jim'|'Bob'|\d+) WHERE \(\(id = (\d+)\) AND \(#{lv} = (\d+)\)\)/
name, nlv = $1 == 'name' ? [$2, $3] : [$3, $2]
m = h[$4.to_i]
if m && m[:lock_version] == $5.to_i
m[:name] = name.delete("'")
m[:lock_version] = nlv.to_i
1
else
0
end
when /UPDATE people SET #{lv} = (\d+) WHERE \(\(id = (\d+)\) AND \(#{lv} = (\d+)\)\)/
m = h[$2.to_i]
if m && m[:lock_version] == $3.to_i
m[:lock_version] = $1.to_i
1
else
0
end
when /DELETE FROM people WHERE \(\(id = (\d+)\) AND \(#{lv} = (\d+)\)\)/
m = h[$1.to_i]
if m && m[lv.to_sym] == $2.to_i
h.delete[$1.to_i]
1
else
0
end
else
puts sql
end
end).with_fetch(proc do |sql|
m = h[1].dup
v = m.delete(:lock_version)
m[lv.to_sym] = v
m
end)
@c.columns :id, :name, :lock_version
@c.plugin :optimistic_locking
end
it "should raise an error when updating a stale record" do
p1 = @c[1]
p2 = @c[1]
p1.update(:name=>'Jim')
proc{p2.update(:name=>'Bob')}.must_raise(Sequel::Plugins::OptimisticLocking::Error)
end
it "should raise an error when destroying a stale record" do
p1 = @c[1]
p2 = @c[1]
p1.update(:name=>'Jim')
proc{p2.destroy}.must_raise(Sequel::Plugins::OptimisticLocking::Error)
end
it "should not raise an error when updating the same record twice" do
p1 = @c[1]
p1.update(:name=>'Jim')
p1.update(:name=>'Bob')
end
it "should allow changing the lock column via model.lock_column=" do
@lv.replace('lv')
@c.columns :id, :name, :lv
@c.lock_column = :lv
p1 = @c[1]
p2 = @c[1]
p1.update(:name=>'Jim')
proc{p2.update(:name=>'Bob')}.must_raise(Sequel::Plugins::OptimisticLocking::Error)
end
it "should allow changing the lock column via plugin option" do
@lv.replace('lv')
@c.columns :id, :name, :lv
@c.plugin :optimistic_locking, :lock_column=>:lv
p1 = @c[1]
p2 = @c[1]
p1.update(:name=>'Jim')
proc{p2.destroy}.must_raise(Sequel::Plugins::OptimisticLocking::Error)
end
it "should work when subclassing" do
c = Class.new(@c)
p1 = c[1]
p2 = c[1]
p1.update(:name=>'Jim')
proc{p2.update(:name=>'Bob')}.must_raise(Sequel::Plugins::OptimisticLocking::Error)
end
it "should work correctly if attempting to refresh and save again after a failed save" do
p1 = @c[1]
p2 = @c[1]
p1.update(:name=>'Jim')
begin
p2.update(:name=>'Bob')
rescue Sequel::Plugins::OptimisticLocking::Error
p2.refresh
@c.db.sqls
p2.update(:name=>'Bob')
end
@c.db.sqls.must_equal ["UPDATE people SET name = 'Bob', lock_version = 4 WHERE ((id = 1) AND (lock_version = 3))"]
end
it "should increment the lock column when #modified! even if no columns are changed" do
p1 = @c[1]
p1.modified!
lv = p1.lock_version
p1.save_changes
p1.lock_version.must_equal lv + 1
end
it "should not increment the lock column when the update fails" do
@c.dataset = @c.dataset.with_extend{def update(_) raise end}
p1 = @c[1]
p1.modified!
lv = p1.lock_version
proc{p1.save_changes}.must_raise(RuntimeError)
p1.lock_version.must_equal lv
end
it "should not marked the lock column changed after saving" do
p1 = @c[1]
p1.modified!
p1.save_changes
p1.changed_columns.must_be_empty
end
end
|