viking-sequel 3.10.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 (237) hide show
  1. data/CHANGELOG +3134 -0
  2. data/COPYING +19 -0
  3. data/README.rdoc +723 -0
  4. data/Rakefile +193 -0
  5. data/bin/sequel +196 -0
  6. data/doc/advanced_associations.rdoc +644 -0
  7. data/doc/cheat_sheet.rdoc +218 -0
  8. data/doc/dataset_basics.rdoc +106 -0
  9. data/doc/dataset_filtering.rdoc +158 -0
  10. data/doc/opening_databases.rdoc +296 -0
  11. data/doc/prepared_statements.rdoc +104 -0
  12. data/doc/reflection.rdoc +84 -0
  13. data/doc/release_notes/1.0.txt +38 -0
  14. data/doc/release_notes/1.1.txt +143 -0
  15. data/doc/release_notes/1.3.txt +101 -0
  16. data/doc/release_notes/1.4.0.txt +53 -0
  17. data/doc/release_notes/1.5.0.txt +155 -0
  18. data/doc/release_notes/2.0.0.txt +298 -0
  19. data/doc/release_notes/2.1.0.txt +271 -0
  20. data/doc/release_notes/2.10.0.txt +328 -0
  21. data/doc/release_notes/2.11.0.txt +215 -0
  22. data/doc/release_notes/2.12.0.txt +534 -0
  23. data/doc/release_notes/2.2.0.txt +253 -0
  24. data/doc/release_notes/2.3.0.txt +88 -0
  25. data/doc/release_notes/2.4.0.txt +106 -0
  26. data/doc/release_notes/2.5.0.txt +137 -0
  27. data/doc/release_notes/2.6.0.txt +157 -0
  28. data/doc/release_notes/2.7.0.txt +166 -0
  29. data/doc/release_notes/2.8.0.txt +171 -0
  30. data/doc/release_notes/2.9.0.txt +97 -0
  31. data/doc/release_notes/3.0.0.txt +221 -0
  32. data/doc/release_notes/3.1.0.txt +406 -0
  33. data/doc/release_notes/3.10.0.txt +286 -0
  34. data/doc/release_notes/3.2.0.txt +268 -0
  35. data/doc/release_notes/3.3.0.txt +192 -0
  36. data/doc/release_notes/3.4.0.txt +325 -0
  37. data/doc/release_notes/3.5.0.txt +510 -0
  38. data/doc/release_notes/3.6.0.txt +366 -0
  39. data/doc/release_notes/3.7.0.txt +179 -0
  40. data/doc/release_notes/3.8.0.txt +151 -0
  41. data/doc/release_notes/3.9.0.txt +233 -0
  42. data/doc/schema.rdoc +36 -0
  43. data/doc/sharding.rdoc +113 -0
  44. data/doc/virtual_rows.rdoc +205 -0
  45. data/lib/sequel.rb +1 -0
  46. data/lib/sequel/adapters/ado.rb +90 -0
  47. data/lib/sequel/adapters/ado/mssql.rb +30 -0
  48. data/lib/sequel/adapters/amalgalite.rb +176 -0
  49. data/lib/sequel/adapters/db2.rb +139 -0
  50. data/lib/sequel/adapters/dbi.rb +113 -0
  51. data/lib/sequel/adapters/do.rb +188 -0
  52. data/lib/sequel/adapters/do/mysql.rb +49 -0
  53. data/lib/sequel/adapters/do/postgres.rb +91 -0
  54. data/lib/sequel/adapters/do/sqlite.rb +40 -0
  55. data/lib/sequel/adapters/firebird.rb +283 -0
  56. data/lib/sequel/adapters/informix.rb +77 -0
  57. data/lib/sequel/adapters/jdbc.rb +587 -0
  58. data/lib/sequel/adapters/jdbc/as400.rb +58 -0
  59. data/lib/sequel/adapters/jdbc/h2.rb +133 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
  61. data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
  62. data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
  64. data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
  65. data/lib/sequel/adapters/mysql.rb +421 -0
  66. data/lib/sequel/adapters/odbc.rb +143 -0
  67. data/lib/sequel/adapters/odbc/mssql.rb +42 -0
  68. data/lib/sequel/adapters/openbase.rb +64 -0
  69. data/lib/sequel/adapters/oracle.rb +131 -0
  70. data/lib/sequel/adapters/postgres.rb +504 -0
  71. data/lib/sequel/adapters/shared/mssql.rb +490 -0
  72. data/lib/sequel/adapters/shared/mysql.rb +498 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +195 -0
  74. data/lib/sequel/adapters/shared/postgres.rb +830 -0
  75. data/lib/sequel/adapters/shared/progress.rb +44 -0
  76. data/lib/sequel/adapters/shared/sqlite.rb +389 -0
  77. data/lib/sequel/adapters/sqlite.rb +224 -0
  78. data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
  79. data/lib/sequel/connection_pool.rb +99 -0
  80. data/lib/sequel/connection_pool/sharded_single.rb +84 -0
  81. data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
  82. data/lib/sequel/connection_pool/single.rb +29 -0
  83. data/lib/sequel/connection_pool/threaded.rb +150 -0
  84. data/lib/sequel/core.rb +293 -0
  85. data/lib/sequel/core_sql.rb +241 -0
  86. data/lib/sequel/database.rb +1079 -0
  87. data/lib/sequel/database/schema_generator.rb +327 -0
  88. data/lib/sequel/database/schema_methods.rb +203 -0
  89. data/lib/sequel/database/schema_sql.rb +320 -0
  90. data/lib/sequel/dataset.rb +32 -0
  91. data/lib/sequel/dataset/actions.rb +441 -0
  92. data/lib/sequel/dataset/features.rb +86 -0
  93. data/lib/sequel/dataset/graph.rb +254 -0
  94. data/lib/sequel/dataset/misc.rb +119 -0
  95. data/lib/sequel/dataset/mutation.rb +64 -0
  96. data/lib/sequel/dataset/prepared_statements.rb +227 -0
  97. data/lib/sequel/dataset/query.rb +709 -0
  98. data/lib/sequel/dataset/sql.rb +996 -0
  99. data/lib/sequel/exceptions.rb +51 -0
  100. data/lib/sequel/extensions/blank.rb +43 -0
  101. data/lib/sequel/extensions/inflector.rb +242 -0
  102. data/lib/sequel/extensions/looser_typecasting.rb +21 -0
  103. data/lib/sequel/extensions/migration.rb +239 -0
  104. data/lib/sequel/extensions/named_timezones.rb +61 -0
  105. data/lib/sequel/extensions/pagination.rb +100 -0
  106. data/lib/sequel/extensions/pretty_table.rb +82 -0
  107. data/lib/sequel/extensions/query.rb +52 -0
  108. data/lib/sequel/extensions/schema_dumper.rb +271 -0
  109. data/lib/sequel/extensions/sql_expr.rb +122 -0
  110. data/lib/sequel/extensions/string_date_time.rb +46 -0
  111. data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
  112. data/lib/sequel/metaprogramming.rb +9 -0
  113. data/lib/sequel/model.rb +120 -0
  114. data/lib/sequel/model/associations.rb +1514 -0
  115. data/lib/sequel/model/base.rb +1069 -0
  116. data/lib/sequel/model/default_inflections.rb +45 -0
  117. data/lib/sequel/model/errors.rb +39 -0
  118. data/lib/sequel/model/exceptions.rb +21 -0
  119. data/lib/sequel/model/inflections.rb +162 -0
  120. data/lib/sequel/model/plugins.rb +70 -0
  121. data/lib/sequel/plugins/active_model.rb +59 -0
  122. data/lib/sequel/plugins/association_dependencies.rb +103 -0
  123. data/lib/sequel/plugins/association_proxies.rb +41 -0
  124. data/lib/sequel/plugins/boolean_readers.rb +53 -0
  125. data/lib/sequel/plugins/caching.rb +141 -0
  126. data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
  127. data/lib/sequel/plugins/composition.rb +138 -0
  128. data/lib/sequel/plugins/force_encoding.rb +72 -0
  129. data/lib/sequel/plugins/hook_class_methods.rb +126 -0
  130. data/lib/sequel/plugins/identity_map.rb +116 -0
  131. data/lib/sequel/plugins/instance_filters.rb +98 -0
  132. data/lib/sequel/plugins/instance_hooks.rb +57 -0
  133. data/lib/sequel/plugins/lazy_attributes.rb +77 -0
  134. data/lib/sequel/plugins/many_through_many.rb +208 -0
  135. data/lib/sequel/plugins/nested_attributes.rb +206 -0
  136. data/lib/sequel/plugins/optimistic_locking.rb +81 -0
  137. data/lib/sequel/plugins/rcte_tree.rb +281 -0
  138. data/lib/sequel/plugins/schema.rb +66 -0
  139. data/lib/sequel/plugins/serialization.rb +166 -0
  140. data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
  141. data/lib/sequel/plugins/subclasses.rb +45 -0
  142. data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
  143. data/lib/sequel/plugins/timestamps.rb +87 -0
  144. data/lib/sequel/plugins/touch.rb +118 -0
  145. data/lib/sequel/plugins/typecast_on_load.rb +72 -0
  146. data/lib/sequel/plugins/validation_class_methods.rb +405 -0
  147. data/lib/sequel/plugins/validation_helpers.rb +223 -0
  148. data/lib/sequel/sql.rb +1020 -0
  149. data/lib/sequel/timezones.rb +161 -0
  150. data/lib/sequel/version.rb +12 -0
  151. data/lib/sequel_core.rb +1 -0
  152. data/lib/sequel_model.rb +1 -0
  153. data/spec/adapters/firebird_spec.rb +407 -0
  154. data/spec/adapters/informix_spec.rb +97 -0
  155. data/spec/adapters/mssql_spec.rb +403 -0
  156. data/spec/adapters/mysql_spec.rb +1019 -0
  157. data/spec/adapters/oracle_spec.rb +286 -0
  158. data/spec/adapters/postgres_spec.rb +969 -0
  159. data/spec/adapters/spec_helper.rb +51 -0
  160. data/spec/adapters/sqlite_spec.rb +432 -0
  161. data/spec/core/connection_pool_spec.rb +808 -0
  162. data/spec/core/core_sql_spec.rb +417 -0
  163. data/spec/core/database_spec.rb +1662 -0
  164. data/spec/core/dataset_spec.rb +3827 -0
  165. data/spec/core/expression_filters_spec.rb +595 -0
  166. data/spec/core/object_graph_spec.rb +296 -0
  167. data/spec/core/schema_generator_spec.rb +159 -0
  168. data/spec/core/schema_spec.rb +830 -0
  169. data/spec/core/spec_helper.rb +56 -0
  170. data/spec/core/version_spec.rb +7 -0
  171. data/spec/extensions/active_model_spec.rb +76 -0
  172. data/spec/extensions/association_dependencies_spec.rb +127 -0
  173. data/spec/extensions/association_proxies_spec.rb +50 -0
  174. data/spec/extensions/blank_spec.rb +67 -0
  175. data/spec/extensions/boolean_readers_spec.rb +92 -0
  176. data/spec/extensions/caching_spec.rb +250 -0
  177. data/spec/extensions/class_table_inheritance_spec.rb +252 -0
  178. data/spec/extensions/composition_spec.rb +194 -0
  179. data/spec/extensions/force_encoding_spec.rb +117 -0
  180. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  181. data/spec/extensions/identity_map_spec.rb +202 -0
  182. data/spec/extensions/inflector_spec.rb +181 -0
  183. data/spec/extensions/instance_filters_spec.rb +55 -0
  184. data/spec/extensions/instance_hooks_spec.rb +133 -0
  185. data/spec/extensions/lazy_attributes_spec.rb +153 -0
  186. data/spec/extensions/looser_typecasting_spec.rb +39 -0
  187. data/spec/extensions/many_through_many_spec.rb +884 -0
  188. data/spec/extensions/migration_spec.rb +332 -0
  189. data/spec/extensions/named_timezones_spec.rb +72 -0
  190. data/spec/extensions/nested_attributes_spec.rb +396 -0
  191. data/spec/extensions/optimistic_locking_spec.rb +100 -0
  192. data/spec/extensions/pagination_spec.rb +99 -0
  193. data/spec/extensions/pretty_table_spec.rb +91 -0
  194. data/spec/extensions/query_spec.rb +85 -0
  195. data/spec/extensions/rcte_tree_spec.rb +205 -0
  196. data/spec/extensions/schema_dumper_spec.rb +357 -0
  197. data/spec/extensions/schema_spec.rb +127 -0
  198. data/spec/extensions/serialization_spec.rb +209 -0
  199. data/spec/extensions/single_table_inheritance_spec.rb +96 -0
  200. data/spec/extensions/spec_helper.rb +91 -0
  201. data/spec/extensions/sql_expr_spec.rb +89 -0
  202. data/spec/extensions/string_date_time_spec.rb +93 -0
  203. data/spec/extensions/subclasses_spec.rb +52 -0
  204. data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
  205. data/spec/extensions/thread_local_timezones_spec.rb +45 -0
  206. data/spec/extensions/timestamps_spec.rb +150 -0
  207. data/spec/extensions/touch_spec.rb +155 -0
  208. data/spec/extensions/typecast_on_load_spec.rb +69 -0
  209. data/spec/extensions/validation_class_methods_spec.rb +984 -0
  210. data/spec/extensions/validation_helpers_spec.rb +438 -0
  211. data/spec/integration/associations_test.rb +281 -0
  212. data/spec/integration/database_test.rb +26 -0
  213. data/spec/integration/dataset_test.rb +963 -0
  214. data/spec/integration/eager_loader_test.rb +734 -0
  215. data/spec/integration/model_test.rb +130 -0
  216. data/spec/integration/plugin_test.rb +814 -0
  217. data/spec/integration/prepared_statement_test.rb +213 -0
  218. data/spec/integration/schema_test.rb +361 -0
  219. data/spec/integration/spec_helper.rb +73 -0
  220. data/spec/integration/timezone_test.rb +55 -0
  221. data/spec/integration/transaction_test.rb +122 -0
  222. data/spec/integration/type_test.rb +96 -0
  223. data/spec/model/association_reflection_spec.rb +175 -0
  224. data/spec/model/associations_spec.rb +2633 -0
  225. data/spec/model/base_spec.rb +418 -0
  226. data/spec/model/dataset_methods_spec.rb +78 -0
  227. data/spec/model/eager_loading_spec.rb +1391 -0
  228. data/spec/model/hooks_spec.rb +240 -0
  229. data/spec/model/inflector_spec.rb +26 -0
  230. data/spec/model/model_spec.rb +593 -0
  231. data/spec/model/plugins_spec.rb +236 -0
  232. data/spec/model/record_spec.rb +1500 -0
  233. data/spec/model/spec_helper.rb +97 -0
  234. data/spec/model/validations_spec.rb +153 -0
  235. data/spec/rcov.opts +6 -0
  236. data/spec/spec_config.rb.example +10 -0
  237. metadata +346 -0
