sequel 3.11.0 → 3.12.0

Sign up to get free protection for your applications and to get access to all the features.
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