sequel 4.45.0 → 4.46.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (173) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +108 -0
  3. data/doc/release_notes/4.46.0.txt +404 -0
  4. data/doc/security.rdoc +9 -0
  5. data/doc/sql.rdoc +2 -2
  6. data/doc/testing.rdoc +1 -1
  7. data/doc/validations.rdoc +1 -2
  8. data/lib/sequel/adapters/ado.rb +8 -3
  9. data/lib/sequel/adapters/ado/access.rb +8 -4
  10. data/lib/sequel/adapters/ado/mssql.rb +3 -1
  11. data/lib/sequel/adapters/amalgalite.rb +5 -0
  12. data/lib/sequel/adapters/cubrid.rb +16 -7
  13. data/lib/sequel/adapters/do.rb +7 -1
  14. data/lib/sequel/adapters/do/mysql.rb +8 -4
  15. data/lib/sequel/adapters/ibmdb.rb +10 -5
  16. data/lib/sequel/adapters/jdbc.rb +8 -2
  17. data/lib/sequel/adapters/jdbc/as400.rb +10 -3
  18. data/lib/sequel/adapters/jdbc/db2.rb +27 -16
  19. data/lib/sequel/adapters/jdbc/derby.rb +47 -20
  20. data/lib/sequel/adapters/jdbc/h2.rb +13 -7
  21. data/lib/sequel/adapters/jdbc/hsqldb.rb +18 -9
  22. data/lib/sequel/adapters/jdbc/mssql.rb +5 -2
  23. data/lib/sequel/adapters/jdbc/mysql.rb +3 -2
  24. data/lib/sequel/adapters/jdbc/oracle.rb +3 -2
  25. data/lib/sequel/adapters/jdbc/postgresql.rb +4 -3
  26. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +2 -1
  27. data/lib/sequel/adapters/jdbc/sqlite.rb +10 -3
  28. data/lib/sequel/adapters/jdbc/sqlserver.rb +23 -0
  29. data/lib/sequel/adapters/jdbc/transactions.rb +16 -10
  30. data/lib/sequel/adapters/mock.rb +5 -0
  31. data/lib/sequel/adapters/mysql.rb +8 -1
  32. data/lib/sequel/adapters/mysql2.rb +6 -1
  33. data/lib/sequel/adapters/odbc.rb +20 -8
  34. data/lib/sequel/adapters/odbc/mssql.rb +6 -3
  35. data/lib/sequel/adapters/oracle.rb +12 -6
  36. data/lib/sequel/adapters/postgres.rb +20 -8
  37. data/lib/sequel/adapters/shared/access.rb +76 -47
  38. data/lib/sequel/adapters/shared/cubrid.rb +16 -11
  39. data/lib/sequel/adapters/shared/db2.rb +46 -19
  40. data/lib/sequel/adapters/shared/firebird.rb +20 -8
  41. data/lib/sequel/adapters/shared/informix.rb +6 -3
  42. data/lib/sequel/adapters/shared/mssql.rb +132 -72
  43. data/lib/sequel/adapters/shared/mysql.rb +112 -65
  44. data/lib/sequel/adapters/shared/oracle.rb +36 -21
  45. data/lib/sequel/adapters/shared/postgres.rb +91 -56
  46. data/lib/sequel/adapters/shared/sqlanywhere.rb +65 -37
  47. data/lib/sequel/adapters/shared/sqlite.rb +67 -32
  48. data/lib/sequel/adapters/sqlanywhere.rb +9 -1
  49. data/lib/sequel/adapters/sqlite.rb +8 -1
  50. data/lib/sequel/adapters/swift.rb +5 -0
  51. data/lib/sequel/adapters/swift/mysql.rb +4 -2
  52. data/lib/sequel/adapters/swift/sqlite.rb +1 -1
  53. data/lib/sequel/adapters/tinytds.rb +10 -3
  54. data/lib/sequel/adapters/utils/emulate_offset_with_reverse_and_count.rb +1 -1
  55. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +1 -1
  56. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -0
  57. data/lib/sequel/adapters/utils/pg_types.rb +14 -6
  58. data/lib/sequel/adapters/utils/replace.rb +4 -2
  59. data/lib/sequel/connection_pool/single.rb +2 -2
  60. data/lib/sequel/core.rb +24 -11
  61. data/lib/sequel/database/connecting.rb +9 -3
  62. data/lib/sequel/database/dataset_defaults.rb +7 -1
  63. data/lib/sequel/database/logging.rb +1 -0
  64. data/lib/sequel/database/misc.rb +5 -2
  65. data/lib/sequel/database/query.rb +7 -5
  66. data/lib/sequel/database/schema_generator.rb +1 -0
  67. data/lib/sequel/database/schema_methods.rb +50 -27
  68. data/lib/sequel/database/transactions.rb +19 -9
  69. data/lib/sequel/dataset/actions.rb +15 -6
  70. data/lib/sequel/dataset/graph.rb +15 -5
  71. data/lib/sequel/dataset/misc.rb +12 -4
  72. data/lib/sequel/dataset/mutation.rb +17 -8
  73. data/lib/sequel/dataset/prepared_statements.rb +3 -2
  74. data/lib/sequel/dataset/query.rb +84 -38
  75. data/lib/sequel/dataset/sql.rb +302 -191
  76. data/lib/sequel/deprecated.rb +26 -17
  77. data/lib/sequel/extensions/_deprecated_identifier_mangling.rb +2 -2
  78. data/lib/sequel/extensions/auto_literal_strings.rb +74 -0
  79. data/lib/sequel/extensions/from_block.rb +1 -0
  80. data/lib/sequel/extensions/graph_each.rb +1 -1
  81. data/lib/sequel/extensions/identifier_mangling.rb +2 -2
  82. data/lib/sequel/extensions/migration.rb +28 -4
  83. data/lib/sequel/extensions/no_auto_literal_strings.rb +2 -0
  84. data/lib/sequel/extensions/schema_dumper.rb +4 -4
  85. data/lib/sequel/extensions/sequel_3_dataset_methods.rb +5 -3
  86. data/lib/sequel/extensions/set_overrides.rb +2 -0
  87. data/lib/sequel/extensions/split_array_nil.rb +2 -2
  88. data/lib/sequel/extensions/virtual_row_method_block.rb +44 -0
  89. data/lib/sequel/model.rb +11 -7
  90. data/lib/sequel/model/associations.rb +5 -7
  91. data/lib/sequel/model/base.rb +47 -45
  92. data/lib/sequel/model/dataset_module.rb +9 -14
  93. data/lib/sequel/model/plugins.rb +3 -0
  94. data/lib/sequel/no_core_ext.rb +1 -0
  95. data/lib/sequel/plugins/blacklist_security.rb +1 -1
  96. data/lib/sequel/plugins/boolean_subsets.rb +7 -5
  97. data/lib/sequel/plugins/class_table_inheritance.rb +47 -10
  98. data/lib/sequel/plugins/dataset_associations.rb +1 -1
  99. data/lib/sequel/plugins/def_dataset_method.rb +90 -0
  100. data/lib/sequel/plugins/finder.rb +240 -0
  101. data/lib/sequel/plugins/inverted_subsets.rb +19 -12
  102. data/lib/sequel/plugins/many_through_many.rb +1 -1
  103. data/lib/sequel/plugins/nested_attributes.rb +1 -1
  104. data/lib/sequel/plugins/schema.rb +1 -1
  105. data/lib/sequel/plugins/single_table_inheritance.rb +7 -1
  106. data/lib/sequel/plugins/subset_conditions.rb +11 -3
  107. data/lib/sequel/plugins/whitelist_security.rb +118 -0
  108. data/lib/sequel/sql.rb +80 -36
  109. data/lib/sequel/timezones.rb +2 -0
  110. data/lib/sequel/version.rb +1 -1
  111. data/spec/adapters/mssql_spec.rb +20 -0
  112. data/spec/adapters/mysql_spec.rb +1 -1
  113. data/spec/adapters/oracle_spec.rb +12 -8
  114. data/spec/adapters/postgres_spec.rb +1 -1
  115. data/spec/adapters/spec_helper.rb +1 -1
  116. data/spec/adapters/sqlite_spec.rb +36 -34
  117. data/spec/core/connection_pool_spec.rb +2 -1
  118. data/spec/core/database_spec.rb +87 -9
  119. data/spec/core/dataset_spec.rb +501 -129
  120. data/spec/core/deprecated_spec.rb +1 -1
  121. data/spec/core/expression_filters_spec.rb +146 -60
  122. data/spec/core/mock_adapter_spec.rb +1 -1
  123. data/spec/core/object_graph_spec.rb +61 -9
  124. data/spec/core/placeholder_literalizer_spec.rb +20 -2
  125. data/spec/core/schema_generator_spec.rb +6 -6
  126. data/spec/core/schema_spec.rb +54 -5
  127. data/spec/core_extensions_spec.rb +122 -18
  128. data/spec/deprecation_helper.rb +27 -2
  129. data/spec/extensions/_deprecated_identifier_mangling_spec.rb +6 -6
  130. data/spec/extensions/association_proxies_spec.rb +2 -2
  131. data/spec/extensions/auto_literal_strings_spec.rb +212 -0
  132. data/spec/extensions/blacklist_security_spec.rb +1 -0
  133. data/spec/extensions/class_table_inheritance_spec.rb +1037 -39
  134. data/spec/extensions/column_select_spec.rb +20 -8
  135. data/spec/extensions/columns_introspection_spec.rb +3 -3
  136. data/spec/extensions/core_refinements_spec.rb +29 -12
  137. data/spec/extensions/dataset_associations_spec.rb +12 -12
  138. data/spec/extensions/def_dataset_method_spec.rb +100 -0
  139. data/spec/extensions/error_sql_spec.rb +1 -1
  140. data/spec/extensions/finder_spec.rb +260 -0
  141. data/spec/extensions/graph_each_spec.rb +2 -2
  142. data/spec/extensions/identifier_mangling_spec.rb +14 -8
  143. data/spec/extensions/inverted_subsets_spec.rb +4 -4
  144. data/spec/extensions/lazy_attributes_spec.rb +7 -0
  145. data/spec/extensions/many_through_many_spec.rb +38 -14
  146. data/spec/extensions/nested_attributes_spec.rb +18 -6
  147. data/spec/extensions/no_auto_literal_strings_spec.rb +1 -1
  148. data/spec/extensions/pg_enum_spec.rb +16 -1
  149. data/spec/extensions/pg_interval_spec.rb +11 -2
  150. data/spec/extensions/pg_loose_count_spec.rb +5 -0
  151. data/spec/extensions/pg_row_spec.rb +25 -0
  152. data/spec/extensions/prepared_statements_spec.rb +10 -1
  153. data/spec/extensions/query_spec.rb +2 -2
  154. data/spec/extensions/schema_dumper_spec.rb +2 -2
  155. data/spec/extensions/schema_spec.rb +2 -2
  156. data/spec/extensions/set_overrides_spec.rb +7 -3
  157. data/spec/extensions/sql_expr_spec.rb +0 -1
  158. data/spec/extensions/subset_conditions_spec.rb +6 -6
  159. data/spec/extensions/table_select_spec.rb +24 -12
  160. data/spec/extensions/to_dot_spec.rb +4 -4
  161. data/spec/extensions/whitelist_security_spec.rb +131 -0
  162. data/spec/integration/dataset_test.rb +9 -5
  163. data/spec/integration/model_test.rb +2 -0
  164. data/spec/integration/plugin_test.rb +2 -2
  165. data/spec/integration/spec_helper.rb +1 -1
  166. data/spec/model/associations_spec.rb +39 -11
  167. data/spec/model/base_spec.rb +44 -24
  168. data/spec/model/class_dataset_methods_spec.rb +18 -16
  169. data/spec/model/dataset_methods_spec.rb +4 -4
  170. data/spec/model/eager_loading_spec.rb +84 -24
  171. data/spec/model/model_spec.rb +97 -63
  172. data/spec/model/record_spec.rb +21 -13
  173. metadata +13 -2
@@ -111,9 +111,9 @@ describe "identifier_mangling extension" do
111
111
  deprecated "should respect the quote_indentifiers_default method if Sequel.quote_identifiers = nil" do
112
112
  Sequel.quote_identifiers = nil
113
113
  Sequel::Database.new(:identifier_mangling=>true).quote_identifiers?.must_equal true
114
- x = Class.new(Sequel::Database){def quote_identifiers_default; false end}
114
+ x = Class.new(Sequel::Database){def dataset_class_default; Sequel::Dataset end; def quote_identifiers_default; false end}
115
115
  x.new(:identifier_mangling=>true).quote_identifiers?.must_equal false
116
- y = Class.new(Sequel::Database){def quote_identifiers_default; true end}
116
+ y = Class.new(Sequel::Database){def dataset_class_default; Sequel::Dataset end; def quote_identifiers_default; true end}
117
117
  y.new(:identifier_mangling=>true).quote_identifiers?.must_equal true
118
118
  end
119
119
 
@@ -121,9 +121,9 @@ describe "identifier_mangling extension" do
121
121
  class Sequel::Database
122
122
  @identifier_input_method = nil
123
123
  end
