sequel 3.11.0 → 3.12.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 (136) hide show
  1. data/CHANGELOG +70 -0
  2. data/Rakefile +1 -1
  3. data/doc/active_record.rdoc +896 -0
  4. data/doc/advanced_associations.rdoc +46 -31
  5. data/doc/association_basics.rdoc +14 -9
  6. data/doc/dataset_basics.rdoc +3 -3
  7. data/doc/migration.rdoc +1011 -0
  8. data/doc/model_hooks.rdoc +198 -0
  9. data/doc/querying.rdoc +811 -86
  10. data/doc/release_notes/3.12.0.txt +304 -0
  11. data/doc/sharding.rdoc +17 -0
  12. data/doc/sql.rdoc +537 -0
  13. data/doc/validations.rdoc +501 -0
  14. data/lib/sequel/adapters/jdbc.rb +19 -27
  15. data/lib/sequel/adapters/jdbc/postgresql.rb +0 -7
  16. data/lib/sequel/adapters/mysql.rb +5 -4
  17. data/lib/sequel/adapters/odbc.rb +3 -2
  18. data/lib/sequel/adapters/shared/mssql.rb +7 -6
  19. data/lib/sequel/adapters/shared/mysql.rb +2 -7
  20. data/lib/sequel/adapters/shared/postgres.rb +2 -8
  21. data/lib/sequel/adapters/shared/sqlite.rb +2 -5
  22. data/lib/sequel/adapters/sqlite.rb +4 -4
  23. data/lib/sequel/core.rb +0 -1
  24. data/lib/sequel/database.rb +2 -1060
  25. data/lib/sequel/database/connecting.rb +227 -0
  26. data/lib/sequel/database/dataset.rb +58 -0
  27. data/lib/sequel/database/dataset_defaults.rb +127 -0
  28. data/lib/sequel/database/logging.rb +62 -0
  29. data/lib/sequel/database/misc.rb +246 -0
  30. data/lib/sequel/database/query.rb +390 -0
  31. data/lib/sequel/database/schema_generator.rb +7 -3
  32. data/lib/sequel/database/schema_methods.rb +351 -7
  33. data/lib/sequel/dataset/actions.rb +9 -2
  34. data/lib/sequel/dataset/misc.rb +6 -2
  35. data/lib/sequel/dataset/mutation.rb +3 -11
  36. data/lib/sequel/dataset/query.rb +49 -6
  37. data/lib/sequel/exceptions.rb +3 -0
  38. data/lib/sequel/extensions/migration.rb +395 -113
  39. data/lib/sequel/extensions/schema_dumper.rb +21 -13
  40. data/lib/sequel/model.rb +27 -25
  41. data/lib/sequel/model/associations.rb +72 -34
  42. data/lib/sequel/model/base.rb +74 -18
  43. data/lib/sequel/model/errors.rb +8 -1
  44. data/lib/sequel/plugins/active_model.rb +8 -0
  45. data/lib/sequel/plugins/association_pks.rb +87 -0
  46. data/lib/sequel/plugins/association_proxies.rb +8 -0
  47. data/lib/sequel/plugins/boolean_readers.rb +12 -6
  48. data/lib/sequel/plugins/caching.rb +14 -7
  49. data/lib/sequel/plugins/class_table_inheritance.rb +15 -9
  50. data/lib/sequel/plugins/composition.rb +2 -1
  51. data/lib/sequel/plugins/force_encoding.rb +10 -7
  52. data/lib/sequel/plugins/hook_class_methods.rb +12 -11
  53. data/lib/sequel/plugins/identity_map.rb +9 -0
  54. data/lib/sequel/plugins/instance_hooks.rb +23 -13
  55. data/lib/sequel/plugins/lazy_attributes.rb +4 -1
  56. data/lib/sequel/plugins/many_through_many.rb +18 -4
  57. data/lib/sequel/plugins/nested_attributes.rb +1 -0
  58. data/lib/sequel/plugins/optimistic_locking.rb +1 -1
  59. data/lib/sequel/plugins/rcte_tree.rb +9 -8
  60. data/lib/sequel/plugins/schema.rb +8 -0
  61. data/lib/sequel/plugins/serialization.rb +1 -3
  62. data/lib/sequel/plugins/sharding.rb +135 -0
  63. data/lib/sequel/plugins/single_table_inheritance.rb +117 -25
  64. data/lib/sequel/plugins/skip_create_refresh.rb +35 -0
  65. data/lib/sequel/plugins/string_stripper.rb +26 -0
  66. data/lib/sequel/plugins/tactical_eager_loading.rb +8 -0
  67. data/lib/sequel/plugins/timestamps.rb +15 -2
  68. data/lib/sequel/plugins/touch.rb +13 -0
  69. data/lib/sequel/plugins/update_primary_key.rb +48 -0
  70. data/lib/sequel/plugins/validation_class_methods.rb +8 -0
  71. data/lib/sequel/plugins/validation_helpers.rb +1 -1
  72. data/lib/sequel/sql.rb +17 -20
  73. data/lib/sequel/version.rb +1 -1
  74. data/spec/adapters/postgres_spec.rb +5 -5
  75. data/spec/core/core_sql_spec.rb +17 -1
  76. data/spec/core/database_spec.rb +17 -5
  77. data/spec/core/dataset_spec.rb +31 -8
  78. data/spec/core/schema_generator_spec.rb +8 -1
  79. data/spec/core/schema_spec.rb +13 -0
  80. data/spec/extensions/association_pks_spec.rb +85 -0
  81. data/spec/extensions/hook_class_methods_spec.rb +9 -9
  82. data/spec/extensions/migration_spec.rb +339 -219
  83. data/spec/extensions/schema_dumper_spec.rb +28 -17
  84. data/spec/extensions/sharding_spec.rb +272 -0
  85. data/spec/extensions/single_table_inheritance_spec.rb +92 -4
  86. data/spec/extensions/skip_create_refresh_spec.rb +17 -0
  87. data/spec/extensions/string_stripper_spec.rb +23 -0
  88. data/spec/extensions/update_primary_key_spec.rb +65 -0
  89. data/spec/extensions/validation_class_methods_spec.rb +5 -5
  90. data/spec/files/bad_down_migration/001_create_alt_basic.rb +4 -0
  91. data/spec/files/bad_down_migration/002_create_alt_advanced.rb +4 -0
  92. data/spec/files/bad_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  93. data/spec/files/bad_timestamped_migrations/1273253851_create_nodes.rb +9 -0
  94. data/spec/files/bad_timestamped_migrations/1273253853_3_create_users.rb +3 -0
  95. data/spec/files/bad_up_migration/001_create_alt_basic.rb +4 -0
  96. data/spec/files/bad_up_migration/002_create_alt_advanced.rb +3 -0
  97. data/spec/files/convert_to_timestamp_migrations/001_create_sessions.rb +9 -0
  98. data/spec/files/convert_to_timestamp_migrations/002_create_nodes.rb +9 -0
  99. data/spec/files/convert_to_timestamp_migrations/003_3_create_users.rb +4 -0
  100. data/spec/files/convert_to_timestamp_migrations/1273253850_create_artists.rb +9 -0
  101. data/spec/files/convert_to_timestamp_migrations/1273253852_create_albums.rb +9 -0
  102. data/spec/files/duplicate_integer_migrations/001_create_alt_advanced.rb +4 -0
  103. data/spec/files/duplicate_integer_migrations/001_create_alt_basic.rb +4 -0
  104. data/spec/files/duplicate_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  105. data/spec/files/duplicate_timestamped_migrations/1273253853_create_nodes.rb +9 -0
  106. data/spec/files/duplicate_timestamped_migrations/1273253853_create_users.rb +4 -0
  107. data/spec/files/integer_migrations/001_create_sessions.rb +9 -0
  108. data/spec/files/integer_migrations/002_create_nodes.rb +9 -0
  109. data/spec/files/integer_migrations/003_3_create_users.rb +4 -0
  110. data/spec/files/interleaved_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  111. data/spec/files/interleaved_timestamped_migrations/1273253850_create_artists.rb +9 -0
  112. data/spec/files/interleaved_timestamped_migrations/1273253851_create_nodes.rb +9 -0
  113. data/spec/files/interleaved_timestamped_migrations/1273253852_create_albums.rb +9 -0
  114. data/spec/files/interleaved_timestamped_migrations/1273253853_3_create_users.rb +4 -0
  115. data/spec/files/missing_integer_migrations/001_create_alt_basic.rb +4 -0
  116. data/spec/files/missing_integer_migrations/003_create_alt_advanced.rb +4 -0
  117. data/spec/files/missing_timestamped_migrations/1273253849_create_sessions.rb +9 -0
  118. data/spec/files/missing_timestamped_migrations/1273253853_3_create_users.rb +4 -0
  119. data/spec/files/timestamped_migrations/1273253849_create_sessions.rb +9 -0
  120. data/spec/files/timestamped_migrations/1273253851_create_nodes.rb +9 -0
  121. data/spec/files/timestamped_migrations/1273253853_3_create_users.rb +4 -0
  122. data/spec/files/uppercase_timestamped_migrations/1273253849_CREATE_SESSIONS.RB +9 -0
  123. data/spec/files/uppercase_timestamped_migrations/1273253851_CREATE_NODES.RB +9 -0
  124. data/spec/files/uppercase_timestamped_migrations/1273253853_3_CREATE_USERS.RB +4 -0
  125. data/spec/integration/eager_loader_test.rb +20 -20
  126. data/spec/integration/migrator_test.rb +187 -0
  127. data/spec/integration/plugin_test.rb +150 -0
  128. data/spec/integration/schema_test.rb +13 -2
  129. data/spec/model/associations_spec.rb +41 -14
  130. data/spec/model/base_spec.rb +69 -0
  131. data/spec/model/eager_loading_spec.rb +7 -3
  132. data/spec/model/record_spec.rb +79 -4
  133. data/spec/model/validations_spec.rb +21 -9
  134. metadata +66 -5
  135. data/doc/schema.rdoc +0 -36
  136. data/lib/sequel/database/schema_sql.rb +0 -320
