sequel 4.7.0 → 4.8.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +46 -0
  3. data/README.rdoc +25 -1
  4. data/doc/active_record.rdoc +1 -1
  5. data/doc/advanced_associations.rdoc +143 -17
  6. data/doc/association_basics.rdoc +80 -59
  7. data/doc/release_notes/4.8.0.txt +175 -0
  8. data/lib/sequel/adapters/odbc.rb +1 -1
  9. data/lib/sequel/adapters/odbc/mssql.rb +4 -2
  10. data/lib/sequel/adapters/shared/postgres.rb +19 -3
  11. data/lib/sequel/adapters/shared/sqlite.rb +3 -3
  12. data/lib/sequel/ast_transformer.rb +1 -1
  13. data/lib/sequel/dataset/actions.rb +1 -1
  14. data/lib/sequel/dataset/graph.rb +23 -9
  15. data/lib/sequel/dataset/misc.rb +2 -2
  16. data/lib/sequel/dataset/sql.rb +3 -3
  17. data/lib/sequel/extensions/columns_introspection.rb +1 -1
  18. data/lib/sequel/extensions/mssql_emulate_lateral_with_apply.rb +1 -1
  19. data/lib/sequel/extensions/pg_array.rb +1 -1
  20. data/lib/sequel/extensions/pg_array_ops.rb +6 -0
  21. data/lib/sequel/extensions/pg_hstore_ops.rb +7 -0
  22. data/lib/sequel/extensions/pg_json_ops.rb +5 -0
  23. data/lib/sequel/extensions/query.rb +8 -2
  24. data/lib/sequel/extensions/to_dot.rb +1 -1
  25. data/lib/sequel/model/associations.rb +476 -152
  26. data/lib/sequel/plugins/class_table_inheritance.rb +11 -3
  27. data/lib/sequel/plugins/dataset_associations.rb +21 -18
  28. data/lib/sequel/plugins/many_through_many.rb +87 -20
  29. data/lib/sequel/plugins/nested_attributes.rb +12 -0
  30. data/lib/sequel/plugins/pg_array_associations.rb +31 -12
  31. data/lib/sequel/plugins/single_table_inheritance.rb +9 -1
  32. data/lib/sequel/sql.rb +1 -0
  33. data/lib/sequel/version.rb +1 -1
  34. data/spec/adapters/mssql_spec.rb +2 -2
  35. data/spec/adapters/postgres_spec.rb +7 -0
  36. data/spec/core/object_graph_spec.rb +250 -196
  37. data/spec/extensions/core_refinements_spec.rb +1 -1
  38. data/spec/extensions/dataset_associations_spec.rb +100 -6
  39. data/spec/extensions/many_through_many_spec.rb +1002 -19
  40. data/spec/extensions/nested_attributes_spec.rb +24 -0
  41. data/spec/extensions/pg_array_associations_spec.rb +17 -12
  42. data/spec/extensions/pg_array_spec.rb +4 -2
  43. data/spec/extensions/spec_helper.rb +1 -1
  44. data/spec/integration/associations_test.rb +1003 -48
  45. data/spec/integration/dataset_test.rb +12 -5
  46. data/spec/integration/prepared_statement_test.rb +1 -1
  47. data/spec/integration/type_test.rb +1 -1
  48. data/spec/model/associations_spec.rb +467 -130
  49. data/spec/model/eager_loading_spec.rb +332 -5
  50. metadata +5 -3
@@ -59,10 +59,18 @@ module Sequel
59
59
  #
60
60
  # # Set up class table inheritance in the parent class
61
61
  # # (Not in the subclasses)
62
- # Employee.plugin :class_table_inheritance
62
+ # class Employee < Sequel::Model
63
+ # plugin :class_table_inheritance
64
+ # end
63
65
  #
64
- # # Set the +kind+ column to hold the class name, and
65
- # # set the subclass table to map to for each subclass
66
+ # # Have subclasses inherit from the appropriate class
67
+ # class Staff < Employee; end
68
+ # class Manager < Employee; end
69
+ # class Executive < Manager; end
70
+ #
71
+ # # You can also set options when loading the plugin:
72
+ # # :kind :: column to hold the class name
73
+ # # :table_map :: map of class name symbols to table name symbols
66
74
  # Employee.plugin :class_table_inheritance, :key=>:kind, :table_map=>{:Staff=>:staff}