124
- x = Class.new(Sequel::Database){def identifier_input_method_default; :downcase end}
124
+ x = Class.new(Sequel::Database){def dataset_class_default; Sequel::Dataset end; def identifier_input_method_default; :downcase end}
125
125
  x.new(:identifier_mangling=>true).identifier_input_method.must_equal :downcase
126
- y = Class.new(Sequel::Database){def identifier_input_method_default; :camelize end}
126
+ y = Class.new(Sequel::Database){def dataset_class_default; Sequel::Dataset end; def identifier_input_method_default; :camelize end}
127
127
  y.new(:identifier_mangling=>true).identifier_input_method.must_equal :camelize
128
128
  end
129
129
 
@@ -131,9 +131,9 @@ describe "identifier_mangling extension" do
131
131
  class Sequel::Database
132
132
  @identifier_output_method = nil
133
133
  end
134
- x = Class.new(Sequel::Database){def identifier_output_method_default; :upcase end}
134
+ x = Class.new(Sequel::Database){def dataset_class_default; Sequel::Dataset end; def identifier_output_method_default; :upcase end}
135
135
  x.new(:identifier_mangling=>true).identifier_output_method.must_equal :upcase
136
- y = Class.new(Sequel::Database){def identifier_output_method_default; :underscore end}
136
+ y = Class.new(Sequel::Database){def dataset_class_default; Sequel::Dataset end; def identifier_output_method_default; :underscore end}
137
137
  y.new(:identifier_mangling=>true).identifier_output_method.must_equal :underscore
138
138
  end
139
139
  end
@@ -31,11 +31,11 @@ describe "Sequel::Plugins::AssociationProxies" do
31
31
 
32
32
  it "should accept block to plugin to specify which methods to proxy to dataset" do
33
33
  Item.plugin :association_proxies do |opts|
34
- opts[:method] == :where || opts[:arguments].length == 2 || opts[:block]
34
+ opts[:method] == :where || opts[:arguments].first.is_a?(Sequel::LiteralString) || opts[:block]
35
35
  end
36
36
  @i.associations.has_key?(:tags).must_equal false
37
37
  @t.where(:a=>1).sql.must_equal "SELECT tags.* FROM tags INNER JOIN items_tags ON (items_tags.tag_id = tags.id) WHERE ((items_tags.item_id = 1) AND (a = 1))"
38
- @t.filter('a = ?', 1).sql.must_equal "SELECT tags.* FROM tags INNER JOIN items_tags ON (items_tags.tag_id = tags.id) WHERE ((items_tags.item_id = 1) AND (a = 1))"
38
+ @t.filter(Sequel.lit('a = 1')).sql.must_equal "SELECT tags.* FROM tags INNER JOIN items_tags ON (items_tags.tag_id = tags.id) WHERE ((items_tags.item_id = 1) AND (a = 1))"
39
39
  @t.filter{{:a=>1}}.sql.must_equal "SELECT tags.* FROM tags INNER JOIN items_tags ON (items_tags.tag_id = tags.id) WHERE ((items_tags.item_id = 1) AND (a = 1))"
40
40
 
41
41
  @i.associations.has_key?(:tags).must_equal false
@@ -0,0 +1,212 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ describe "Dataset#where" do
4
+ before do
5
+ @dataset = Sequel.mock[:test].extension(:auto_literal_strings)
6
+ end
7
+
8
+ it "should work with a string with placeholders and arguments for those placeholders" do
9
+ @dataset.where('price < ? AND id in ?', 100, [1, 2, 3]).select_sql.must_equal "SELECT * FROM test WHERE (price < 100 AND id in (1, 2, 3))"
10
+ end
11
+
12
+ it "should not modify passed array with placeholders" do
13
+ a = ['price < ? AND id in ?', 100, 1, 2, 3]
14
+ b = a.dup
15
+ @dataset.where(a)
16
+ b.must_equal a
17
+ end
18
+
19
+ it "should work with strings (custom SQL expressions)" do
20
+ @dataset.where('(a = 1 AND b = 2)').select_sql.must_equal "SELECT * FROM test WHERE ((a = 1 AND b = 2))"
21
+ end
22
+
23
+ it "should work with a string with named placeholders and a hash of placeholder value arguments" do
24
+ @dataset.where('price < :price AND id in :ids', :price=>100, :ids=>[1, 2, 3]).select_sql.must_equal "SELECT * FROM test WHERE (price < 100 AND id in (1, 2, 3))"
25
+ end
26
+
27
+ it "should not modify passed array with named placeholders" do
28
+ a = ['price < :price AND id in :ids', {:price=>100}]
29
+ b = a.dup
30
+ @dataset.where(a)
31
+ b.must_equal a
32
+ end
33
+
34
+ it "should not replace named placeholders that don't exist in the hash" do
35
+ @dataset.where('price < :price AND id in :ids', :price=>100).select_sql.must_equal "SELECT * FROM test WHERE (price < 100 AND id in :ids)"
36
+ end
37
+
38
+ it "should raise an error for a mismatched number of placeholders" do
39
+ proc{@dataset.where('price < ? AND id in ?', 100).select_sql}.must_raise(Sequel::Error)
40
+ proc{@dataset.where('price < ? AND id in ?', 100, [1, 2, 3], 4).select_sql}.must_raise(Sequel::Error)
41
+ end
42
+
43
+ it "should handle partial names" do
44
+ @dataset.where('price < :price AND id = :p', :p=>2, :price=>100).select_sql.must_equal "SELECT * FROM test WHERE (price < 100 AND id = 2)"
45
+ end
46
+
47
+ it "should handle ::cast syntax when no parameters are supplied" do
48
+ @dataset.where('price::float = 10', {}).select_sql.must_equal "SELECT * FROM test WHERE (price::float = 10)"
49
+ @dataset.where('price::float ? 10', {}).select_sql.must_equal "SELECT * FROM test WHERE (price::float ? 10)"
50
+ end
51
+
52
+ it "should affect select, delete and update statements when using strings" do
53
+ @d2 = @dataset.where('region = ?', 'Asia')
54
+ @d2.select_sql.must_equal "SELECT * FROM test WHERE (region = 'Asia')"
55
+ @d2.delete_sql.must_equal "DELETE FROM test WHERE (region = 'Asia')"
56
+ @d2.update_sql(:GDP => 0).must_equal "UPDATE test SET GDP = 0 WHERE (region = 'Asia')"
57
+
58
+ @d3 = @dataset.where("a = 1")
59
+ @d3.select_sql.must_equal "SELECT * FROM test WHERE (a = 1)"
60
+ @d3.delete_sql.must_equal "DELETE FROM test WHERE (a = 1)"
61
+ @d3.update_sql(:GDP => 0).must_equal "UPDATE test SET GDP = 0 WHERE (a = 1)"
62
+ end
63
+
64
+ it "should be composable using AND operator (for scoping) when using strings" do
65
+ @d2 = @dataset.where('region = ?', 'Asia')
66
+ @d2.where('GDP > ?', 1000).select_sql.must_equal "SELECT * FROM test WHERE ((region = 'Asia') AND (GDP > 1000))"
67
+ @d2.where(:name => ['Japan', 'China']).select_sql.must_equal "SELECT * FROM test WHERE ((region = 'Asia') AND (name IN ('Japan', 'China')))"
68
+ @d2.where('GDP > ?').select_sql.must_equal "SELECT * FROM test WHERE ((region = 'Asia') AND (GDP > ?))"
69
+
70
+ @d3 = @dataset.where("a = 1")
71
+ @d3.where('b = 2').select_sql.must_equal "SELECT * FROM test WHERE ((a = 1) AND (b = 2))"
72
+ @d3.where(:c => 3).select_sql.must_equal "SELECT * FROM test WHERE ((a = 1) AND (c = 3))"
73
+ @d3.where('d = ?', 4).select_sql.must_equal "SELECT * FROM test WHERE ((a = 1) AND (d = 4))"
74
+ end
75
+
76
+ it "should be composable using AND operator (for scoping) with block and string" do
77
+ @dataset.where("a = 1").where{e < 5}.select_sql.must_equal "SELECT * FROM test WHERE ((a = 1) AND (e < 5))"
78
+ end
79
+ end
80
+
81
+ describe "Dataset #first and #last" do
82
+ before do
83
+ @d = Sequel.mock(:fetch=>proc{|s| {:s=>s}})[:test].extension(:auto_literal_strings)
84
+ end
85
+
86
+ it "should combine block and standard argument filters if argument is not an Integer" do
87
+ ds = @d.order(:name).freeze
88
+ 5.times do
89
+ @d.first('y = 25'){z > 26}.must_equal(:s=>'SELECT * FROM test WHERE ((y = 25) AND (z > 26)) LIMIT 1')
90
+ ds.last('y = 16'){z > 26}.must_equal(:s=>'SELECT * FROM test WHERE ((y = 16) AND (z > 26)) ORDER BY name DESC LIMIT 1')
91
+ @d.first('y = ?', 25){z > 26}.must_equal(:s=>'SELECT * FROM test WHERE ((y = 25) AND (z > 26)) LIMIT 1')
92
+ ds.last('y = ?', 16){z > 26}.must_equal(:s=>'SELECT * FROM test WHERE ((y = 16) AND (z > 26)) ORDER BY name DESC LIMIT 1')
93
+ end
94
+ end
95
+ end
96
+
97
+ describe "Dataset#exclude" do
98
+ before do
99
+ @dataset = Sequel.mock.dataset.from(:test).extension(:auto_literal_strings)
100
+ end
101
+
102
+ it "should parenthesize a single string condition correctly" do
103
+ @dataset.exclude("region = 'Asia' AND name = 'Japan'").select_sql.must_equal "SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
104
+ end
105
+
106
+ it "should parenthesize an array condition correctly" do
107
+ @dataset.exclude('region = ? AND name = ?', 'Asia', 'Japan').select_sql.must_equal "SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
108
+ end
109
+ end
110
+
111
+ describe "Dataset#and" do
112
+ before do
113
+ @dataset = Sequel.mock.dataset.from(:test).extension(:auto_literal_strings)
114
+ @d1 = @dataset.where(:x => 1)
115
+ end
116
+
117
+ it "should accept string filters with placeholders" do
118
+ @d1.and('y > ?', 2).sql.must_equal 'SELECT * FROM test WHERE ((x = 1) AND (y > 2))'
119
+ end
120
+ end
121
+
122
+ describe "Dataset#or" do
123
+ before do
124
+ @dataset = Sequel.mock.dataset.from(:test).extension(:auto_literal_strings)
125
+ @d1 = @dataset.where(:x => 1)
126
+ end
127
+
128
+ it "should accept string filters" do
129
+ @d1.or('y > ?', 2).sql.must_equal 'SELECT * FROM test WHERE ((x = 1) OR (y > 2))'
130
+ end
131
+ end
132
+
133
+ describe "Dataset#having" do
134
+ before do
135
+ @dataset = Sequel.mock.dataset.from(:test).extension(:auto_literal_strings)
136
+ @grouped = @dataset.group(:region).select(:region, Sequel.function(:sum, :population), Sequel.function(:avg, :gdp))
137
+ end
138
+
139
+ it "should handle string arguments" do
140
+ @grouped.having('sum(population) > 10').select_sql.must_equal "SELECT region, sum(population), avg(gdp) FROM test GROUP BY region HAVING (sum(population) > 10)"
141
+ end
142
+ end
143
+
144
+ describe "Dataset#join_table" do
145
+ before do
146
+ @d = Sequel.mock.dataset.from(:items).with_quote_identifiers(true).extension(:auto_literal_strings)
147
+ end
148
+
149
+ it "should support using a string as the join condition" do
150
+ @d.join(:categories, "c.item_id = items.id", :table_alias=>:c).sql.must_equal 'SELECT * FROM "items" INNER JOIN "categories" AS "c" ON (c.item_id = items.id)'
151
+ end
152
+ end
153
+
154
+ describe "Dataset prepared statements and bound variables " do
155
+ before do
156
+ @db = Sequel.mock
157
+ @ds = @db[:items].with_extend{def insert_select_sql(*v) "#{insert_sql(*v)} RETURNING *" end}.extension(:auto_literal_strings)
158
+ end
159
+
160
+ it "should handle literal strings" do
161
+ @ds.filter("num = ?", :$n).call(:select, :n=>1)
162
+ @db.sqls.must_equal ['SELECT * FROM items WHERE (num = 1)']
163
+ end
164
+
165
+ it "should handle subselects with strings" do
166
+ @ds.filter(:$b).filter(:num=>@ds.select(:num).filter("num = ?", :$n)).call(:select, :n=>1, :b=>0)
167
+ @db.sqls.must_equal ['SELECT * FROM items WHERE (0 AND (num IN (SELECT num FROM items WHERE (num = 1))))']
168
+ end
169
+ end
170
+
171
+ describe "Dataset#update_sql" do
172
+ before do
173
+ @ds = Sequel.mock.dataset.from(:items).extension(:auto_literal_strings)
174
+ end
175
+
176
+ it "should accept strings" do
177
+ @ds.update_sql("a = b").must_equal "UPDATE items SET a = b"
178
+ end
179
+
180
+ it "should accept literal strings" do
181
+ @ds.update_sql(Sequel.lit("a = b")).must_equal "UPDATE items SET a = b"
182
+ end
183
+
184
+ it "should accept hash" do
185
+ @ds.update_sql(:c => 'd').must_equal "UPDATE items SET c = 'd'"
186
+ end
187
+ end
188
+
189
+ describe "Dataset::PlaceholderLiteralizer" do
190
+ before do
191
+ @c = Sequel::Dataset::PlaceholderLiteralizer
192
+ @db = Sequel.mock
193
+ @ds = @db[:items].extension(:auto_literal_strings)
194
+ @h = {:id=>1}
195
+ @ds.db.fetch = @h
196
+ end
197
+
198
+ it "should handle calls with a placeholders used as filter arguments" do
199
+ loader = @c.loader(@ds){|pl, ds| ds.where(pl.arg)}
200
+ loader.first(:id=>1).must_equal @h
201
+ loader.first(Sequel.expr{a(b)}).must_equal @h
202
+ loader.first("a = 1").must_equal @h
203
+ @db.sqls.must_equal ["SELECT * FROM items WHERE (id = 1)", "SELECT * FROM items WHERE a(b)", "SELECT * FROM items WHERE (a = 1)"]
204
+ end
205
+
206
+ it "should handle calls with a placeholder used multiple times in different capacities" do
207
+ loader = @c.loader(@ds){|pl, ds| a = pl.arg; ds.where(a).where(:b=>a)}
208
+ loader.first("a = 1").must_equal @h
209
+ loader.first(["a = ?", 2]).must_equal @h
210
+ @db.sqls.must_equal ["SELECT * FROM items WHERE ((a = 1) AND (b = 'a = 1'))", "SELECT * FROM items WHERE ((a = 2) AND (b IN ('a = ?', 2)))"]
211
+ end
212
+ end
@@ -72,6 +72,7 @@ describe Sequel::Model, ".restricted_columns " do
72
72
  end