@@ -69,7 +69,7 @@ describe "Sequel::Database dump methods" do
69
69
  @d.meta_def(:tables){|o| [:t1, :t2]}
70
70
  @d.meta_def(:schema) do |t, *o|
71
71
  case t
72
- when :t1
72
+ when :t1, 't__t1', :t__t1.identifier
73
73
  [[:c1, {:db_type=>'integer', :primary_key=>true, :allow_null=>false}],
74
74
  [:c2, {:db_type=>'varchar(20)', :allow_null=>true}]]
75
75
  when :t2
@@ -92,6 +92,14 @@ describe "Sequel::Database dump methods" do
92
92
  @d.dump_table_schema(:t1).should == "create_table(:t1) do\n primary_key :c1\n String :c2, :size=>20\nend"
93
93
  end
94
94
 
95
+ it "should support dumping table schemas when given a string" do
96
+ @d.dump_table_schema('t__t1').should == "create_table(\"t__t1\") do\n primary_key :c1\n String :c2, :size=>20\nend"
97
+ end
98
+
99
+ it "should support dumping table schemas when given an identifier" do
100
+ @d.dump_table_schema(:t__t1.identifier).should == "create_table(\"t__t1\") do\n primary_key :c1\n String :c2, :size=>20\nend"
101
+ end
102
+
95
103
  it "should dump non-Integer primary key columns with explicit :type" do