67
75
  module ClassTableInheritance
68
76
  # The class_table_inheritance plugin requires the lazy_attributes plugin
@@ -17,12 +17,10 @@ module Sequel
17
17
  # # WHERE ((id >= 1) AND (id <= 100))))
18
18
  #
19
19
  # This works for all of the association types that ship with Sequel,
20
- # including the many_through_many type. Most association options that
20
+ # including ones implemented in other plugins. Most association options that
21
21
  # are supported when eager loading are supported when using a
22
- # dataset association. However, associations that use :limit or
23
- # one_to_one associations that are really one_to_many relationships
24
- # in the database will not work correctly, returning all associated
25
- # objects.
22
+ # dataset association. However, it will only work for limited associations or
23
+ # *_one associations with orders if the database supports window functions.
26
24
  #
27
25
  # As the dataset methods return datasets, you can easily chain the
28
26
  # methods to get associated datasets of associated datasets:
@@ -66,10 +64,9 @@ module Sequel
66
64
  # such that it would return the union of calling the association method on
67
65
  # all objects returned by the current dataset.
68
66
  #
69
- # This supports most options that are supported when eager loading. It doesn't
70
- # support limits on the associations, or one_to_one associations that are really
71
- # one_to_many and use an order to select the first matching object. In both of
72
- # those cases, this will return an array of all matching objects.
67
+ # This supports most options that are supported when eager loading. However, it
68
+ # will only work for limited associations or *_one associations with orders if the
69
+ # database supports window functions.
73
70
  def associated(name)
74
71
  raise Error, "unrecognized association name: #{name.inspect}" unless r = model.association_reflection(name)
75
72
  ds = r.associated_class.dataset
@@ -78,17 +75,23 @@ module Sequel
78
75
  when :many_to_one
79
76
  ds.filter(r.qualified_primary_key=>sds.select(*Array(r[:qualified_key])))
80
77
  when :one_to_one, :one_to_many
81
- ds.filter(r.qualified_key=>sds.select(*Array(r.qualified_primary_key)))
82
- when :many_to_many
83
- ds.filter(r.qualified_right_primary_key=>sds.select(*Array(r.qualified_right_key)).
84
- join(r[:join_table], r[:left_keys].zip(r[:left_primary_keys]), :implicit_qualifier=>model.table_name))
85
- when :many_through_many
78
+ r.send(:apply_filter_by_associations_limit_strategy, ds.filter(r.qualified_key=>sds.select(*Array(r.qualified_primary_key))))
79
+ when :many_to_many, :one_through_one
80
+ mds = r.associated_class.dataset.
81
+ join(r[:join_table], r[:right_keys].zip(r.right_primary_keys)).
82
+ select(*Array(r.qualified_right_key)).
83
+ where(r.qualify(r.join_table_alias, r[:left_keys])=>sds.select(*r.qualify(model.table_name, r[:left_primary_key_columns])))
84
+ ds.filter(r.qualified_right_primary_key=>r.send(:apply_filter_by_associations_limit_strategy, mds))
85
+ when :many_through_many, :one_through_many
86
86
  fre = r.reverse_edges.first
87
87
  fe, *edges = r.edges
88
- sds = sds.select(*Array(r.qualify(fre[:table], fre[:left]))).
89
- join(fe[:table], Array(fe[:right]).zip(Array(fe[:left])), :implicit_qualifier=>model.table_name)
90
- edges.each{|e| sds = sds.join(e[:table], Array(e[:right]).zip(Array(e[:left])))}
91
- ds.filter(r.qualified_right_primary_key=>sds)
88
+ edges << r.final_edge
89
+ mds = model.
90
+ select(*Array(r.qualify(fre[:table], fre[:left]))).
91
+ join(fe[:table], Array(fe[:right]).zip(Array(fe[:left])), :implicit_qualifier=>model.table_name).
92
+ where(r.qualify(fe[:table], fe[:right])=>sds.select(*r.qualify(model.table_name, r[:left_primary_key_columns])))
93
+ edges.each{|e| mds = mds.join(e[:table], Array(e[:right]).zip(Array(e[:left])))}
94
+ ds.filter(r.qualified_right_primary_key=>r.send(:apply_filter_by_associations_limit_strategy, mds))
92
95
  when :pg_array_to_many