73
73
 
74
74
  it "should have allowed take precedence over restricted" do
75
+ @c.plugin :whitelist_security
75
76
  @c.set_allowed_columns :x, :y
76
77
  @c.set_restricted_columns :y, :z
77
78
  i = @c.new(:x => 1, :y => 2, :z => 3)
@@ -30,6 +30,1004 @@ describe "class_table_inheritance plugin" do
30
30
  end
31
31
  plugin :class_table_inheritance, :key=>:kind, :table_map=>{:Staff=>:staff}
32
32
  end
33
+ deprecated do
34
+ class ::Manager < Employee
35
+ one_to_many :staff_members, :class=>:Staff
36
+ end
37
+ class ::Executive < Manager
38
+ end
39
+ class ::Ceo < Executive
40
+ end
41
+ class ::Staff < Employee
42
+ many_to_one :manager
43
+ end
44
+ end
45
+ @ds = Employee.dataset
46
+ @db.sqls
47
+ end
48
+ after do
49
+ Object.send(:remove_const, :Ceo)
50
+ Object.send(:remove_const, :Executive)
51
+ Object.send(:remove_const, :Manager)
52
+ Object.send(:remove_const, :Staff)
53
+ Object.send(:remove_const, :Employee)
54
+ end
55
+
56
+ deprecated "should freeze CTI information when freezing model class" do
57
+ Employee.freeze
58
+ Employee.cti_models.frozen?.must_equal true
59
+ Employee.cti_tables.frozen?.must_equal true
60
+ Employee.cti_instance_dataset.frozen?.must_equal true
61
+ Employee.cti_table_columns.frozen?.must_equal true
62
+ Employee.cti_table_map.frozen?.must_equal true
63
+ end
64
+
65
+ deprecated "should not attempt to use prepared statements" do
66
+ Manager.plugin :prepared_statements
67
+ Manager[1]
68
+ @db.sqls.must_equal ["SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id) WHERE (managers.id = 1) LIMIT 1"]
69
+ Manager.load(:id=>1, :kind=>'Manager', :num_staff=>2).save
70
+ @db.sqls.must_equal ["UPDATE employees SET kind = 'Manager' WHERE (id = 1)", "UPDATE managers SET num_staff = 2 WHERE (id = 1)"]
71
+ @db.fetch = {:id=>1, :kind=>'Manager', :num_staff=>2}
72
+ Manager.load(:id=>1, :kind=>'Manager', :num_staff=>2).refresh
73
+ @db.sqls.must_equal ["SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id) WHERE (managers.id = 1) LIMIT 1"]
74
+ end
75
+
76
+ deprecated "#cti_base_model should be the model that loaded the plugin" do
77
+ Executive.cti_base_model.must_equal Employee
78
+ end
79
+
80
+ deprecated "#cti_columns should be a mapping of table names to columns" do
81
+ Executive.cti_columns.must_equal(:employees=>[:id, :name, :kind], :managers=>[:id, :num_staff], :executives=>[:id, :num_managers])
82
+ end
83
+
84
+ deprecated "should have simple_table = nil for all subclasses" do
85
+ Manager.simple_table.must_be_nil
86
+ Executive.simple_table.must_be_nil
87
+ Ceo.simple_table.must_be_nil
88
+ Staff.simple_table.must_be_nil
89
+ end
90
+
91
+ deprecated "should have working row_proc if using set_dataset in subclass to remove columns" do
92
+ Manager.set_dataset(Manager.dataset.select(*(Manager.columns - [:blah])))
93
+ Manager.dataset = Manager.dataset.with_fetch(:id=>1, :kind=>'Ceo')
94
+ Manager[1].must_equal Ceo.load(:id=>1, :kind=>'Ceo')
95
+ end
96
+
97
+ deprecated "should use a joined dataset in subclasses" do
98
+ Employee.dataset.sql.must_equal 'SELECT * FROM employees'
99
+ Manager.dataset.sql.must_equal 'SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)'
100
+ Executive.dataset.sql.must_equal 'SELECT employees.id, employees.name, employees.kind, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id)'
101
+ Ceo.dataset.sql.must_equal 'SELECT employees.id, employees.name, employees.kind, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id) WHERE (employees.kind IN (\'Ceo\'))'
102
+ Staff.dataset.sql.must_equal 'SELECT employees.id, employees.name, employees.kind, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id)'
103
+ end
104
+
105
+ deprecated "should return rows with the correct class based on the polymorphic_key value" do
106
+ @ds.with_fetch([{:kind=>'Employee'}, {:kind=>'Manager'}, {:kind=>'Executive'}, {:kind=>'Ceo'}, {:kind=>'Staff'}]).all.collect{|x| x.class}.must_equal [Employee, Manager, Executive, Ceo, Staff]
107
+ end
108
+
109
+ deprecated "should return rows with the correct class based on the polymorphic_key value for subclasses" do
110
+ Manager.dataset.with_fetch([{:kind=>'Manager'}, {:kind=>'Executive'}, {:kind=>'Ceo'}]).all.collect{|x| x.class}.must_equal [Manager, Executive, Ceo]
111
+ end
112
+
113
+ deprecated "should have refresh return all columns in subclass after loading from superclass" do
114
+ Employee.dataset = Employee.dataset.with_fetch([{:id=>1, :name=>'A', :kind=>'Ceo'}])
115
+ Ceo.dataset = Ceo.dataset.with_fetch([{:id=>1, :name=>'A', :kind=>'Ceo', :num_staff=>3, :num_managers=>2}])
116
+ a = Employee.first
117
+ a.class.must_equal Ceo
118
+ a.values.must_equal(:id=>1, :name=>'A', :kind=>'Ceo')
119
+ a.refresh.values.must_equal(:id=>1, :name=>'A', :kind=>'Ceo', :num_staff=>3, :num_managers=>2)
120
+ @db.sqls.must_equal ["SELECT * FROM employees LIMIT 1",
121
+ "SELECT employees.id, employees.name, employees.kind, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id) WHERE ((employees.kind IN ('Ceo')) AND (executives.id = 1)) LIMIT 1"]
122
+ end
123
+
124
+ deprecated "should return rows with the current class if cti_key is nil" do
125
+ Employee.plugin(:class_table_inheritance)
126
+ Employee.dataset.with_fetch([{:kind=>'Employee'}, {:kind=>'Manager'}, {:kind=>'Executive'}, {:kind=>'Ceo'}, {:kind=>'Staff'}]).all.map{|x| x.class}.must_equal [Employee, Employee, Employee, Employee, Employee]
127
+ end
128
+
129
+ deprecated "should return rows with the current class if cti_key is nil in subclasses" do
130
+ Employee.plugin(:class_table_inheritance)
131
+ Object.send(:remove_const, :Executive)
132
+ Object.send(:remove_const, :Manager)
133
+ class ::Manager < Employee; end
134
+ class ::Executive < Manager; end
135
+ Manager.dataset.with_fetch([{:kind=>'Manager'}, {:kind=>'Executive'}]).all.map{|x| x.class}.must_equal [Manager, Manager]
136
+ end
137
+
138
+ deprecated "should handle a model map with integer values" do
139
+ Employee.plugin(:class_table_inheritance, :key=>:kind, :model_map=>{0=>:Employee, 1=>:Manager, 2=>:Executive, 3=>:Ceo})
140
+ Object.send(:remove_const, :Ceo)
141
+ Object.send(:remove_const, :Executive)
142
+ Object.send(:remove_const, :Manager)
143
+ class ::Manager < Employee; end
144
+ class ::Executive < Manager; end
145
+ class ::Ceo < Executive; end
146
+ Employee.dataset = Employee.dataset.with_fetch([{:kind=>nil},{:kind=>0},{:kind=>1}, {:kind=>2}, {:kind=>3}])
147
+ Employee.all.collect{|x| x.class}.must_equal [Employee, Employee, Manager, Executive, Ceo]
148
+ Manager.dataset = Manager.dataset.with_fetch([{:kind=>nil},{:kind=>0},{:kind=>1}, {:kind=>2}, {:kind=>3}])
149
+ Manager.all.collect{|x| x.class}.must_equal [Manager, Employee, Manager, Executive, Ceo]
150
+ end
151
+
152
+ deprecated "should fallback to the main class if the given class does not exist" do
153
+ @ds.with_fetch([{:kind=>'Employee'}, {:kind=>'Manager'}, {:kind=>'Blah'}, {:kind=>'Staff'}]).all.map{|x| x.class}.must_equal [Employee, Manager, Employee, Staff]
154
+ end
155
+
156
+ deprecated "should fallback to the main class if the given class does not exist in subclasses" do
157
+ Manager.dataset.with_fetch([{:kind=>'Manager'}, {:kind=>'Executive'}, {:kind=>'Ceo'}, {:kind=>'Blah'}]).all.map{|x| x.class}.must_equal [Manager, Executive, Ceo, Manager]
158
+ end
159
+
160
+ deprecated "should sets the model class name for the key when creating new parent class records" do
161
+ Employee.create
162
+ @db.sqls.must_equal ["INSERT INTO employees (kind) VALUES ('Employee')"]
163
+ end
164
+
165
+ deprecated "should sets the model class name for the key when creating new subclass records" do
166
+ Ceo.create
167
+ @db.sqls.must_equal ["INSERT INTO employees (kind) VALUES ('Ceo')",
168
+ "INSERT INTO managers (id) VALUES (1)",
169
+ "INSERT INTO executives (id) VALUES (1)"]
170
+ end
171
+
172
+ deprecated "should ignore existing cti_key value when creating new records" do
173
+ Employee.create(:kind=>'Manager')
174
+ @db.sqls.must_equal ["INSERT INTO employees (kind) VALUES ('Employee')"]
175
+ end
176
+
177
+ deprecated "should ignore existing cti_key value in subclasses" do
178
+ Manager.create(:kind=>'Executive')
179
+ @db.sqls.must_equal ["INSERT INTO employees (kind) VALUES ('Manager')",
180
+ "INSERT INTO managers (id) VALUES (1)"]
181
+ end
182
+
183
+ deprecated "should handle validations on the type column field" do
184
+ o = Employee.new
185
+ def o.validate
186
+ errors.add(:kind, 'not present') unless kind
187
+ end
188
+ o.valid?.must_equal true
189
+ end
190
+
191
+ deprecated "should set the type column field even when not validating" do
192
+ Employee.new.save(:validate=>false)
193
+ @db.sqls.must_equal ["INSERT INTO employees (kind) VALUES ('Employee')"]
194
+ end
195
+
196
+ deprecated "should allow specifying a map of names to tables to override implicit mapping" do
197
+ Manager.dataset.sql.must_equal 'SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)'
198
+ Staff.dataset.sql.must_equal 'SELECT employees.id, employees.name, employees.kind, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id)'
199
+ end
200
+
201
+ deprecated "should lazily load attributes for columns in subclass tables" do
202
+ Manager.dataset = Manager.dataset.with_fetch(:id=>1, :name=>'J', :kind=>'Ceo', :num_staff=>2)
203
+ m = Manager[1]
204
+ @db.sqls.must_equal ['SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id) WHERE (managers.id = 1) LIMIT 1']
205
+ @db.fetch = {:num_managers=>3}
206
+ m.must_be_kind_of Ceo
207
+ m.num_managers.must_equal 3
208
+ @db.sqls.must_equal ['SELECT executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id) WHERE (executives.id = 1) LIMIT 1']
209
+ m.values.must_equal(:id=>1, :name=>'J', :kind=>'Ceo', :num_staff=>2, :num_managers=>3)
210
+ end
211
+
212
+ deprecated "should lazily load columns in middle classes correctly when loaded from parent class" do
213
+ Employee.dataset = Employee.dataset.with_fetch(:id=>1, :kind=>'Ceo')
214
+ @db.fetch = [[:num_staff=>2]]
215
+ e = Employee[1]
216
+ e.must_be_kind_of(Ceo)
217
+ @db.sqls.must_equal ["SELECT * FROM employees WHERE (id = 1) LIMIT 1"]
218
+ e.num_staff.must_equal 2
219
+ @db.sqls.must_equal ["SELECT managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id) WHERE (managers.id = 1) LIMIT 1"]
220
+ end
221
+
222
+ deprecated "should eagerly load lazily columns in subclasses when loaded from parent class" do
223
+ Employee.dataset = Employee.dataset.with_fetch(:id=>1, :kind=>'Ceo')
224
+ @db.fetch = [[{:id=>1, :num_staff=>2}], [{:id=>1, :num_managers=>3}]]
225
+ e = Employee.all.first
226
+ e.must_be_kind_of(Ceo)
227
+ @db.sqls.must_equal ["SELECT * FROM employees"]
228
+ e.num_staff.must_equal 2
229
+ @db.sqls.must_equal ["SELECT managers.id, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id) WHERE (managers.id IN (1))"]
230
+ e.num_managers.must_equal 3
231
+ @db.sqls.must_equal ['SELECT executives.id, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id) WHERE (executives.id IN (1))']
232
+ end
233
+
234
+ deprecated "should include schema for columns for tables for ancestor classes" do
235
+ Employee.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string}, :kind=>{:type=>:string})
236
+ Manager.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string}, :kind=>{:type=>:string}, :num_staff=>{:type=>:integer})
237
+ Executive.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string}, :kind=>{:type=>:string}, :num_staff=>{:type=>:integer}, :num_managers=>{:type=>:integer})
238
+ Staff.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string}, :kind=>{:type=>:string}, :manager_id=>{:type=>:integer})
239
+ end
240
+
241
+ deprecated "should use the correct primary key (which should have the same name in all subclasses)" do
242
+ [Employee, Manager, Executive, Ceo, Staff].each{|c| c.primary_key.must_equal :id}
243
+ end
244
+
245
+ deprecated "should have table_name return the table name of the most specific table" do
246
+ Employee.table_name.must_equal :employees
247
+ Manager.table_name.must_equal :managers
248
+ Executive.table_name.must_equal :executives
249
+ Ceo.table_name.must_equal :executives
250
+ Staff.table_name.must_equal :staff
251
+ end
252
+
253
+ deprecated "should delete the correct rows from all tables when deleting" do
254
+ Ceo.load(:id=>1).delete
255
+ @db.sqls.must_equal ["DELETE FROM executives WHERE (id = 1)", "DELETE FROM managers WHERE (id = 1)", "DELETE FROM employees WHERE (id = 1)"]
256
+ end
257
+
258
+ deprecated "should not allow deletion of frozen object" do
259
+ o = Ceo.load(:id=>1)
260
+ o.freeze
261
+ proc{o.delete}.must_raise(Sequel::Error)
262
+ @db.sqls.must_equal []
263
+ end
264
+
265
+ deprecated "should insert the correct rows into all tables when inserting" do
266
+ Ceo.create(:num_managers=>3, :num_staff=>2, :name=>'E')
267
+ sqls = @db.sqls
268
+ sqls.length.must_equal 3
269
+ sqls[0].must_match(/INSERT INTO employees \((name|kind), (name|kind)\) VALUES \('(E|Ceo)', '(E|Ceo)'\)/)
270
+ sqls[1].must_match(/INSERT INTO managers \((num_staff|id), (num_staff|id)\) VALUES \([12], [12]\)/)
271
+ sqls[2].must_match(/INSERT INTO executives \((num_managers|id), (num_managers|id)\) VALUES \([13], [13]\)/)
272
+ end
273
+
274
+ deprecated "should insert the correct rows into all tables when inserting when insert_select is supported" do
275
+ [Executive, Manager, Employee].each do |klass|
276
+ klass.instance_variable_set(:@cti_instance_dataset, klass.cti_instance_dataset.with_extend do
277
+ def supports_insert_select?; true; end
278
+ def insert_select(v)
279
+ db.run(insert_sql(v) + " RETURNING *")
280
+ v.merge(:id=>1)
281
+ end
282
+ end)
283
+ end
284
+ Ceo.create(:num_managers=>3, :num_staff=>2, :name=>'E')
285
+ sqls = @db.sqls
286
+ sqls.length.must_equal 3
287
+ sqls[0].must_match(/INSERT INTO employees \((name|kind), (name|kind)\) VALUES \('(E|Ceo)', '(E|Ceo)'\) RETURNING \*/)
288
+ sqls[1].must_match(/INSERT INTO managers \((num_staff|id), (num_staff|id)\) VALUES \([12], [12]\) RETURNING \*/)
289
+ sqls[2].must_match(/INSERT INTO executives \((num_managers|id), (num_managers|id)\) VALUES \([13], [13]\) RETURNING \*/)
290
+ end
291
+
292
+ deprecated "should insert the correct rows into all tables with a given primary key" do
293
+ e = Ceo.new(:num_managers=>3, :num_staff=>2, :name=>'E')
294
+ e.id = 2
295
+ e.save
296
+ sqls = @db.sqls
297
+ sqls.length.must_equal 3
298
+ sqls[0].must_match(/INSERT INTO employees \((name|kind|id), (name|kind|id), (name|kind|id)\) VALUES \(('E'|'Ceo'|2), ('E'|'Ceo'|2), ('E'|'Ceo'|2)\)/)
299
+ sqls[1].must_match(/INSERT INTO managers \((num_staff|id), (num_staff|id)\) VALUES \(2, 2\)/)
300
+ sqls[2].must_match(/INSERT INTO executives \((num_managers|id), (num_managers|id)\) VALUES \([23], [23]\)/)
301
+ end
302
+
303
+ deprecated "should update the correct rows in all tables when updating" do
304
+ Ceo.load(:id=>2).update(:num_managers=>3, :num_staff=>2, :name=>'E')
305
+ @db.sqls.must_equal ["UPDATE employees SET name = 'E' WHERE (id = 2)", "UPDATE managers SET num_staff = 2 WHERE (id = 2)", "UPDATE executives SET num_managers = 3 WHERE (id = 2)"]
306
+ end
307
+
308
+ deprecated "should handle many_to_one relationships correctly" do
309
+ Manager.dataset = Manager.dataset.with_fetch(:id=>3, :name=>'E', :kind=>'Ceo', :num_managers=>3)
310
+ Staff.load(:manager_id=>3).manager.must_equal Ceo.load(:id=>3, :name=>'E', :kind=>'Ceo', :num_managers=>3)
311
+ @db.sqls.must_equal ['SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id) WHERE (managers.id = 3) LIMIT 1']
312
+ end
313
+
314
+ deprecated "should handle one_to_many relationships correctly" do
315
+ Staff.dataset = Staff.dataset.with_fetch(:id=>1, :name=>'S', :kind=>'Staff', :manager_id=>3)
316
+ Ceo.load(:id=>3).staff_members.must_equal [Staff.load(:id=>1, :name=>'S', :kind=>'Staff', :manager_id=>3)]
317
+ @db.sqls.must_equal ['SELECT employees.id, employees.name, employees.kind, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id) WHERE (staff.manager_id = 3)']
318
+ end
319
+ end
320
+
321
+ describe "class_table_inheritance plugin without sti_key" do
322
+ before do
323
+ @db = Sequel.mock(:autoid=>proc{|sql| 1})
324
+ def @db.supports_schema_parsing?() true end
325
+ def @db.schema(table, opts={})
326
+ {:employees=>[[:id, {:primary_key=>true, :type=>:integer}], [:name, {:type=>:string}]],
327
+ :managers=>[[:id, {:type=>:integer}], [:num_staff, {:type=>:integer}]],
328
+ :executives=>[[:id, {:type=>:integer}], [:num_managers, {:type=>:integer}]],
329
+ :staff=>[[:id, {:type=>:integer}], [:manager_id, {:type=>:integer}]],
330
+ }[table.is_a?(Sequel::Dataset) ? table.first_source_table : table]
331
+ end
332
+ @db.extend_datasets do
333
+ def columns
334
+ {[:employees]=>[:id, :name],
335
+ [:managers]=>[:id, :num_staff],
336
+ [:executives]=>[:id, :num_managers],
337
+ [:staff]=>[:id, :manager_id],
338
+ [:employees, :managers]=>[:id, :name, :num_staff],
339
+ [:employees, :managers, :executives]=>[:id, :name, :num_staff, :num_managers],
340
+ [:employees, :staff]=>[:id, :name, :manager_id],
341
+ }[opts[:from] + (opts[:join] || []).map{|x| x.table}]
342
+ end
343
+ end
344
+ class ::Employee < Sequel::Model(@db)
345
+ def _save_refresh; @values[:id] = 1 end
346
+ def self.columns
347
+ dataset.columns
348
+ end
349
+ plugin :class_table_inheritance, :table_map=>{:Staff=>:staff}
350
+ end
351
+ deprecated do
352
+ class ::Manager < Employee
353
+ one_to_many :staff_members, :class=>:Staff
354
+ end
355
+ class ::Executive < Manager
356
+ end
357
+ class ::Staff < Employee
358
+ many_to_one :manager
359
+ end
360
+ end
361
+ @ds = Employee.dataset
362
+ @db.sqls
363
+ end
364
+ after do
365
+ Object.send(:remove_const, :Executive)
366
+ Object.send(:remove_const, :Manager)
367
+ Object.send(:remove_const, :Staff)
368
+ Object.send(:remove_const, :Employee)
369
+ end
370
+
371
+ deprecated "should have simple_table = nil for all subclasses" do
372
+ Manager.simple_table.must_be_nil
373
+ Executive.simple_table.must_be_nil
374
+ Staff.simple_table.must_be_nil
375
+ end
376
+
377
+ deprecated "should have working row_proc if using set_dataset in subclass to remove columns" do
378
+ Manager.set_dataset(Manager.dataset.select(*(Manager.columns - [:blah])))
379
+ Manager.dataset = Manager.dataset.with_fetch(:id=>1)
380
+ Manager[1].must_equal Manager.load(:id=>1)
381
+ end
382
+
383
+ deprecated "should use a dataset in subclasses" do
384
+ Employee.dataset.sql.must_equal 'SELECT * FROM employees'
385
+ Manager.dataset.sql.must_equal 'SELECT employees.id, employees.name, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)'
386
+ Executive.dataset.sql.must_equal 'SELECT employees.id, employees.name, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id)'
387
+ Staff.dataset.sql.must_equal 'SELECT employees.id, employees.name, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id)'
388
+ end
389
+
390
+ deprecated "should return rows with the current class if cti_key is nil" do
391
+ Employee.plugin(:class_table_inheritance)
392
+ Employee.dataset = Employee.dataset.with_fetch([{}])
393
+ Employee.first.class.must_equal Employee
394
+ end
395
+
396
+
397
+ deprecated "should include schema for columns for tables for ancestor classes" do
398
+ Employee.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string})
399
+ Manager.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string}, :num_staff=>{:type=>:integer})
400
+ Executive.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string}, :num_staff=>{:type=>:integer}, :num_managers=>{:type=>:integer})
401
+ Staff.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string}, :manager_id=>{:type=>:integer})
402
+ end
403
+
404
+ deprecated "should use the correct primary key (which should have the same name in all subclasses)" do
405
+ [Employee, Manager, Executive, Staff].each{|c| c.primary_key.must_equal :id}
406
+ end
407
+
408
+ deprecated "should have table_name return the table name of the most specific table" do
409
+ Employee.table_name.must_equal :employees
410
+ Manager.table_name.must_equal :managers
411
+ Executive.table_name.must_equal :executives
412
+ Staff.table_name.must_equal :staff
413
+ end
414
+
415
+ deprecated "should delete the correct rows from all tables when deleting" do
416
+ Executive.load(:id=>1).delete
417
+ @db.sqls.must_equal ["DELETE FROM executives WHERE (id = 1)", "DELETE FROM managers WHERE (id = 1)", "DELETE FROM employees WHERE (id = 1)"]
418
+ end
419
+
420
+ deprecated "should not allow deletion of frozen object" do
421
+ o = Executive.load(:id=>1)
422
+ o.freeze
423
+ proc{o.delete}.must_raise(Sequel::Error)
424
+ @db.sqls.must_equal []
425
+ end
426
+
427
+ deprecated "should insert the correct rows into all tables when inserting" do
428
+ Executive.create(:num_managers=>3, :num_staff=>2, :name=>'E')
429
+ sqls = @db.sqls
430
+ sqls.length.must_equal 3
431
+ sqls[0].must_match(/INSERT INTO employees \(name\) VALUES \('E'\)/)
432
+ sqls[1].must_match(/INSERT INTO managers \((num_staff|id), (num_staff|id)\) VALUES \([12], [12]\)/)
433
+ sqls[2].must_match(/INSERT INTO executives \((num_managers|id), (num_managers|id)\) VALUES \([13], [13]\)/)
434
+ end
435
+
436
+ deprecated "should insert the correct rows into all tables with a given primary key" do
437
+ e = Executive.new(:num_managers=>3, :num_staff=>2, :name=>'E')
438
+ e.id = 2
439
+ e.save
440
+ sqls = @db.sqls
441
+ sqls.length.must_equal 3
442
+ sqls[0].must_match(/INSERT INTO employees \((name|id), (name|id)\) VALUES \(('E'|2), ('E'|2)\)/)
443
+ sqls[1].must_match(/INSERT INTO managers \((num_staff|id), (num_staff|id)\) VALUES \(2, 2\)/)
444
+ sqls[2].must_match(/INSERT INTO executives \((num_managers|id), (num_managers|id)\) VALUES \([23], [23]\)/)
445
+ end
446
+
447
+ deprecated "should update the correct rows in all tables when updating" do
448
+ Executive.load(:id=>2).update(:num_managers=>3, :num_staff=>2, :name=>'E')
449
+ @db.sqls.must_equal ["UPDATE employees SET name = 'E' WHERE (id = 2)", "UPDATE managers SET num_staff = 2 WHERE (id = 2)", "UPDATE executives SET num_managers = 3 WHERE (id = 2)"]
450
+ end
451
+
452
+ deprecated "should handle many_to_one relationships correctly" do
453
+ Manager.dataset = Manager.dataset.with_fetch(:id=>3, :name=>'E', :num_staff=>3)
454
+ Staff.load(:manager_id=>3).manager.must_equal Manager.load(:id=>3, :name=>'E', :num_staff=>3)
455
+ @db.sqls.must_equal ['SELECT employees.id, employees.name, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id) WHERE (managers.id = 3) LIMIT 1']
456
+ end
457
+
458
+ deprecated "should handle one_to_many relationships correctly" do
459
+ Staff.dataset = Staff.dataset.with_fetch(:id=>1, :name=>'S', :manager_id=>3)
460
+ Executive.load(:id=>3).staff_members.must_equal [Staff.load(:id=>1, :name=>'S', :manager_id=>3)]
461
+ @db.sqls.must_equal ['SELECT employees.id, employees.name, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id) WHERE (staff.manager_id = 3)']
462
+ end
463
+ end
464
+
465
+ describe "class_table_inheritance plugin with duplicate columns" do
466
+ before do
467
+ @db = Sequel.mock(:autoid=>proc{|sql| 1})
468
+ def @db.supports_schema_parsing?() true end
469
+ def @db.schema(table, opts={})
470
+ {:employees=>[[:id, {:primary_key=>true, :type=>:integer}], [:name, {:type=>:string}], [:kind, {:type=>:string}]],
471
+ :managers=>[[:id, {:type=>:integer}], [:name, {:type=>:string}]],
472
+ }[table.is_a?(Sequel::Dataset) ? table.first_source_table : table]
473
+ end
474
+ @db.extend_datasets do
475
+ def columns
476
+ {[:employees]=>[:id, :name, :kind],
477
+ [:managers]=>[:id, :name],
478
+ }[opts[:from] + (opts[:join] || []).map{|x| x.table}]
479
+ end
480
+ end
481
+ class ::Employee < Sequel::Model(@db)
482
+ def _save_refresh; @values[:id] = 1 end
483
+ def self.columns
484
+ dataset.columns
485
+ end
486
+ plugin :class_table_inheritance, :key=>:kind, :table_map=>{:Staff=>:staff}
487
+ end
488
+ deprecated do
489
+ class ::Manager < Employee; end
490
+ end
491
+ @ds = Employee.dataset
492
+ @db.sqls
493
+ end
494
+ after do
495
+ Object.send(:remove_const, :Manager)
496
+ Object.send(:remove_const, :Employee)
497
+ end
498
+
499
+ deprecated "should select names from both tables" do
500
+ Manager.dataset.sql.must_equal 'SELECT employees.id, employees.name, employees.kind, managers.name FROM employees INNER JOIN managers ON (managers.id = employees.id)'
501
+ end
502
+ end
503
+
504
+ describe "class_table_inheritance plugin with :alias option" do
505
+ before do
506
+ @db = Sequel.mock(:autoid=>proc{|sql| 1})
507
+ def @db.supports_schema_parsing?() true end
508
+ def @db.schema(table, opts={})
509
+ {:employees=>[[:id, {:primary_key=>true, :type=>:integer}], [:name, {:type=>:string}], [:kind, {:type=>:string}]],
510
+ :managers=>[[:id, {:type=>:integer}], [:num_staff, {:type=>:integer}]],
511
+ :executives=>[[:id, {:type=>:integer}], [:num_managers, {:type=>:integer}]],
512
+ :staff=>[[:id, {:type=>:integer}], [:manager_id, {:type=>:integer}]],
513
+ }[table.is_a?(Sequel::Dataset) ? table.first_source_table : table]
514
+ end
515
+ @db.extend_datasets do
516
+ def columns
517
+ {[:employees]=>[:id, :name, :kind],
518
+ [:managers]=>[:id, :num_staff],
519
+ [:executives]=>[:id, :num_managers],
520
+ [:staff]=>[:id, :manager_id],
521
+ [:employees, :managers]=>[:id, :name, :kind, :num_staff],
522
+ [:employees, :managers, :executives]=>[:id, :name, :kind, :num_staff, :num_managers],
523
+ [:employees, :staff]=>[:id, :name, :kind, :manager_id],
524
+ }[opts[:from] + (opts[:join] || []).map{|x| x.table}]
525
+ end
526
+ end
527
+ class ::Employee < Sequel::Model(@db)
528
+ def _save_refresh; @values[:id] = 1 end
529
+ def self.columns
530
+ dataset.columns || dataset.opts[:from].first.expression.columns
531
+ end
532
+ plugin :class_table_inheritance, :key=>:kind, :table_map=>{:Staff=>:staff}, :alias=>:employees
533
+ end
534
+ class ::Manager < Employee
535
+ one_to_many :staff_members, :class=>:Staff
536
+ end
537
+ class ::Executive < Manager
538
+ end
539
+ class ::Ceo < Executive
540
+ end
541
+ class ::Staff < Employee
542
+ many_to_one :manager
543
+ end
544
+ @ds = Employee.dataset
545
+ @db.sqls
546
+ end
547
+ after do
548
+ Object.send(:remove_const, :Ceo)
549
+ Object.send(:remove_const, :Executive)
550
+ Object.send(:remove_const, :Manager)
551
+ Object.send(:remove_const, :Staff)
552
+ Object.send(:remove_const, :Employee)
553
+ end
554
+
555
+ it "should freeze CTI information when freezing model class" do
556
+ Employee.freeze
557
+ Employee.cti_models.frozen?.must_equal true
558
+ Employee.cti_tables.frozen?.must_equal true
559
+ Employee.cti_instance_dataset.frozen?.must_equal true
560
+ Employee.cti_table_columns.frozen?.must_equal true
561
+ Employee.cti_table_map.frozen?.must_equal true
562
+ end
563
+
564
+ it "should not attempt to use prepared statements" do
565
+ Manager.plugin :prepared_statements
566
+ Manager[1]
567
+ @db.sqls.must_equal ["SELECT id, name, kind, num_staff FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees WHERE (id = 1) LIMIT 1"]
568
+ Manager.load(:id=>1, :kind=>'Manager', :num_staff=>2).save
569
+ @db.sqls.must_equal ["UPDATE employees SET kind = 'Manager' WHERE (id = 1)", "UPDATE managers SET num_staff = 2 WHERE (id = 1)"]
570
+ @db.fetch = {:id=>1, :kind=>'Manager', :num_staff=>2}
571
+ Manager.load(:id=>1, :kind=>'Manager', :num_staff=>2).refresh
572
+ @db.sqls.must_equal ["SELECT * FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees WHERE (id = 1) LIMIT 1"]
573
+ end
574
+
575
+ it "#cti_base_model should be the model that loaded the plugin" do
576
+ Executive.cti_base_model.must_equal Employee
577
+ end
578
+
579
+ it "#cti_columns should be a mapping of table names to columns" do
580
+ Executive.cti_columns.must_equal(:employees=>[:id, :name, :kind], :managers=>[:id, :num_staff], :executives=>[:id, :num_managers])
581
+ end
582
+
583
+ it "should have simple_table = nil for all subclasses" do
584
+ Manager.simple_table.must_be_nil
585
+ Executive.simple_table.must_be_nil
586
+ Ceo.simple_table.must_be_nil
587
+ Staff.simple_table.must_be_nil
588
+ end
589
+
590
+ it "should have working row_proc if using set_dataset in subclass to remove columns" do
591
+ Manager.set_dataset(Manager.dataset.select(*(Manager.columns - [:blah])))
592
+ Manager.dataset = Manager.dataset.with_fetch(:id=>1, :kind=>'Ceo')
593
+ Manager[1].must_equal Ceo.load(:id=>1, :kind=>'Ceo')
594
+ end
595
+
596
+ it "should use a subquery in subclasses" do
597
+ Employee.dataset.sql.must_equal 'SELECT * FROM employees'
598
+ Manager.dataset.sql.must_equal 'SELECT * FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees'
599
+ Executive.dataset.sql.must_equal 'SELECT * FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id)) AS employees'
600
+ Ceo.dataset.sql.must_equal 'SELECT * FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id) WHERE (employees.kind IN (\'Ceo\'))) AS employees'
601
+ Staff.dataset.sql.must_equal 'SELECT * FROM (SELECT employees.id, employees.name, employees.kind, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id)) AS employees'
602
+ end
603
+
604
+ it "should return rows with the correct class based on the polymorphic_key value" do
605
+ @ds.with_fetch([{:kind=>'Employee'}, {:kind=>'Manager'}, {:kind=>'Executive'}, {:kind=>'Ceo'}, {:kind=>'Staff'}]).all.collect{|x| x.class}.must_equal [Employee, Manager, Executive, Ceo, Staff]
606
+ end
607
+
608
+ it "should return rows with the correct class based on the polymorphic_key value for subclasses" do
609
+ Manager.dataset.with_fetch([{:kind=>'Manager'}, {:kind=>'Executive'}, {:kind=>'Ceo'}]).all.collect{|x| x.class}.must_equal [Manager, Executive, Ceo]
610
+ end
611
+
612
+ it "should have refresh return all columns in subclass after loading from superclass" do
613
+ Employee.dataset = Employee.dataset.with_fetch([{:id=>1, :name=>'A', :kind=>'Ceo'}])
614
+ Ceo.dataset = Ceo.dataset.with_fetch([{:id=>1, :name=>'A', :kind=>'Ceo', :num_staff=>3, :num_managers=>2}])
615
+ a = Employee.first
616
+ a.class.must_equal Ceo
617
+ a.values.must_equal(:id=>1, :name=>'A', :kind=>'Ceo')
618
+ a.refresh.values.must_equal(:id=>1, :name=>'A', :kind=>'Ceo', :num_staff=>3, :num_managers=>2)
619
+ @db.sqls.must_equal ["SELECT * FROM employees LIMIT 1",
620
+ "SELECT * FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id) WHERE (employees.kind IN ('Ceo'))) AS employees WHERE (id = 1) LIMIT 1"]
621
+ end
622
+
623
+ it "should return rows with the current class if cti_key is nil" do
624
+ Employee.plugin(:class_table_inheritance, :alias=>:employees)
625
+ Employee.dataset.with_fetch([{:kind=>'Employee'}, {:kind=>'Manager'}, {:kind=>'Executive'}, {:kind=>'Ceo'}, {:kind=>'Staff'}]).all.map{|x| x.class}.must_equal [Employee, Employee, Employee, Employee, Employee]
626
+ end
627
+
628
+ it "should return rows with the current class if cti_key is nil in subclasses" do
629
+ Employee.plugin(:class_table_inheritance, :alias=>:employees)
630
+ Object.send(:remove_const, :Executive)
631
+ Object.send(:remove_const, :Manager)
632
+ class ::Manager < Employee; end
633
+ class ::Executive < Manager; end
634
+ Manager.dataset.with_fetch([{:kind=>'Manager'}, {:kind=>'Executive'}]).all.map{|x| x.class}.must_equal [Manager, Manager]
635
+ end
636
+
637
+ it "should handle a model map with integer values" do
638
+ Employee.plugin(:class_table_inheritance, :key=>:kind, :model_map=>{0=>:Employee, 1=>:Manager, 2=>:Executive, 3=>:Ceo}, :alias=>:employees)
639
+ Object.send(:remove_const, :Ceo)
640
+ Object.send(:remove_const, :Executive)
641
+ Object.send(:remove_const, :Manager)
642
+ class ::Manager < Employee; end
643
+ class ::Executive < Manager; end
644
+ class ::Ceo < Executive; end
645
+ Employee.dataset = Employee.dataset.with_fetch([{:kind=>nil},{:kind=>0},{:kind=>1}, {:kind=>2}, {:kind=>3}])
646
+ Employee.all.collect{|x| x.class}.must_equal [Employee, Employee, Manager, Executive, Ceo]
647
+ Manager.dataset = Manager.dataset.with_fetch([{:kind=>nil},{:kind=>0},{:kind=>1}, {:kind=>2}, {:kind=>3}])
648
+ Manager.all.collect{|x| x.class}.must_equal [Manager, Employee, Manager, Executive, Ceo]
649
+ end
650
+
651
+ it "should fallback to the main class if the given class does not exist" do
652
+ @ds.with_fetch([{:kind=>'Employee'}, {:kind=>'Manager'}, {:kind=>'Blah'}, {:kind=>'Staff'}]).all.map{|x| x.class}.must_equal [Employee, Manager, Employee, Staff]
653
+ end
654
+
655
+ it "should fallback to the main class if the given class does not exist in subclasses" do
656
+ Manager.dataset.with_fetch([{:kind=>'Manager'}, {:kind=>'Executive'}, {:kind=>'Ceo'}, {:kind=>'Blah'}]).all.map{|x| x.class}.must_equal [Manager, Executive, Ceo, Manager]
657
+ end
658
+
659
+ it "should sets the model class name for the key when creating new parent class records" do
660
+ Employee.create
661
+ @db.sqls.must_equal ["INSERT INTO employees (kind) VALUES ('Employee')"]
662
+ end
663
+
664
+ it "should sets the model class name for the key when creating new subclass records" do
665
+ Ceo.create
666
+ @db.sqls.must_equal ["INSERT INTO employees (kind) VALUES ('Ceo')",
667
+ "INSERT INTO managers (id) VALUES (1)",
668
+ "INSERT INTO executives (id) VALUES (1)"]
669
+ end
670
+
671
+ it "should ignore existing cti_key value when creating new records" do
672
+ Employee.create(:kind=>'Manager')
673
+ @db.sqls.must_equal ["INSERT INTO employees (kind) VALUES ('Employee')"]
674
+ end
675
+
676
+ it "should ignore existing cti_key value in subclasses" do
677
+ Manager.create(:kind=>'Executive')
678
+ @db.sqls.must_equal ["INSERT INTO employees (kind) VALUES ('Manager')",
679
+ "INSERT INTO managers (id) VALUES (1)"]
680
+ end
681
+
682
+ it "should handle validations on the type column field" do
683
+ o = Employee.new
684
+ def o.validate
685
+ errors.add(:kind, 'not present') unless kind
686
+ end
687
+ o.valid?.must_equal true
688
+ end
689
+
690
+ it "should set the type column field even when not validating" do
691
+ Employee.new.save(:validate=>false)
692
+ @db.sqls.must_equal ["INSERT INTO employees (kind) VALUES ('Employee')"]
693
+ end
694
+
695
+ it "should allow specifying a map of names to tables to override implicit mapping" do
696
+ Manager.dataset.sql.must_equal 'SELECT * FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees'
697
+ Staff.dataset.sql.must_equal 'SELECT * FROM (SELECT employees.id, employees.name, employees.kind, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id)) AS employees'
698
+ end
699
+
700
+ it "should lazily load attributes for columns in subclass tables" do
701
+ Manager.dataset = Manager.dataset.with_fetch(:id=>1, :name=>'J', :kind=>'Ceo', :num_staff=>2)
702
+ m = Manager[1]
703
+ @db.sqls.must_equal ['SELECT * FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees WHERE (id = 1) LIMIT 1']
704
+ @db.fetch = {:num_managers=>3}
705
+ m.must_be_kind_of Ceo
706
+ m.num_managers.must_equal 3
707
+ @db.sqls.must_equal ['SELECT employees.num_managers FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id)) AS employees WHERE (employees.id = 1) LIMIT 1']
708
+ m.values.must_equal(:id=>1, :name=>'J', :kind=>'Ceo', :num_staff=>2, :num_managers=>3)
709
+ end
710
+
711
+ it "should lazily load columns in middle classes correctly when loaded from parent class" do
712
+ Employee.dataset = Employee.dataset.with_fetch(:id=>1, :kind=>'Ceo')
713
+ @db.fetch = [[:num_staff=>2]]
714
+ e = Employee[1]
715
+ e.must_be_kind_of(Ceo)
716
+ @db.sqls.must_equal ["SELECT * FROM employees WHERE (id = 1) LIMIT 1"]
717
+ e.num_staff.must_equal 2
718
+ @db.sqls.must_equal ["SELECT employees.num_staff FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees WHERE (employees.id = 1) LIMIT 1"]
719
+ end
720
+
721
+ it "should eagerly load lazily columns in subclasses when loaded from parent class" do
722
+ Employee.dataset = Employee.dataset.with_fetch(:id=>1, :kind=>'Ceo')
723
+ @db.fetch = [[{:id=>1, :num_staff=>2}], [{:id=>1, :num_managers=>3}]]
724
+ e = Employee.all.first
725
+ e.must_be_kind_of(Ceo)
726
+ @db.sqls.must_equal ["SELECT * FROM employees"]
727
+ e.num_staff.must_equal 2
728
+ @db.sqls.must_equal ["SELECT employees.id, employees.num_staff FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees WHERE (employees.id IN (1))"]
729
+ e.num_managers.must_equal 3
730
+ @db.sqls.must_equal ['SELECT employees.id, employees.num_managers FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id)) AS employees WHERE (employees.id IN (1))']
731
+ end
732
+
733
+ it "should include schema for columns for tables for ancestor classes" do
734
+ Employee.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string}, :kind=>{:type=>:string})
735
+ Manager.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string}, :kind=>{:type=>:string}, :num_staff=>{:type=>:integer})
736
+ Executive.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string}, :kind=>{:type=>:string}, :num_staff=>{:type=>:integer}, :num_managers=>{:type=>:integer})
737
+ Staff.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string}, :kind=>{:type=>:string}, :manager_id=>{:type=>:integer})
738
+ end
739
+
740
+ it "should use the correct primary key (which should have the same name in all subclasses)" do
741
+ [Employee, Manager, Executive, Ceo, Staff].each{|c| c.primary_key.must_equal :id}
742
+ end
743
+
744
+ it "should have table_name return the table name of the most specific table" do
745
+ Employee.table_name.must_equal :employees
746
+ Manager.table_name.must_equal :employees
747
+ Executive.table_name.must_equal :employees
748
+ Ceo.table_name.must_equal :employees
749
+ Staff.table_name.must_equal :employees
750
+ end
751
+
752
+ it "should delete the correct rows from all tables when deleting" do
753
+ Ceo.load(:id=>1).delete
754
+ @db.sqls.must_equal ["DELETE FROM executives WHERE (id = 1)", "DELETE FROM managers WHERE (id = 1)", "DELETE FROM employees WHERE (id = 1)"]
755
+ end
756
+
757
+ it "should not allow deletion of frozen object" do
758
+ o = Ceo.load(:id=>1)
759
+ o.freeze
760
+ proc{o.delete}.must_raise(Sequel::Error)
761
+ @db.sqls.must_equal []
762
+ end
763
+
764
+ it "should insert the correct rows into all tables when inserting" do
765
+ Ceo.create(:num_managers=>3, :num_staff=>2, :name=>'E')
766
+ sqls = @db.sqls
767
+ sqls.length.must_equal 3
768
+ sqls[0].must_match(/INSERT INTO employees \((name|kind), (name|kind)\) VALUES \('(E|Ceo)', '(E|Ceo)'\)/)
769
+ sqls[1].must_match(/INSERT INTO managers \((num_staff|id), (num_staff|id)\) VALUES \([12], [12]\)/)
770
+ sqls[2].must_match(/INSERT INTO executives \((num_managers|id), (num_managers|id)\) VALUES \([13], [13]\)/)
771
+ end
772
+
773
+ it "should insert the correct rows into all tables when inserting when insert_select is supported" do
774
+ [Executive, Manager, Employee].each do |klass|
775
+ klass.instance_variable_set(:@cti_instance_dataset, klass.cti_instance_dataset.with_extend do
776
+ def supports_insert_select?; true; end
777
+ def insert_select(v)
778
+ db.run(insert_sql(v) + " RETURNING *")
779
+ v.merge(:id=>1)
780
+ end
781
+ end)
782
+ end
783
+ Ceo.create(:num_managers=>3, :num_staff=>2, :name=>'E')
784
+ sqls = @db.sqls
785
+ sqls.length.must_equal 3
786
+ sqls[0].must_match(/INSERT INTO employees \((name|kind), (name|kind)\) VALUES \('(E|Ceo)', '(E|Ceo)'\) RETURNING \*/)
787
+ sqls[1].must_match(/INSERT INTO managers \((num_staff|id), (num_staff|id)\) VALUES \([12], [12]\) RETURNING \*/)
788
+ sqls[2].must_match(/INSERT INTO executives \((num_managers|id), (num_managers|id)\) VALUES \([13], [13]\) RETURNING \*/)
789
+ end
790
+
791
+ it "should insert the correct rows into all tables with a given primary key" do
792
+ e = Ceo.new(:num_managers=>3, :num_staff=>2, :name=>'E')
793
+ e.id = 2
794
+ e.save
795
+ sqls = @db.sqls
796
+ sqls.length.must_equal 3
797
+ sqls[0].must_match(/INSERT INTO employees \((name|kind|id), (name|kind|id), (name|kind|id)\) VALUES \(('E'|'Ceo'|2), ('E'|'Ceo'|2), ('E'|'Ceo'|2)\)/)
798
+ sqls[1].must_match(/INSERT INTO managers \((num_staff|id), (num_staff|id)\) VALUES \(2, 2\)/)
799
+ sqls[2].must_match(/INSERT INTO executives \((num_managers|id), (num_managers|id)\) VALUES \([23], [23]\)/)
800
+ end
801
+
802
+ it "should update the correct rows in all tables when updating" do
803
+ Ceo.load(:id=>2).update(:num_managers=>3, :num_staff=>2, :name=>'E')
804
+ @db.sqls.must_equal ["UPDATE employees SET name = 'E' WHERE (id = 2)", "UPDATE managers SET num_staff = 2 WHERE (id = 2)", "UPDATE executives SET num_managers = 3 WHERE (id = 2)"]
805
+ end
806
+
807
+ it "should handle many_to_one relationships correctly" do
808
+ Manager.dataset = Manager.dataset.with_fetch(:id=>3, :name=>'E', :kind=>'Ceo', :num_managers=>3)
809
+ Staff.load(:manager_id=>3).manager.must_equal Ceo.load(:id=>3, :name=>'E', :kind=>'Ceo', :num_managers=>3)
810
+ @db.sqls.must_equal ['SELECT * FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees WHERE (id = 3) LIMIT 1']
811
+ end
812
+
813
+ it "should handle one_to_many relationships correctly" do
814
+ Staff.dataset = Staff.dataset.with_fetch(:id=>1, :name=>'S', :kind=>'Staff', :manager_id=>3)
815
+ Ceo.load(:id=>3).staff_members.must_equal [Staff.load(:id=>1, :name=>'S', :kind=>'Staff', :manager_id=>3)]
816
+ @db.sqls.must_equal ['SELECT * FROM (SELECT employees.id, employees.name, employees.kind, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id)) AS employees WHERE (employees.manager_id = 3)']
817
+ end
818
+ end
819
+
820
+ describe "class_table_inheritance plugin without sti_key with :alias option" do
821
+ before do
822
+ @db = Sequel.mock(:autoid=>proc{|sql| 1})
823
+ def @db.supports_schema_parsing?() true end
824
+ def @db.schema(table, opts={})
825
+ {:employees=>[[:id, {:primary_key=>true, :type=>:integer}], [:name, {:type=>:string}]],
826
+ :managers=>[[:id, {:type=>:integer}], [:num_staff, {:type=>:integer}]],
827
+ :executives=>[[:id, {:type=>:integer}], [:num_managers, {:type=>:integer}]],
828
+ :staff=>[[:id, {:type=>:integer}], [:manager_id, {:type=>:integer}]],
829
+ }[table.is_a?(Sequel::Dataset) ? table.first_source_table : table]
830
+ end
831
+ @db.extend_datasets do
832
+ def columns
833
+ {[:employees]=>[:id, :name],
834
+ [:managers]=>[:id, :num_staff],
835
+ [:executives]=>[:id, :num_managers],
836
+ [:staff]=>[:id, :manager_id],
837
+ [:employees, :managers]=>[:id, :name, :num_staff],
838
+ [:employees, :managers, :executives]=>[:id, :name, :num_staff, :num_managers],
839
+ [:employees, :staff]=>[:id, :name, :manager_id],
840
+ }[opts[:from] + (opts[:join] || []).map{|x| x.table}]
841
+ end
842
+ end
843
+ class ::Employee < Sequel::Model(@db)
844
+ def _save_refresh; @values[:id] = 1 end
845
+ def self.columns
846
+ dataset.columns || dataset.opts[:from].first.expression.columns
847
+ end
848
+ plugin :class_table_inheritance, :table_map=>{:Staff=>:staff}, :alias=>:employees
849
+ end
850
+ class ::Manager < Employee
851
+ one_to_many :staff_members, :class=>:Staff
852
+ end
853
+ class ::Executive < Manager
854
+ end
855
+ class ::Staff < Employee
856
+ many_to_one :manager
857
+ end
858
+ @ds = Employee.dataset
859
+ @db.sqls
860
+ end
861
+ after do
862
+ Object.send(:remove_const, :Executive)
863
+ Object.send(:remove_const, :Manager)
864
+ Object.send(:remove_const, :Staff)
865
+ Object.send(:remove_const, :Employee)
866
+ end
867
+
868
+ it "should have simple_table = nil for all subclasses" do
869
+ Manager.simple_table.must_be_nil
870
+ Executive.simple_table.must_be_nil
871
+ Staff.simple_table.must_be_nil
872
+ end
873
+
874
+ it "should have working row_proc if using set_dataset in subclass to remove columns" do
875
+ Manager.set_dataset(Manager.dataset.select(*(Manager.columns - [:blah])))
876
+ Manager.dataset = Manager.dataset.with_fetch(:id=>1)
877
+ Manager[1].must_equal Manager.load(:id=>1)
878
+ end
879
+
880
+ it "should use a joined dataset in subclasses" do
881
+ Employee.dataset.sql.must_equal 'SELECT * FROM employees'
882
+ Manager.dataset.sql.must_equal 'SELECT * FROM (SELECT employees.id, employees.name, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees'
883
+ Executive.dataset.sql.must_equal 'SELECT * FROM (SELECT employees.id, employees.name, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id)) AS employees'
884
+ Staff.dataset.sql.must_equal 'SELECT * FROM (SELECT employees.id, employees.name, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id)) AS employees'
885
+ end
886
+
887
+ it "should return rows with the current class if cti_key is nil" do
888
+ Employee.plugin(:class_table_inheritance)
889
+ Employee.dataset = Employee.dataset.with_fetch([{}])
890
+ Employee.first.class.must_equal Employee
891
+ end
892
+
893
+
894
+ it "should include schema for columns for tables for ancestor classes" do
895
+ Employee.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string})
896
+ Manager.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string}, :num_staff=>{:type=>:integer})
897
+ Executive.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string}, :num_staff=>{:type=>:integer}, :num_managers=>{:type=>:integer})
898
+ Staff.db_schema.must_equal(:id=>{:primary_key=>true, :type=>:integer}, :name=>{:type=>:string}, :manager_id=>{:type=>:integer})
899
+ end
900
+
901
+ it "should use the correct primary key (which should have the same name in all subclasses)" do
902
+ [Employee, Manager, Executive, Staff].each{|c| c.primary_key.must_equal :id}
903
+ end
904
+
905
+ it "should have table_name return the table name of the most specific table" do
906
+ Employee.table_name.must_equal :employees
907
+ Manager.table_name.must_equal :employees
908
+ Executive.table_name.must_equal :employees
909
+ Staff.table_name.must_equal :employees
910
+ end
911
+
912
+ it "should delete the correct rows from all tables when deleting" do
913
+ Executive.load(:id=>1).delete
914
+ @db.sqls.must_equal ["DELETE FROM executives WHERE (id = 1)", "DELETE FROM managers WHERE (id = 1)", "DELETE FROM employees WHERE (id = 1)"]
915
+ end
916
+
917
+ it "should not allow deletion of frozen object" do
918
+ o = Executive.load(:id=>1)
919
+ o.freeze
920
+ proc{o.delete}.must_raise(Sequel::Error)
921
+ @db.sqls.must_equal []
922
+ end
923
+
924
+ it "should insert the correct rows into all tables when inserting" do
925
+ Executive.create(:num_managers=>3, :num_staff=>2, :name=>'E')
926
+ sqls = @db.sqls
927
+ sqls.length.must_equal 3
928
+ sqls[0].must_match(/INSERT INTO employees \(name\) VALUES \('E'\)/)
929
+ sqls[1].must_match(/INSERT INTO managers \((num_staff|id), (num_staff|id)\) VALUES \([12], [12]\)/)
930
+ sqls[2].must_match(/INSERT INTO executives \((num_managers|id), (num_managers|id)\) VALUES \([13], [13]\)/)
931
+ end
932
+
933
+ it "should insert the correct rows into all tables with a given primary key" do
934
+ e = Executive.new(:num_managers=>3, :num_staff=>2, :name=>'E')
935
+ e.id = 2
936
+ e.save
937
+ sqls = @db.sqls
938
+ sqls.length.must_equal 3
939
+ sqls[0].must_match(/INSERT INTO employees \((name|id), (name|id)\) VALUES \(('E'|2), ('E'|2)\)/)
940
+ sqls[1].must_match(/INSERT INTO managers \((num_staff|id), (num_staff|id)\) VALUES \(2, 2\)/)
941
+ sqls[2].must_match(/INSERT INTO executives \((num_managers|id), (num_managers|id)\) VALUES \([23], [23]\)/)
942
+ end
943
+
944
+ it "should update the correct rows in all tables when updating" do
945
+ Executive.load(:id=>2).update(:num_managers=>3, :num_staff=>2, :name=>'E')
946
+ @db.sqls.must_equal ["UPDATE employees SET name = 'E' WHERE (id = 2)", "UPDATE managers SET num_staff = 2 WHERE (id = 2)", "UPDATE executives SET num_managers = 3 WHERE (id = 2)"]
947
+ end
948
+
949
+ it "should handle many_to_one relationships correctly" do
950
+ Manager.dataset = Manager.dataset.with_fetch(:id=>3, :name=>'E', :num_staff=>3)
951
+ Staff.load(:manager_id=>3).manager.must_equal Manager.load(:id=>3, :name=>'E', :num_staff=>3)
952
+ @db.sqls.must_equal ['SELECT * FROM (SELECT employees.id, employees.name, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees WHERE (id = 3) LIMIT 1']
953
+ end
954
+
955
+ it "should handle one_to_many relationships correctly" do
956
+ Staff.dataset = Staff.dataset.with_fetch(:id=>1, :name=>'S', :manager_id=>3)
957
+ Executive.load(:id=>3).staff_members.must_equal [Staff.load(:id=>1, :name=>'S', :manager_id=>3)]
958
+ @db.sqls.must_equal ['SELECT * FROM (SELECT employees.id, employees.name, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id)) AS employees WHERE (employees.manager_id = 3)']
959
+ end
960
+ end
961
+
962
+ describe "class_table_inheritance plugin with duplicate columns with :alias option" do
963
+ before do
964
+ @db = Sequel.mock(:autoid=>proc{|sql| 1})
965
+ def @db.supports_schema_parsing?() true end
966
+ def @db.schema(table, opts={})
967
+ {:employees=>[[:id, {:primary_key=>true, :type=>:integer}], [:name, {:type=>:string}], [:kind, {:type=>:string}]],
968
+ :managers=>[[:id, {:type=>:integer}], [:name, {:type=>:string}]],
969
+ }[table.is_a?(Sequel::Dataset) ? table.first_source_table : table]
970
+ end
971
+ @db.extend_datasets do
972
+ def columns
973
+ {[:employees]=>[:id, :name, :kind],
974
+ [:managers]=>[:id, :name],
975
+ }[opts[:from] + (opts[:join] || []).map{|x| x.table}]
976
+ end
977
+ end
978
+ class ::Employee < Sequel::Model(@db)
979
+ def _save_refresh; @values[:id] = 1 end
980
+ def self.columns
981
+ dataset.columns || dataset.opts[:from].first.expression.columns
982
+ end
983
+ plugin :class_table_inheritance, :key=>:kind, :table_map=>{:Staff=>:staff}, :alias=>:employees
984
+ end
985
+ deprecated do
986
+ class ::Manager < Employee; end
987
+ end
988
+ @ds = Employee.dataset
989
+ @db.sqls
990
+ end
991
+ after do
992
+ Object.send(:remove_const, :Manager)
993
+ Object.send(:remove_const, :Employee)
994
+ end
995
+
996
+ it "should select names from both tables" do
997
+ Manager.dataset.sql.must_equal 'SELECT * FROM (SELECT employees.id, employees.name, employees.kind, managers.name FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees'
998
+ end
999
+ end
1000
+
1001
+ describe "class_table_inheritance plugin with :alias option" do
1002
+ before do
1003
+ @db = Sequel.mock(:autoid=>proc{|sql| 1})
1004
+ def @db.supports_schema_parsing?() true end
1005
+ def @db.schema(table, opts={})
1006
+ {:employees=>[[:id, {:primary_key=>true, :type=>:integer}], [:name, {:type=>:string}], [:kind, {:type=>:string}]],
1007
+ :managers=>[[:id, {:type=>:integer}], [:num_staff, {:type=>:integer}]],
1008
+ :executives=>[[:id, {:type=>:integer}], [:num_managers, {:type=>:integer}]],
1009
+ :staff=>[[:id, {:type=>:integer}], [:manager_id, {:type=>:integer}]],
1010
+ }[table.is_a?(Sequel::Dataset) ? table.first_source_table : table]
1011
+ end
1012
+ @db.extend_datasets do
1013
+ def columns
1014
+ {[:employees]=>[:id, :name, :kind],
1015
+ [:managers]=>[:id, :num_staff],
1016
+ [:executives]=>[:id, :num_managers],
1017
+ [:staff]=>[:id, :manager_id],
1018
+ [:employees, :managers]=>[:id, :name, :kind, :num_staff],
1019
+ [:employees, :managers, :executives]=>[:id, :name, :kind, :num_staff, :num_managers],
1020
+ [:employees, :staff]=>[:id, :name, :kind, :manager_id],
1021
+ }[opts[:from] + (opts[:join] || []).map{|x| x.table}]
1022
+ end
1023
+ end
1024
+ class ::Employee < Sequel::Model(@db)
1025
+ def _save_refresh; @values[:id] = 1 end
1026
+ def self.columns
1027
+ dataset.columns || dataset.opts[:from].first.expression.columns
1028
+ end
1029
+ plugin :class_table_inheritance, :key=>:kind, :table_map=>{:Staff=>:staff}, :alias=>:employees
1030
+ end
33
1031
  class ::Manager < Employee