96
104
  @d.dump_table_schema(:t6).should == "create_table(:t6) do\n primary_key :c1, :type=>Bignum\nend"
97
105
  end
@@ -114,8 +122,8 @@ describe "Sequel::Database dump methods" do
114
122
 
115
123
  it "should support dumping the whole database as a migration" do
116
124
  @d.dump_schema_migration.should == <<-END_MIG
117
- Class.new(Sequel::Migration) do
118
- def up
125
+ Sequel.migration do
126
+ up do
119
127
  create_table(:t1) do
120
128
  primary_key :c1
121
129
  String :c2, :size=>20
@@ -129,7 +137,7 @@ Class.new(Sequel::Migration) do
129
137
  end
130
138
  end
131
139
 
132
- def down
140
+ down do
133
141
  drop_table(:t1, :t2)
134
142
  end
135
143
  end
@@ -139,8 +147,8 @@ END_MIG
139
147
  it "should sort table names when dumping a migration" do
140
148
  @d.meta_def(:tables){|o| [:t2, :t1]}
141
149
  @d.dump_schema_migration.should == <<-END_MIG
142
- Class.new(Sequel::Migration) do
143
- def up
150
+ Sequel.migration do
151
+ up do
144
152
  create_table(:t1) do
145
153
  primary_key :c1
146
154
  String :c2, :size=>20
@@ -154,7 +162,7 @@ Class.new(Sequel::Migration) do
154
162
  end
155
163
  end
156
164
 
157
- def down
165
+ down do
158
166
  drop_table(:t1, :t2)
159
167
  end
160
168
  end
@@ -164,8 +172,8 @@ END_MIG
164
172
  it "should honor the :same_db option to not convert types" do
165
173
  @d.dump_table_schema(:t1, :same_db=>true).should == "create_table(:t1) do\n primary_key :c1\n column :c2, \"varchar(20)\"\nend"
166
174
  @d.dump_schema_migration(:same_db=>true).should == <<-END_MIG
167
- Class.new(Sequel::Migration) do
168
- def up
175
+ Sequel.migration do
176
+ up do
169
177
  create_table(:t1) do
170
178
  primary_key :c1
171
179
  column :c2, "varchar(20)"
@@ -179,7 +187,7 @@ Class.new(Sequel::Migration) do
179
187
  end
180
188
  end
181
189
 
182
- def down
190
+ down do
183
191
  drop_table(:t1, :t2)
184
192
  end
185
193
  end
@@ -193,8 +201,8 @@ END_MIG
193
201
  end
194
202
  @d.dump_table_schema(:t1, :indexes=>false).should == "create_table(:t1) do\n primary_key :c1\n String :c2, :size=>20\nend"
195
203
  @d.dump_schema_migration(:indexes=>false).should == <<-END_MIG
196
- Class.new(Sequel::Migration) do
197
- def up
204
+ Sequel.migration do
205
+ up do
198
206
  create_table(:t1) do
199
207
  primary_key :c1
200
208
  String :c2, :size=>20
@@ -208,7 +216,7 @@ Class.new(Sequel::Migration) do
208
216
  end
