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 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
|
require_relative "spec_helper"
describe Sequel::Model, "single table inheritance plugin" do
before do
class ::StiTest < Sequel::Model
columns :id, :kind, :blah
plugin :single_table_inheritance, :kind
end
class ::StiTestSub1 < StiTest
end
class ::StiTestSub2 < StiTest
end
@ds = StiTest.dataset
DB.reset
end
after do
Object.send(:remove_const, :StiTestSub1)
Object.send(:remove_const, :StiTestSub2)
Object.send(:remove_const, :StiTest)
end
describe ".sti_load" do
it "should load instances of the correct type" do
StiTest.sti_load(:id => 3).must_be_instance_of StiTest
StiTest.sti_load(:id => 3, :kind => 'StiTestSub1').must_be_instance_of StiTestSub1
StiTest.sti_load(:id => 3, :kind => 'StiTestSub2').must_be_instance_of StiTestSub2
end
end
describe ".sti_class_from_sti_key" do
it "should load the correct subclass based on the key" do
StiTest.sti_class_from_sti_key('StiTest').must_equal StiTest
StiTest.sti_class_from_sti_key('StiTestSub1').must_equal StiTestSub1
StiTest.sti_class_from_sti_key('StiTestSub2').must_equal StiTestSub2
end
end
it "should freeze sti metadata when freezing model class" do
StiTest.freeze
StiTest.sti_dataset.frozen?.must_equal true
StiTestSub1.freeze
StiTestSub1.sti_key_array.frozen?.must_equal true
proc{class ::StiTestSub1Sub1 < StiTestSub1; end}.must_raise RuntimeError, TypeError
end
it "should have simple_table = nil" do
StiTest.simple_table.must_equal "sti_tests"
StiTestSub1.simple_table.must_be_nil
end
it "should not attempt to use prepared statements" do
StiTestSub1.plugin :prepared_statements
StiTestSub1.load(:id=>1, :kind=>'StiTestSub1').save
DB.sqls.must_equal ["UPDATE sti_tests SET kind = 'StiTestSub1' WHERE ((sti_tests.kind IN ('StiTestSub1')) AND (id = 1))"]
StiTest.plugin :prepared_statements
StiTest.load(:id=>2, :kind=>'StiTest').save
DB.sqls.must_equal ["UPDATE sti_tests SET kind = 'StiTest' WHERE (id = 2)"]
end
it "should allow changing the inheritance column via a plugin :single_table_inheritance call" do
StiTest.plugin :single_table_inheritance, :blah
Object.send(:remove_const, :StiTestSub1)
Object.send(:remove_const, :StiTestSub2)
class ::StiTestSub1 < StiTest; end
class ::StiTestSub2 < StiTest; end
StiTest.dataset = StiTest.dataset.with_fetch([{:blah=>'StiTest'}, {:blah=>'StiTestSub1'}, {:blah=>'StiTestSub2'}])
StiTest.all.collect{|x| x.class}.must_equal [StiTest, StiTestSub1, StiTestSub2]
StiTest.dataset.sql.must_equal "SELECT * FROM sti_tests"
StiTestSub1.dataset.sql.must_equal "SELECT * FROM sti_tests WHERE (sti_tests.blah IN ('StiTestSub1'))"
StiTestSub2.dataset.sql.must_equal "SELECT * FROM sti_tests WHERE (sti_tests.blah IN ('StiTestSub2'))"
end
it "should return rows with the correct class based on the polymorphic_key value" do
StiTest.dataset = StiTest.dataset.with_fetch([{:kind=>'StiTest'}, {:kind=>'StiTestSub1'}, {:kind=>'StiTestSub2'}])
StiTest.all.collect{|x| x.class}.must_equal [StiTest, StiTestSub1, StiTestSub2]
end
it "should return rows with the correct class based on the polymorphic_key value when retreiving by primary key" do
StiTest.dataset = StiTest.dataset.with_fetch([{:kind=>'StiTestSub1'}])
StiTest[1].class.must_equal StiTestSub1
end
it "should return rows with the correct class for subclasses based on the polymorphic_key value" do
class ::StiTestSub1Sub < StiTestSub1; end
StiTestSub1.dataset = StiTestSub1.dataset.with_fetch([{:kind=>'StiTestSub1'}, {:kind=>'StiTestSub1Sub'}])
StiTestSub1.all.collect{|x| x.class}.must_equal [StiTestSub1, StiTestSub1Sub]
end
it "should fallback to the main class if the given class does not exist" do
StiTest.dataset = StiTest.dataset.with_fetch(:kind=>'StiTestSub3')
StiTest.all.collect{|x| x.class}.must_equal [StiTest]
end
it "should inherit dataset_modules correctly in subclass" do
StiTest.dataset_module{def foo; 1; end}
Object.send(:remove_const, :StiTestSub1)
Object.send(:remove_const, :StiTestSub2)
class ::StiTestSub1 < StiTest; end
StiTestSub1.dataset_module{def bar; 2; end}
class ::StiTestSub2 < StiTestSub1; end
StiTestSub2.dataset_module{def baz; 3; end}
StiTest.dataset.foo.must_equal 1
proc{StiTest.dataset.bar}.must_raise NoMethodError
proc{StiTest.dataset.baz}.must_raise NoMethodError
StiTestSub1.dataset.foo.must_equal 1
StiTestSub1.dataset.bar.must_equal 2
proc{StiTestSub1.dataset.baz}.must_raise NoMethodError
StiTestSub2.dataset.foo.must_equal 1
StiTestSub2.dataset.bar.must_equal 2
StiTestSub2.dataset.baz.must_equal 3
end
it "should fallback to the main class if the sti_key field is empty or nil without calling constantize" do
called = false
StiTest.define_singleton_method(:constantize){|_| called = true}
StiTest.singleton_class.send(:private, :constantize)
StiTest.plugin :single_table_inheritance, :kind
StiTest.dataset = StiTest.dataset.with_fetch([{:kind=>''}, {:kind=>nil}])
StiTest.all.collect{|x| x.class}.must_equal [StiTest, StiTest]
called.must_equal false
end
it "should set the model class name when saving" do
StiTest.new.save
StiTestSub1.new.save
StiTestSub2.new.save
DB.sqls.must_equal ["INSERT INTO sti_tests (kind) VALUES ('StiTest')", "SELECT * FROM sti_tests WHERE id = 10", "INSERT INTO sti_tests (kind) VALUES ('StiTestSub1')", "SELECT * FROM sti_tests WHERE ((sti_tests.kind IN ('StiTestSub1')) AND (id = 10)) LIMIT 1", "INSERT INTO sti_tests (kind) VALUES ('StiTestSub2')", "SELECT * FROM sti_tests WHERE ((sti_tests.kind IN ('StiTestSub2')) AND (id = 10)) LIMIT 1"]
end
it "should destroy the model correctly" do
StiTest.load(:id=>1).destroy
StiTestSub1.load(:id=>1).destroy
StiTestSub2.load(:id=>1).destroy
DB.sqls.must_equal ["DELETE FROM sti_tests WHERE id = 1", "DELETE FROM sti_tests WHERE ((sti_tests.kind IN ('StiTestSub1')) AND (id = 1))", "DELETE FROM sti_tests WHERE ((sti_tests.kind IN ('StiTestSub2')) AND (id = 1))"]
end
it "should handle validations on the type column field" do
o = StiTestSub1.new
def o.validate
errors.add(:kind, 'not present') unless kind
end
o.valid?.must_equal true
end
it "should set type column field even if validations are skipped" do
StiTestSub1.new.save(:validate=>false)
DB.sqls.must_equal ["INSERT INTO sti_tests (kind) VALUES ('StiTestSub1')", "SELECT * FROM sti_tests WHERE ((sti_tests.kind IN ('StiTestSub1')) AND (id = 10)) LIMIT 1"]
end
it "should override an existing value in the class name field" do
StiTest.create(:kind=>'StiTestSub1')
DB.sqls.must_equal ["INSERT INTO sti_tests (kind) VALUES ('StiTestSub1')", "SELECT * FROM sti_tests WHERE id = 10"]
end
it "should handle type column with the same name as existing method names" do
StiTest.plugin :single_table_inheritance, :type
StiTest.columns :id, :type
StiTest.create
DB.sqls.must_equal ["INSERT INTO sti_tests (type) VALUES ('StiTest')", "SELECT * FROM sti_tests WHERE id = 10"]
end
it "should add a filter to model datasets inside subclasses hook to only retreive objects with the matching key" do
StiTest.dataset.sql.must_equal "SELECT * FROM sti_tests"
StiTestSub1.dataset.sql.must_equal "SELECT * FROM sti_tests WHERE (sti_tests.kind IN ('StiTestSub1'))"
StiTestSub2.dataset.sql.must_equal "SELECT * FROM sti_tests WHERE (sti_tests.kind IN ('StiTestSub2'))"
end
it "should add a correct filter for multiple levels of subclasses" do
class ::StiTestSub1A < StiTestSub1; end
StiTestSub1.dataset.sql.must_equal "SELECT * FROM sti_tests WHERE (sti_tests.kind IN ('StiTestSub1', 'StiTestSub1A'))"
StiTestSub1A.dataset.sql.must_equal "SELECT * FROM sti_tests WHERE (sti_tests.kind IN ('StiTestSub1A'))"
class ::StiTestSub2A < StiTestSub2; end
StiTestSub2.dataset.sql.must_equal "SELECT * FROM sti_tests WHERE (sti_tests.kind IN ('StiTestSub2', 'StiTestSub2A'))"
StiTestSub2A.dataset.sql.must_equal "SELECT * FROM sti_tests WHERE (sti_tests.kind IN ('StiTestSub2A'))"
class ::StiTestSub1B < StiTestSub1A; end
StiTestSub1.dataset.sql.must_equal "SELECT * FROM sti_tests WHERE (sti_tests.kind IN ('StiTestSub1', 'StiTestSub1A', 'StiTestSub1B'))"
StiTestSub1A.dataset.sql.must_equal "SELECT * FROM sti_tests WHERE (sti_tests.kind IN ('StiTestSub1A', 'StiTestSub1B'))"
StiTestSub1B.dataset.sql.must_equal "SELECT * FROM sti_tests WHERE (sti_tests.kind IN ('StiTestSub1B'))"
end
it "should work correctly with the :caching plugin" do
cache_class = Class.new(Hash) do
attr_accessor :ttl
def set(k, v, ttl); self[k] = v; @ttl = ttl; end
def get(k); self[k]; end
end
cache = cache_class.new
StiTest.plugin :caching, cache
def StiTest.cache_key_prefix; "stitest" end
c2 = Class.new StiTest
c2.cache_key(:id).must_equal StiTest.cache_key(:id)
obj2 = c2.new
obj2.values[:x] = 2
obj2.save
c2[obj2.id]
c2.cache_get_pk(obj2.id).values.must_equal StiTest.cache_get_pk(obj2.id).values
obj2.save
c2.cache_get_pk(obj2.id).must_be_nil
StiTest.cache_get_pk(obj2.id).must_be_nil
end
describe "with custom options" do
before do
class ::StiTest2 < Sequel::Model
columns :id, :kind
private
def _save_refresh; end
end
end
after do
Object.send(:remove_const, :StiTest2)
Object.send(:remove_const, :StiTest3) if defined?(StiTest3)
Object.send(:remove_const, :StiTest4) if defined?(StiTest4)
end
it "should freeze sti key and model map if given as hashes when freezing model class" do
StiTest2.plugin :single_table_inheritance, :kind, :model_map=>{0=>StiTest2, 1=>:StiTest3, 2=>'StiTest4'}, :key_map=>{StiTest2=>4, 'StiTest3'=>5, 'StiTest4'=>6}
StiTest2.freeze
StiTest2.sti_key_map.frozen?.must_equal true
StiTest2.sti_model_map.frozen?.must_equal true
end
it "should have working row_proc if using set_dataset in subclass to remove columns" do
StiTest2.plugin :single_table_inheritance, :kind
class ::StiTest3 < ::StiTest2
set_dataset(dataset.select(*(columns - [:blah])))
end
class ::StiTest4 < ::StiTest3; end
StiTest3.dataset = StiTest3.dataset.with_fetch(:id=>1, :kind=>'StiTest4')
StiTest3[1].must_equal StiTest4.load(:id=>1, :kind=>'StiTest4')
end
it "should work with custom procs with strings" do
StiTest2.plugin :single_table_inheritance, :kind, :model_map=>proc{|v| v == 1 ? 'StiTest3' : 'StiTest4'}, :key_map=>proc{|klass| klass.name == 'StiTest3' ? 1 : 2}
class ::StiTest3 < ::StiTest2; end
class ::StiTest4 < ::StiTest2; end
StiTest2.dataset.row_proc.call(:kind=>0).must_be_instance_of(StiTest4)
StiTest2.dataset.row_proc.call(:kind=>1).must_be_instance_of(StiTest3)
StiTest2.dataset.row_proc.call(:kind=>2).must_be_instance_of(StiTest4)
StiTest2.create.kind.must_equal 2
StiTest3.create.kind.must_equal 1
StiTest4.create.kind.must_equal 2
end
it "should work with custom procs with symbols" do
StiTest2.plugin :single_table_inheritance, :kind, :model_map=>proc{|v| v == 1 ? :StiTest3 : :StiTest4}, :key_map=>proc{|klass| klass.name == 'StiTest3' ? 1 : 2}
class ::StiTest3 < ::StiTest2; end
class ::StiTest4 < ::StiTest2; end
StiTest2.dataset.row_proc.call(:kind=>0).must_be_instance_of(StiTest4)
StiTest2.dataset.row_proc.call(:kind=>1).must_be_instance_of(StiTest3)
StiTest2.dataset.row_proc.call(:kind=>2).must_be_instance_of(StiTest4)
StiTest2.create.kind.must_equal 2
StiTest3.create.kind.must_equal 1
StiTest4.create.kind.must_equal 2
end
it "should work with custom hashes" do
StiTest2.plugin :single_table_inheritance, :kind, :model_map=>{0=>StiTest2, 1=>:StiTest3, 2=>'StiTest4'}, :key_map=>{'StiTest2'=>7, StiTest2=>4, 'StiTest3'=>5, 'StiTest4'=>6}
class ::StiTest3 < ::StiTest2; end
class ::StiTest4 < ::StiTest2; end
StiTest2.dataset.row_proc.call(:kind=>0).must_be_instance_of(StiTest2)
StiTest2.dataset.row_proc.call(:kind=>1).must_be_instance_of(StiTest3)
StiTest2.dataset.row_proc.call(:kind=>2).must_be_instance_of(StiTest4)
StiTest3.sti_model_map.must_equal StiTest2.sti_model_map
StiTest2.create.kind.must_equal 4
StiTest3.create.kind.must_equal 5
StiTest4.create.kind.must_equal 6
class ::StiTest5 < ::StiTest4; end
StiTest5.create.kind.must_be_nil
end
it "should infer key_map from model_map if provided as a hash" do
StiTest2.plugin :single_table_inheritance, :kind, :model_map=>{0=>StiTest2, 1=>'StiTest3', 2=>:StiTest4}
class ::StiTest3 < ::StiTest2; end
class ::StiTest4 < ::StiTest2; end
StiTest2.dataset.row_proc.call(:kind=>0).must_be_instance_of(StiTest2)
StiTest2.dataset.row_proc.call(:kind=>1).must_be_instance_of(StiTest3)
StiTest2.dataset.row_proc.call(:kind=>2).must_be_instance_of(StiTest4)
StiTest2.create.kind.must_equal 0
StiTest3.create.kind.must_equal 1
StiTest4.create.kind.must_equal 2
end
it "should raise exceptions if a bad model value is used" do
StiTest2.plugin :single_table_inheritance, :kind, :model_map=>{0=>1,1=>1.5, 2=>Date.today}
class ::StiTest3 < ::StiTest2; end
class ::StiTest4 < ::StiTest2; end
proc{StiTest2.dataset.row_proc.call(:kind=>0)}.must_raise(Sequel::Error)
proc{StiTest2.dataset.row_proc.call(:kind=>1)}.must_raise(Sequel::Error)
proc{StiTest2.dataset.row_proc.call(:kind=>2)}.must_raise(Sequel::Error)
end
it "should work with non-bijective mappings" do
StiTest2.plugin :single_table_inheritance, :kind, :model_map=>{0=>'StiTest3', 1=>'StiTest3', 2=>'StiTest4'}
class ::StiTest3 < ::StiTest2; end
class ::StiTest4 < ::StiTest2; end
StiTest2.dataset.row_proc.call(:kind=>0).must_be_instance_of(StiTest3)
StiTest2.dataset.row_proc.call(:kind=>1).must_be_instance_of(StiTest3)
StiTest2.dataset.row_proc.call(:kind=>2).must_be_instance_of(StiTest4)
[0,1].must_include(StiTest3.create.kind)
StiTest4.create.kind.must_equal 2
end
it "should work with non-bijective mappings and key map procs" do
StiTest2.plugin :single_table_inheritance, :kind,
:key_map=>proc{|model| model.to_s == 'StiTest4' ? 2 : [0,1] }
class ::StiTest3 < ::StiTest2; end
class ::StiTest4 < ::StiTest2; end
StiTest2.dataset.sql.must_equal "SELECT * FROM sti_test2s"
StiTest3.dataset.sql.must_equal "SELECT * FROM sti_test2s WHERE (sti_test2s.kind IN (0, 1))"
StiTest4.dataset.sql.must_equal "SELECT * FROM sti_test2s WHERE (sti_test2s.kind IN (2))"
end
it "should create correct sql with non-bijective mappings" do
StiTest2.plugin :single_table_inheritance, :kind, :model_map=>{0=>'StiTest3', 1=>'StiTest3', 2=>'StiTest4'}
class ::StiTest3 < ::StiTest2; end
class ::StiTest4 < ::StiTest2; end
StiTest2.dataset.sql.must_equal "SELECT * FROM sti_test2s"
["SELECT * FROM sti_test2s WHERE (sti_test2s.kind IN (0, 1))",
"SELECT * FROM sti_test2s WHERE (sti_test2s.kind IN (1, 0))"].must_include(StiTest3.dataset.sql)
end
it "should destroy the model correctly" do
StiTest2.plugin :single_table_inheritance, :kind, :model_map=>{'sti3'=>'StiTest3', 'sti3b'=>'StiTest3', 'sti4'=>'StiTest4'}
class ::StiTest3 < ::StiTest2; end
class ::StiTest4 < ::StiTest2; end
StiTest2.load(:id=>1).destroy
StiTest3.load(:id=>1).destroy
sqls = DB.sqls
sqls.shift.must_equal "DELETE FROM sti_test2s WHERE id = 1"
["DELETE FROM sti_test2s WHERE ((sti_test2s.kind IN ('sti3', 'sti3b')) AND (id = 1))",
"DELETE FROM sti_test2s WHERE ((sti_test2s.kind IN ('sti3b', 'sti3')) AND (id = 1))"].must_include(sqls.pop)
sqls.must_equal []
end
it "should honor a :key_chooser" do
StiTest2.plugin :single_table_inheritance, :kind, :key_chooser => proc{|inst| inst.model.to_s.downcase }
class ::StiTest3 < ::StiTest2; end
class ::StiTest4 < ::StiTest2; end
StiTest3.create.kind.must_equal 'stitest3'
StiTest4.create.kind.must_equal 'stitest4'
end
end
end
|