34
1032
  one_to_many :staff_members, :class=>:Staff
35
1033
  end
@@ -63,12 +1061,12 @@ describe "class_table_inheritance plugin" do
63
1061
  it "should not attempt to use prepared statements" do
64
1062
  Manager.plugin :prepared_statements
65
1063
  Manager[1]
66
- @db.sqls.must_equal ["SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id) WHERE (managers.id = 1) LIMIT 1"]
1064
+ @db.sqls.must_equal ["SELECT id, name, kind, num_staff FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees WHERE (id = 1) LIMIT 1"]
67
1065
  Manager.load(:id=>1, :kind=>'Manager', :num_staff=>2).save
68
1066
  @db.sqls.must_equal ["UPDATE employees SET kind = 'Manager' WHERE (id = 1)", "UPDATE managers SET num_staff = 2 WHERE (id = 1)"]
69
1067
  @db.fetch = {:id=>1, :kind=>'Manager', :num_staff=>2}
70
1068
  Manager.load(:id=>1, :kind=>'Manager', :num_staff=>2).refresh
71
- @db.sqls.must_equal ["SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id) WHERE (managers.id = 1) LIMIT 1"]
1069
+ @db.sqls.must_equal ["SELECT * FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees WHERE (id = 1) LIMIT 1"]
72
1070
  end