@@ -0,0 +1,130 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper.rb')
2
+
3
+ describe "Sequel::Model basic support" do
4
+ before do
5
+ @db = INTEGRATION_DB
6
+ @db.create_table!(:items) do
7
+ primary_key :id
8
+ String :name
9
+ end
10
+ class ::Item < Sequel::Model(@db)
11
+ end
12
+ end
13
+ after do
14
+ @db.drop_table(:items)
15
+ Object.send(:remove_const, :Item)
16
+ end
17
+
18
+ specify ".find should return first matching item" do
19
+ Item.all.should == []
20
+ Item.find(:name=>'J').should == nil
21
+ Item.create(:name=>'J')
22
+ Item.find(:name=>'J').should == Item.load(:id=>1, :name=>'J')
23
+ end
24
+
25
+ specify ".find_or_create should return first matching item, or create it if it doesn't exist" do
26
+ Item.all.should == []
27
+ Item.find_or_create(:name=>'J').should == Item.load(:id=>1, :name=>'J')
28
+ Item.all.should == [Item.load(:id=>1, :name=>'J')]
29
+ Item.find_or_create(:name=>'J').should == Item.load(:id=>1, :name=>'J')
30
+ Item.all.should == [Item.load(:id=>1, :name=>'J')]
31
+ end
32
+
33
+ specify "should not raise an error if the implied database table doesn't exist " do
34
+ class ::Item::Thing < Sequel::Model(@db)
35
+ set_dataset :items
36
+ end
37
+ Item.create(:name=>'J')
38
+ Item::Thing.first.should == Item::Thing.load(:id=>1, :name=>'J')
39
+ end
40
+
41
+ specify "should work correctly when a dataset restricts the colums it selects" do
42
+ class ::Item::Thing < Sequel::Model(@db[:items].select(:name))
43
+ end
44
+ Item.create(:name=>'J')
45
+ Item::Thing.first.should == Item::Thing.load(:name=>'J')
46
+ end
47
+
48
+ specify "#delete should delete items correctly" do
49
+ i = Item.create(:name=>'J')
50
+ Item.count.should == 1
51
+ i.delete
52
+ Item.count.should == 0
53
+ end
54
+
55
+ specify "#exists? should return whether the item is still in the database" do
56
+ i = Item.create(:name=>'J')
57
+ i.exists?.should == true
58
+ Item.delete
59
+ i.exists?.should == false
60
+ end
61
+
62
+ specify "#save should only update specified columns when saving" do
63
+ @db.create_table!(:items) do
64
+ primary_key :id
65
+ String :name
66
+ Integer :num
67
+ end
68
+ Item.dataset = Item.dataset
69
+ i = Item.create(:name=>'J', :num=>1)
70
+ Item.all.should == [Item.load(:id=>1, :name=>'J', :num=>1)]
71
+ i.set(:name=>'K', :num=>2)
72
+ i.save(:name)
73
+ Item.all.should == [Item.load(:id=>1, :name=>'K', :num=>1)]
74
+ i.set(:name=>'L')
75
+ i.save(:num)
76
+ Item.all.should == [Item.load(:id=>1, :name=>'K', :num=>2)]
77
+ end
78
+
79
+ specify "#save should check that the only a single row is modified, unless require_modification is false" do
80
+ i = Item.create(:name=>'a')
81
+ i.require_modification = true
82
+ i.delete
83
+ proc{i.save}.should raise_error(Sequel::NoExistingObject)
84
+ proc{i.delete}.should raise_error(Sequel::NoExistingObject)
85
+
86
+ i.require_modification = false
87
+ i.save
88
+ i.delete
89
+ end
90
+
91
+ specify ".to_hash should return a hash keyed on primary key if no argument provided" do
92
+ i = Item.create(:name=>'J')
93
+ Item.to_hash.should == {1=>Item.load(:id=>1, :name=>'J')}
94
+ end
95
+
96
+ specify ".to_hash should return a hash keyed on argument if one argument provided" do
97
+ i = Item.create(:name=>'J')
98
+ Item.to_hash(:name).should == {'J'=>Item.load(:id=>1, :name=>'J')}
99
+ end
100
+
101
+ specify "should be marshallable before and after saving if marshallable! is called" do
102
+ i = Item.new(:name=>'J')
103
+ s = nil
104
+ i2 = nil
105
+ i.marshallable!
106
+ proc{s = Marshal.dump(i)}.should_not raise_error
107
+ proc{i2 = Marshal.load(s)}.should_not raise_error
108
+ i2.should == i
109
+
110
+ i.save
111
+ i.marshallable!
112
+ proc{s = Marshal.dump(i)}.should_not raise_error
113
+ proc{i2 = Marshal.load(s)}.should_not raise_error
114
+ i2.should == i
115
+
116
+ i.save
117
+ i.marshallable!
118
+ proc{s = Marshal.dump(i)}.should_not raise_error
119
+ proc{i2 = Marshal.load(s)}.should_not raise_error
120
+ i2.should == i
121
+ end
122
+
123
+ specify "#lock! should lock records" do
124
+ Item.db.transaction do
125
+ i = Item.create(:name=>'J')
126
+ i.lock!
127
+ i.update(:name=>'K')
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,814 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper.rb')
2
+
3
+ # H2 and MSSQL don't support USING joins
4
+ unless [:h2, :mssql].include?(INTEGRATION_DB.database_type)
5
+ describe "Class Table Inheritance Plugin" do
6
+ before do
7
+ @db = INTEGRATION_DB
8
+ @db.instance_variable_set(:@schemas, {})
9
+ @db.create_table!(:employees) do
10
+ primary_key :id
11
+ String :name
12
+ String :kind
13
+ end
14
+ @db.create_table!(:managers) do
15
+ foreign_key :id, :employees, :primary_key=>true
16
+ Integer :num_staff
17
+ end
18
+ @db.create_table!(:executives) do
19
+ foreign_key :id, :managers, :primary_key=>true
20
+ Integer :num_managers
21
+ end
22
+ @db.create_table!(:staff) do
23
+ foreign_key :id, :employees, :primary_key=>true
24
+ foreign_key :manager_id, :managers
25
+ end
26
+ class ::Employee < Sequel::Model(@db)
27
+ plugin :class_table_inheritance, :key=>:kind, :table_map=>{:Staff=>:staff}
28
+ end
29
+ class ::Manager < Employee
30
+ one_to_many :staff_members, :class=>:Staff
31
+ end
32
+ class ::Executive < Manager
33
+ end
34
+ class ::Staff < Employee
35
+ many_to_one :manager
36
+ end
37
+
38
+ @i1 =@db[:employees].insert(:name=>'E', :kind=>'Employee')
39
+ @i2 = @db[:employees].insert(:name=>'S', :kind=>'Staff')
40
+ @i3 = @db[:employees].insert(:name=>'M', :kind=>'Manager')
41
+ @i4 = @db[:employees].insert(:name=>'Ex', :kind=>'Executive')
42
+ @db[:managers].insert(:id=>@i3, :num_staff=>7)
43
+ @db[:managers].insert(:id=>@i4, :num_staff=>5)
44
+ @db[:executives].insert(:id=>@i4, :num_managers=>6)
45
+ @db[:staff].insert(:id=>@i2, :manager_id=>@i4)
46
+
47
+ clear_sqls
48
+ end
49
+ after do
50
+ @db.drop_table :staff, :executives, :managers, :employees
51
+ [:Executive, :Manager, :Staff, :Employee].each{|s| Object.send(:remove_const, s)}
52
+ end
53
+
54
+ specify "should return rows as subclass instances" do
55
+ Employee.order(:id).all.should == [
56
+ Employee.load(:id=>@i1, :name=>'E', :kind=>'Employee'),
57
+ Staff.load(:id=>@i2, :name=>'S', :kind=>'Staff'),
58
+ Manager.load(:id=>@i3, :name=>'M', :kind=>'Manager'),
59
+ Executive.load(:id=>@i4, :name=>'Ex', :kind=>'Executive')
60
+ ]
61
+ end
62
+
63
+ specify "should lazily load columns in subclass tables" do
64
+ a = Employee.order(:id).all
65
+ a[1][:manager_id].should == nil
66
+ a[1].manager_id.should == @i4
67
+ end
68
+
69
+ specify "should include schema for columns for tables for ancestor classes" do
70
+ Employee.db_schema.keys.sort_by{|x| x.to_s}.should == [:id, :kind, :name]
71
+ Staff.db_schema.keys.sort_by{|x| x.to_s}.should == [:id, :kind, :manager_id, :name]
72
+ Manager.db_schema.keys.sort_by{|x| x.to_s}.should == [:id, :kind, :name, :num_staff]
73
+ Executive.db_schema.keys.sort_by{|x| x.to_s}.should == [:id, :kind, :name, :num_managers, :num_staff]
74
+ end
75
+
76
+ specify "should include columns for tables for ancestor classes" do
77
+ Employee.columns.should == [:id, :name, :kind]
78
+ Staff.columns.should == [:id, :name, :kind, :manager_id]
79
+ Manager.columns.should == [:id, :name, :kind, :num_staff]
80
+ Executive.columns.should == [:id, :name, :kind, :num_staff, :num_managers]
81
+ end
82
+
83
+ specify "should delete rows from all tables" do
84
+ e = Executive.first
85
+ i = e.id
86
+ e.staff_members_dataset.destroy
87
+ e.destroy
88
+ @db[:executives][:id=>i].should == nil
89
+ @db[:managers][:id=>i].should == nil
90
+ @db[:employees][:id=>i].should == nil
91
+ end
92
+
93
+ # See http://www.sqlite.org/src/tktview/3338b3fa19ac4abee6c475126a2e6d9d61f26ab1
94
+ cspecify "should insert rows into all tables", :sqlite do
95
+ e = Executive.create(:name=>'Ex2', :num_managers=>8, :num_staff=>9)
96
+ i = e.id
97
+ @db[:employees][:id=>i].should == {:id=>i, :name=>'Ex2', :kind=>'Executive'}
98
+ @db[:managers][:id=>i].should == {:id=>i, :num_staff=>9}
99
+ @db[:executives][:id=>i].should == {:id=>i, :num_managers=>8}
100
+ end
101
+
102
+ specify "should update rows in all tables" do
103
+ Executive.first.update(:name=>'Ex2', :num_managers=>8, :num_staff=>9)
104
+ @db[:employees][:id=>@i4].should == {:id=>@i4, :name=>'Ex2', :kind=>'Executive'}
105
+ @db[:managers][:id=>@i4].should == {:id=>@i4, :num_staff=>9}
106
+ @db[:executives][:id=>@i4].should == {:id=>@i4, :num_managers=>8}
107
+ end
108
+
109
+ cspecify "should handle many_to_one relationships", :sqlite do
110
+ m = Staff.first.manager
111
+ m.should == Manager[@i4]
112
+ m.should be_a_kind_of(Executive)
113
+ end
114
+
115
+ cspecify "should handle eagerly loading many_to_one relationships", :sqlite do
116
+ Staff.limit(1).eager(:manager).all.map{|x| x.manager}.should == [Manager[@i4]]
117
+ end
118
+
119
+ cspecify "should handle eagerly graphing many_to_one relationships", :sqlite do
120
+ ss = Staff.eager_graph(:manager).all
121
+ ss.should == [Staff[@i2]]
122
+ ss.map{|x| x.manager}.should == [Manager[@i4]]
123
+ end
124
+
125
+ specify "should handle one_to_many relationships" do
126
+ Executive.first.staff_members.should == [Staff[@i2]]
127
+ end
128
+
129
+ specify "should handle eagerly loading one_to_many relationships" do
130
+ Executive.limit(1).eager(:staff_members).first.staff_members.should == [Staff[@i2]]
131
+ end
132
+
133
+ cspecify "should handle eagerly graphing one_to_many relationships", :sqlite do
134
+ es = Executive.limit(1).eager_graph(:staff_members).all
135
+ es.should == [Executive[@i4]]
136
+ es.map{|x| x.staff_members}.should == [[Staff[@i2]]]
137
+ end
138
+ end
139
+ end
140
+
141
+ describe "Many Through Many Plugin" do
142
+ before do
143
+ @db = INTEGRATION_DB
144
+ @db.instance_variable_set(:@schemas, {})
145
+ @db.create_table!(:albums) do
146
+ primary_key :id
147
+ String :name
148
+ end
149
+ @db.create_table!(:artists) do
150
+ primary_key :id
151
+ String :name
152
+ end
153
+ @db.create_table!(:albums_artists) do
154
+ foreign_key :album_id, :albums
155
+ foreign_key :artist_id, :artists
156
+ end
157
+ class ::Album < Sequel::Model(@db)
158
+ many_to_many :artists
159
+ end
160
+ class ::Artist < Sequel::Model(@db)
161
+ plugin :many_through_many
162
+ end
163
+
164
+ @artist1 = Artist.create(:name=>'1')
165
+ @artist2 = Artist.create(:name=>'2')
166
+ @artist3 = Artist.create(:name=>'3')
167
+ @artist4 = Artist.create(:name=>'4')
168
+ @album1 = Album.create(:name=>'A')
169
+ @album1.add_artist(@artist1)
170
+ @album1.add_artist(@artist2)
171
+ @album2 = Album.create(:name=>'B')
172
+ @album2.add_artist(@artist3)
173
+ @album2.add_artist(@artist4)
174
+ @album3 = Album.create(:name=>'C')
175
+ @album3.add_artist(@artist2)
176
+ @album3.add_artist(@artist3)
177
+ @album4 = Album.create(:name=>'D')
178
+ @album4.add_artist(@artist1)
179
+ @album4.add_artist(@artist4)
180
+
181
+ clear_sqls
182
+ end
183
+ after do
184
+ @db.drop_table :albums_artists, :albums, :artists
185
+ [:Album, :Artist].each{|s| Object.send(:remove_const, s)}
186
+ end
187
+
188
+ specify "should handle super simple case with 1 join table" do
189
+ Artist.many_through_many :albums, [[:albums_artists, :artist_id, :album_id]]
190
+ Artist[1].albums.map{|x| x.name}.sort.should == %w'A D'
191
+ Artist[2].albums.map{|x| x.name}.sort.should == %w'A C'
192
+ Artist[3].albums.map{|x| x.name}.sort.should == %w'B C'
193
+ Artist[4].albums.map{|x| x.name}.sort.should == %w'B D'
194
+
195
+ Artist.filter(:id=>1).eager(:albums).all.map{|x| x.albums.map{|a| a.name}}.flatten.sort.should == %w'A D'
196
+ Artist.filter(:id=>2).eager(:albums).all.map{|x| x.albums.map{|a| a.name}}.flatten.sort.should == %w'A C'
197
+ Artist.filter(:id=>3).eager(:albums).all.map{|x| x.albums.map{|a| a.name}}.flatten.sort.should == %w'B C'
198
+ Artist.filter(:id=>4).eager(:albums).all.map{|x| x.albums.map{|a| a.name}}.flatten.sort.should == %w'B D'
199
+
200
+ Artist.filter(:artists__id=>1).eager_graph(:albums).all.map{|x| x.albums.map{|a| a.name}}.flatten.sort.should == %w'A D'
201
+ Artist.filter(:artists__id=>2).eager_graph(:albums).all.map{|x| x.albums.map{|a| a.name}}.flatten.sort.should == %w'A C'
202
+ Artist.filter(:artists__id=>3).eager_graph(:albums).all.map{|x| x.albums.map{|a| a.name}}.flatten.sort.should == %w'B C'
203
+ Artist.filter(:artists__id=>4).eager_graph(:albums).all.map{|x| x.albums.map{|a| a.name}}.flatten.sort.should == %w'B D'
204
+ end
205
+
206
+ specify "should handle typical case with 3 join tables" do
207
+ Artist.many_through_many :related_artists, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_artists, :album_id, :artist_id]], :class=>Artist, :distinct=>true
208
+ Artist[1].related_artists.map{|x| x.name}.sort.should == %w'1 2 4'
209
+ Artist[2].related_artists.map{|x| x.name}.sort.should == %w'1 2 3'
210
+ Artist[3].related_artists.map{|x| x.name}.sort.should == %w'2 3 4'
211
+ Artist[4].related_artists.map{|x| x.name}.sort.should == %w'1 3 4'
212
+
213
+ Artist.filter(:id=>1).eager(:related_artists).all.map{|x| x.related_artists.map{|a| a.name}}.flatten.sort.should == %w'1 2 4'
214
+ Artist.filter(:id=>2).eager(:related_artists).all.map{|x| x.related_artists.map{|a| a.name}}.flatten.sort.should == %w'1 2 3'
215
+ Artist.filter(:id=>3).eager(:related_artists).all.map{|x| x.related_artists.map{|a| a.name}}.flatten.sort.should == %w'2 3 4'
216
+ Artist.filter(:id=>4).eager(:related_artists).all.map{|x| x.related_artists.map{|a| a.name}}.flatten.sort.should == %w'1 3 4'
217
+
218
+ Artist.filter(:artists__id=>1).eager_graph(:related_artists).all.map{|x| x.related_artists.map{|a| a.name}}.flatten.sort.should == %w'1 2 4'
219
+ Artist.filter(:artists__id=>2).eager_graph(:related_artists).all.map{|x| x.related_artists.map{|a| a.name}}.flatten.sort.should == %w'1 2 3'
220
+ Artist.filter(:artists__id=>3).eager_graph(:related_artists).all.map{|x| x.related_artists.map{|a| a.name}}.flatten.sort.should == %w'2 3 4'
221
+ Artist.filter(:artists__id=>4).eager_graph(:related_artists).all.map{|x| x.related_artists.map{|a| a.name}}.flatten.sort.should == %w'1 3 4'
222
+ end
223
+
224
+ specify "should handle extreme case with 5 join tables" do
225
+ Artist.many_through_many :related_albums, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_artists, :album_id, :artist_id], [:artists, :id, :id], [:albums_artists, :artist_id, :album_id]], :class=>Album, :distinct=>true
226
+ @db[:albums_artists].delete
227
+ @album1.add_artist(@artist1)
228
+ @album1.add_artist(@artist2)
229
+ @album2.add_artist(@artist2)
230
+ @album2.add_artist(@artist3)
231
+ @album3.add_artist(@artist1)
232
+ @album4.add_artist(@artist3)
233
+ @album4.add_artist(@artist4)
234
+
235
+ Artist[1].related_albums.map{|x| x.name}.sort.should == %w'A B C'
236
+ Artist[2].related_albums.map{|x| x.name}.sort.should == %w'A B C D'
237
+ Artist[3].related_albums.map{|x| x.name}.sort.should == %w'A B D'
238
+ Artist[4].related_albums.map{|x| x.name}.sort.should == %w'B D'
239
+
240
+ Artist.filter(:id=>1).eager(:related_albums).all.map{|x| x.related_albums.map{|a| a.name}}.flatten.sort.should == %w'A B C'
241
+ Artist.filter(:id=>2).eager(:related_albums).all.map{|x| x.related_albums.map{|a| a.name}}.flatten.sort.should == %w'A B C D'
242
+ Artist.filter(:id=>3).eager(:related_albums).all.map{|x| x.related_albums.map{|a| a.name}}.flatten.sort.should == %w'A B D'
243
+ Artist.filter(:id=>4).eager(:related_albums).all.map{|x| x.related_albums.map{|a| a.name}}.flatten.sort.should == %w'B D'
244
+
245
+ Artist.filter(:artists__id=>1).eager_graph(:related_albums).all.map{|x| x.related_albums.map{|a| a.name}}.flatten.sort.should == %w'A B C'
246
+ Artist.filter(:artists__id=>2).eager_graph(:related_albums).all.map{|x| x.related_albums.map{|a| a.name}}.flatten.sort.should == %w'A B C D'
247
+ Artist.filter(:artists__id=>3).eager_graph(:related_albums).all.map{|x| x.related_albums.map{|a| a.name}}.flatten.sort.should == %w'A B D'
248
+ Artist.filter(:artists__id=>4).eager_graph(:related_albums).all.map{|x| x.related_albums.map{|a| a.name}}.flatten.sort.should == %w'B D'
249
+ end
250
+ end
251
+
252
+ describe "Lazy Attributes plugin" do
253
+ before do
254
+ @db = INTEGRATION_DB
255
+ @db.create_table!(:items) do
256
+ primary_key :id
257
+ String :name
258
+ Integer :num
259
+ end
260
+ class ::Item < Sequel::Model(@db)
261
+ plugin :lazy_attributes, :num
262
+ end
263
+ Item.create(:name=>'J', :num=>1)
264
+ end
265
+ after do
266
+ @db.drop_table(:items)
267
+ Object.send(:remove_const, :Item)
268
+ end
269
+
270
+ specify "should not include lazy attribute columns by default" do
271
+ Item.first.should == Item.load(:id=>1, :name=>'J')
272
+ end
273
+
274
+ specify "should load lazy attribute on access" do
275
+ Item.first.num.should == 1
276
+ end
277
+
278
+ specify "should load lazy attribute for all items returned when accessing any item if using identity map " do
279
+ Item.create(:name=>'K', :num=>2)
280
+ Item.with_identity_map do
281
+ a = Item.order(:name).all
282
+ a.should == [Item.load(:id=>1, :name=>'J'), Item.load(:id=>2, :name=>'K')]
283
+ a.map{|x| x[:num]}.should == [nil, nil]
284
+ a.first.num.should == 1
285
+ a.map{|x| x[:num]}.should == [1, 2]
286
+ a.last.num.should == 2
287
+ end
288
+ end
289
+ end
290
+
291
+ describe "Tactical Eager Loading Plugin" do
292
+ before do
293
+ @db = INTEGRATION_DB
294
+ @db.instance_variable_set(:@schemas, {})
295
+ @db.create_table!(:artists) do
296
+ primary_key :id
297
+ String :name
298
+ end
299
+ @db.create_table!(:albums) do
300
+ primary_key :id
301
+ String :name
302
+ foreign_key :artist_id, :artists
303
+ end
304
+ class ::Album < Sequel::Model(@db)
305
+ plugin :tactical_eager_loading
306
+ many_to_one :artist
307
+ end
308
+ class ::Artist < Sequel::Model(@db)
309
+ plugin :tactical_eager_loading
310
+ one_to_many :albums, :order=>:name
311
+ end
312
+
313
+ @artist1 = Artist.create(:name=>'1')
314
+ @artist2 = Artist.create(:name=>'2')
315
+ @artist3 = Artist.create(:name=>'3')
316
+ @artist4 = Artist.create(:name=>'4')
317
+ @album1 = Album.create(:name=>'A', :artist=>@artist1)
318
+ @album2 = Album.create(:name=>'B', :artist=>@artist1)
319
+ @album3 = Album.create(:name=>'C', :artist=>@artist2)
320
+ @album4 = Album.create(:name=>'D', :artist=>@artist3)
321
+
322
+ clear_sqls
323
+ end
324
+ after do
325
+ @db.drop_table :albums, :artists
326
+ [:Album, :Artist].each{|s| Object.send(:remove_const, s)}
327
+ end
328
+
329
+ specify "should eagerly load associations for all items when accessing any item" do
330
+ a = Artist.order(:name).all
331
+ a.map{|x| x.associations}.should == [{}, {}, {}, {}]
332
+ a.first.albums.should == [@album1, @album2]
333
+ a.map{|x| x.associations}.should == [{:albums=>[@album1, @album2]}, {:albums=>[@album3]}, {:albums=>[@album4]}, {:albums=>[]}]
334
+
335
+ a = Album.order(:name).all
336
+ a.map{|x| x.associations}.should == [{}, {}, {}, {}]
337
+ a.first.artist.should == @artist1
338
+ a.map{|x| x.associations}.should == [{:artist=>@artist1}, {:artist=>@artist1}, {:artist=>@artist2}, {:artist=>@artist3}]
339
+ end
340
+ end
341
+
342
+ describe "Identity Map plugin" do
343
+ before do
344
+ @db = INTEGRATION_DB
345
+ @db.create_table!(:items) do
346
+ primary_key :id
347
+ String :name
348
+ Integer :num
349
+ end
350
+ class ::Item < Sequel::Model(@db)
351
+ plugin :identity_map
352
+ end
353
+ Item.create(:name=>'J', :num=>3)
354
+ end
355
+ after do
356
+ @db.drop_table(:items)
357
+ Object.send(:remove_const, :Item)
358
+ end
359
+
360
+ specify "should return the same instance if retrieved more than once" do
361
+ Item.with_identity_map{Item.first.object_id.should == Item.first.object_id}
362
+ end
363
+
364
+ specify "should merge attributes that don't exist in the model" do
365
+ Item.with_identity_map do
366
+ i = Item.select(:id, :name).first
367
+ i.values.should == {:id=>1, :name=>'J'}
368
+ Item.first
369
+ i.values.should == {:id=>1, :name=>'J', :num=>3}
370
+ end
371
+ end
372
+ end
373
+
374
+ describe "Touch plugin" do
375
+ before do
376
+ @db = INTEGRATION_DB
377
+ @db.instance_variable_set(:@schemas, {})
378
+ @db.create_table!(:artists) do
379
+ primary_key :id
380
+ String :name
381
+ DateTime :updated_at
382
+ end
383
+ @db.create_table!(:albums) do
384
+ primary_key :id
385
+ String :name
386
+ foreign_key :artist_id, :artists
387
+ DateTime :updated_at
388
+ end
389
+ class ::Album < Sequel::Model(@db)
390
+ many_to_one :artist
391
+ plugin :touch, :associations=>:artist
392
+ end
393
+ class ::Artist < Sequel::Model(@db)
394
+ end
395
+
396
+ @artist = Artist.create(:name=>'1')
397
+ @album = Album.create(:name=>'A', :artist=>@artist)
398
+ end
399
+ after do
400
+ @db.drop_table :albums, :artists
401
+ [:Album, :Artist].each{|s| Object.send(:remove_const, s)}
402
+ end
403
+
404
+ specify "should update the timestamp column when touching the record" do
405
+ @album.updated_at.should == nil
406
+ @album.touch
407
+ @album.updated_at.to_i.should be_close(Time.now.to_i, 2)
408
+ end
409
+
410
+ cspecify "should update the timestamp column for associated records when the record is updated or destroyed", [:do], [:jdbc, :sqlite] do
411
+ @artist.updated_at.should == nil
412
+ @album.update(:name=>'B')
413
+ @artist.reload.updated_at.to_i.should be_close(Time.now.to_i, 2)
414
+ @artist.update(:updated_at=>nil)
415
+ @album.destroy
416
+ @artist.reload.updated_at.to_i.should be_close(Time.now.to_i, 2)
417
+ end
418
+ end
419
+
420
+ describe "Serialization plugin" do
421
+ before do
422
+ @db = INTEGRATION_DB
423
+ @db.create_table!(:items) do
424
+ primary_key :id
425
+ String :stuff
426
+ end
427
+ class ::Item < Sequel::Model(@db)
428
+ plugin :serialization, :marshal, :stuff
429
+ end
430
+ end
431
+ after do
432
+ @db.drop_table(:items)
433
+ Object.send(:remove_const, :Item)
434
+ end
435
+
436
+ specify "should serialize and deserialize items as needed" do
437
+ i = Item.create(:stuff=>{:a=>1})
438
+ i.stuff.should == {:a=>1}
439
+ i.stuff = [1, 2, 3]
440
+ i.save
441
+ Item.first.stuff.should == [1, 2, 3]
442
+ i.update(:stuff=>Item.new)
443
+ Item.first.stuff.should == Item.new
444
+ end
445
+ end
446
+
447
+ describe "OptimisticLocking plugin" do
448
+ before do
449
+ @db = INTEGRATION_DB
450
+ @db.create_table!(:people) do
451
+ primary_key :id
452
+ String :name
453
+ Integer :lock_version, :default=>0, :null=>false
454
+ end
455
+ class ::Person < Sequel::Model(@db)
456
+ plugin :optimistic_locking
457
+ create(:name=>'John')
458
+ end
459
+ end
460
+ after do
461
+ @db.drop_table(:people)
462
+ Object.send(:remove_const, :Person)
463
+ end
464
+
465
+ specify "should raise an error when updating a stale record" do
466
+ p1 = Person[1]
467
+ p2 = Person[1]
468
+ p1.update(:name=>'Jim')
469
+ proc{p2.update(:name=>'Bob')}.should raise_error(Sequel::Plugins::OptimisticLocking::Error)
470
+ end
471
+
472
+ specify "should raise an error when destroying a stale record" do
473
+ p1 = Person[1]
474
+ p2 = Person[1]
475
+ p1.update(:name=>'Jim')
476
+ proc{p2.destroy}.should raise_error(Sequel::Plugins::OptimisticLocking::Error)
477
+ end
478
+
479
+ specify "should not raise an error when updating the same record twice" do
480
+ p1 = Person[1]
481
+ p1.update(:name=>'Jim')
482
+ proc{p1.update(:name=>'Bob')}.should_not raise_error
483
+ end
484
+ end
485
+
486
+ describe "Composition plugin" do
487
+ before do
488
+ @db = INTEGRATION_DB
489
+ @db.create_table!(:events) do
490
+ primary_key :id
491
+ Integer :year
492
+ Integer :month
493
+ Integer :day
494
+ end
495
+ class ::Event < Sequel::Model(@db)
496
+ plugin :composition
497
+ composition :date, :composer=>proc{Date.new(year, month, day) if year && month && day}, :decomposer=>(proc do
498
+ if date
499
+ self.year = date.year
500
+ self.month = date.month
501
+ self.day = date.day
502
+ else
503
+ self.year, self.month, self.day = nil
504
+ end
505
+ end)
506
+ composition :date, :mapping=>[:year, :month, :day]
507
+ end
508
+ @e1 = Event.create(:year=>2010, :month=>2, :day=>15)
509
+ @e2 = Event.create({})
510
+ end
511
+ after do
512
+ @db.drop_table(:events)
513
+ Object.send(:remove_const, :Event)
514
+ end
515
+
516
+ specify "should return a composed object if the underlying columns have a value" do
517
+ @e1.date.should == Date.civil(2010, 2, 15)
518
+ @e2.date.should == nil
519
+ end
520
+
521
+ specify "should decompose the object when saving the record" do
522
+ @e1.date = Date.civil(2009, 1, 2)
523
+ @e1.save
524
+ @e1.year.should == 2009
525
+ @e1.month.should == 1
526
+ @e1.day.should == 2
527
+ end
528
+
529
+ specify "should save all columns when saving changes" do
530
+ @e2.date = Date.civil(2009, 10, 2)
531
+ @e2.save_changes
532
+ @e2.reload
533
+ @e2.year.should == 2009
534
+ @e2.month.should == 10
535
+ @e2.day.should == 2
536
+ end
537
+ end
538
+
539
+ if INTEGRATION_DB.dataset.supports_cte?
540
+ describe "RcteTree Plugin" do
541
+ before do
542
+ @db = INTEGRATION_DB
543
+ @db.create_table!(:nodes) do
544
+ primary_key :id
545
+ Integer :parent_id
546
+ String :name
547
+ end
548
+ class ::Node < Sequel::Model(@db)
549
+ plugin :rcte_tree, :order=>:name
550
+ end
551
+
552
+ @a = Node.create(:name=>'a')
553
+ @b = Node.create(:name=>'b')
554
+ @aa = Node.create(:name=>'aa', :parent=>@a)
555
+ @ab = Node.create(:name=>'ab', :parent=>@a)
556
+ @ba = Node.create(:name=>'ba', :parent=>@b)
557
+ @bb = Node.create(:name=>'bb', :parent=>@b)
558
+ @aaa = Node.create(:name=>'aaa', :parent=>@aa)
559
+ @aab = Node.create(:name=>'aab', :parent=>@aa)
560
+ @aba = Node.create(:name=>'aba', :parent=>@ab)
561
+ @abb = Node.create(:name=>'abb', :parent=>@ab)
562
+ @aaaa = Node.create(:name=>'aaaa', :parent=>@aaa)
563
+ @aaab = Node.create(:name=>'aaab', :parent=>@aaa)
564
+ @aaaaa = Node.create(:name=>'aaaaa', :parent=>@aaaa)
565
+ end
566
+ after do
567
+ @db.drop_table :nodes
568
+ Object.send(:remove_const, :Node)
569
+ end
570
+
571
+ specify "should load all standard (not-CTE) methods correctly" do
572
+ @a.children.should == [@aa, @ab]
573
+ @b.children.should == [@ba, @bb]
574
+ @aa.children.should == [@aaa, @aab]
575
+ @ab.children.should == [@aba, @abb]
576
+ @ba.children.should == []
577
+ @bb.children.should == []
578
+ @aaa.children.should == [@aaaa, @aaab]
579
+ @aab.children.should == []
580
+ @aba.children.should == []
581
+ @abb.children.should == []
582
+ @aaaa.children.should == [@aaaaa]
583
+ @aaab.children.should == []
584
+ @aaaaa.children.should == []
585
+
586
+ @a.parent.should == nil
587
+ @b.parent.should == nil
588
+ @aa.parent.should == @a
589
+ @ab.parent.should == @a
590
+ @ba.parent.should == @b
591
+ @bb.parent.should == @b
592
+ @aaa.parent.should == @aa
593
+ @aab.parent.should == @aa
594
+ @aba.parent.should == @ab
595
+ @abb.parent.should == @ab
596
+ @aaaa.parent.should == @aaa
597
+ @aaab.parent.should == @aaa
598
+ @aaaaa.parent.should == @aaaa
599
+ end
600
+
601
+ specify "should load all ancestors and descendants lazily for a given instance" do
602
+ @a.descendants.should == [@aa, @aaa, @aaaa, @aaaaa, @aaab, @aab, @ab, @aba, @abb]
603
+ @b.descendants.should == [@ba, @bb]
604
+ @aa.descendants.should == [@aaa, @aaaa, @aaaaa, @aaab, @aab]
605
+ @ab.descendants.should == [@aba, @abb]
606
+ @ba.descendants.should == []
607
+ @bb.descendants.should == []
608
+ @aaa.descendants.should == [@aaaa, @aaaaa, @aaab]
609
+ @aab.descendants.should == []
610
+ @aba.descendants.should == []
611
+ @abb.descendants.should == []
612
+ @aaaa.descendants.should == [@aaaaa]
613
+ @aaab.descendants.should == []
614
+ @aaaaa.descendants.should == []
615
+
616
+ @a.ancestors.should == []
617
+ @b.ancestors.should == []
618
+ @aa.ancestors.should == [@a]
619
+ @ab.ancestors.should == [@a]
620
+ @ba.ancestors.should == [@b]
621
+ @bb.ancestors.should == [@b]
622
+ @aaa.ancestors.should == [@a, @aa]
623
+ @aab.ancestors.should == [@a, @aa]
624
+ @aba.ancestors.should == [@a, @ab]
625
+ @abb.ancestors.should == [@a, @ab]
626
+ @aaaa.ancestors.should == [@a, @aa, @aaa]
627
+ @aaab.ancestors.should == [@a, @aa, @aaa]
628
+ @aaaaa.ancestors.should == [@a, @aa, @aaa, @aaaa]
629
+ end
630
+
631
+ specify "should eagerly load all ancestors and descendants for a dataset" do
632
+ nodes = Node.filter(:id=>[@a.id, @b.id, @aaa.id]).order(:name).eager(:ancestors, :descendants).all
633
+ nodes.should == [@a, @aaa, @b]
634
+ nodes[0].descendants.should == [@aa, @aaa, @aaaa, @aaaaa, @aaab, @aab, @ab, @aba, @abb]
635
+ nodes[1].descendants.should == [@aaaa, @aaaaa, @aaab]
636
+ nodes[2].descendants.should == [@ba, @bb]
637
+ nodes[0].ancestors.should == []
638
+ nodes[1].ancestors.should == [@a, @aa]
639
+ nodes[2].ancestors.should == []
640
+ end
641
+
642
+ specify "should eagerly load descendants to a given level" do
643
+ nodes = Node.filter(:id=>[@a.id, @b.id, @aaa.id]).order(:name).eager(:descendants=>1).all
644
+ nodes.should == [@a, @aaa, @b]
645
+ nodes[0].descendants.should == [@aa, @ab]
646
+ nodes[1].descendants.should == [@aaaa, @aaab]
647
+ nodes[2].descendants.should == [@ba, @bb]
648
+
649
+ nodes = Node.filter(:id=>[@a.id, @b.id, @aaa.id]).order(:name).eager(:descendants=>2).all
650
+ nodes.should == [@a, @aaa, @b]
651
+ nodes[0].descendants.should == [@aa, @aaa, @aab, @ab, @aba, @abb]
652
+ nodes[1].descendants.should == [@aaaa, @aaaaa, @aaab]
653
+ nodes[2].descendants.should == [@ba, @bb]
654
+ end
655
+
656
+ specify "should populate all :children associations when eagerly loading descendants for a dataset" do
657
+ nodes = Node.filter(:id=>[@a.id, @b.id, @aaa.id]).order(:name).eager(:descendants).all
658
+ nodes[0].associations[:children].should == [@aa, @ab]
659
+ nodes[1].associations[:children].should == [@aaaa, @aaab]
660
+ nodes[2].associations[:children].should == [@ba, @bb]
661
+ nodes[0].associations[:children].map{|c1| c1.associations[:children]}.should == [[@aaa, @aab], [@aba, @abb]]
662
+ nodes[1].associations[:children].map{|c1| c1.associations[:children]}.should == [[@aaaaa], []]
663
+ nodes[2].associations[:children].map{|c1| c1.associations[:children]}.should == [[], []]
664
+ nodes[0].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children]}}.should == [[[@aaaa, @aaab], []], [[], []]]
665
+ nodes[1].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children]}}.should == [[[]], []]
666
+ nodes[0].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children].map{|c3| c3.associations[:children]}}}.should == [[[[@aaaaa], []], []], [[], []]]
667
+ nodes[0].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children].map{|c3| c3.associations[:children].map{|c4| c4.associations[:children]}}}}.should == [[[[[]], []], []], [[], []]]
668
+ end
669
+
670
+ specify "should not populate :children associations for final level when loading descendants to a given level" do
671
+ nodes = Node.filter(:id=>[@a.id, @b.id, @aaa.id]).order(:name).eager(:descendants=>1).all
672
+ nodes[0].associations[:children].should == [@aa, @ab]
673
+ nodes[0].associations[:children].map{|c1| c1.associations[:children]}.should == [nil, nil]
674
+ nodes[1].associations[:children].should == [@aaaa, @aaab]
675
+ nodes[1].associations[:children].map{|c1| c1.associations[:children]}.should == [nil, nil]
676
+ nodes[2].associations[:children].should == [@ba, @bb]
677
+ nodes[2].associations[:children].map{|c1| c1.associations[:children]}.should == [nil, nil]
678
+
679
+ nodes[0].associations[:children].map{|c1| c1.children}.should == [[@aaa, @aab], [@aba, @abb]]
680
+ nodes[1].associations[:children].map{|c1| c1.children}.should == [[@aaaaa], []]
681
+ nodes[2].associations[:children].map{|c1| c1.children}.should == [[], []]
682
+
683
+ nodes = Node.filter(:id=>[@a.id, @b.id, @aaa.id]).order(:name).eager(:descendants=>2).all
684
+ nodes[0].associations[:children].should == [@aa, @ab]
685
+ nodes[0].associations[:children].map{|c1| c1.associations[:children]}.should == [[@aaa, @aab], [@aba, @abb]]
686
+ nodes[0].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children]}}.should == [[[@aaaa, @aaab], nil], [nil, nil]]
687
+ nodes[0].associations[:children].map{|c1| c1.associations[:children].map{|c2| (cc2 = c2.associations[:children]) ? cc2.map{|c3| c3.associations[:children]} : nil}}.should == [[[[@aaaaa], []], nil], [nil, nil]]
688
+ nodes[0].associations[:children].map{|c1| c1.associations[:children].map{|c2| (cc2 = c2.associations[:children]) ? cc2.map{|c3| (cc3 = c3.associations[:children]) ? cc3.map{|c4| c4.associations[:children]} : nil} : nil}}.should == [[[[nil], []], nil], [nil, nil]]
689
+
690
+ nodes[1].associations[:children].should == [@aaaa, @aaab]
691
+ nodes[1].associations[:children].map{|c1| c1.associations[:children]}.should == [[@aaaaa], []]
692
+ nodes[1].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children]}}.should == [[nil], []]
693
+
694
+ nodes[2].associations[:children].should == [@ba, @bb]
695
+ nodes[2].associations[:children].map{|c1| c1.associations[:children]}.should == [[], []]
696
+
697
+ nodes[0].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.children}}.should == [[[@aaaa, @aaab], []], [[], []]]
698
+ nodes[0].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.children.map{|c3| c3.children}}}.should == [[[[@aaaaa], []], []], [[], []]]
699
+ nodes[0].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.children.map{|c3| c3.children.map{|c4| c4.children}}}}.should == [[[[[]], []], []], [[], []]]
700
+ nodes[1].associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.children}}.should == [[[]], []]
701
+ end
702
+
703
+ specify "should populate all :children associations when lazily loading descendants" do
704
+ @a.descendants
705
+ @a.associations[:children].should == [@aa, @ab]
706
+ @a.associations[:children].map{|c1| c1.associations[:children]}.should == [[@aaa, @aab], [@aba, @abb]]
707
+ @a.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children]}}.should == [[[@aaaa, @aaab], []], [[], []]]
708
+ @a.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children].map{|c3| c3.associations[:children]}}}.should == [[[[@aaaaa], []], []], [[], []]]
709
+ @a.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children].map{|c3| c3.associations[:children].map{|c4| c4.associations[:children]}}}}.should == [[[[[]], []], []], [[], []]]
710
+
711
+ @b.descendants
712
+ @b.associations[:children].should == [@ba, @bb]
713
+ @b.associations[:children].map{|c1| c1.associations[:children]}.should == [[], []]
714
+
715
+ @aaa.descendants
716
+ @aaa.associations[:children].map{|c1| c1.associations[:children]}.should == [[@aaaaa], []]
717
+ @aaa.associations[:children].map{|c1| c1.associations[:children].map{|c2| c2.associations[:children]}}.should == [[[]], []]
718
+ end
719
+
720
+ specify "should populate all :parent associations when eagerly loading ancestors for a dataset" do
721
+ nodes = Node.filter(:id=>[@a.id, @ba.id, @aaa.id, @aaaaa.id]).order(:name).eager(:ancestors).all
722
+ nodes[0].associations.fetch(:parent, 1).should == nil
723
+ nodes[1].associations[:parent].should == @aa
724
+ nodes[1].associations[:parent].associations[:parent].should == @a
725
+ nodes[1].associations[:parent].associations[:parent].associations.fetch(:parent, 1) == nil
726
+ nodes[2].associations[:parent].should == @aaaa
727
+ nodes[2].associations[:parent].associations[:parent].should == @aaa
728
+ nodes[2].associations[:parent].associations[:parent].associations[:parent].should == @aa
729
+ nodes[2].associations[:parent].associations[:parent].associations[:parent].associations[:parent].should == @a
730
+ nodes[2].associations[:parent].associations[:parent].associations[:parent].associations[:parent].associations.fetch(:parent, 1).should == nil
731
+ nodes[3].associations[:parent].should == @b
732
+ nodes[3].associations[:parent].associations.fetch(:parent, 1).should == nil
733
+ end
734
+
735
+ specify "should populate all :parent associations when lazily loading ancestors" do
736
+ @a.reload
737
+ @a.ancestors
738
+ @a.associations[:parent].should == nil
739
+
740
+ @ba.reload
741
+ @ba.ancestors
742
+ @ba.associations[:parent].should == @b
743
+ @ba.associations[:parent].associations.fetch(:parent, 1).should == nil
744
+
745
+ @ba.reload
746
+ @aaaaa.ancestors
747
+ @aaaaa.associations[:parent].should == @aaaa
748
+ @aaaaa.associations[:parent].associations[:parent].should == @aaa
749
+ @aaaaa.associations[:parent].associations[:parent].associations[:parent].should == @aa
750
+ @aaaaa.associations[:parent].associations[:parent].associations[:parent].associations[:parent].should == @a
751
+ @aaaaa.associations[:parent].associations[:parent].associations[:parent].associations[:parent].associations.fetch(:parent, 1).should == nil
752
+ end
753
+ end
754
+ end
755
+
756
+ describe "Instance Filters plugin" do
757
+ before do
758
+ @db = INTEGRATION_DB
759
+ @db.create_table!(:items) do
760
+ primary_key :id
761
+ String :name
762
+ Integer :cost
763
+ Integer :number
764
+ end
765
+ class ::Item < Sequel::Model(@db)
766
+ plugin :instance_filters
767
+ end
768
+ @i = Item.create(:name=>'J', :number=>1, :cost=>2)
769
+ @i.instance_filter(:number=>1)
770
+ @i.set(:name=>'K')
771
+ end
772
+ after do
773
+ @db.drop_table(:items)
774
+ Object.send(:remove_const, :Item)
775
+ end
776
+
777
+ specify "should not raise an error if saving only updates one row" do
778
+ @i.save
779
+ @i.refresh.name.should == 'K'
780
+ end
781
+
782
+ specify "should raise error if saving doesn't update a row" do
783
+ @i.this.update(:number=>2)
784
+ proc{@i.save}.should raise_error(Sequel::Error)
785
+ end
786
+
787
+ specify "should apply all instance filters" do
788
+ @i.instance_filter{cost <= 2}
789
+ @i.this.update(:number=>2)
790
+ proc{@i.save}.should raise_error(Sequel::Error)
791
+ @i.this.update(:number=>1, :cost=>3)
792
+ proc{@i.save}.should raise_error(Sequel::Error)
793
+ @i.this.update(:cost=>2)
794
+ @i.save
795
+ @i.refresh.name.should == 'K'
796
+ end
797
+
798
+ specify "should clear instance filters after successful save" do
799
+ @i.save
800
+ @i.this.update(:number=>2)
801
+ @i.update(:name=>'L')
802
+ @i.refresh.name.should == 'L'
803
+ end
804
+
805
+ specify "should not raise an error if deleting only deletes one row" do
806
+ @i.destroy
807
+ proc{@i.refresh}.should raise_error(Sequel::Error, 'Record not found')
808
+ end
809
+
810
+ specify "should raise error if destroying doesn't delete a row" do
811
+ @i.this.update(:number=>2)
812
+ proc{@i.destroy}.should raise_error(Sequel::Error)
813
+ end
814
+ end