93
96
  ds.filter(Sequel.expr(r.primary_key=>sds.select{Sequel.pg_array_op(r.qualify(r[:model].table_name, r[:key])).unnest}))
94
97
  when :many_to_pg_array
@@ -1,6 +1,6 @@
1
1
  module Sequel
2
2
  module Plugins
3
- # The many_through_many plugin allow you to create an association to multiple objects using multiple join tables.
3
+ # The many_through_many plugin allow you to create an association using multiple join tables.
4
4
  # For example, assume the following associations:
5
5
  #
6
6
  # Artist.many_to_many :albums
@@ -65,11 +65,28 @@ module Sequel
65
65
  #
66
66
  # Artist.many_through_many :artists, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_artists, :album_id, :artist_id]],
67
67
  # :distinct=>true
68
+ #
69
+ # In addition to many_through_many, this plugin also adds one_through_many, for an association to a single object through multiple join tables.
70
+ # This is useful if there are unique constraints on the foreign keys in the join tables that reference back to the current table, or if you want
71
+ # to set an order on the association and just want the first record.
72
+ #
73
+ # Usage:
74
+ #
75
+ # # Make all model subclasses support many_through_many associations
76
+ # Sequel::Model.plugin :many_through_many
77
+ #
78
+ # # Make the Album class support many_through_many associations
79
+ # Album.plugin :many_through_many
68
80
  module ManyThroughMany
69
81
  # The AssociationReflection subclass for many_through_many associations.
70
82
  class ManyThroughManyAssociationReflection < Sequel::Model::Associations::ManyToManyAssociationReflection
71
83
  Sequel::Model::Associations::ASSOCIATION_TYPES[:many_through_many] = self
72
84
 
85
+ # many_through_many and one_through_many associations can be clones
86
+ def cloneable?(ref)
87
+ ref[:type] == :many_through_many || ref[:type] == :one_through_many
88
+ end
89
+
73
90
  # The default associated key alias(es) to use when eager loading
74
91
  # associations via eager.
75
92
  def default_associated_key_alias
@@ -142,9 +159,28 @@ module Sequel
142
159
  def filter_by_associations_add_conditions_dataset_filter(ds)
143
160
  reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
144
161
  ft = final_reverse_edge
162
+ k = qualify(ft[:alias], Array(self[:left_key]))
145
163
  ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])), :table_alias=>ft[:alias], :qualify=>:deep).
146
- select(*qualify(ft[:alias], Array(self[:left_key])))
164
+ where(Sequel.negate(k.zip([]))).
165
+ select(*k)
147
166
  end
167
+
168
+ def filter_by_associations_limit_key
169
+ fe = edges.first
170
+ Array(qualify(fe[:table], fe[:right])) + Array(qualify(associated_class.table_name, associated_class.primary_key))
171
+ end
172
+
173
+ def filter_by_associations_limit_subquery
174
+ subquery = associated_eager_dataset.unlimited
175
+ reverse_edges.each{|t| subquery = subquery.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
176
+ ft = final_reverse_edge
177
+ subquery.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])), :table_alias=>ft[:alias], :qualify=>:deep)
178
+ end
179
+ end
180
+
181
+ class OneThroughManyAssociationReflection < ManyThroughManyAssociationReflection
182
+ Sequel::Model::Associations::ASSOCIATION_TYPES[:one_through_many] = self
183
+ include Sequel::Model::Associations::SingularAssociationReflection
148
184
  end
149
185
 
150
186
  module ClassMethods
@@ -168,15 +204,21 @@ module Sequel
168
204
  associate(:many_through_many, name, opts.merge(through.is_a?(Hash) ? through : {:through=>through}), &block)