73
1071
 
74
1072
  it "#cti_base_model should be the model that loaded the plugin" do
@@ -92,12 +1090,12 @@ describe "class_table_inheritance plugin" do
92
1090
  Manager[1].must_equal Ceo.load(:id=>1, :kind=>'Ceo')
93
1091
  end
94
1092
 
95
- it "should use a joined dataset in subclasses" do
1093
+ it "should use a subquery in subclasses" do
96
1094
  Employee.dataset.sql.must_equal 'SELECT * FROM employees'
97
- Manager.dataset.sql.must_equal 'SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)'
98
- Executive.dataset.sql.must_equal 'SELECT employees.id, employees.name, employees.kind, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id)'
99
- Ceo.dataset.sql.must_equal 'SELECT employees.id, employees.name, employees.kind, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id) WHERE (employees.kind IN (\'Ceo\'))'
100
- Staff.dataset.sql.must_equal 'SELECT employees.id, employees.name, employees.kind, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id)'
1095
+ Manager.dataset.sql.must_equal 'SELECT * FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees'
1096
+ Executive.dataset.sql.must_equal 'SELECT * FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id)) AS employees'
1097
+ Ceo.dataset.sql.must_equal 'SELECT * FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id) WHERE (employees.kind IN (\'Ceo\'))) AS employees'
1098
+ Staff.dataset.sql.must_equal 'SELECT * FROM (SELECT employees.id, employees.name, employees.kind, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id)) AS employees'
101
1099
  end