209
217
  end
210
218
 
211
- def down
219
+ down do
212
220
  drop_table(:t1, :t2)
213
221
  end
214
222
  end
@@ -222,13 +230,13 @@ END_MIG
222
230
  :t1_c2_c1_index=>{:columns=>[:c2, :c1], :unique=>true}}
223
231
  end
224
232
  @d.dump_indexes_migration.should == <<-END_MIG
225
- Class.new(Sequel::Migration) do
226
- def up
233
+ Sequel.migration do
234
+ up do
227
235
  add_index :t1, [:c1], :ignore_errors=>true, :name=>:i1
228
236
  add_index :t1, [:c2, :c1], :ignore_errors=>true, :unique=>true
229
237
  end
230
238
 
231
- def down
239
+ down do
232
240
  drop_index :t1, [:c1], :ignore_errors=>true, :name=>:i1
233
241
  drop_index :t1, [:c2, :c1], :ignore_errors=>true, :unique=>true
234
242
  end
@@ -282,7 +290,8 @@ END_MIG
282
290
  blob varbinary varbinary(10) binary binary(20) year" +
283
291
  ["double precision", "timestamp with time zone", "timestamp without time zone",
284
292
  "time with time zone", "time without time zone", "character varying(20)"] +
285
- %w"nvarchar ntext smalldatetime smallmoney binary varbinary nchar"
293
+ %w"nvarchar ntext smalldatetime smallmoney binary varbinary nchar" +
294
+ ["timestamp(6) without time zone", "timestamp(6) with time zone"]
286
295
  @d.meta_def(:schema) do |t, *o|
287
296
  i = 0
288
297
  types.map{|x| [:"c#{i+=1}", {:db_type=>x, :allow_null=>true}]}
@@ -350,6 +359,8 @@ create_table(:x) do
350
359
  File :c59
351
360
  File :c60
352
361
  String :c61, :fixed=>true
362
+ DateTime :c62, :size=>6
363
+ DateTime :c63, :size=>6
353
364
  end
354
365
  END_MIG
355
366
  @d.dump_table_schema(:x).should == table.chomp
