epugh-sequel 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. data/README.rdoc +652 -0
  2. data/VERSION.yml +4 -0
  3. data/bin/sequel +104 -0
  4. data/lib/sequel.rb +1 -0
  5. data/lib/sequel/adapters/ado.rb +85 -0
  6. data/lib/sequel/adapters/db2.rb +132 -0
  7. data/lib/sequel/adapters/dbi.rb +101 -0
  8. data/lib/sequel/adapters/do.rb +197 -0
  9. data/lib/sequel/adapters/do/mysql.rb +38 -0
  10. data/lib/sequel/adapters/do/postgres.rb +92 -0
  11. data/lib/sequel/adapters/do/sqlite.rb +31 -0
  12. data/lib/sequel/adapters/firebird.rb +307 -0
  13. data/lib/sequel/adapters/informix.rb +75 -0
  14. data/lib/sequel/adapters/jdbc.rb +485 -0
  15. data/lib/sequel/adapters/jdbc/h2.rb +62 -0
  16. data/lib/sequel/adapters/jdbc/mysql.rb +56 -0
  17. data/lib/sequel/adapters/jdbc/oracle.rb +23 -0
  18. data/lib/sequel/adapters/jdbc/postgresql.rb +101 -0
  19. data/lib/sequel/adapters/jdbc/sqlite.rb +43 -0
  20. data/lib/sequel/adapters/mysql.rb +370 -0
  21. data/lib/sequel/adapters/odbc.rb +184 -0
  22. data/lib/sequel/adapters/openbase.rb +57 -0
  23. data/lib/sequel/adapters/oracle.rb +140 -0
  24. data/lib/sequel/adapters/postgres.rb +453 -0
  25. data/lib/sequel/adapters/shared/mssql.rb +93 -0
  26. data/lib/sequel/adapters/shared/mysql.rb +341 -0
  27. data/lib/sequel/adapters/shared/oracle.rb +62 -0
  28. data/lib/sequel/adapters/shared/postgres.rb +743 -0
  29. data/lib/sequel/adapters/shared/progress.rb +34 -0
  30. data/lib/sequel/adapters/shared/sqlite.rb +263 -0
  31. data/lib/sequel/adapters/sqlite.rb +243 -0
  32. data/lib/sequel/adapters/utils/date_format.rb +21 -0
  33. data/lib/sequel/adapters/utils/stored_procedures.rb +75 -0
  34. data/lib/sequel/adapters/utils/unsupported.rb +62 -0
  35. data/lib/sequel/connection_pool.rb +258 -0
  36. data/lib/sequel/core.rb +204 -0
  37. data/lib/sequel/core_sql.rb +185 -0
  38. data/lib/sequel/database.rb +687 -0
  39. data/lib/sequel/database/schema_generator.rb +324 -0
  40. data/lib/sequel/database/schema_methods.rb +164 -0
  41. data/lib/sequel/database/schema_sql.rb +324 -0
  42. data/lib/sequel/dataset.rb +422 -0
  43. data/lib/sequel/dataset/convenience.rb +237 -0
  44. data/lib/sequel/dataset/prepared_statements.rb +220 -0
  45. data/lib/sequel/dataset/sql.rb +1105 -0
  46. data/lib/sequel/deprecated.rb +529 -0
  47. data/lib/sequel/exceptions.rb +44 -0
  48. data/lib/sequel/extensions/blank.rb +42 -0
  49. data/lib/sequel/extensions/inflector.rb +288 -0
  50. data/lib/sequel/extensions/pagination.rb +96 -0
  51. data/lib/sequel/extensions/pretty_table.rb +78 -0
  52. data/lib/sequel/extensions/query.rb +48 -0
  53. data/lib/sequel/extensions/string_date_time.rb +47 -0
  54. data/lib/sequel/metaprogramming.rb +44 -0
  55. data/lib/sequel/migration.rb +212 -0
  56. data/lib/sequel/model.rb +142 -0
  57. data/lib/sequel/model/association_reflection.rb +263 -0
  58. data/lib/sequel/model/associations.rb +1024 -0
  59. data/lib/sequel/model/base.rb +911 -0
  60. data/lib/sequel/model/deprecated.rb +188 -0
  61. data/lib/sequel/model/deprecated_hooks.rb +103 -0
  62. data/lib/sequel/model/deprecated_inflector.rb +335 -0
  63. data/lib/sequel/model/deprecated_validations.rb +384 -0
  64. data/lib/sequel/model/errors.rb +37 -0
  65. data/lib/sequel/model/exceptions.rb +7 -0
  66. data/lib/sequel/model/inflections.rb +230 -0
  67. data/lib/sequel/model/plugins.rb +74 -0
  68. data/lib/sequel/object_graph.rb +230 -0
  69. data/lib/sequel/plugins/caching.rb +122 -0
  70. data/lib/sequel/plugins/hook_class_methods.rb +122 -0
  71. data/lib/sequel/plugins/schema.rb +53 -0
  72. data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
  73. data/lib/sequel/plugins/validation_class_methods.rb +373 -0
  74. data/lib/sequel/sql.rb +854 -0
  75. data/lib/sequel/version.rb +11 -0
  76. data/lib/sequel_core.rb +1 -0
  77. data/lib/sequel_model.rb +1 -0
  78. data/spec/adapters/ado_spec.rb +46 -0
  79. data/spec/adapters/firebird_spec.rb +376 -0
  80. data/spec/adapters/informix_spec.rb +96 -0
  81. data/spec/adapters/mysql_spec.rb +875 -0
  82. data/spec/adapters/oracle_spec.rb +272 -0
  83. data/spec/adapters/postgres_spec.rb +692 -0
  84. data/spec/adapters/spec_helper.rb +10 -0
  85. data/spec/adapters/sqlite_spec.rb +550 -0
  86. data/spec/core/connection_pool_spec.rb +526 -0
  87. data/spec/core/core_ext_spec.rb +156 -0
  88. data/spec/core/core_sql_spec.rb +528 -0
  89. data/spec/core/database_spec.rb +1214 -0
  90. data/spec/core/dataset_spec.rb +3513 -0
  91. data/spec/core/expression_filters_spec.rb +363 -0
  92. data/spec/core/migration_spec.rb +261 -0
  93. data/spec/core/object_graph_spec.rb +280 -0
  94. data/spec/core/pretty_table_spec.rb +58 -0
  95. data/spec/core/schema_generator_spec.rb +167 -0
  96. data/spec/core/schema_spec.rb +778 -0
  97. data/spec/core/spec_helper.rb +82 -0
  98. data/spec/core/version_spec.rb +7 -0
  99. data/spec/extensions/blank_spec.rb +67 -0
  100. data/spec/extensions/caching_spec.rb +201 -0
  101. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  102. data/spec/extensions/inflector_spec.rb +122 -0
  103. data/spec/extensions/pagination_spec.rb +99 -0
  104. data/spec/extensions/pretty_table_spec.rb +91 -0
  105. data/spec/extensions/query_spec.rb +85 -0
  106. data/spec/extensions/schema_spec.rb +111 -0
  107. data/spec/extensions/single_table_inheritance_spec.rb +53 -0
  108. data/spec/extensions/spec_helper.rb +90 -0
  109. data/spec/extensions/string_date_time_spec.rb +93 -0
  110. data/spec/extensions/validation_class_methods_spec.rb +1054 -0
  111. data/spec/integration/dataset_test.rb +160 -0
  112. data/spec/integration/eager_loader_test.rb +683 -0
  113. data/spec/integration/prepared_statement_test.rb +130 -0
  114. data/spec/integration/schema_test.rb +183 -0
  115. data/spec/integration/spec_helper.rb +75 -0
  116. data/spec/integration/type_test.rb +96 -0
  117. data/spec/model/association_reflection_spec.rb +93 -0
  118. data/spec/model/associations_spec.rb +1780 -0
  119. data/spec/model/base_spec.rb +494 -0
  120. data/spec/model/caching_spec.rb +217 -0
  121. data/spec/model/dataset_methods_spec.rb +78 -0
  122. data/spec/model/eager_loading_spec.rb +1165 -0
  123. data/spec/model/hooks_spec.rb +472 -0
  124. data/spec/model/inflector_spec.rb +126 -0
  125. data/spec/model/model_spec.rb +588 -0
  126. data/spec/model/plugins_spec.rb +142 -0
  127. data/spec/model/record_spec.rb +1243 -0
  128. data/spec/model/schema_spec.rb +92 -0
  129. data/spec/model/spec_helper.rb +124 -0
  130. data/spec/model/validations_spec.rb +1080 -0
  131. data/spec/rcov.opts +6 -0
  132. data/spec/spec.opts +0 -0
  133. data/spec/spec_config.rb.example +10 -0
  134. metadata +202 -0