169
205
  end
170
206
 
207
+ # Creates a one_through_many association. See many_through_many for arguments.
208
+ def one_through_many(name, through, opts=OPTS, &block)
209
+ associate(:one_through_many, name, opts.merge(through.is_a?(Hash) ? through : {:through=>through}), &block)
210
+ end
211
+
171
212
  private
172
213
 
173
214
  # Create the association methods and :eager_loader and :eager_grapher procs.
174
215
  def def_many_through_many(opts)
216
+ one_through_many = opts[:type] == :one_through_many
175
217
  name = opts[:name]
176
218
  model = self
177
219
  opts[:read_only] = true
178
220
  opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
179
- opts[:cartesian_product_number] ||= 2
221
+ opts[:cartesian_product_number] ||= one_through_many ? 0 : 2
180
222
  opts[:through] = opts[:through].map do |e|
181
223
  case e
182
224
  when Array
@@ -197,7 +239,7 @@ module Sequel
197
239
  opts[:eager_loader_key] = left_pk unless opts.has_key?(:eager_loader_key)
198
240
  left_pks = opts[:left_primary_keys] = Array(left_pk)
199
241
  lpkc = opts[:left_primary_key_column] ||= left_pk
200
- opts[:left_primary_key_columns] ||= Array(lpkc)
242
+ lpkcs = opts[:left_primary_key_columns] ||= Array(lpkc)
201
243
  opts[:dataset] ||= lambda do
202
244
  ds = opts.associated_dataset
203
245
  opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
@@ -210,30 +252,36 @@ module Sequel
210
252
  opts[:eager_loader] ||= lambda do |eo|
211
253
  h = eo[:id_map]
212
254
  rows = eo[:rows]
213
- rows.each{|object| object.associations[name] = []}
214
255
  ds = opts.associated_class
215
256
  opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
216
257
  ft = opts.final_reverse_edge
258
+
217
259
  ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + [[opts.predicate_key, h.keys]], :table_alias=>ft[:alias], :qualify=>:deep)
218
260
  ds = model.eager_loading_dataset(opts, ds, nil, eo[:associations], eo)
219
- if opts.eager_limit_strategy == :window_function
220
- delete_rn = true
221
- rn = ds.row_number_column
222
- ds = apply_window_function_eager_limit_strategy(ds, opts)
223
- end
261
+ ds = opts.apply_eager_limit_strategy(ds)
262
+ opts.initialize_association_cache(rows)
263
+
264
+ assign_singular = opts.assign_singular?
265
+ delete_rn = opts.delete_row_number_column(ds)
224
266
  ds.all do |assoc_record|
225
- assoc_record.values.delete(rn) if delete_rn
267
+ assoc_record.values.delete(delete_rn) if delete_rn
226
268
  hash_key = if uses_lcks
227
269
  left_key_alias.map{|k| assoc_record.values.delete(k)}
228
270
  else
229
271
  assoc_record.values.delete(left_key_alias)
230
272
  end
231
273
  next unless objects = h[hash_key]
232
- objects.each{|object| object.associations[name].push(assoc_record)}
233
- end
234
- if opts.eager_limit_strategy == :ruby
235
- rows.each{|o| o.associations[name] = o.associations[name][slice_range] || []}
274
+ if assign_singular
275
+ objects.each do |object|
276
+ object.associations[name] ||= assoc_record
277
+ end
278
+ else
279
+ objects.each do |object|
280
+ object.associations[name].push(assoc_record)
281
+ end
282
+ end
236
283
  end
284
+ opts.apply_ruby_eager_limit_strategy(rows)
237
285
  end
238
286
 
239
287
  join_type = opts[:graph_join_type]
@@ -245,16 +293,34 @@ module Sequel
245
293
  opts[:eager_grapher] ||= proc do |eo|
246
294
  ds = eo[:self]
247
295
  iq = eo[:implicit_qualifier]