@@ -0,0 +1,272 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ describe "sharding plugin" do
4
+ before do
5
+ @db = Sequel::Model.db.clone
6
+ @Artist = Class.new(Sequel::Model(@db[:artists]))
7
+ @Artist.class_eval do
8
+ columns :id, :name
9
+
10
+ def self.y
11
+ {:id=>2, :name=>'YJM'}
12
+ end
13
+ end
14
+ @Album = Class.new(Sequel::Model(@db[:albums]))
15
+ @Album.class_eval do
16
+ columns :id, :artist_id, :name
17
+
18
+ def self.ds_ext(m=nil)
19
+ @ds_ext = m if m
20
+ @ds_ext
21
+ end
22
+
23
+ def self.y
24
+ {:id=>1, :name=>'RF', :artist_id=>2}
25
+ end
26
+
27
+ private
28
+
29
+ def _join_table_dataset(opts)
30
+ ds = super
31
+ m = model
32
+ ds.meta_def(:model){m}
33
+ ds.extend model.ds_ext
34
+ ds
35
+ end
36
+ end
37
+ @Tag = Class.new(Sequel::Model(@db[:tags]))
38
+ @Tag.class_eval do
39
+ columns :id, :name
40
+
41
+ def self.y
42
+ {:id=>3, :name=>'M'}
43
+ end
44
+ end
45
+ models = [@Artist, @Album, @Tag]
46
+ @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id
47
+ @Album.many_to_one :artist, :class=>@Artist
48
+ @Album.many_to_many :tags, :class=>@Tag, :left_key=>:album_id, :right_key=>:tag_id, :join_table=>:albums_tags
49
+ m = Module.new do
50
+ def actions
51
+ @actions ||= []
52
+ end
53
+ end
54
+ models.each do |model|
55
+ model.extend m
56
+ model.plugin :sharding
57
+ model.dataset.extend(ds_ext = Module.new do
58
+ def insert(h={})
59
+ model.actions << [:insert, h.dup, opts[:server]]
60
+ 1
61
+ end
62
+ def delete
63
+ model.actions << [:delete,(literal(opts[:where]) if opts[:where]), opts[:server]]
64
+ 1
65
+ end
66
+ def update(h={})
67
+ model.actions << [:update, h.dup, (literal(opts[:where]) if opts[:where]), opts[:server]]
68
+ 1
69
+ end
70
+ def fetch_rows(sql)
71
+ model.actions << [:fetch, (literal(opts[:where] || opts[:join]) if opts[:where] || opts[:join]), opts[:server]]
72
+ yield(model.y)
73
+ end
74
+ end)
75
+ @Album.ds_ext(ds_ext)
76
+ end
77
+ def @db.actions; @actions ||= []; end
78
+ def @db.transaction(opts)
79
+ actions << [:transaction, opts[:server]]
80
+ super
81
+ end
82
+ end
83
+
84
+ specify "should allow you to instantiate a new object for a specified shard" do
85
+ @Album.new_using_server(:s1, :name=>'RF').save
86
+ @Album.actions.should == [[:insert, {:name=>"RF"}, :s1], [:fetch, "(id = 1)", :s1]]
87
+
88
+ @Album.actions.clear
89
+ @Album.new_using_server(:s2){|o| o.name = 'MO'}.save
90
+ @Album.actions.should == [[:insert, {:name=>"MO"}, :s2], [:fetch, "(id = 1)", :s2]]
91
+ end
92
+
93
+ specify "should allow you to create and save a new object for a specified shard" do
94
+ @Album.create_using_server(:s1, :name=>'RF')
95
+ @Album.actions.should == [[:insert, {:name=>"RF"}, :s1], [:fetch, "(id = 1)", :s1]]
96
+
97
+ @Album.actions.clear
98
+ @Album.create_using_server(:s2){|o| o.name = 'MO'}
99
+ @Album.actions.should == [[:insert, {:name=>"MO"}, :s2], [:fetch, "(id = 1)", :s2]]
100
+ end
101
+
102
+ specify "should have objects retrieved from a specific shard update that shard" do
103
+ @Album.server(:s1).first.update(:name=>'MO')
104
+ @Album.actions.should == [[:fetch, nil, :s1], [:update, {:name=>"MO"}, "(id = 1)", :s1]]
105
+ end
106
+
107
+ specify "should have objects retrieved from a specific shard delete from that shard" do
108
+ @Album.server(:s1).first.delete
109
+ @Album.actions.should == [[:fetch, nil, :s1], [:delete, "(id = 1)", :s1]]
110
+ end
111
+
112
+ specify "should have objects retrieved from a specific shard reload from that shard" do
113
+ @Album.server(:s1).first.reload
114
+ @Album.actions.should == [[:fetch, nil, :s1], [:fetch, "(id = 1)", :s1]]
115
+ end
116
+
117
+ specify "should use current dataset's shard when eager loading if eagerly loaded dataset doesn't have its own shard" do
118
+ albums = @Album.server(:s1).eager(:artist).all
119
+ @Album.actions.should == [[:fetch, nil, :s1]]
120
+ @Artist.actions.should == [[:fetch, "(artists.id IN (2))", :s1]]
121
+ @Artist.actions.clear
122
+ albums.length == 1
123
+ albums.first.artist.save
124
+ @Artist.actions.should == [[:update, {:name=>"YJM"}, "(id = 2)", :s1]]
125
+ end
126
+
127
+ specify "should not use current dataset's shard when eager loading if eagerly loaded dataset has its own shard" do
128
+ @Artist.dataset.opts[:server] = :s2
129
+ albums = @Album.server(:s1).eager(:artist).all
130
+ @Album.actions.should == [[:fetch, nil, :s1]]
131
+ @Artist.actions.should == [[:fetch, "(artists.id IN (2))", :s2]]
132
+ @Artist.actions.clear
133
+ albums.length == 1
134
+ albums.first.artist.save
135
+ @Artist.actions.should == [[:update, {:name=>"YJM"}, "(id = 2)", :s2]]
136
+ end
137
+
138
+ specify "should use current dataset's shard when eager graphing if eagerly graphed dataset doesn't have its own shard" do
139
+ ds = @Album.server(:s1).eager_graph(:artist)
140
+ def ds.fetch_rows(sql)
141
+ super(sql)
142
+ yield({:id=>1, :artist_id=>2, :name=>'RF', :artist_id_0=>2, :artist_name=>'YJM'})
143
+ end
144
+ albums = ds.all
145
+ @Album.actions.should == [[:fetch, "( LEFT OUTER JOIN artists AS artist ON (artist.id = albums.artist_id))", :s1]]
146
+ albums.length == 1
147
+ albums.first.artist.save
148
+ @Artist.actions.should == [[:update, {:name=>"YJM"}, "(id = 2)", :s1]]
149
+ end
150
+
151
+ specify "should not use current dataset's shard when eager graphing if eagerly graphed dataset has its own shard" do
152
+ @Artist.dataset.opts[:server] = :s2
153
+ ds = @Album.server(:s1).eager_graph(:artist)
154
+ def ds.fetch_rows(sql)
155
+ super(sql)
156
+ yield({:id=>1, :artist_id=>2, :name=>'RF', :artist_id_0=>2, :artist_name=>'YJM'})
157
+ end
158
+ albums = ds.all
159
+ @Album.actions.should == [[:fetch, "( LEFT OUTER JOIN artists AS artist ON (artist.id = albums.artist_id))", :s1]]
160
+ albums.length == 1
161
+ albums.first.artist.save
162
+ @Artist.actions.should == [[:update, {:name=>"YJM"}, "(id = 2)", :s2]]
163
+ end
164
+
165
+ specify "should use eagerly graphed dataset shard for eagerly graphed objects even if current dataset does not have a shard" do
166
+ @Artist.dataset.opts[:server] = :s2
167
+ ds = @Album.eager_graph(:artist)
168
+ def ds.fetch_rows(sql)
169
+ super(sql)
170
+ yield({:id=>1, :artist_id=>2, :name=>'RF', :artist_id_0=>2, :artist_name=>'YJM'})
171
+ end
172
+ albums = ds.all
173
+ @Album.actions.should == [[:fetch, "( LEFT OUTER JOIN artists AS artist ON (artist.id = albums.artist_id))", nil]]
174
+ albums.length == 1
175
+ albums.first.artist.save
176
+ @Artist.actions.should == [[:update, {:name=>"YJM"}, "(id = 2)", :s2]]
177
+ end
178
+
179
+ specify "should have objects retrieved from a specific shard use associated objects from that shard, with modifications to the associated objects using that shard" do
180
+ album = @Album.server(:s1).first
181
+ @Album.actions.should == [[:fetch, nil, :s1]]
182
+ album.artist.update(:name=>'AS')
183
+ @Artist.actions.should == [[:fetch, "(artists.id = 2)", :s1], [:update, {:name=>"AS"}, "(id = 2)", :s1]]
184
+ album.tags.map{|a| a.update(:name=>'SR')}
185
+ @Tag.actions.should == [[:fetch, "( INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.album_id = 1)))", :s1], [:update, {:name=>"SR"}, "(id = 3)", :s1]]
186
+
187
+ @Album.actions.clear
188
+ @Artist.actions.clear
189
+ @Artist.server(:s2).first.albums.map{|a| a.update(:name=>'MO')}
190
+ @Artist.actions.should == [[:fetch, nil, :s2]]
191
+ @Album.actions.should == [[:fetch, "(albums.artist_id = 2)", :s2], [:update, {:name=>"MO"}, "(id = 1)", :s2]]
192
+ end
193
+
194
+ specify "should have objects retrieved from a specific shard add associated objects to that shard" do
195
+ album = @Album.server(:s1).first
196
+ artist = @Artist.server(:s2).first
197
+ @Album.actions.clear
198
+ @Artist.actions.clear
199
+
200
+ artist.add_album(:name=>'MO')
201
+ @Album.actions.should == [[:insert, {:name=>"MO", :artist_id=>2}, :s2], [:fetch, "(id = 1)", :s2]]
202
+ @Album.actions.clear
203
+
204
+ album.add_tag(:name=>'SR')
205
+ @Tag.actions.should == [[:insert, {:name=>"SR"}, :s1], [:fetch, "(id = 1)", :s1]]
206
+ @Album.actions.should == [[:insert, {:album_id=>1, :tag_id=>3}, :s1]]
207
+ end
208
+
209
+ specify "should have objects retrieved from a specific shard remove associated objects from that shard" do
210
+ album = @Album.server(:s1).first
211
+ artist = @Artist.server(:s2).first
212
+ @Album.actions.clear
213
+ @Artist.actions.clear
214
+
215
+ artist.remove_album(1)
216
+ @Album.actions.should == [[:fetch, "((albums.artist_id = 2) AND (albums.id = 1))", :s2], [:update, {:name=>"RF", :artist_id=>nil}, "(id = 1)", :s2]]
217
+ @Album.actions.clear
218
+
219
+ album.remove_tag(3)
220
+ @Tag.actions.should == [[:fetch, "(tags.id = 3)", :s1]]
221
+ @Album.actions.should == [[:delete, "((album_id = 1) AND (tag_id = 3))", :s1]]
222
+ end
223
+
224
+ specify "should have objects retrieved from a specific shard remove all associated objects from that shard" do
225
+ album = @Album.server(:s1).first
226
+ artist = @Artist.server(:s2).first
227
+ @Album.actions.clear
228
+ @Artist.actions.clear
229
+
230
+ artist.remove_all_albums
231
+ @Album.actions.should == [[:update, {:artist_id=>nil}, "(artist_id = 2)", :s2]]
232
+ @Album.actions.clear
233
+
234
+ album.remove_all_tags
235
+ @Album.actions.should == [[:delete, "(album_id = 1)", :s1]]
236
+ end
237
+
238
+ specify "should not override a server already set on an associated object" do
239
+ album = @Album.server(:s1).first
240
+ artist = @Artist.server(:s2).first
241
+ @Album.actions.clear
242
+ @Artist.actions.clear
243
+
244
+ artist.add_album(@Album.load(:id=>4, :name=>'MO').set_server(:s3))
245
+ @Album.actions.should == [[:update, {:name=>"MO", :artist_id=>2}, "(id = 4)", :s3]]
246
+ @Album.actions.clear
247
+
248
+ artist.remove_album(@Album.load(:id=>5, :name=>'T', :artist_id=>2).set_server(:s4))
249
+ # Should select from current object's shard to check existing association, but update associated object's shard
250
+ @Album.actions.should == [[:fetch, "((albums.artist_id = 2) AND (id = 5))", :s2], [:update, {:name=>"T", :artist_id=>nil}, "(id = 5)", :s4]]
251
+ @Album.actions.clear
252
+ end
253
+
254
+ specify "should be able to set a shard to use for any object using set_server" do
255
+ @Album.server(:s1).first.set_server(:s2).reload
256
+ @Album.actions.should == [[:fetch, nil, :s1], [:fetch, "(id = 1)", :s2]]
257
+ end
258
+
259
+ specify "should use transactions on the correct shard" do
260
+ @Album.use_transactions = true
261
+ @Album.server(:s2).first.save
262
+ @Album.actions.should == [[:fetch, nil, :s2], [:update, {:name=>"RF", :artist_id=>2}, "(id = 1)", :s2]]
263
+ @db.actions.should == [[:transaction, :s2]]
264
+ end
265
+
266
+ specify "should use not override shard given when saving" do
267
+ @Album.use_transactions = true
268
+ @Album.server(:s2).first.save(:server=>:s1)
269
+ @Album.actions.should == [[:fetch, nil, :s2], [:update, {:name=>"RF", :artist_id=>2}, "(id = 1)", :s2]]
270
+ @db.actions.should == [[:transaction, :s1]]
271
+ end
272
+ end
@@ -41,8 +41,8 @@ describe Sequel::Model, "#sti_key" do
41
41
  end
