epugh-sequel 0.0.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 (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