248
- opts.edges.each do |t|
249
- ds = ds.graph(t[:table], t.fetch(:only_conditions, (Array(t[:right]).zip(Array(t[:left])) + t[:conditions])), :select=>false, :table_alias=>ds.unused_table_alias(t[:table]), :join_type=>t[:join_type], :qualify=>:deep, :implicit_qualifier=>iq, &t[:block])
250
- iq = nil
296
+ egls = eo[:limit_strategy]
297
+ if egls && egls != :ruby
298
+ associated_key_array = opts.associated_key_array
299
+ orig_egds = egds = eager_graph_dataset(opts, eo)
300
+ opts.reverse_edges.each{|t| egds = egds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
301
+ ft = opts.final_reverse_edge
302
+ egds = egds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])), :table_alias=>ft[:alias], :qualify=>:deep).
303
+ select_all(egds.first_source).
304
+ select_append(*associated_key_array)
305
+ egds = opts.apply_eager_graph_limit_strategy(egls, egds)
306
+ ds.graph(egds, associated_key_array.map{|v| v.alias}.zip(Array(lpkcs)) + conditions, :qualify=>:deep, :table_alias=>eo[:table_alias], :implicit_qualifier=>iq, :join_type=>eo[:join_type]||join_type, :from_self_alias=>eo[:from_self_alias], :select=>select||orig_egds.columns, &graph_block)
307
+ else
308
+ opts.edges.each do |t|
309
+ ds = ds.graph(t[:table], t.fetch(:only_conditions, (Array(t[:right]).zip(Array(t[:left])) + t[:conditions])), :select=>false, :table_alias=>ds.unused_table_alias(t[:table]), :join_type=>eo[:join_type]||t[:join_type], :qualify=>:deep, :implicit_qualifier=>iq, :from_self_alias=>eo[:from_self_alias], &t[:block])
310
+ iq = nil
311
+ end
312
+ fe = opts.final_edge
313
+ ds.graph(opts.associated_class, use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>eo[:table_alias], :qualify=>:deep, :join_type=>eo[:join_type]||join_type, &graph_block)
251
314
  end
252
- fe = opts.final_edge
253
- ds.graph(opts.associated_class, use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>eo[:table_alias], :qualify=>:deep, :join_type=>join_type, &graph_block)
254
315
  end
255
316
 
256
317
  def_association_dataset_methods(opts)
257
318
  end
319
+
320
+ # Use def_many_through_many, since they share pretty much the same code.
321
+ def def_one_through_many(opts)
322
+ def_many_through_many(opts)
323
+ end
258
324
  end
259
325
 
260
326
  module DatasetMethods
@@ -291,6 +357,7 @@ module Sequel
291
357
 
292
358
  association_filter_handle_inversion(op, expr, Array(lpks))
293
359
  end
360
+ alias one_through_many_association_filter_expression many_through_many_association_filter_expression
294
361
  end
295
362
  end
296
363
  end
@@ -286,7 +286,19 @@ module Sequel
286
286
  def validate_associated_object(reflection, obj)
287
287
  return if reflection[:validate] == false
288
288
  association = reflection[:name]
289
+ if (reflection[:type] == :one_to_many || reflection[:type] == :one_to_one) && (key = reflection[:key]).is_a?(Symbol) && !obj.values[key]
290
+ # There could be a presence validation on the foreign key in the associated model,
291
+ # which will fail if we validate before saving the current object. If there is
292
+ # no value for the foreign key, set it to the current primary key value, or a dummy
293
+ # value of 0 if we haven't saved the current object.
294
+ obj.values[key] = pk || 0
295
+ key = nil if pk
296
+ end
289
297
  obj.errors.full_messages.each{|m| errors.add(association, m)} unless obj.valid?
298
+ if key
299
+ # If we used a dummy value of 0, remove it so it doesn't accidently remain.
300
+ obj.values.delete(key)
301
+ end
290
302
  end
291
303
  end
292
304
  end
@@ -95,6 +95,18 @@ module Sequel
95
95
  :"#{underscore(demodulize(self[:model].name))}_ids"
96
96
  end
97
97
 