102
1100
 
103
1101
  it "should return rows with the correct class based on the polymorphic_key value" do
@@ -116,16 +1114,16 @@ describe "class_table_inheritance plugin" do
116
1114
  a.values.must_equal(:id=>1, :name=>'A', :kind=>'Ceo')
117
1115
  a.refresh.values.must_equal(:id=>1, :name=>'A', :kind=>'Ceo', :num_staff=>3, :num_managers=>2)
118
1116
  @db.sqls.must_equal ["SELECT * FROM employees LIMIT 1",
119
- "SELECT employees.id, employees.name, employees.kind, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id) WHERE ((employees.kind IN ('Ceo')) AND (executives.id = 1)) LIMIT 1"]
1117
+ "SELECT * FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id) WHERE (employees.kind IN ('Ceo'))) AS employees WHERE (id = 1) LIMIT 1"]
120
1118
  end
121
1119
 
122
1120
  it "should return rows with the current class if cti_key is nil" do
123
- Employee.plugin(:class_table_inheritance)
1121
+ Employee.plugin(:class_table_inheritance, :alias=>:employees)
124
1122
  Employee.dataset.with_fetch([{:kind=>'Employee'}, {:kind=>'Manager'}, {:kind=>'Executive'}, {:kind=>'Ceo'}, {:kind=>'Staff'}]).all.map{|x| x.class}.must_equal [Employee, Employee, Employee, Employee, Employee]