@@ -0,0 +1,160 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper.rb')
2
+
3
+ describe "Simple Dataset operations" do
4
+ before do
5
+ INTEGRATION_DB.create_table!(:items) do
6
+ primary_key :id
7
+ integer :number
8
+ end
9
+ @ds = INTEGRATION_DB[:items]
10
+ @ds.insert(:number=>10)
11
+ clear_sqls
12
+ end
13
+ after do
14
+ INTEGRATION_DB.drop_table(:items)
15
+ end
16
+
17
+ specify "should insert with a primary key specified" do
18
+ @ds.insert(:id=>100, :number=>20)
19
+ sqls_should_be(/INSERT INTO items \((number, id|id, number)\) VALUES \((100, 20|20, 100)\)/)
20
+ @ds.count.should == 2
21
+ @ds.order(:id).all.should == [{:id=>1, :number=>10}, {:id=>100, :number=>20}]
22
+ end
23
+
24
+ specify "should have insert return primary key value" do
25
+ @ds.insert(:number=>20).should == 2
26
+ sqls_should_be('INSERT INTO items (number) VALUES (20)')
27
+ @ds.filter(:id=>2).first[:number].should == 20
28
+ end
29
+
30
+ specify "should delete correctly" do
31
+ @ds.filter(1=>1).delete.should == 1
32
+ sqls_should_be('DELETE FROM items WHERE (1 = 1)')
33
+ @ds.count.should == 0
34
+ end
35
+
36
+ specify "should update correctly" do
37
+ @ds.update(:number=>:number+1).should == 1
38
+ sqls_should_be('UPDATE items SET number = (number + 1)')
39
+ @ds.all.should == [{:id=>1, :number=>11}]
40
+ end
41
+
42
+ specify "should fetch all results correctly" do
43
+ @ds.all.should == [{:id=>1, :number=>10}]
44
+ sqls_should_be('SELECT * FROM items')
45
+ end
46
+
47
+ specify "should fetch a single row correctly" do
48
+ @ds.first.should == {:id=>1, :number=>10}
49
+ sqls_should_be('SELECT * FROM items LIMIT 1')
50
+ end
51
+ end
52
+
53
+ describe "Simple Dataset operations" do
54
+ before do
55
+ INTEGRATION_DB.create_table!(:items) do
56
+ Integer :number
57
+ TrueClass :flag
58
+ end
59
+ @ds = INTEGRATION_DB[:items]
60
+ end
61
+ after do
62
+ INTEGRATION_DB.drop_table(:items)
63
+ end
64
+
65
+ specify "should deal with boolean conditions correctly" do
66
+ @ds.insert(:number=>1, :flag=>true)
67
+ @ds.insert(:number=>2, :flag=>false)
68
+ @ds.insert(:number=>3, :flag=>nil)
69
+ @ds.order!(:number)
70
+ @ds.filter(:flag=>true).map(:number).should == [1]
71
+ @ds.filter(:flag=>false).map(:number).should == [2]
72
+ @ds.filter(:flag=>nil).map(:number).should == [3]
73
+ @ds.exclude(:flag=>true).map(:number).should == [2, 3]
74
+ @ds.exclude(:flag=>false).map(:number).should == [1, 3]
75
+ @ds.exclude(:flag=>nil).map(:number).should == [1, 2]
76
+ end
77
+ end
78
+
79
+ describe "Simple Dataset operations in transactions" do
80
+ before do
81
+ INTEGRATION_DB.create_table!(:items_insert_in_transaction) do
82
+ primary_key :id
83
+ integer :number
84
+ end
85
+ @ds = INTEGRATION_DB[:items_insert_in_transaction]
86
+ clear_sqls
87
+ end
88
+ after do
89
+ INTEGRATION_DB.drop_table(:items_insert_in_transaction)
90
+ end
91
+
92
+ specify "should insert correctly with a primary key specified inside a transaction" do
93
+ INTEGRATION_DB.transaction do
94
+ @ds.insert(:id=>100, :number=>20)
95
+ sqls_should_be('Transaction.begin', /INSERT INTO items_insert_in_transaction \((number, id|id, number)\) VALUES \((100, 20|20, 100)\)/)
96
+ @ds.count.should == 1
97
+ @ds.order(:id).all.should == [{:id=>100, :number=>20}]
98
+ end
99
+ end
100
+
101
+ specify "should have insert return primary key value inside a transaction" do
102
+ INTEGRATION_DB.transaction do
103
+ @ds.insert(:number=>20).should == 1
104
+ sqls_should_be('Transaction.begin', /INSERT INTO items_insert_in_transaction \(number\) VALUES \(20\)/)
105
+ @ds.count.should == 1
106
+ @ds.order(:id).all.should == [{:id=>1, :number=>20}]
107
+ end
108
+ end
109
+ end
110
+
111
+ describe "Dataset UNION, EXCEPT, and INTERSECT" do
112
+ before do
113
+ INTEGRATION_DB.create_table!(:i1){integer :number}
114
+ INTEGRATION_DB.create_table!(:i2){integer :number}
115
+ INTEGRATION_DB.create_table!(:i3){integer :number}
116
+ @ds1 = INTEGRATION_DB[:i1]
117
+ @ds1.insert(:number=>10)
118
+ @ds1.insert(:number=>20)
119
+ @ds2 = INTEGRATION_DB[:i2]
120
+ @ds2.insert(:number=>10)
121
+ @ds2.insert(:number=>30)
122
+ @ds3 = INTEGRATION_DB[:i3]
123
+ @ds3.insert(:number=>10)
124
+ @ds3.insert(:number=>40)
125
+ clear_sqls
126
+ end
127
+
128
+ specify "should give the correct results for simple UNION, EXCEPT, and INTERSECT" do
129
+ @ds1.union(@ds2).order(:number).map{|x| x[:number].to_s}.should == %w'10 20 30'
130
+ unless defined?(Sequel::Dataset::UnsupportedIntersectExcept) and @ds1.class.ancestors.include?(Sequel::Dataset::UnsupportedIntersectExcept)
131
+ @ds1.except(@ds2).order(:number).map{|x| x[:number].to_s}.should == %w'20'
132
+ @ds1.intersect(@ds2).order(:number).map{|x| x[:number].to_s}.should == %w'10'
133
+ end
134
+ end
135
+
136
+ specify "should give the correct results for compound UNION, EXCEPT, and INTERSECT" do
137
+ @ds1.union(@ds2).union(@ds3).order(:number).map{|x| x[:number].to_s}.should == %w'10 20 30 40'
138
+ @ds1.union(@ds2.union(@ds3)).order(:number).map{|x| x[:number].to_s}.should == %w'10 20 30 40'
139
+ unless defined?(Sequel::Dataset::UnsupportedIntersectExcept) and @ds1.class.ancestors.include?(Sequel::Dataset::UnsupportedIntersectExcept)
140
+ @ds1.union(@ds2).except(@ds3).order(:number).map{|x| x[:number].to_s}.should == %w'20 30'
141
+ @ds1.union(@ds2.except(@ds3)).order(:number).map{|x| x[:number].to_s}.should == %w'10 20 30'
142
+ @ds1.union(@ds2).intersect(@ds3).order(:number).map{|x| x[:number].to_s}.should == %w'10 '
143
+ @ds1.union(@ds2.intersect(@ds3)).order(:number).map{|x| x[:number].to_s}.should == %w'10 20'
144
+
145
+ @ds1.except(@ds2).union(@ds3).order(:number).map{|x| x[:number].to_s}.should == %w'10 20 40'
146
+ @ds1.except(@ds2.union(@ds3)).order(:number).map{|x| x[:number].to_s}.should == %w'20'
147
+ @ds1.except(@ds2).except(@ds3).order(:number).map{|x| x[:number].to_s}.should == %w'20'
148
+ @ds1.except(@ds2.except(@ds3)).order(:number).map{|x| x[:number].to_s}.should == %w'10 20'
149
+ @ds1.except(@ds2).intersect(@ds3).order(:number).map{|x| x[:number].to_s}.should == %w''
150
+ @ds1.except(@ds2.intersect(@ds3)).order(:number).map{|x| x[:number].to_s}.should == %w'20'
151
+
152
+ @ds1.intersect(@ds2).union(@ds3).order(:number).map{|x| x[:number].to_s}.should == %w'10 40'
153
+ @ds1.intersect(@ds2.union(@ds3)).order(:number).map{|x| x[:number].to_s}.should == %w'10'
154
+ @ds1.intersect(@ds2).except(@ds3).order(:number).map{|x| x[:number].to_s}.should == %w''
155
+ @ds1.intersect(@ds2.except(@ds3)).order(:number).map{|x| x[:number].to_s}.should == %w''
156
+ @ds1.intersect(@ds2).intersect(@ds3).order(:number).map{|x| x[:number].to_s}.should == %w'10'
157
+ @ds1.intersect(@ds2.intersect(@ds3)).order(:number).map{|x| x[:number].to_s}.should == %w'10'
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,683 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper.rb')
2
+
3
+ describe "Eagerly loading a tree structure" do
4
+ before do
5
+ INTEGRATION_DB.instance_variable_set(:@schemas, nil)
6
+ INTEGRATION_DB.create_table!(:nodes) do
7
+ primary_key :id
8
+ foreign_key :parent_id, :nodes
9
+ end
10
+ class ::Node < Sequel::Model
11
+ many_to_one :parent
12
+ one_to_many :children, :key=>:parent_id
13
+
14
+ # Only useful when eager loading
15
+ many_to_one :ancestors, :eager_loader=>(proc do |key_hash, nodes, associations|
16
+ # Handle cases where the root node has the same parent_id as primary_key
17
+ # and also when it is NULL
18
+ non_root_nodes = nodes.reject do |n|
19
+ if [nil, n.pk].include?(n.parent_id)
20
+ # Make sure root nodes have their parent association set to nil
21
+ n.associations[:parent] = nil
22
+ true
23
+ else
24
+ false
25
+ end
26
+ end
27
+ unless non_root_nodes.empty?
28
+ id_map = {}
29
+ # Create an map of parent_ids to nodes that have that parent id
30
+ non_root_nodes.each{|n| (id_map[n.parent_id] ||= []) << n}
31
+ # Doesn't cause an infinte loop, because when only the root node
32
+ # is left, this is not called.
33
+ Node.filter(Node.primary_key=>id_map.keys.sort).eager(:ancestors).all do |node|
34
+ # Populate the parent association for each node
35
+ id_map[node.pk].each{|n| n.associations[:parent] = node}
36
+ end
37
+ end
38
+ end)
39
+ many_to_one :descendants, :eager_loader=>(proc do |key_hash, nodes, associations|
40
+ id_map = {}
41
+ nodes.each do |n|
42
+ # Initialize an empty array of child associations for each parent node
43
+ n.associations[:children] = []
44
+ # Populate identity map of nodes
45
+ id_map[n.pk] = n
46
+ end
47
+ # Doesn't cause an infinite loop, because the :eager_loader is not called
48
+ # if no records are returned. Exclude id = parent_id to avoid infinite loop
49
+ # if the root note is one of the returned records and it has parent_id = id
50
+ # instead of parent_id = NULL.
51
+ Node.filter(:parent_id=>id_map.keys.sort).exclude(:id=>:parent_id).eager(:descendants).all do |node|
52
+ # Get the parent from the identity map
53
+ parent = id_map[node.parent_id]
54
+ # Set the child's parent association to the parent
55
+ node.associations[:parent] = parent
56
+ # Add the child association to the array of children in the parent
57
+ parent.associations[:children] << node
58
+ end
59
+ end)
60
+ end
61
+
62
+ Node.insert(:parent_id=>1)
63
+ Node.insert(:parent_id=>1)
64
+ Node.insert(:parent_id=>1)
65
+ Node.insert(:parent_id=>2)
66
+ Node.insert(:parent_id=>4)
67
+ Node.insert(:parent_id=>5)
68
+ Node.insert(:parent_id=>6)
69
+ clear_sqls
70
+ end
71
+ after do
72
+ INTEGRATION_DB.drop_table :nodes
73
+ Object.send(:remove_const, :Node)
74
+ end
75
+
76
+ it "#descendants should get all descendants in one call" do
77
+ nodes = Node.filter(:id=>1).eager(:descendants).all
78
+ sqls_should_be('SELECT * FROM nodes WHERE (id = 1)',
79
+ 'SELECT * FROM nodes WHERE ((parent_id IN (1)) AND (id != parent_id))',
80
+ 'SELECT * FROM nodes WHERE ((parent_id IN (2, 3)) AND (id != parent_id))',
81
+ 'SELECT * FROM nodes WHERE ((parent_id IN (4)) AND (id != parent_id))',
82
+ 'SELECT * FROM nodes WHERE ((parent_id IN (5)) AND (id != parent_id))',
83
+ 'SELECT * FROM nodes WHERE ((parent_id IN (6)) AND (id != parent_id))',
84
+ 'SELECT * FROM nodes WHERE ((parent_id IN (7)) AND (id != parent_id))')
85
+ nodes.length.should == 1
86
+ node = nodes.first
87
+ node.pk.should == 1
88
+ node.children.length.should == 2
89
+ node.children.collect{|x| x.pk}.sort.should == [2, 3]
90
+ node.children.collect{|x| x.parent}.should == [node, node]
91
+ node = nodes.first.children.find{|x| x.pk == 2}
92
+ node.children.length.should == 1
93
+ node.children.first.pk.should == 4
94
+ node.children.first.parent.should == node
95
+ node = node.children.first
96
+ node.children.length.should == 1
97
+ node.children.first.pk.should == 5
98
+ node.children.first.parent.should == node
99
+ node = node.children.first
100
+ node.children.length.should == 1
101
+ node.children.first.pk.should == 6
102
+ node.children.first.parent.should == node
103
+ node = node.children.first
104
+ node.children.length.should == 1
105
+ node.children.first.pk.should == 7
106
+ node.children.first.parent.should == node
107
+ sqls_should_be
108
+ end
109
+
110
+ it "#ancestors should get all ancestors in one call" do
111
+ nodes = Node.filter(:id=>[7,3]).order(:id).eager(:ancestors).all
112
+ sqls_should_be('SELECT * FROM nodes WHERE (id IN (7, 3)) ORDER BY id',
113
+ 'SELECT * FROM nodes WHERE (id IN (1, 6))',
114
+ 'SELECT * FROM nodes WHERE (id IN (5))',
115
+ 'SELECT * FROM nodes WHERE (id IN (4))',
116
+ 'SELECT * FROM nodes WHERE (id IN (2))',
117
+ 'SELECT * FROM nodes WHERE (id IN (1))')
118
+ nodes.length.should == 2
119
+ nodes.collect{|x| x.pk}.should == [3, 7]
120
+ nodes.first.parent.pk.should == 1
121
+ nodes.first.parent.parent.should == nil
122
+ node = nodes.last
123
+ node.parent.pk.should == 6
124
+ node = node.parent
125
+ node.parent.pk.should == 5
126
+ node = node.parent
127
+ node.parent.pk.should == 4
128
+ node = node.parent
129
+ node.parent.pk.should == 2
130
+ node = node.parent
131
+ node.parent.pk.should == 1
132
+ node.parent.parent.should == nil
133
+ sqls_should_be
134
+ end
135
+ end
136
+
137
+ describe "Association Extensions" do
138
+ before do
139
+ module ::FindOrCreate
140
+ def find_or_create(vals)
141
+ first(vals) || model.create(vals.merge(:author_id=>model_object.pk))
142
+ end
143
+ def find_or_create_by_name(name)
144
+ first(:name=>name) || model.create(:name=>name, :author_id=>model_object.pk)
145
+ end
146
+ end
147
+ INTEGRATION_DB.instance_variable_set(:@schemas, nil)
148
+ INTEGRATION_DB.create_table!(:authors) do
149
+ primary_key :id
150
+ end
151
+ class ::Author < Sequel::Model
152
+ one_to_many :authorships, :extend=>FindOrCreate
153
+ end
154
+ INTEGRATION_DB.create_table!(:authorships) do
155
+ primary_key :id
156
+ foreign_key :author_id, :authors
157
+ text :name
158
+ end
159
+ class ::Authorship < Sequel::Model
160
+ many_to_one :author
161
+ end
162
+ @author = Author.create
163
+ clear_sqls
164
+ end
165
+ after do
166
+ INTEGRATION_DB.drop_table :authorships, :authors
167
+ Object.send(:remove_const, :Author)
168
+ Object.send(:remove_const, :Authorship)
169
+ end
170
+
171
+ it "should allow methods to be called on the dataset method" do
172
+ Authorship.count.should == 0
173
+ sqls_should_be('SELECT COUNT(*) FROM authorships LIMIT 1')
174
+ authorship = @author.authorships_dataset.find_or_create_by_name('Bob')
175
+ sqls_should_be("SELECT * FROM authorships WHERE ((authorships.author_id = 1) AND (name = 'Bob')) LIMIT 1",
176
+ /INSERT INTO authorships \((author_id, name|name, author_id)\) VALUES \((1, 'Bob'|'Bob', 1)\)/,
177
+ "SELECT * FROM authorships WHERE (id = 1) LIMIT 1")
178
+ Authorship.count.should == 1
179
+ Authorship.first.should == authorship
180
+ sqls_should_be('SELECT COUNT(*) FROM authorships LIMIT 1', "SELECT * FROM authorships LIMIT 1")
181
+ authorship.name.should == 'Bob'
182
+ authorship.author_id.should == @author.id
183
+ @author.authorships_dataset.find_or_create_by_name('Bob').should == authorship
184
+ sqls_should_be("SELECT * FROM authorships WHERE ((authorships.author_id = 1) AND (name = 'Bob')) LIMIT 1")
185
+ Authorship.count.should == 1
186
+ sqls_should_be('SELECT COUNT(*) FROM authorships LIMIT 1')
187
+ authorship2 = @author.authorships_dataset.find_or_create(:name=>'Jim')
188
+ sqls_should_be("SELECT * FROM authorships WHERE ((authorships.author_id = 1) AND (name = 'Jim')) LIMIT 1",
189
+ /INSERT INTO authorships \((author_id, name|name, author_id)\) VALUES \((1, 'Jim'|'Jim', 1)\)/,
190
+ "SELECT * FROM authorships WHERE (id = 2) LIMIT 1")
191
+ Authorship.count.should == 2
192
+ sqls_should_be('SELECT COUNT(*) FROM authorships LIMIT 1')
193
+ Authorship.order(:name).map(:name).should == ['Bob', 'Jim']
194
+ sqls_should_be('SELECT * FROM authorships ORDER BY name')
195
+ authorship2.name.should == 'Jim'
196
+ authorship2.author_id.should == @author.id
197
+ @author.authorships_dataset.find_or_create(:name=>'Jim').should == authorship2
198
+ sqls_should_be("SELECT * FROM authorships WHERE ((authorships.author_id = 1) AND (name = 'Jim')) LIMIT 1")
199
+ end
200
+ end
201
+
202
+ describe "has_many :through has_many and has_one :through belongs_to" do
203
+ before do
204
+ INTEGRATION_DB.instance_variable_set(:@schemas, nil)
205
+ INTEGRATION_DB.create_table!(:firms) do
206
+ primary_key :id
207
+ end
208
+ class ::Firm < Sequel::Model
209
+ one_to_many :clients
210
+ one_to_many :invoices, :read_only=>true, \
211
+ :dataset=>proc{Invoice.eager_graph(:client).filter(:client__firm_id=>pk)}, \
212
+ :after_load=>(proc do |firm, invs|
213
+ invs.each do |inv|
214
+ inv.client.associations[:firm] = inv.associations[:firm] = firm
215
+ end
216
+ end), \
217
+ :eager_loader=>(proc do |key_hash, firms, associations|
218
+ id_map = key_hash[Firm.primary_key]
219
+ firms.each{|firm| firm.associations[:invoices] = []}
220
+ Invoice.eager_graph(:client).filter(:client__firm_id=>id_map.keys).all do |inv|
221
+ id_map[inv.client.firm_id].each do |firm|
222
+ firm.associations[:invoices] << inv
223
+ end
224
+ end
225
+ end)
226
+ end
227
+
228
+ INTEGRATION_DB.create_table!(:clients) do
229
+ primary_key :id
230
+ foreign_key :firm_id, :firms
231
+ end
232
+ class ::Client < Sequel::Model
233
+ many_to_one :firm
234
+ one_to_many :invoices
235
+ end
236
+
237
+ INTEGRATION_DB.create_table!(:invoices) do
238
+ primary_key :id
239
+ foreign_key :client_id, :clients
240
+ end
241
+ class ::Invoice < Sequel::Model
242
+ many_to_one :client
243
+ many_to_one :firm, :key=>nil, :read_only=>true, \
244
+ :dataset=>proc{Firm.eager_graph(:clients).filter(:clients__id=>client_id)}, \
245
+ :after_load=>(proc do |inv, firm|
246
+ # Delete the cached associations from firm, because it only has the
247
+ # client with this invoice, instead of all clients of the firm
248
+ if c = firm.associations.delete(:clients)
249
+ firm.associations[:invoice_client] = c.first
250
+ end
251
+ inv.associations[:client] ||= firm.associations[:invoice_client]
252
+ end), \
253
+ :eager_loader=>(proc do |key_hash, invoices, associations|
254
+ id_map = {}
255
+ invoices.each do |inv|
256
+ inv.associations[:firm] = nil
257
+ (id_map[inv.client_id] ||= []) << inv
258
+ end
259
+ Firm.eager_graph(:clients).filter(:clients__id=>id_map.keys).all do |firm|
260
+ # Delete the cached associations from firm, because it only has the
261
+ # clients related the invoices being eagerly loaded, instead of all
262
+ # clients of the firm.
263
+ firm.associations[:clients].each do |client|
264
+ id_map[client.pk].each do |inv|
265
+ inv.associations[:firm] = firm
266
+ inv.associations[:client] = client
267
+ end
268
+ end
269
+ end
270
+ end)
271
+ end
272
+ @firm1 = Firm.create
273
+ @firm2 = Firm.create
274
+ @client1 = Client.create(:firm => @firm1)
275
+ @client2 = Client.create(:firm => @firm1)
276
+ @client3 = Client.create(:firm => @firm2)
277
+ @invoice1 = Invoice.create(:client => @client1)
278
+ @invoice2 = Invoice.create(:client => @client1)
279
+ @invoice3 = Invoice.create(:client => @client2)
280
+ @invoice4 = Invoice.create(:client => @client3)
281
+ @invoice5 = Invoice.create(:client => @client3)
282
+ clear_sqls
283
+ end
284
+ after do
285
+ INTEGRATION_DB.drop_table :invoices, :clients, :firms
286
+ Object.send(:remove_const, :Firm)
287
+ Object.send(:remove_const, :Client)
288
+ Object.send(:remove_const, :Invoice)
289
+ end
290
+
291
+ it "should return has_many :through has_many records for a single object" do
292
+ invs = @firm1.invoices.sort_by{|x| x.pk}
293
+ sqls_should_be("SELECT invoices.id, invoices.client_id, client.id AS 'client_id_0', client.firm_id FROM invoices LEFT OUTER JOIN clients AS 'client' ON (client.id = invoices.client_id) WHERE (client.firm_id = 1)")
294
+ invs.should == [@invoice1, @invoice2, @invoice3]
295
+ invs[0].client.should == @client1
296
+ invs[1].client.should == @client1
297
+ invs[2].client.should == @client2
298
+ invs.collect{|i| i.firm}.should == [@firm1, @firm1, @firm1]
299
+ invs.collect{|i| i.client.firm}.should == [@firm1, @firm1, @firm1]
300
+ sqls_should_be
301
+ end
302
+
303
+ it "should eagerly load has_many :through has_many records for multiple objects" do
304
+ firms = Firm.order(:id).eager(:invoices).all
305
+ sqls_should_be("SELECT * FROM firms ORDER BY id", "SELECT invoices.id, invoices.client_id, client.id AS 'client_id_0', client.firm_id FROM invoices LEFT OUTER JOIN clients AS 'client' ON (client.id = invoices.client_id) WHERE (client.firm_id IN (1, 2))")
306
+ firms.should == [@firm1, @firm2]
307
+ firm1, firm2 = firms
308
+ invs1 = firm1.invoices.sort_by{|x| x.pk}
309
+ invs2 = firm2.invoices.sort_by{|x| x.pk}
310
+ invs1.should == [@invoice1, @invoice2, @invoice3]
311
+ invs2.should == [@invoice4, @invoice5]
312
+ invs1[0].client.should == @client1
313
+ invs1[1].client.should == @client1
314
+ invs1[2].client.should == @client2
315
+ invs2[0].client.should == @client3
316
+ invs2[1].client.should == @client3
317
+ invs1.collect{|i| i.firm}.should == [@firm1, @firm1, @firm1]
318
+ invs2.collect{|i| i.firm}.should == [@firm2, @firm2]
319
+ invs1.collect{|i| i.client.firm}.should == [@firm1, @firm1, @firm1]
320
+ invs2.collect{|i| i.client.firm}.should == [@firm2, @firm2]
321
+ sqls_should_be
322
+ end
323
+
324
+ it "should return has_one :through belongs_to records for a single object" do
325
+ firm = @invoice1.firm
326
+ sqls_should_be("SELECT firms.id, clients.id AS 'clients_id', clients.firm_id FROM firms LEFT OUTER JOIN clients ON (clients.firm_id = firms.id) WHERE (clients.id = 1)")
327
+ firm.should == @firm1
328
+ @invoice1.client.should == @client1
329
+ @invoice1.client.firm.should == @firm1
330
+ sqls_should_be
331
+ firm.associations[:clients].should == nil
332
+ end
333
+
334
+ it "should eagerly load has_one :through belongs_to records for multiple objects" do
335
+ invs = Invoice.order(:id).eager(:firm).all
336
+ sqls_should_be("SELECT * FROM invoices ORDER BY id", "SELECT firms.id, clients.id AS 'clients_id', clients.firm_id FROM firms LEFT OUTER JOIN clients ON (clients.firm_id = firms.id) WHERE (clients.id IN (1, 2, 3))")
337
+ invs.should == [@invoice1, @invoice2, @invoice3, @invoice4, @invoice5]
338
+ invs[0].firm.should == @firm1
339
+ invs[0].client.should == @client1
340
+ invs[0].client.firm.should == @firm1
341
+ invs[0].firm.associations[:clients].should == nil
342
+ invs[1].firm.should == @firm1
343
+ invs[1].client.should == @client1
344
+ invs[1].client.firm.should == @firm1
345
+ invs[1].firm.associations[:clients].should == nil
346
+ invs[2].firm.should == @firm1
347
+ invs[2].client.should == @client2
348
+ invs[2].client.firm.should == @firm1
349
+ invs[2].firm.associations[:clients].should == nil
350
+ invs[3].firm.should == @firm2
351
+ invs[3].client.should == @client3
352
+ invs[3].client.firm.should == @firm2
353
+ invs[3].firm.associations[:clients].should == nil
354
+ invs[4].firm.should == @firm2
355
+ invs[4].client.should == @client3
356
+ invs[4].client.firm.should == @firm2
357
+ invs[4].firm.associations[:clients].should == nil
358
+ sqls_should_be
359
+ end
360
+ end
361
+
362
+ describe "Polymorphic Associations" do
363
+ before do
364
+ INTEGRATION_DB.instance_variable_set(:@schemas, nil)
365
+ INTEGRATION_DB.create_table!(:assets) do
366
+ primary_key :id
367
+ integer :attachable_id
368
+ text :attachable_type
369
+ end
370
+ class ::Asset < Sequel::Model
371
+ m = method(:constantize)
372
+ many_to_one :attachable, :reciprocal=>:assets, \
373
+ :dataset=>(proc do
374
+ klass = m.call(attachable_type)
375
+ klass.filter(klass.primary_key=>attachable_id)
376
+ end), \
377
+ :eager_loader=>(proc do |key_hash, assets, associations|
378
+ id_map = {}
379
+ assets.each do |asset|
380
+ asset.associations[:attachable] = nil
381
+ ((id_map[asset.attachable_type] ||= {})[asset.attachable_id] ||= []) << asset
382
+ end
383
+ id_map.each do |klass_name, id_map|
384
+ klass = m.call(klass_name)
385
+ klass.filter(klass.primary_key=>id_map.keys).all do |attach|
386
+ id_map[attach.pk].each do |asset|
387
+ asset.associations[:attachable] = attach
388
+ end
389
+ end
390
+ end
391
+ end)
392
+
393
+ private
394
+
395
+ def _attachable=(attachable)
396
+ self[:attachable_id] = (attachable.pk if attachable)
397
+ self[:attachable_type] = (attachable.class.name if attachable)
398
+ end
399
+ end
400
+
401
+ INTEGRATION_DB.create_table!(:posts) do
402
+ primary_key :id
403
+ end
404
+ class ::Post < Sequel::Model
405
+ one_to_many :assets, :key=>:attachable_id do |ds|
406
+ ds.filter(:attachable_type=>'Post')
407
+ end
408
+
409
+ private
410
+
411
+ def _add_asset(asset)
412
+ asset.attachable_id = pk
413
+ asset.attachable_type = 'Post'
414
+ asset.save
415
+ end
416
+ def _remove_asset(asset)
417
+ asset.attachable_id = nil
418
+ asset.attachable_type = nil
419
+ asset.save
420
+ end
421
+ def _remove_all_assets
422
+ Asset.filter(:attachable_id=>pk, :attachable_type=>'Post')\
423
+ .update(:attachable_id=>nil, :attachable_type=>nil)
424
+ end
425
+ end
426
+
427
+ INTEGRATION_DB.create_table!(:notes) do
428
+ primary_key :id
429
+ end
430
+ class ::Note < Sequel::Model
431
+ one_to_many :assets, :key=>:attachable_id do |ds|
432
+ ds.filter(:attachable_type=>'Note')
433
+ end
434
+
435
+ private
436
+
437
+ def _add_asset(asset)
438
+ asset.attachable_id = pk
439
+ asset.attachable_type = 'Note'
440
+ asset.save
441
+ end
442
+ def _remove_asset(asset)
443
+ asset.attachable_id = nil
444
+ asset.attachable_type = nil
445
+ asset.save
446
+ end
447
+ def _remove_all_assets
448
+ Asset.filter(:attachable_id=>pk, :attachable_type=>'Note')\
449
+ .update(:attachable_id=>nil, :attachable_type=>nil)
450
+ end
451
+ end
452
+ @post = Post.create
453
+ Note.create
454
+ @note = Note.create
455
+ @asset1 = Asset.create(:attachable=>@post)
456
+ @asset2 = Asset.create(:attachable=>@note)
457
+ @asset1.associations.clear
458
+ @asset2.associations.clear
459
+ clear_sqls
460
+ end
461
+ after do
462
+ INTEGRATION_DB.drop_table :assets, :posts, :notes
463
+ Object.send(:remove_const, :Asset)
464
+ Object.send(:remove_const, :Post)
465
+ Object.send(:remove_const, :Note)
466
+ end
467
+
468
+ it "should load the correct associated object for a single object" do
469
+ @asset1.attachable.should == @post
470
+ @asset2.attachable.should == @note
471
+ sqls_should_be("SELECT * FROM posts WHERE (id = 1) LIMIT 1", "SELECT * FROM notes WHERE (id = 2) LIMIT 1")
472
+ end
473
+
474
+ it "should eagerly load the correct associated object for a group of objects" do
475
+ assets = Asset.order(:id).eager(:attachable).all
476
+ sqls_should_be("SELECT * FROM assets ORDER BY id", "SELECT * FROM posts WHERE (id IN (1))", "SELECT * FROM notes WHERE (id IN (2))")
477
+ assets.should == [@asset1, @asset2]
478
+ assets[0].attachable.should == @post
479
+ assets[1].attachable.should == @note
480
+ sqls_should_be
481
+ end
482
+
483
+ it "should set items correctly" do
484
+ @asset1.attachable = @note
485
+ @asset2.attachable = @post
486
+ sqls_should_be("SELECT * FROM posts WHERE (id = 1) LIMIT 1", "SELECT * FROM notes WHERE (id = 2) LIMIT 1")
487
+ @asset1.attachable.should == @note
488
+ @asset1.attachable_id.should == @note.pk
489
+ @asset1.attachable_type.should == 'Note'
490
+ @asset2.attachable.should == @post
491
+ @asset2.attachable_id.should == @post.pk
492
+ @asset2.attachable_type.should == 'Post'
493
+ @asset1.attachable = nil
494
+ @asset1.attachable.should == nil
495
+ @asset1.attachable_id.should == nil
496
+ @asset1.attachable_type.should == nil
497
+ sqls_should_be
498
+ end
499
+
500
+ it "should add items correctly" do
501
+ @post.assets.should == [@asset1]
502
+ sqls_should_be("SELECT * FROM assets WHERE ((assets.attachable_id = 1) AND (attachable_type = 'Post'))")
503
+ @post.add_asset(@asset2)
504
+ sqls_should_be(/UPDATE assets SET ((attachable_id = 1|attachable_type = 'Post'|id = 2)(, )?){3} WHERE \(id = 2\)/)
505
+ @post.assets.should == [@asset1, @asset2]
506
+ @asset2.attachable.should == @post
507
+ @asset2.attachable_id.should == @post.pk
508
+ @asset2.attachable_type.should == 'Post'
509
+ sqls_should_be
510
+ end
511
+
512
+ it "should remove items correctly" do
513
+ @note.assets.should == [@asset2]
514
+ sqls_should_be("SELECT * FROM assets WHERE ((assets.attachable_id = 2) AND (attachable_type = 'Note'))")
515
+ @note.remove_asset(@asset2)
516
+ sqls_should_be(/UPDATE assets SET ((attachable_id = NULL|attachable_type = NULL|id = 2)(, )?){3} WHERE \(id = 2\)/)
517
+ @note.assets.should == []
518
+ @asset2.attachable.should == nil
519
+ @asset2.attachable_id.should == nil
520
+ @asset2.attachable_type.should == nil
521
+ sqls_should_be
522
+ end
523
+
524
+ it "should remove all items correctly" do
525
+ @post.remove_all_assets
526
+ @note.remove_all_assets
527
+ sqls_should_be(/UPDATE assets SET attachable_(id|type) = NULL, attachable_(type|id) = NULL WHERE \(\(attachable_(id|type) = (1|'Post')\) AND \(attachable_(type|id) = ('Post'|1)\)\)/,
528
+ /UPDATE assets SET attachable_(id|type) = NULL, attachable_(type|id) = NULL WHERE \(\(attachable_(id|type) = (2|'Note')\) AND \(attachable_(type|id) = ('Note'|2)\)\)/)
529
+ @asset1.reload.attachable.should == nil
530
+ @asset2.reload.attachable.should == nil
531
+ end
532
+ end
533
+
534
+ describe "many_to_one/one_to_many not referencing primary key" do
535
+ before do
536
+ INTEGRATION_DB.instance_variable_set(:@schemas, nil)
537
+ INTEGRATION_DB.create_table!(:clients) do
538
+ primary_key :id
539
+ text :name
540
+ end
541
+ class ::Client < Sequel::Model
542
+ one_to_many :invoices, :reciprocal=>:client, \
543
+ :dataset=>proc{Invoice.filter(:client_name=>name)}, \
544
+ :eager_loader=>(proc do |key_hash, clients, associations|
545
+ id_map = {}
546
+ clients.each do |client|
547
+ id_map[client.name] = client
548
+ client.associations[:invoices] = []
549
+ end
550
+ Invoice.filter(:client_name=>id_map.keys.sort).all do |inv|
551
+ inv.associations[:client] = client = id_map[inv.client_name]
552
+ client.associations[:invoices] << inv
553
+ end
554
+ end)
555
+
556
+ private
557
+
558
+ def _add_invoice(invoice)
559
+ invoice.client_name = name
560
+ invoice.save
561
+ end
562
+ def _remove_invoice(invoice)
563
+ invoice.client_name = nil
564
+ invoice.save
565
+ end
566
+ def _remove_all_invoices
567
+ Invoice.filter(:client_name=>name).update(:client_name=>nil)
568
+ end
569
+ end
570
+
571
+ INTEGRATION_DB.create_table!(:invoices) do
572
+ primary_key :id
573
+ text :client_name
574
+ end
575
+ class ::Invoice < Sequel::Model
576
+ many_to_one :client, :key=>:client_name, \
577
+ :dataset=>proc{Client.filter(:name=>client_name)}, \
578
+ :eager_loader=>(proc do |key_hash, invoices, associations|
579
+ id_map = key_hash[:client_name]
580
+ invoices.each{|inv| inv.associations[:client] = nil}
581
+ Client.filter(:name=>id_map.keys).all do |client|
582
+ id_map[client.name].each{|inv| inv.associations[:client] = client}
583
+ end
584
+ end)
585
+
586
+ private
587
+
588
+ def _client=(client)
589
+ self.client_name = (client.name if client)
590
+ end
591
+ end
592
+
593
+ @client1 = Client.create(:name=>'X')
594
+ @client2 = Client.create(:name=>'Y')
595
+ @invoice1 = Invoice.create(:client_name=>'X')
596
+ @invoice2 = Invoice.create(:client_name=>'X')
597
+ clear_sqls
598
+ end
599
+ after do
600
+ INTEGRATION_DB.drop_table :invoices, :clients
601
+ Object.send(:remove_const, :Client)
602
+ Object.send(:remove_const, :Invoice)
603
+ end
604
+
605
+ it "should load all associated one_to_many objects for a single object" do
606
+ invs = @client1.invoices
607
+ sqls_should_be("SELECT * FROM invoices WHERE (client_name = 'X')")
608
+ invs.should == [@invoice1, @invoice2]
609
+ invs[0].client.should == @client1
610
+ invs[1].client.should == @client1
611
+ sqls_should_be
612
+ end
613
+
614
+ it "should load the associated many_to_one object for a single object" do
615
+ client = @invoice1.client
616
+ sqls_should_be("SELECT * FROM clients WHERE (name = 'X') LIMIT 1")
617
+ client.should == @client1
618
+ end
619
+
620
+ it "should eagerly load all associated one_to_many objects for a group of objects" do
621
+ clients = Client.order(:id).eager(:invoices).all
622
+ sqls_should_be("SELECT * FROM clients ORDER BY id", "SELECT * FROM invoices WHERE (client_name IN ('X', 'Y'))")
623
+ clients.should == [@client1, @client2]
624
+ clients[1].invoices.should == []
625
+ invs = clients[0].invoices.sort_by{|x| x.pk}
626
+ invs.should == [@invoice1, @invoice2]
627
+ invs[0].client.should == @client1
628
+ invs[1].client.should == @client1
629
+ sqls_should_be
630
+ end
631
+
632
+ it "should eagerly load the associated many_to_one object for a group of objects" do
633
+ invoices = Invoice.order(:id).eager(:client).all
634
+ sqls_should_be("SELECT * FROM invoices ORDER BY id", "SELECT * FROM clients WHERE (name IN ('X'))")
635
+ invoices.should == [@invoice1, @invoice2]
636
+ invoices[0].client.should == @client1
637
+ invoices[1].client.should == @client1
638
+ sqls_should_be
639
+ end
640
+
641
+ it "should set the associated object correctly" do
642
+ @invoice1.client = @client2
643
+ sqls_should_be("SELECT * FROM clients WHERE (name = 'X') LIMIT 1")
644
+ @invoice1.client.should == @client2
645
+ @invoice1.client_name.should == 'Y'
646
+ @invoice1.client = nil
647
+ @invoice1.client_name.should == nil
648
+ sqls_should_be
649
+ end
650
+
651
+ it "should add the associated object correctly" do
652
+ @client2.invoices.should == []
653
+ sqls_should_be("SELECT * FROM invoices WHERE (client_name = 'Y')")
654
+ @client2.add_invoice(@invoice1)
655
+ sqls_should_be(/UPDATE invoices SET (client_name = 'Y'|id = 1), (client_name = 'Y'|id = 1) WHERE \(id = 1\)/)
656
+ @client2.invoices.should == [@invoice1]
657
+ @invoice1.client_name.should == 'Y'
658
+ @invoice1.client = nil
659
+ @invoice1.client_name.should == nil
660
+ sqls_should_be
661
+ end
662
+
663
+ it "should remove the associated object correctly" do
664
+ invs = @client1.invoices.sort_by{|x| x.pk}
665
+ sqls_should_be("SELECT * FROM invoices WHERE (client_name = 'X')")
666
+ invs.should == [@invoice1, @invoice2]
667
+ @client1.remove_invoice(@invoice1)
668
+ sqls_should_be(/UPDATE invoices SET (client_name = NULL|id = 1), (client_name = NULL|id = 1) WHERE \(id = 1\)/)
669
+ @client1.invoices.should == [@invoice2]
670
+ @invoice1.client_name.should == nil
671
+ @invoice1.client.should == nil
672
+ sqls_should_be
673
+ end
674
+
675
+ it "should remove all associated objects correctly" do
676
+ invs = @client1.remove_all_invoices
677
+ sqls_should_be("UPDATE invoices SET client_name = NULL WHERE (client_name = 'X')")
678
+ @invoice1.refresh.client.should == nil
679
+ @invoice1.client_name.should == nil
680
+ @invoice2.refresh.client.should == nil
681
+ @invoice2.client_name.should == nil
682
+ end
683
+ end