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,332 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ context "Migration classes" do
4
+ before do
5
+ Sequel::Migration.descendants.clear
6
+ end
7
+
8
+ specify "should be registred in Migration.descendants" do
9
+ @class = Class.new(Sequel::Migration)
10
+
11
+ Sequel::Migration.descendants.should == [@class]
12
+ end
13
+
14
+ specify "should be registered in the right order" do
15
+ @c1 = Class.new(Sequel::Migration)
16
+ @c2 = Class.new(Sequel::Migration)
17
+ @c3 = Class.new(Sequel::Migration)
18
+
19
+ Sequel::Migration.descendants.should == [@c1, @c2, @c3]
20
+ end
21
+ end
22
+
23
+ context "Migration#apply" do
24
+ before do
25
+ @c = Class.new do
26
+ define_method(:one) {|x| [1111, x]}
27
+ define_method(:two) {|x| [2222, x]}
28
+ end
29
+ @db = @c.new
30
+
31
+ @migration = Class.new(Sequel::Migration) do
32
+ define_method(:up) {one(3333)}
33
+ define_method(:down) {two(4444)}
34
+ end
35
+ end
36
+
37
+ specify "should raise for an invalid direction" do
38
+ proc {@migration.apply(@db, :hahaha)}.should raise_error(ArgumentError)
39
+ end
40
+
41
+ specify "should apply the up direction correctly" do
42
+ @migration.apply(@db, :up).should == [1111, 3333]
43
+ end
44
+
45
+ specify "should apply the down direction correctly" do
46
+ @migration.apply(@db, :down).should == [2222, 4444]
47
+ end
48
+ end
49
+
50
+ MIGRATION_001 = %[
51
+ class CreateSessions < Sequel::Migration
52
+ def up
53
+ create(1111)
54
+ end
55
+
56
+ def down
57
+ drop(1111)
58
+ end
59
+ end
60
+ ]
61
+
62
+ MIGRATION_002 = %[
63
+ class CreateNodes < Sequel::Migration
64
+ def up
65
+ create(2222)
66
+ end
67
+
68
+ def down
69
+ drop(2222)
70
+ end
71
+ end
72
+ ]
73
+
74
+ MIGRATION_003 = %[
75
+ class CreateUsers < Sequel::Migration
76
+ def up
77
+ create(3333)
78
+ end
79
+
80
+ def down
81
+ drop(3333)
82
+ end
83
+ end
84
+ ]
85
+
86
+ MIGRATION_005 = %[
87
+ class CreateAttributes < Sequel::Migration
88
+ def up
89
+ create(5555)
90
+ end
91
+
92
+ def down
93
+ drop(5555)
94
+ end
95
+ end
96
+ ]
97
+
98
+ ALT_MIGRATION_001 = %[
99
+ class CreateAltBasic < Sequel::Migration
100
+ def up
101
+ create(11111)
102
+ end
103
+
104
+ def down
105
+ drop(11111)
106
+ end
107
+ end
108
+ ]
109
+
110
+ ALT_MIGRATION_003 = %[
111
+ class CreateAltAdvanced < Sequel::Migration
112
+ def up
113
+ create(33333)
114
+ end
115
+
116
+ def down
117
+ drop(33333)
118
+ end
119
+ end
120
+ ]
121
+
122
+ context "Sequel::Migrator" do
123
+ before do
124
+ dbc = Class.new(MockDatabase) do
125
+ attr_reader :creates, :drops, :tables_created, :columns_created, :versions
126
+ def initialize(*args)
127
+ super
128
+ @creates = []
129
+ @drops = []
130
+ @tables_created = []
131
+ @columns_created = []
132
+ @versions = {}
133
+ end
134
+
135
+ def create(x); @creates << x; end
136
+ def drop(x); @drops << x; end
137
+
138
+ def create_table(name, opts={}, &block)
139
+ super
140
+ @columns_created << / \(?(\w+) integer\)?\z/.match(sqls.last)[1].to_sym
141
+ @tables_created << name
142
+ end
143
+
144
+ def dataset(opts={})
145
+ ds = super
146
+ ds.extend(Module.new do
147
+ def columns; db.columns_created end
148
+ def insert(h); db.versions.merge!(h); super(h) end
149
+ def update(h); db.versions.merge!(h); super(h) end
150
+ def fetch_rows(sql); db.execute(sql); yield(db.versions) unless db.versions.empty? end
151
+ end)
152
+ ds
153
+ end
154
+
155
+ def table_exists?(name)
156
+ @tables_created.include?(name)
157
+ end
158
+ end
159
+ @db = dbc.new
160
+
161
+ @dirname = "migrate_#{$$}"
162
+ Dir.mkdir(@dirname)
163
+ File.open("#{@dirname}/001_create_sessions.rb", 'w') {|f| f << MIGRATION_001}
164
+ File.open("#{@dirname}/002_create_nodes.rb", 'w') {|f| f << MIGRATION_002}
165
+ File.open("#{@dirname}/003_create_users.rb", 'w') {|f| f << MIGRATION_003}
166
+ File.open("#{@dirname}/005_5_create_attributes.rb", 'w') {|f| f << MIGRATION_005}
167
+
168
+ @alt_dirname = "migrate_alt_#{$$}"
169
+ Dir.mkdir(@alt_dirname)
170
+ File.open("#{@alt_dirname}/001_create_alt_basic.rb", 'w') {|f| f << ALT_MIGRATION_001}
171
+ File.open("#{@alt_dirname}/003_create_alt_advanced.rb", 'w') {|f| f << ALT_MIGRATION_003}
172
+ end
173
+
174
+ after do
175
+ Object.send(:remove_const, "CreateSessions") if Object.const_defined?("CreateSessions")
176
+ Object.send(:remove_const, "CreateNodes") if Object.const_defined?("CreateNodes")
177
+ Object.send(:remove_const, "CreateUsers") if Object.const_defined?("CreateUsers")
178
+ Object.send(:remove_const, "CreateAttributes") if Object.const_defined?("CreateAttributes")
179
+ Object.send(:remove_const, "CreateAltBasic") if Object.const_defined?("CreateAltBasic")
180
+ Object.send(:remove_const, "CreateAltAdvanced") if Object.const_defined?("CreateAltAdvanced")
181
+
182
+ File.delete("#{@dirname}/001_create_sessions.rb")
183
+ File.delete("#{@dirname}/002_create_nodes.rb")
184
+ File.delete("#{@dirname}/003_create_users.rb")
185
+ File.delete("#{@dirname}/005_5_create_attributes.rb")
186
+ Dir.rmdir(@dirname)
187
+ File.delete("#{@alt_dirname}/001_create_alt_basic.rb")
188
+ File.delete("#{@alt_dirname}/003_create_alt_advanced.rb")
189
+ Dir.rmdir(@alt_dirname)
190
+ end
191
+
192
+ specify "#migration_files should return the list of files for a specified version range" do
193
+ Sequel::Migrator.migration_files(@dirname, 1..1).map{|f| File.basename(f)}.should == ['001_create_sessions.rb']
194
+ Sequel::Migrator.migration_files(@dirname, 1..3).map{|f| File.basename(f)}.should == ['001_create_sessions.rb', '002_create_nodes.rb', '003_create_users.rb']
195
+ Sequel::Migrator.migration_files(@dirname, 3..6).map{|f| File.basename(f)}.should == ['003_create_users.rb', '005_5_create_attributes.rb']
196
+ Sequel::Migrator.migration_files(@dirname, 7..8).map{|f| File.basename(f)}.should == []
197
+ Sequel::Migrator.migration_files(@alt_dirname, 1..1).map{|f| File.basename(f)}.should == ['001_create_alt_basic.rb']
198
+ Sequel::Migrator.migration_files(@alt_dirname, 1..3).map{|f| File.basename(f)}.should == ['001_create_alt_basic.rb','003_create_alt_advanced.rb']
199
+ end
200
+
201
+ specify "#latest_migration_version should return the latest version available" do
202
+ Sequel::Migrator.latest_migration_version(@dirname).should == 5
203
+ Sequel::Migrator.latest_migration_version(@alt_dirname).should == 3
204
+ end
205
+
206
+ specify "#migration_classes should load the migration classes for the specified range for the up direction" do
207
+ Sequel::Migrator.migration_classes(@dirname, 3, 0, :up).should == [CreateSessions, CreateNodes, CreateUsers]
208
+ Sequel::Migrator.migration_classes(@alt_dirname, 3, 0, :up).should == [CreateAltBasic, CreateAltAdvanced]
209
+ end
210
+
211
+ specify "#migration_classes should load the migration classes for the specified range for the down direction" do
212
+ Sequel::Migrator.migration_classes(@dirname, 0, 5, :down).should == [CreateAttributes, CreateUsers, CreateNodes, CreateSessions]
213
+ Sequel::Migrator.migration_classes(@alt_dirname, 0, 3, :down).should == [CreateAltAdvanced, CreateAltBasic]
214
+ end
215
+
216
+ specify "#migration_classes should start from current + 1 for the up direction" do
217
+ Sequel::Migrator.migration_classes(@dirname, 3, 1, :up).should == [CreateNodes, CreateUsers]
218
+ Sequel::Migrator.migration_classes(@alt_dirname, 3, 2, :up).should == [CreateAltAdvanced]
219
+ end
220
+
221
+ specify "#migration_classes should end on current + 1 for the down direction" do
222
+ Sequel::Migrator.migration_classes(@dirname, 2, 5, :down).should == [CreateAttributes, CreateUsers]
223
+ Sequel::Migrator.migration_classes(@alt_dirname, 2, 4, :down).should == [CreateAltAdvanced]
224
+ end
225
+
226
+ specify "#schema_info_dataset should automatically create the schema_info table" do
227
+ @db.table_exists?(:schema_info).should be_false
228
+ Sequel::Migrator.schema_info_dataset(@db)
229
+ @db.table_exists?(:schema_info).should be_true
230
+ @db.sqls.should == ["CREATE TABLE schema_info (version integer)"]
231
+ end
232
+
233
+ specify "should automatically create new APP_version column in schema_info" do
234
+ @db.table_exists?(:schema_info).should be_false
235
+ Sequel::Migrator.schema_info_dataset(@db, :column => :alt_version)
236
+ @db.table_exists?(:schema_info).should be_true
237
+ @db.sqls.should == ["CREATE TABLE schema_info (alt_version integer)"]
238
+ end
239
+
240
+ specify "should automatically create new APP_version column in schema_info" do
241
+ @db.table_exists?(:alt_table).should be_false
242
+ Sequel::Migrator.schema_info_dataset(@db, :table => :alt_table)
243
+ @db.table_exists?(:alt_table).should be_true
244
+ Sequel::Migrator.schema_info_dataset(@db, :table => :alt_table, :column=>:alt_version)
245
+ @db.sqls.should == ["CREATE TABLE alt_table (version integer)",
246
+ "ALTER TABLE alt_table ADD COLUMN alt_version integer"]
247
+ end
248
+
249
+ specify "should return a dataset for the correct table" do
250
+ Sequel::Migrator.schema_info_dataset(@db).first_source_alias.should == :schema_info
251
+ Sequel::Migrator.schema_info_dataset(@db, :table=>:blah).first_source_alias.should == :blah
252
+ end
253
+
254
+ specify "should assume a migration version of 0 if no migration information exists in the database" do
255
+ Sequel::Migrator.get_current_migration_version(@db).should == 0
256
+ @db.sqls.should == ["CREATE TABLE schema_info (version integer)", "SELECT * FROM schema_info LIMIT 1"]
257
+ end
258
+ specify "should use the migration version stored in the database" do
259
+ Sequel::Migrator.schema_info_dataset(@db).insert(:version => 4321)
260
+ Sequel::Migrator.get_current_migration_version(@db).should == 4321
261
+ @db.sqls.should == ["CREATE TABLE schema_info (version integer)", "INSERT INTO schema_info (version) VALUES (4321)", "SELECT * FROM schema_info LIMIT 1"]
262
+ end
263
+
264
+ specify "should set the migration version stored in the database" do
265
+ Sequel::Migrator.set_current_migration_version(@db, 6666)
266
+ Sequel::Migrator.get_current_migration_version(@db).should == 6666
267
+ @db.sqls.should == ["CREATE TABLE schema_info (version integer)",
268
+ "SELECT * FROM schema_info LIMIT 1",
269
+ "INSERT INTO schema_info (version) VALUES (6666)",
270
+ "SELECT * FROM schema_info LIMIT 1"]
271
+ end
272
+
273
+ specify "should apply migrations correctly in the up direction" do
274
+ Sequel::Migrator.apply(@db, @dirname, 3, 2)
275
+ @db.creates.should == [3333]
276
+
277
+ Sequel::Migrator.get_current_migration_version(@db).should == 3
278
+
279
+ Sequel::Migrator.apply(@db, @dirname, 5)
280
+ @db.creates.should == [3333, 5555]
281
+
282
+ Sequel::Migrator.get_current_migration_version(@db).should == 5
283
+ @db.sqls.should == ["CREATE TABLE schema_info (version integer)",
284
+ "SELECT * FROM schema_info LIMIT 1",
285
+ "INSERT INTO schema_info (version) VALUES (3)",
286
+ "SELECT * FROM schema_info LIMIT 1",
287
+ "SELECT * FROM schema_info LIMIT 1",
288
+ "SELECT * FROM schema_info LIMIT 1",
289
+ "UPDATE schema_info SET version = 5",
290
+ "SELECT * FROM schema_info LIMIT 1"]
291
+ end
292
+
293
+ specify "should apply migrations correctly in the down direction" do
294
+ Sequel::Migrator.apply(@db, @dirname, 1, 5)
295
+ @db.drops.should == [5555, 3333, 2222]
296
+
297
+ Sequel::Migrator.get_current_migration_version(@db).should == 1
298
+ @db.sqls.should == ["CREATE TABLE schema_info (version integer)",
299
+ "SELECT * FROM schema_info LIMIT 1",
300
+ "INSERT INTO schema_info (version) VALUES (1)",
301
+ "SELECT * FROM schema_info LIMIT 1"]
302
+ end
303
+
304
+ specify "should apply migrations up to the latest version if no target is given" do
305
+ Sequel::Migrator.apply(@db, @dirname)
306
+ @db.creates.should == [1111, 2222, 3333, 5555]
307
+
308
+ Sequel::Migrator.get_current_migration_version(@db).should == 5
309
+ @db.sqls.should == ["CREATE TABLE schema_info (version integer)",
310
+ "SELECT * FROM schema_info LIMIT 1",
311
+ "SELECT * FROM schema_info LIMIT 1",
312
+ "INSERT INTO schema_info (version) VALUES (5)",
313
+ "SELECT * FROM schema_info LIMIT 1"]
314
+ end
315
+
316
+ specify "should apply migrations down to 0 version correctly" do
317
+ Sequel::Migrator.apply(@db, @dirname, 0, 5)
318
+ @db.drops.should == [5555, 3333, 2222, 1111]
319
+
320
+ Sequel::Migrator.get_current_migration_version(@db).should == 0
321
+ @db.sqls.should == ["CREATE TABLE schema_info (version integer)",
322
+ "SELECT * FROM schema_info LIMIT 1",
323
+ "INSERT INTO schema_info (version) VALUES (0)",
324
+ "SELECT * FROM schema_info LIMIT 1"]
325
+ end
326
+
327
+ specify "should return the target version" do
328
+ Sequel::Migrator.apply(@db, @dirname, 3, 2).should == 3
329
+ Sequel::Migrator.apply(@db, @dirname, 0).should == 0
330
+ Sequel::Migrator.apply(@db, @dirname).should == 5
331
+ end
332
+ end
@@ -0,0 +1,72 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ if (begin
4
+ require 'tzinfo'
5
+ true
6
+ rescue LoadError
7
+ end)
8
+
9
+ Sequel.extension :named_timezones
10
+ Sequel.datetime_class = Time
11
+
12
+ describe "Sequel named_timezones extension" do
13
+ before do
14
+ @tz_in = TZInfo::Timezone.get('America/Los_Angeles')
15
+ @tz_out = TZInfo::Timezone.get('America/New_York')
16
+ @db = MockDatabase.new
17
+ @dt = DateTime.civil(2009,6,1,10,20,30,0)
18
+ Sequel.application_timezone = 'America/Los_Angeles'
19
+ Sequel.database_timezone = 'America/New_York'
20
+ Sequel.datetime_class = DateTime
21
+ end
22
+ after do
23
+ Sequel.default_timezone = nil
24
+ Sequel.datetime_class = Time
25
+ end
26
+
27
+ it "should convert string arguments to *_timezone= to TZInfo::Timezone instances" do
28
+ Sequel.application_timezone.should == @tz_in
29
+ Sequel.database_timezone.should == @tz_out
30
+ end
31
+
32
+ it "should accept TZInfo::Timezone instances in *_timezone=" do
33
+ Sequel.application_timezone = @tz_in
34
+ Sequel.database_timezone = @tz_out
35
+ Sequel.application_timezone.should == @tz_in
36
+ Sequel.database_timezone.should == @tz_out
37
+ end
38
+
39
+ it "should convert datetimes going into the database to named database_timezone" do
40
+ ds = @db[:a]
41
+ def ds.supports_timestamp_timezones?; true; end
42
+ def ds.supports_timestamp_usecs?; false; end
43
+ ds.insert([@dt, DateTime.civil(2009,6,1,3,20,30,Rational(-7, 24)), DateTime.civil(2009,6,1,6,20,30,Rational(-1, 6))])
44
+ @db.sqls.should == ["INSERT INTO a VALUES ('2009-06-01 06:20:30-0400', '2009-06-01 06:20:30-0400', '2009-06-01 06:20:30-0400')"]
45
+ end
46
+
47
+ it "should convert datetimes coming out of the database from database_timezone to application_timezone" do
48
+ dt = Sequel.database_to_application_timestamp('2009-06-01 06:20:30-0400')
49
+ dt.should == @dt
50
+ dt.offset.should == Rational(-7, 24)
51
+
52
+ dt = Sequel.database_to_application_timestamp('2009-06-01 10:20:30+0000')
53
+ dt.should == @dt
54
+ dt.offset.should == Rational(-7, 24)
55
+ end
56
+
57
+ it "should assume datetimes coming out of the database that don't have an offset as coming from database_timezone" do
58
+ dt = Sequel.database_to_application_timestamp('2009-06-01 06:20:30')
59
+ dt.should == @dt
60
+ dt.offset.should == Rational(-7, 24)
61
+
62
+ dt = Sequel.database_to_application_timestamp('2009-06-01 10:20:30')
63
+ dt.should == @dt + Rational(1, 6)
64
+ dt.offset.should == Rational(-7, 24)
65
+ end
66
+
67
+ it "should work with the thread_local_timezones extension" do
68
+ [Thread.new{Sequel.thread_application_timezone = 'America/New_York'; sleep 0.03; Sequel.application_timezone.should == @tz_out},
69
+ Thread.new{sleep 0.01; Sequel.thread_application_timezone = 'America/Los_Angeles'; sleep 0.01; Sequel.application_timezone.should == @tz_in}].each{|x| x.join}
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,396 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ describe "NestedAttributes plugin" do
4
+ before do
5
+ mods = @mods = []
6
+ i = 0
7
+ gi = lambda{i}
8
+ ii = lambda{i+=1}
9
+ ds_mod = Module.new do
10
+ def empty?; false; end
11
+ define_method(:insert) do |h|
12
+ x = ii.call
13
+ mods << [:i, first_source, h, x]
14
+ x
15
+ end
16
+ define_method(:insert_select) do |h|
17
+ x = ii.call
18
+ mods << [:is, first_source, h, x]
19
+ h.merge(:id=>x)
20
+ end
21
+ define_method(:update) do |h|
22
+ mods << [:u, first_source, h, literal(opts[:where])]
23
+ 1
24
+ end
25
+ define_method(:delete) do
26
+ mods << [:d, first_source, literal(opts[:where])]
27
+ 1
28
+ end
29
+ end
30
+ db = Sequel::Database.new({})
31
+ db.meta_def(:dataset) do |*a|
32
+ x = super(*a)
33
+ x.extend(ds_mod)
34
+ x
35
+ end
36
+ @c = Class.new(Sequel::Model(db))
37
+ @c.plugin :nested_attributes
38
+ @Artist = Class.new(@c).set_dataset(:artists)
39
+ @Album = Class.new(@c).set_dataset(:albums)
40
+ @Tag = Class.new(@c).set_dataset(:tags)
41
+ [@Artist, @Album, @Tag].each do |m|
42
+ m.dataset.extend(ds_mod)
43
+ end
44
+ @Artist.columns :id, :name
45
+ @Album.columns :id, :name, :artist_id
46
+ @Tag.columns :id, :name
47
+ @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id
48
+ @Artist.one_to_one :first_album, :class=>@Album, :key=>:artist_id
49
+ @Album.many_to_one :artist, :class=>@Artist
50
+ @Album.many_to_many :tags, :class=>@Tag, :left_key=>:album_id, :right_key=>:tag_id, :join_table=>:at
51
+ @Artist.nested_attributes :albums, :first_album, :destroy=>true, :remove=>true
52
+ @Album.nested_attributes :artist, :tags, :destroy=>true, :remove=>true
53
+ end
54
+
55
+ it "should support creating new many_to_one objects" do
56
+ a = @Album.new({:name=>'Al', :artist_attributes=>{:name=>'Ar'}})
57
+ @mods.should == []
58
+ a.save
59
+ @mods.should == [[:is, :artists, {:name=>"Ar"}, 1], [:is, :albums, {:name=>"Al", :artist_id=>1}, 2]]
60
+ end
61
+
62
+ it "should support creating new one_to_one objects" do
63
+ a = @Artist.new(:name=>'Ar')
64
+ a.id = 1
65
+ a.first_album_attributes = {:name=>'Al'}
66
+ @mods.should == []
67
+ a.save
68
+ @mods.should == [[:is, :artists, {:name=>"Ar", :id=>1}, 1], [:is, :albums, {:name=>"Al"}, 2], [:u, :albums, {:artist_id=>nil}, "((artist_id = 1) AND (id != 2))"], [:u, :albums, {:name=>"Al", :artist_id=>1}, "(id = 2)"]]
69
+ end
70
+
71
+ it "should support creating new one_to_many objects" do
72
+ a = @Artist.new({:name=>'Ar', :albums_attributes=>[{:name=>'Al'}]})
73
+ @mods.should == []
74
+ a.save
75
+ @mods.should == [[:is, :artists, {:name=>"Ar"}, 1], [:is, :albums, {:name=>"Al", :artist_id=>1}, 2]]
76
+ end
77
+
78
+ it "should support creating new many_to_many objects" do
79
+ a = @Album.new({:name=>'Al', :tags_attributes=>[{:name=>'T'}]})
80
+ @mods.should == []
81
+ a.save
82
+ @mods.should == [[:is, :albums, {:name=>"Al"}, 1], [:is, :tags, {:name=>"T"}, 2], [:i, :at, {:album_id=>1, :tag_id=>2}, 3]]
83
+ end
84
+
85
+ it "should add new objects to the cached association array as soon as the *_attributes= method is called" do
86
+ a = @Artist.new({:name=>'Ar', :albums_attributes=>[{:name=>'Al', :tags_attributes=>[{:name=>'T'}]}]})
87
+ a.albums.should == [@Album.new(:name=>'Al')]
88
+ a.albums.first.tags.should == [@Tag.new(:name=>'T')]
89
+ end
90
+
91
+ it "should support updating many_to_one objects" do
92
+ al = @Album.load(:id=>10, :name=>'Al')
93
+ ar = @Artist.load(:id=>20, :name=>'Ar')
94
+ al.associations[:artist] = ar
95
+ al.set(:artist_attributes=>{:id=>'20', :name=>'Ar2'})
96
+ @mods.should == []
97
+ al.save
98
+ @mods.should == [[:u, :albums, {:name=>"Al"}, '(id = 10)'], [:u, :artists, {:name=>"Ar2"}, '(id = 20)']]
99
+ end
100
+
101
+ it "should support updating one_to_one objects" do
102
+ al = @Album.load(:id=>10, :name=>'Al')
103
+ ar = @Artist.load(:id=>20, :name=>'Ar')
104
+ ar.associations[:first_album] = al
105
+ ar.set(:first_album_attributes=>{:id=>10, :name=>'Al2'})
106
+ @mods.should == []
107
+ ar.save
108
+ @mods.should == [[:u, :artists, {:name=>"Ar"}, '(id = 20)'], [:u, :albums, {:name=>"Al2"}, '(id = 10)']]
109
+ end
110
+
111
+ it "should support updating one_to_many objects" do
112
+ al = @Album.load(:id=>10, :name=>'Al')
113
+ ar = @Artist.load(:id=>20, :name=>'Ar')
114
+ ar.associations[:albums] = [al]
115
+ ar.set(:albums_attributes=>[{:id=>10, :name=>'Al2'}])
116
+ @mods.should == []
117
+ ar.save
118
+ @mods.should == [[:u, :artists, {:name=>"Ar"}, '(id = 20)'], [:u, :albums, {:name=>"Al2"}, '(id = 10)']]
119
+ end
120
+
121
+ it "should support updating many_to_many objects" do
122
+ a = @Album.load(:id=>10, :name=>'Al')
123
+ t = @Tag.load(:id=>20, :name=>'T')
124
+ a.associations[:tags] = [t]
125
+ a.set(:tags_attributes=>[{:id=>20, :name=>'T2'}])
126
+ @mods.should == []
127
+ a.save
128
+ @mods.should == [[:u, :albums, {:name=>"Al"}, '(id = 10)'], [:u, :tags, {:name=>"T2"}, '(id = 20)']]
129
+ end
130
+
131
+ it "should support removing many_to_one objects" do
132
+ al = @Album.load(:id=>10, :name=>'Al')
133
+ ar = @Artist.load(:id=>20, :name=>'Ar')
134
+ al.associations[:artist] = ar
135
+ al.set(:artist_attributes=>{:id=>'20', :_remove=>'1'})
136
+ @mods.should == []
137
+ al.save
138
+ @mods.should == [[:u, :albums, {:artist_id=>nil, :name=>'Al'}, '(id = 10)']]
139
+ end
140
+
141
+ it "should support removing one_to_one objects" do
142
+ al = @Album.load(:id=>10, :name=>'Al')
143
+ ar = @Artist.load(:id=>20, :name=>'Ar')
144
+ ar.associations[:first_album] = al
145
+ ar.set(:first_album_attributes=>{:id=>10, :_remove=>'t'})
146
+ @mods.should == []
147
+ ar.save
148
+ @mods.should == [[:u, :albums, {:artist_id=>nil}, "(artist_id = 20)"], [:u, :artists, {:name=>"Ar"}, "(id = 20)"]]
149
+
150
+ end
151
+
152
+ it "should support removing one_to_many objects" do
153
+ al = @Album.load(:id=>10, :name=>'Al')
154
+ ar = @Artist.load(:id=>20, :name=>'Ar')
155
+ ar.associations[:albums] = [al]
156
+ ar.set(:albums_attributes=>[{:id=>10, :_remove=>'t'}])
157
+ @mods.should == []
158
+ ar.save
159
+ @mods.should == [[:u, :albums, {:name=>"Al", :artist_id=>nil}, '(id = 10)'], [:u, :artists, {:name=>"Ar"}, '(id = 20)']]
160
+ end
161
+
162
+ it "should support removing many_to_many objects" do
163
+ a = @Album.load(:id=>10, :name=>'Al')
164
+ t = @Tag.load(:id=>20, :name=>'T')
165
+ a.associations[:tags] = [t]
166
+ a.set(:tags_attributes=>[{:id=>20, :_remove=>true}])
167
+ @mods.should == []
168
+ a.save
169
+ @mods.should == [[:d, :at, '((album_id = 10) AND (tag_id = 20))'], [:u, :albums, {:name=>"Al"}, '(id = 10)']]
170
+ end
171
+
172
+ it "should support destroying many_to_one objects" do
173
+ al = @Album.load(:id=>10, :name=>'Al')
174
+ ar = @Artist.load(:id=>20, :name=>'Ar')
175
+ al.associations[:artist] = ar
176
+ al.set(:artist_attributes=>{:id=>'20', :_delete=>'1'})
177
+ @mods.should == []
178
+ al.save
179
+ @mods.should == [[:u, :albums, {:artist_id=>nil, :name=>'Al'}, '(id = 10)'], [:d, :artists, '(id = 20)']]
180
+ end
181
+
182
+ it "should support destroying one_to_one objects" do
183
+ al = @Album.load(:id=>10, :name=>'Al')
184
+ ar = @Artist.load(:id=>20, :name=>'Ar')
185
+ ar.associations[:first_album] = al
186
+ ar.set(:first_album_attributes=>{:id=>10, :_delete=>'t'})
187
+ @mods.should == []
188
+ ar.save
189
+ @mods.should == [[:u, :albums, {:artist_id=>nil}, "(artist_id = 20)"], [:u, :artists, {:name=>"Ar"}, "(id = 20)"], [:d, :albums, "(id = 10)"]]
190
+ end
191
+
192
+ it "should support destroying one_to_many objects" do
193
+ al = @Album.load(:id=>10, :name=>'Al')
194
+ ar = @Artist.load(:id=>20, :name=>'Ar')
195
+ ar.associations[:albums] = [al]
196
+ ar.set(:albums_attributes=>[{:id=>10, :_delete=>'t'}])
197
+ @mods.should == []
198
+ ar.save
199
+ @mods.should == [[:u, :albums, {:name=>"Al", :artist_id=>nil}, '(id = 10)'], [:u, :artists, {:name=>"Ar"}, '(id = 20)'], [:d, :albums, '(id = 10)']]
200
+ end
201
+
202
+ it "should support destroying many_to_many objects" do
203
+ a = @Album.load(:id=>10, :name=>'Al')
204
+ t = @Tag.load(:id=>20, :name=>'T')
205
+ a.associations[:tags] = [t]
206
+ a.set(:tags_attributes=>[{:id=>20, :_delete=>true}])
207
+ @mods.should == []
208
+ a.save
209
+ @mods.should == [[:d, :at, '((album_id = 10) AND (tag_id = 20))'], [:u, :albums, {:name=>"Al"}, '(id = 10)'], [:d, :tags, '(id = 20)']]
210
+ end
211
+
212
+ it "should support both string and symbol keys in nested attribute hashes" do
213
+ a = @Album.load(:id=>10, :name=>'Al')
214
+ t = @Tag.load(:id=>20, :name=>'T')
215
+ a.associations[:tags] = [t]
216
+ a.set('tags_attributes'=>[{'id'=>20, '_delete'=>true}])
217
+ @mods.should == []
218
+ a.save
219
+ @mods.should == [[:d, :at, '((album_id = 10) AND (tag_id = 20))'], [:u, :albums, {:name=>"Al"}, '(id = 10)'], [:d, :tags, '(id = 20)']]
220
+ end
221
+
222
+ it "should support using a hash instead of an array for to_many nested attributes" do
223
+ a = @Album.load(:id=>10, :name=>'Al')
224
+ t = @Tag.load(:id=>20, :name=>'T')
225
+ a.associations[:tags] = [t]
226
+ a.set('tags_attributes'=>{'1'=>{'id'=>20, '_delete'=>true}})
227
+ @mods.should == []
228
+ a.save
229
+ @mods.should == [[:d, :at, '((album_id = 10) AND (tag_id = 20))'], [:u, :albums, {:name=>"Al"}, '(id = 10)'], [:d, :tags, '(id = 20)']]
230
+ end
231
+
232
+ it "should only allow destroying associated objects if :destroy option is used in the nested_attributes call" do
233
+ a = @Album.load(:id=>10, :name=>'Al')
234
+ ar = @Artist.load(:id=>20, :name=>'Ar')
235
+ a.associations[:artist] = ar
236
+ @Album.nested_attributes :artist
237
+ proc{a.set(:artist_attributes=>{:id=>'20', :_delete=>'1'})}.should raise_error(Sequel::Error)
238
+ @Album.nested_attributes :artist, :destroy=>true
239
+ proc{a.set(:artist_attributes=>{:id=>'20', :_delete=>'1'})}.should_not raise_error(Sequel::Error)
240
+ end
241
+
242
+ it "should only allow removing associated objects if :remove option is used in the nested_attributes call" do
243
+ a = @Album.load(:id=>10, :name=>'Al')
244
+ ar = @Artist.load(:id=>20, :name=>'Ar')
245
+ a.associations[:artist] = ar
246
+ @Album.nested_attributes :artist
247
+ proc{a.set(:artist_attributes=>{:id=>'20', :_remove=>'1'})}.should raise_error(Sequel::Error)
248
+ @Album.nested_attributes :artist, :remove=>true
249
+ proc{a.set(:artist_attributes=>{:id=>'20', :_remove=>'1'})}.should_not raise_error(Sequel::Error)
250
+ end
251
+
252
+ it "should raise an Error if a primary key is given in a nested attribute hash, but no matching associated object exists" do
253
+ al = @Album.load(:id=>10, :name=>'Al')
254
+ ar = @Artist.load(:id=>20, :name=>'Ar')
255
+ ar.associations[:albums] = [al]
256
+ proc{ar.set(:albums_attributes=>[{:id=>30, :_delete=>'t'}])}.should raise_error(Sequel::Error)
257
+ proc{ar.set(:albums_attributes=>[{:id=>10, :_delete=>'t'}])}.should_not raise_error(Sequel::Error)
258
+ end
259
+
260
+ it "should not raise an Error if an unmatched primary key is given, if the :strict=>false option is used" do
261
+ @Artist.nested_attributes :albums, :strict=>false
262
+ al = @Album.load(:id=>10, :name=>'Al')
263
+ ar = @Artist.load(:id=>20, :name=>'Ar')
264
+ ar.associations[:albums] = [al]
265
+ ar.set(:albums_attributes=>[{:id=>30, :_delete=>'t'}])
266
+ @mods.should == []
267
+ ar.save
268
+ @mods.should == [[:u, :artists, {:name=>"Ar"}, '(id = 20)']]
269
+ end
270
+
271
+ it "should not save if nested attribute is not valid and should include nested attribute validation errors in the main object's validation errors" do
272
+ @Artist.class_eval do
273
+ def validate
274
+ super
275
+ errors.add(:name, 'cannot be Ar') if name == 'Ar'
276
+ end
277
+ end
278
+ a = @Album.new(:name=>'Al', :artist_attributes=>{:name=>'Ar'})
279
+ @mods.should == []
280
+ proc{a.save}.should raise_error(Sequel::ValidationFailed)
281
+ a.errors.full_messages.should == ['artist name cannot be Ar']
282
+ @mods.should == []
283
+ # Should preserve attributes
284
+ a.artist.name.should == 'Ar'
285
+ end
286
+
287
+ it "should not attempt to validate nested attributes if the :validate=>false association option is used" do
288
+ @Album.many_to_one :artist, :class=>@Artist, :validate=>false
289
+ @Album.nested_attributes :artist, :tags, :destroy=>true, :remove=>true
290
+ @Artist.class_eval do
291
+ def validate
292
+ super
293
+ errors.add(:name, 'cannot be Ar') if name == 'Ar'
294
+ end
295
+ end
296
+ a = @Album.new(:name=>'Al', :artist_attributes=>{:name=>'Ar'})
297
+ @mods.should == []
298
+ a.save
299
+ @mods.should == [[:is, :artists, {:name=>"Ar"}, 1], [:is, :albums, {:name=>"Al", :artist_id=>1}, 2]]
300
+ end
301
+
302
+ it "should not attempt to validate nested attributes if the :validate=>false option is passed to save" do
303
+ @Artist.class_eval do
304
+ def validate
305
+ super
306
+ errors.add(:name, 'cannot be Ar') if name == 'Ar'
307
+ end
308
+ end
309
+ a = @Album.new(:name=>'Al', :artist_attributes=>{:name=>'Ar'})
310
+ @mods.should == []
311
+ a.save(:validate=>false)
312
+ @mods.should == [[:is, :artists, {:name=>"Ar"}, 1], [:is, :albums, {:name=>"Al", :artist_id=>1}, 2]]
313
+ end
314
+
315
+ it "should not accept nested attributes unless explicitly specified" do
316
+ @Artist.many_to_many :tags, :class=>@Tag, :left_key=>:album_id, :right_key=>:tag_id, :join_table=>:at
317
+ proc{@Artist.create({:name=>'Ar', :tags_attributes=>[{:name=>'T'}]})}.should raise_error(Sequel::Error)
318
+ @mods.should == []
319
+ end
320
+
321
+ it "should save when save_changes or update is called if nested attribute associated objects changed but there are no changes to the main object" do
322
+ al = @Album.load(:id=>10, :name=>'Al')
323
+ ar = @Artist.load(:id=>20, :name=>'Ar')
324
+ al.associations[:artist] = ar
325
+ al.update(:artist_attributes=>{:id=>'20', :name=>'Ar2'})
326
+ @mods.should == [[:u, :artists, {:name=>"Ar2"}, '(id = 20)']]
327
+ end
328
+
329
+ it "should have a :limit option limiting the amount of entries" do
330
+ @Album.nested_attributes :tags, :limit=>2
331
+ arr = [{:name=>'T'}]
332
+ proc{@Album.new({:name=>'Al', :tags_attributes=>arr*3})}.should raise_error(Sequel::Error)
333
+ a = @Album.new({:name=>'Al', :tags_attributes=>arr*2})
334
+ @mods.should == []
335
+ a.save
336
+ @mods.should == [[:is, :albums, {:name=>"Al"}, 1], [:is, :tags, {:name=>"T"}, 2], [:i, :at, {:album_id=>1, :tag_id=>2}, 3], [:is, :tags, {:name=>"T"}, 4], [:i, :at, {:album_id=>1, :tag_id=>4}, 5]]
337
+ end
338
+
339
+ it "should accept a block that each hash gets passed to determine if it should be processed" do
340
+ @Album.nested_attributes(:tags){|h| h[:name].empty?}
341
+ a = @Album.new({:name=>'Al', :tags_attributes=>[{:name=>'T'}, {:name=>''}, {:name=>'T2'}]})
342
+ @mods.should == []
343
+ a.save
344
+ @mods.should == [[:is, :albums, {:name=>"Al"}, 1], [:is, :tags, {:name=>"T"}, 2], [:i, :at, {:album_id=>1, :tag_id=>2}, 3], [:is, :tags, {:name=>"T2"}, 4], [:i, :at, {:album_id=>1, :tag_id=>4}, 5]]
345
+ end
346
+
347
+ it "should return objects created/modified in the internal methods" do
348
+ @Album.nested_attributes :tags, :remove=>true, :strict=>false
349
+ objs = []
350
+ @Album.class_eval do
351
+ define_method(:nested_attributes_create){|*a| objs << [super(*a), :create]}
352
+ define_method(:nested_attributes_remove){|*a| objs << [super(*a), :remove]}
353
+ define_method(:nested_attributes_update){|*a| objs << [super(*a), :update]}
354
+ end
355
+ a = @Album.new(:name=>'Al')
356
+ a.associations[:tags] = [@Tag.load(:id=>6, :name=>'A'), @Tag.load(:id=>7, :name=>'A2')]
357
+ a.tags_attributes = [{:id=>6, :name=>'T'}, {:id=>7, :name=>'T2', :_remove=>true}, {:name=>'T3'}, {:id=>8, :name=>'T4'}, {:id=>9, :name=>'T5', :_remove=>true}]
358
+ objs.should == [[@Tag.load(:id=>6, :name=>'T'), :update], [@Tag.load(:id=>7, :name=>'A2'), :remove], [@Tag.new(:name=>'T3'), :create], [nil, :update], [nil, :remove]]
359
+ end
360
+
361
+ it "should raise an error if updating modifies the associated objects keys" do
362
+ @Artist.columns :id, :name, :artist_id
363
+ @Album.columns :id, :name, :artist_id
364
+ @Tag.columns :id, :name, :tag_id
365
+ @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id, :primary_key=>:artist_id
366
+ @Album.many_to_one :artist, :class=>@Artist, :primary_key=>:artist_id
367
+ @Album.many_to_many :tags, :class=>@Tag, :left_key=>:album_id, :right_key=>:tag_id, :join_table=>:at, :right_primary_key=>:tag_id
368
+ @Artist.nested_attributes :albums, :destroy=>true, :remove=>true
369
+ @Album.nested_attributes :artist, :tags, :destroy=>true, :remove=>true
370
+
371
+ al = @Album.load(:id=>10, :name=>'Al', :artist_id=>25)
372
+ ar = @Artist.load(:id=>20, :name=>'Ar', :artist_id=>25)
373
+ t = @Tag.load(:id=>30, :name=>'T', :tag_id=>15)
374
+ al.associations[:artist] = ar
375
+ al.associations[:tags] = [t]
376
+ ar.associations[:albums] = [al]
377
+ proc{ar.set(:albums_attributes=>[{:id=>10, :name=>'Al2', :artist_id=>'3'}])}.should raise_error(Sequel::Error)
378
+ proc{al.set(:artist_attributes=>{:id=>20, :name=>'Ar2', :artist_id=>'3'})}.should raise_error(Sequel::Error)
379
+ proc{al.set(:tags_attributes=>[{:id=>30, :name=>'T2', :tag_id=>'3'}])}.should raise_error(Sequel::Error)
380
+ end
381
+
382
+ it "should accept a :fields option and only allow modification of those fields" do
383
+ @Tag.columns :id, :name, :number
384
+ @Album.nested_attributes :tags, :destroy=>true, :remove=>true, :fields=>[:name]
385
+
386
+ al = @Album.load(:id=>10, :name=>'Al')
387
+ t = @Tag.load(:id=>30, :name=>'T', :number=>10)
388
+ al.associations[:tags] = [t]
389
+ al.set(:tags_attributes=>[{:id=>30, :name=>'T2'}, {:name=>'T3'}])
390
+ @mods.should == []
391
+ al.save
392
+ @mods.should == [[:u, :albums, {:name=>'Al'}, '(id = 10)'], [:u, :tags, {:name=>'T2'}, '(id = 30)'], [:is, :tags, {:name=>"T3"}, 1], [:i, :at, {:album_id=>10, :tag_id=>1}, 2]]
393
+ proc{al.set(:tags_attributes=>[{:id=>30, :name=>'T2', :number=>3}])}.should raise_error(Sequel::Error)
394
+ proc{al.set(:tags_attributes=>[{:name=>'T2', :number=>3}])}.should raise_error(Sequel::Error)
395
+ end
396
+ end