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.
- 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
|