sequel 4.45.0 → 4.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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