sequel 4.7.0 → 4.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +46 -0
- data/README.rdoc +25 -1
- data/doc/active_record.rdoc +1 -1
- data/doc/advanced_associations.rdoc +143 -17
- data/doc/association_basics.rdoc +80 -59
- data/doc/release_notes/4.8.0.txt +175 -0
- data/lib/sequel/adapters/odbc.rb +1 -1
- data/lib/sequel/adapters/odbc/mssql.rb +4 -2
- data/lib/sequel/adapters/shared/postgres.rb +19 -3
- data/lib/sequel/adapters/shared/sqlite.rb +3 -3
- data/lib/sequel/ast_transformer.rb +1 -1
- data/lib/sequel/dataset/actions.rb +1 -1
- data/lib/sequel/dataset/graph.rb +23 -9
- data/lib/sequel/dataset/misc.rb +2 -2
- data/lib/sequel/dataset/sql.rb +3 -3
- data/lib/sequel/extensions/columns_introspection.rb +1 -1
- data/lib/sequel/extensions/mssql_emulate_lateral_with_apply.rb +1 -1
- data/lib/sequel/extensions/pg_array.rb +1 -1
- data/lib/sequel/extensions/pg_array_ops.rb +6 -0
- data/lib/sequel/extensions/pg_hstore_ops.rb +7 -0
- data/lib/sequel/extensions/pg_json_ops.rb +5 -0
- data/lib/sequel/extensions/query.rb +8 -2
- data/lib/sequel/extensions/to_dot.rb +1 -1
- data/lib/sequel/model/associations.rb +476 -152
- data/lib/sequel/plugins/class_table_inheritance.rb +11 -3
- data/lib/sequel/plugins/dataset_associations.rb +21 -18
- data/lib/sequel/plugins/many_through_many.rb +87 -20
- data/lib/sequel/plugins/nested_attributes.rb +12 -0
- data/lib/sequel/plugins/pg_array_associations.rb +31 -12
- data/lib/sequel/plugins/single_table_inheritance.rb +9 -1
- data/lib/sequel/sql.rb +1 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +2 -2
- data/spec/adapters/postgres_spec.rb +7 -0
- data/spec/core/object_graph_spec.rb +250 -196
- data/spec/extensions/core_refinements_spec.rb +1 -1
- data/spec/extensions/dataset_associations_spec.rb +100 -6
- data/spec/extensions/many_through_many_spec.rb +1002 -19
- data/spec/extensions/nested_attributes_spec.rb +24 -0
- data/spec/extensions/pg_array_associations_spec.rb +17 -12
- data/spec/extensions/pg_array_spec.rb +4 -2
- data/spec/extensions/spec_helper.rb +1 -1
- data/spec/integration/associations_test.rb +1003 -48
- data/spec/integration/dataset_test.rb +12 -5
- data/spec/integration/prepared_statement_test.rb +1 -1
- data/spec/integration/type_test.rb +1 -1
- data/spec/model/associations_spec.rb +467 -130
- data/spec/model/eager_loading_spec.rb +332 -5
- 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
|
62
|
+
# class Employee < Sequel::Model
|
63
|
+
# plugin :class_table_inheritance
|
64
|
+
# end
|
63
65
|
#
|
64
|
-
# #
|
65
|
-
#
|
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
|
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.
|
23
|
-
#
|
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.
|
70
|
-
#
|
71
|
-
#
|
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
|
-
|
84
|
-
join(r[:join_table], r[:
|
85
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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
|
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
|
-
|
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
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
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(
|
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
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
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
|
-
|
249
|
-
|
250
|
-
|
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
|
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
|
-
|
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=>
|
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
|
-
|
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=>
|
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
|
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
|
data/lib/sequel/sql.rb
CHANGED
@@ -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)
|
data/lib/sequel/version.rb
CHANGED
@@ -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 =
|
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
|
data/spec/adapters/mssql_spec.rb
CHANGED
@@ -16,12 +16,12 @@ describe "A MSSQL database" do
|
|
16
16
|
@db = DB
|
17
17
|
end
|
18
18
|
|
19
|
-
|
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
|
-
|
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
|