98
+ # Always use the ruby eager_graph limit strategy if association is limited.
99
+ def eager_graph_limit_strategy(_)
100
+ :ruby if self[:limit]
101
+ end
102
+
103
+ # Always use the ruby eager limit strategy
104
+ def eager_limit_strategy
105
+ cached_fetch(:_eager_limit_strategy) do
106
+ :ruby if self[:limit]
107
+ end
108
+ end
109
+
98
110
  # Handle silent failure of add/remove methods if raise_on_save_failure is false.
99
111
  def handle_silent_modification_failure?
100
112
  self[:raise_on_save_failure] == false
@@ -166,6 +178,18 @@ module Sequel
166
178
  :"#{singularize(self[:name])}_ids"
167
179
  end
168
180
 
181
+ # Always use the ruby eager_graph limit strategy if association is limited.
182
+ def eager_graph_limit_strategy(_)
183
+ :ruby if self[:limit]
184
+ end
185
+
186
+ # Always use the ruby eager limit strategy
187
+ def eager_limit_strategy
188
+ cached_fetch(:_eager_limit_strategy) do
189
+ :ruby if self[:limit]
190
+ end
191
+ end
192
+
169
193
  # Handle silent failure of add/remove methods if raise_on_save_failure is false
170
194
  # and save_after_modify is true.
171
195
  def handle_silent_modification_failure?
@@ -247,9 +271,7 @@ module Sequel
247
271
  opts[:eager_loader] ||= proc do |eo|
248
272
  id_map = eo[:id_map]
249
273
  rows = eo[:rows]
250
- rows.each do |object|
251
- object.associations[name] = []
252
- end
274
+ opts.initialize_association_cache(rows)
253
275
 
254
276
  klass = opts.associated_class
255
277
  ds = model.eager_loading_dataset(opts, klass.where(Sequel.pg_array_op(opts.predicate_key).overlaps(id_map.keys)), nil, eo[:associations], eo)
@@ -263,9 +285,7 @@ module Sequel
263
285
  end
264
286
  end
265
287
  end
266
- if slice_range
267
- rows.each{|o| o.associations[name] = o.associations[name][slice_range] || []}
268
- end
288
+ opts.apply_ruby_eager_limit_strategy(rows)
269
289
  end
270
290
 
271
291
  join_type = opts[:graph_join_type]
@@ -292,7 +312,7 @@ module Sequel
292
312
 
293
313
  opts[:eager_grapher] ||= proc do |eo|
294
314
  ds = eo[:self]
295
- ds = ds.graph(eager_graph_dataset(opts, eo), conditions, eo.merge(:select=>select, :join_type=>join_type, :qualify=>:deep, :from_self_alias=>ds.opts[:eager_graph][:master]), &graph_block)
315
+ ds = ds.graph(eager_graph_dataset(opts, eo), conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep, :from_self_alias=>eo[:from_self_alias]), &graph_block)
296
316
  ds
297
317
  end
298
318
 
@@ -348,8 +368,9 @@ module Sequel
348
368
  rows = eo[:rows]
349
369
  id_map = {}
350
370
  pkm = opts.primary_key_method
371
+ opts.initialize_association_cache(rows)
372
+
351
373
  rows.each do |object|
352
- object.associations[name] = []
353
374
  if associated_pks = object.send(key)
354
375
  associated_pks.each do |apk|
355
376
  (id_map[apk] ||= []) << object
@@ -366,9 +387,7 @@ module Sequel
366
387
  end
367
388
  end
368
389
  end
369
- if slice_range
370
- rows.each{|o| o.associations[name] = o.associations[name][slice_range] || []}
371
- end
390
+ opts.apply_ruby_eager_limit_strategy(rows)
372
391
  end
373
392
 
374
393
  join_type = opts[:graph_join_type]
@@ -395,7 +414,7 @@ module Sequel
395
414
 
396
415
  opts[:eager_grapher] ||= proc do |eo|
397
416
  ds = eo[:self]
398
- ds = ds.graph(eager_graph_dataset(opts, eo), conditions, eo.merge(:select=>select, :join_type=>join_type, :qualify=>:deep, :from_self_alias=>ds.opts[:eager_graph][:master]), &graph_block)
417
+ ds = ds.graph(eager_graph_dataset(opts, eo), conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep, :from_self_alias=>eo[:from_self_alias]), &graph_block)
399
418
  ds