125
1123
  end
126
1124
 
127
1125
  it "should return rows with the current class if cti_key is nil in subclasses" do
128
- Employee.plugin(:class_table_inheritance)
1126
+ Employee.plugin(:class_table_inheritance, :alias=>:employees)
129
1127
  Object.send(:remove_const, :Executive)
130
1128
  Object.send(:remove_const, :Manager)
131
1129
  class ::Manager < Employee; end
@@ -134,7 +1132,7 @@ describe "class_table_inheritance plugin" do
134
1132
  end
135
1133
 
136
1134
  it "should handle a model map with integer values" do
137
- Employee.plugin(:class_table_inheritance, :key=>:kind, :model_map=>{0=>:Employee, 1=>:Manager, 2=>:Executive, 3=>:Ceo})
1135
+ Employee.plugin(:class_table_inheritance, :key=>:kind, :model_map=>{0=>:Employee, 1=>:Manager, 2=>:Executive, 3=>:Ceo}, :alias=>:employees)
138
1136
  Object.send(:remove_const, :Ceo)
139
1137
  Object.send(:remove_const, :Executive)
140
1138
  Object.send(:remove_const, :Manager)
@@ -192,18 +1190,18 @@ describe "class_table_inheritance plugin" do
192
1190
  end
193
1191
 