42
42
  StiTest.all.collect{|x| x.class}.should == [StiTest, StiTestSub1, StiTestSub2]
43
43
  StiTest.dataset.sql.should == "SELECT * FROM sti_tests"
44
- StiTestSub1.dataset.sql.should == "SELECT * FROM sti_tests WHERE (sti_tests.blah = 'StiTestSub1')"
45
- StiTestSub2.dataset.sql.should == "SELECT * FROM sti_tests WHERE (sti_tests.blah = 'StiTestSub2')"
44
+ StiTestSub1.dataset.sql.should == "SELECT * FROM sti_tests WHERE (sti_tests.blah IN ('StiTestSub1'))"
45
+ StiTestSub2.dataset.sql.should == "SELECT * FROM sti_tests WHERE (sti_tests.blah IN ('StiTestSub2'))"
46
46
  end
47
47
 
48
48
  it "should return rows with the correct class based on the polymorphic_key value" do
@@ -90,7 +90,95 @@ describe Sequel::Model, "#sti_key" do
90
90
 
91
91
  it "should add a filter to model datasets inside subclasses hook to only retreive objects with the matching key" do
92
92
  StiTest.dataset.sql.should == "SELECT * FROM sti_tests"
93
- StiTestSub1.dataset.sql.should == "SELECT * FROM sti_tests WHERE (sti_tests.kind = 'StiTestSub1')"
94
- StiTestSub2.dataset.sql.should == "SELECT * FROM sti_tests WHERE (sti_tests.kind = 'StiTestSub2')"
93
+ StiTestSub1.dataset.sql.should == "SELECT * FROM sti_tests WHERE (sti_tests.kind IN ('StiTestSub1'))"
94
+ StiTestSub2.dataset.sql.should == "SELECT * FROM sti_tests WHERE (sti_tests.kind IN ('StiTestSub2'))"
95
+ end
96
+
97
+ it "should add a correct filter for multiple levels of subclasses" do
98
+ class ::StiTestSub1A < StiTestSub1; end
99
+ StiTestSub1.dataset.sql.should == "SELECT * FROM sti_tests WHERE (sti_tests.kind IN ('StiTestSub1', 'StiTestSub1A'))"
100
+ StiTestSub1A.dataset.sql.should == "SELECT * FROM sti_tests WHERE (sti_tests.kind IN ('StiTestSub1A'))"
101
+ class ::StiTestSub2A < StiTestSub2; end
102
+ StiTestSub2.dataset.sql.should == "SELECT * FROM sti_tests WHERE (sti_tests.kind IN ('StiTestSub2', 'StiTestSub2A'))"
103
+ StiTestSub2A.dataset.sql.should == "SELECT * FROM sti_tests WHERE (sti_tests.kind IN ('StiTestSub2A'))"
104
+ class ::StiTestSub1B < StiTestSub1A; end
105
+ StiTestSub1.dataset.sql.should == "SELECT * FROM sti_tests WHERE (sti_tests.kind IN ('StiTestSub1', 'StiTestSub1A', 'StiTestSub1B'))"
106
+ StiTestSub1A.dataset.sql.should == "SELECT * FROM sti_tests WHERE (sti_tests.kind IN ('StiTestSub1A', 'StiTestSub1B'))"
107
+ StiTestSub1B.dataset.sql.should == "SELECT * FROM sti_tests WHERE (sti_tests.kind IN ('StiTestSub1B'))"
108
+ end
109
+
110
+ context "with custom options" do
111
+ before do
112
+ class ::StiTest2 < Sequel::Model
113
+ columns :id, :kind
114
+ def _refresh(x); end
115
+ end
116
+ end
117
+ after do
118
+ Object.send(:remove_const, :StiTest2)
119
+ Object.send(:remove_const, :StiTest3)
120
+ Object.send(:remove_const, :StiTest4)
121
+ end
122
+
123
+ it "should work with custom procs with strings" do
124
+ StiTest2.plugin :single_table_inheritance, :kind, :model_map=>proc{|v| v == 1 ? 'StiTest3' : 'StiTest4'}, :key_map=>proc{|klass| klass.name == 'StiTest3' ? 1 : 2}
125
+ class ::StiTest3 < ::StiTest2; end
126
+ class ::StiTest4 < ::StiTest2; end
127
+ StiTest2.dataset.row_proc.call(:kind=>0).should be_a_instance_of(StiTest4)
128
+ StiTest2.dataset.row_proc.call(:kind=>1).should be_a_instance_of(StiTest3)
129
+ StiTest2.dataset.row_proc.call(:kind=>2).should be_a_instance_of(StiTest4)
130
+
131
+ StiTest2.create.kind.should == 2
132
+ StiTest3.create.kind.should == 1
133
+ StiTest4.create.kind.should == 2
134
+ end
135
+
136
+ it "should work with custom procs with symbols" do
137
+ StiTest2.plugin :single_table_inheritance, :kind, :model_map=>proc{|v| v == 1 ? :StiTest3 : :StiTest4}, :key_map=>proc{|klass| klass.name == 'StiTest3' ? 1 : 2}
138
+ class ::StiTest3 < ::StiTest2; end
139
+ class ::StiTest4 < ::StiTest2; end
140
+ StiTest2.dataset.row_proc.call(:kind=>0).should be_a_instance_of(StiTest4)
141
+ StiTest2.dataset.row_proc.call(:kind=>1).should be_a_instance_of(StiTest3)
142
+ StiTest2.dataset.row_proc.call(:kind=>2).should be_a_instance_of(StiTest4)
143
+
144
+ StiTest2.create.kind.should == 2
145
+ StiTest3.create.kind.should == 1
146
+ StiTest4.create.kind.should == 2
147
+ end
148
+
149
+ it "should work with custom hashes" do
150
+ StiTest2.plugin :single_table_inheritance, :kind, :model_map=>{0=>StiTest2, 1=>:StiTest3, 2=>'StiTest4'}, :key_map=>{StiTest2=>4, 'StiTest3'=>5, 'StiTest4'=>6}
151
+ class ::StiTest3 < ::StiTest2; end
152
+ class ::StiTest4 < ::StiTest2; end
153
+ StiTest2.dataset.row_proc.call(:kind=>0).should be_a_instance_of(StiTest2)
154
+ StiTest2.dataset.row_proc.call(:kind=>1).should be_a_instance_of(StiTest3)
155
+ StiTest2.dataset.row_proc.call(:kind=>2).should be_a_instance_of(StiTest4)
156
+
157
+ StiTest2.create.kind.should == 4
158
+ StiTest3.create.kind.should == 5
159
+ StiTest4.create.kind.should == 6
160
+ end
161
+
162
+ it "should infer key_map from model_map if provided as a hash" do
163
+ StiTest2.plugin :single_table_inheritance, :kind, :model_map=>{0=>StiTest2, 1=>'StiTest3', 2=>:StiTest4}
164
+ class ::StiTest3 < ::StiTest2; end
165
+ class ::StiTest4 < ::StiTest2; end
166
+ StiTest2.dataset.row_proc.call(:kind=>0).should be_a_instance_of(StiTest2)
167
+ StiTest2.dataset.row_proc.call(:kind=>1).should be_a_instance_of(StiTest3)
168
+ StiTest2.dataset.row_proc.call(:kind=>2).should be_a_instance_of(StiTest4)
169
+
170
+ StiTest2.create.kind.should == 0
171
+ StiTest3.create.kind.should == 1
172
+ StiTest4.create.kind.should == 2
173
+ end
174
+
175
+ it "should raise exceptions if a bad model value is used" do
176
+ StiTest2.plugin :single_table_inheritance, :kind, :model_map=>{0=>1,1=>1.5, 2=>Date.today}
177
+ class ::StiTest3 < ::StiTest2; end
178
+ class ::StiTest4 < ::StiTest2; end
179
+ proc{StiTest2.dataset.row_proc.call(:kind=>0)}.should raise_error(Sequel::Error)
180
+ proc{StiTest2.dataset.row_proc.call(:kind=>1)}.should raise_error(Sequel::Error)
181
+ proc{StiTest2.dataset.row_proc.call(:kind=>2)}.should raise_error(Sequel::Error)
182
+ end
95
183
  end
96
184
  end