400
419
  end
401
420
 
@@ -24,7 +24,15 @@ module Sequel
24
24
  #
25
25
  # # Use the default of storing the class name in the sti_key
26
26
  # # column (:kind in this case)
27
- # Employee.plugin :single_table_inheritance, :kind
27
+ # class Employee < Sequel::Model
28
+ # plugin :single_table_inheritance, :kind
29
+ # end
30
+ #
31
+ # # Have subclasses inherit from the appropriate class
32
+ # class Staff < Employee; end
33
+ # class Manager < Employee; end
34
+ #
35
+ # # You can also use many different options to configure the plugin:
28
36
  #
29
37
  # # Using integers to store the class type, with a :model_map hash
30
38
  # # and an sti_key of :type
@@ -935,6 +935,7 @@ module Sequel
935
935
  # The alias to use for the expression, not +alias+ since that is
936
936
  # a keyword in ruby.
937
937
  attr_reader :aliaz
938
+ alias_method :alias, :aliaz
938
939
 
939
940
  # Create an object with the given expression and alias.
940
941
  def initialize(expression, aliaz)
@@ -3,7 +3,7 @@ module Sequel
3
3
  MAJOR = 4
4
4
  # The minor version of Sequel. Bumped for every non-patch level
5
5
  # release, generally around once a month.
6
- MINOR = 7
6
+ MINOR = 8
7
7
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
8
8
  # releases that fix regressions from previous versions.
9
9
  TINY = 0
@@ -16,12 +16,12 @@ describe "A MSSQL database" do
16
16
  @db = DB
17
17
  end
18
18
 
19
- cspecify "should be able to read fractional part of timestamp", :odbc do
19
+ specify "should be able to read fractional part of timestamp" do
20
20
  rs = @db["select getutcdate() as full_date, cast(datepart(millisecond, getutcdate()) as int) as milliseconds"].first
21
21
  rs[:milliseconds].should == rs[:full_date].usec/1000
22
22
  end
23
23
 
24
- cspecify "should be able to write fractional part of timestamp", :odbc do
24
+ specify "should be able to write fractional part of timestamp" do
25
25
  t = Time.utc(2001, 12, 31, 23, 59, 59, 997000)
26
26
  (t.usec/1000).should == @db["select cast(datepart(millisecond, ?) as int) as milliseconds", t].get
27
27
  end
@@ -850,6 +850,13 @@ describe "A PostgreSQL database" do
850
850
 
851
851
  @db[:posts].full_text_search(:title, :$n).call(:select, :n=>'rails').should == [{:title=>'ruby rails', :body=>'yowsa'}]
852
852
  @db[:posts].full_text_search(:title, :$n).prepare(:select, :fts_select).call(:n=>'rails').should == [{:title=>'ruby rails', :body=>'yowsa'}]
853
+
854
+ @db[:posts].insert(:title=>'jruby rubinius ruby maglev mri iron')
855
+ @db[:posts].insert(:title=>'ruby jruby maglev mri rubinius iron')
856
+ @db[:posts].full_text_search(:title, 'rubinius ruby', :phrase=>true).select_order_map(:title).should == ['jruby rubinius ruby maglev mri iron']
857
+ @db[:posts].full_text_search(:title, 'jruby maglev', :phrase=>true).select_order_map(:title).should == ['ruby jruby maglev mri rubinius iron']
858
+ @db[:posts].full_text_search(:title, 'rubinius ruby', :plain=>true).select_order_map(:title).should == ['jruby rubinius ruby maglev mri iron', 'ruby jruby maglev mri rubinius iron']
859
+ @db[:posts].full_text_search(:title, 'jruby maglev', :plain=>true).select_order_map(:title).should == ['jruby rubinius ruby maglev mri iron', 'ruby jruby maglev mri rubinius iron']
853
860
  end
854
861
 
855
862
  specify "should support spatial indexes" do