194
1192
  it "should allow specifying a map of names to tables to override implicit mapping" do
195
- Manager.dataset.sql.must_equal 'SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)'
196
- Staff.dataset.sql.must_equal 'SELECT employees.id, employees.name, employees.kind, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id)'
1193
+ Manager.dataset.sql.must_equal 'SELECT * FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees'
1194
+ Staff.dataset.sql.must_equal 'SELECT * FROM (SELECT employees.id, employees.name, employees.kind, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id)) AS employees'
197
1195
  end
198
1196
 
199
1197
  it "should lazily load attributes for columns in subclass tables" do
200
1198
  Manager.dataset = Manager.dataset.with_fetch(:id=>1, :name=>'J', :kind=>'Ceo', :num_staff=>2)
201
1199
  m = Manager[1]
202
- @db.sqls.must_equal ['SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id) WHERE (managers.id = 1) LIMIT 1']
1200
+ @db.sqls.must_equal ['SELECT * FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees WHERE (id = 1) LIMIT 1']
203
1201
  @db.fetch = {:num_managers=>3}
204
1202
  m.must_be_kind_of Ceo
205
1203
  m.num_managers.must_equal 3
206
- @db.sqls.must_equal ['SELECT executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id) WHERE (executives.id = 1) LIMIT 1']
1204
+ @db.sqls.must_equal ['SELECT employees.num_managers FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id)) AS employees WHERE (employees.id = 1) LIMIT 1']
207
1205
  m.values.must_equal(:id=>1, :name=>'J', :kind=>'Ceo', :num_staff=>2, :num_managers=>3)
208
1206
  end
209
1207
 
@@ -214,7 +1212,7 @@ describe "class_table_inheritance plugin" do
214
1212
  e.must_be_kind_of(Ceo)
215
1213
  @db.sqls.must_equal ["SELECT * FROM employees WHERE (id = 1) LIMIT 1"]
216
1214
  e.num_staff.must_equal 2
217
- @db.sqls.must_equal ["SELECT managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id) WHERE (managers.id = 1) LIMIT 1"]
1215
+ @db.sqls.must_equal ["SELECT employees.num_staff FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees WHERE (employees.id = 1) LIMIT 1"]
218
1216
  end
219
1217
 
220
1218
  it "should eagerly load lazily columns in subclasses when loaded from parent class" do
@@ -224,9 +1222,9 @@ describe "class_table_inheritance plugin" do
224
1222
  e.must_be_kind_of(Ceo)
225
1223
  @db.sqls.must_equal ["SELECT * FROM employees"]
226
1224
  e.num_staff.must_equal 2
227
- @db.sqls.must_equal ["SELECT managers.id, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id) WHERE (managers.id IN (1))"]
1225
+ @db.sqls.must_equal ["SELECT employees.id, employees.num_staff FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees WHERE (employees.id IN (1))"]
228
1226
  e.num_managers.must_equal 3
229
- @db.sqls.must_equal ['SELECT executives.id, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id) WHERE (executives.id IN (1))']
1227
+ @db.sqls.must_equal ['SELECT employees.id, employees.num_managers FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id)) AS employees WHERE (employees.id IN (1))']
230
1228
  end
231
1229
 
232
1230
  it "should include schema for columns for tables for ancestor classes" do
@@ -242,10 +1240,10 @@ describe "class_table_inheritance plugin" do
242
1240
 
243
1241
  it "should have table_name return the table name of the most specific table" do
244
1242
  Employee.table_name.must_equal :employees
245
- Manager.table_name.must_equal :managers
246
- Executive.table_name.must_equal :executives
247
- Ceo.table_name.must_equal :executives
248
- Staff.table_name.must_equal :staff
1243
+ Manager.table_name.must_equal :employees
1244
+ Executive.table_name.must_equal :employees
1245
+ Ceo.table_name.must_equal :employees
1246
+ Staff.table_name.must_equal :employees
249
1247
  end
250
1248
 
251
1249
  it "should delete the correct rows from all tables when deleting" do
@@ -306,17 +1304,17 @@ describe "class_table_inheritance plugin" do
306
1304
  it "should handle many_to_one relationships correctly" do
307
1305
  Manager.dataset = Manager.dataset.with_fetch(:id=>3, :name=>'E', :kind=>'Ceo', :num_managers=>3)
308
1306
  Staff.load(:manager_id=>3).manager.must_equal Ceo.load(:id=>3, :name=>'E', :kind=>'Ceo', :num_managers=>3)
309
- @db.sqls.must_equal ['SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id) WHERE (managers.id = 3) LIMIT 1']
1307
+ @db.sqls.must_equal ['SELECT * FROM (SELECT employees.id, employees.name, employees.kind, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees WHERE (id = 3) LIMIT 1']
310
1308
  end
311
1309
 
312
1310
  it "should handle one_to_many relationships correctly" do
313
1311
  Staff.dataset = Staff.dataset.with_fetch(:id=>1, :name=>'S', :kind=>'Staff', :manager_id=>3)
314
1312
  Ceo.load(:id=>3).staff_members.must_equal [Staff.load(:id=>1, :name=>'S', :kind=>'Staff', :manager_id=>3)]
315
- @db.sqls.must_equal ['SELECT employees.id, employees.name, employees.kind, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id) WHERE (staff.manager_id = 3)']
1313
+ @db.sqls.must_equal ['SELECT * FROM (SELECT employees.id, employees.name, employees.kind, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id)) AS employees WHERE (employees.manager_id = 3)']
316
1314
  end
317
1315
  end
318
1316
 
319
- describe "class_table_inheritance plugin without sti_key" do
1317
+ describe "class_table_inheritance plugin without sti_key with :alias option" do
320
1318
  before do
321
1319
  @db = Sequel.mock(:autoid=>proc{|sql| 1})
322
1320
  def @db.supports_schema_parsing?() true end
@@ -342,9 +1340,9 @@ describe "class_table_inheritance plugin without sti_key" do
342
1340
  class ::Employee < Sequel::Model(@db)
343
1341
  def _save_refresh; @values[:id] = 1 end
344
1342
  def self.columns
345
- dataset.columns
1343
+ dataset.columns || dataset.opts[:from].first.expression.columns
346
1344
  end
347
- plugin :class_table_inheritance, :table_map=>{:Staff=>:staff}
1345
+ plugin :class_table_inheritance, :table_map=>{:Staff=>:staff}, :alias=>:employees
348
1346
  end
349
1347
  class ::Manager < Employee
350
1348
  one_to_many :staff_members, :class=>:Staff
@@ -378,9 +1376,9 @@ describe "class_table_inheritance plugin without sti_key" do
378
1376
 
379
1377
  it "should use a joined dataset in subclasses" do
380
1378
  Employee.dataset.sql.must_equal 'SELECT * FROM employees'
381
- Manager.dataset.sql.must_equal 'SELECT employees.id, employees.name, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)'
382
- Executive.dataset.sql.must_equal 'SELECT employees.id, employees.name, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id)'
383
- Staff.dataset.sql.must_equal 'SELECT employees.id, employees.name, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id)'
1379
+ Manager.dataset.sql.must_equal 'SELECT * FROM (SELECT employees.id, employees.name, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees'
1380
+ Executive.dataset.sql.must_equal 'SELECT * FROM (SELECT employees.id, employees.name, managers.num_staff, executives.num_managers FROM employees INNER JOIN managers ON (managers.id = employees.id) INNER JOIN executives ON (executives.id = managers.id)) AS employees'
1381
+ Staff.dataset.sql.must_equal 'SELECT * FROM (SELECT employees.id, employees.name, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id)) AS employees'
384
1382
  end
385
1383
 
386
1384
  it "should return rows with the current class if cti_key is nil" do
@@ -403,9 +1401,9 @@ describe "class_table_inheritance plugin without sti_key" do
403
1401
 
404
1402
  it "should have table_name return the table name of the most specific table" do
405
1403
  Employee.table_name.must_equal :employees
406
- Manager.table_name.must_equal :managers
407
- Executive.table_name.must_equal :executives
408
- Staff.table_name.must_equal :staff
1404
+ Manager.table_name.must_equal :employees
1405
+ Executive.table_name.must_equal :employees
1406
+ Staff.table_name.must_equal :employees
409
1407
  end
410
1408
 
411
1409
  it "should delete the correct rows from all tables when deleting" do
@@ -448,17 +1446,17 @@ describe "class_table_inheritance plugin without sti_key" do
448
1446
  it "should handle many_to_one relationships correctly" do
449
1447
  Manager.dataset = Manager.dataset.with_fetch(:id=>3, :name=>'E', :num_staff=>3)
450
1448
  Staff.load(:manager_id=>3).manager.must_equal Manager.load(:id=>3, :name=>'E', :num_staff=>3)
451
- @db.sqls.must_equal ['SELECT employees.id, employees.name, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id) WHERE (managers.id = 3) LIMIT 1']
1449
+ @db.sqls.must_equal ['SELECT * FROM (SELECT employees.id, employees.name, managers.num_staff FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees WHERE (id = 3) LIMIT 1']
452
1450
  end
453
1451
 
454
1452
  it "should handle one_to_many relationships correctly" do
455
1453
  Staff.dataset = Staff.dataset.with_fetch(:id=>1, :name=>'S', :manager_id=>3)
456
1454
  Executive.load(:id=>3).staff_members.must_equal [Staff.load(:id=>1, :name=>'S', :manager_id=>3)]
457
- @db.sqls.must_equal ['SELECT employees.id, employees.name, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id) WHERE (staff.manager_id = 3)']
1455
+ @db.sqls.must_equal ['SELECT * FROM (SELECT employees.id, employees.name, staff.manager_id FROM employees INNER JOIN staff ON (staff.id = employees.id)) AS employees WHERE (employees.manager_id = 3)']
458
1456
  end
459
1457
  end
460
1458
 
461
- describe "class_table_inheritance plugin with duplicate columns" do
1459
+ describe "class_table_inheritance plugin with duplicate columns with :alias option" do
462
1460
  before do
463
1461
  @db = Sequel.mock(:autoid=>proc{|sql| 1})
464
1462
  def @db.supports_schema_parsing?() true end
@@ -477,9 +1475,9 @@ describe "class_table_inheritance plugin with duplicate columns" do
477
1475
  class ::Employee < Sequel::Model(@db)
478
1476
  def _save_refresh; @values[:id] = 1 end
479
1477
  def self.columns
480
- dataset.columns
1478
+ dataset.columns || dataset.opts[:from].first.expression.columns
481
1479
  end
482
- plugin :class_table_inheritance, :key=>:kind, :table_map=>{:Staff=>:staff}
1480
+ plugin :class_table_inheritance, :key=>:kind, :table_map=>{:Staff=>:staff}, :alias=>:employees
483
1481
  end
484
1482
  deprecated do
485
1483
  class ::Manager < Employee; end
@@ -493,6 +1491,6 @@ describe "class_table_inheritance plugin with duplicate columns" do
493
1491
  end
494
1492
 
495
1493
  it "should select names from both tables" do
496
- Manager.dataset.sql.must_equal 'SELECT employees.id, employees.name, employees.kind, managers.name FROM employees INNER JOIN managers ON (managers.id = employees.id)'
1494
+ Manager.dataset.sql.must_equal 'SELECT * FROM (SELECT employees.id, employees.name, employees.kind, managers.name FROM employees INNER JOIN managers ON (managers.id = employees.id)) AS employees'
497
1495
  end
498
1496
  end