sequel 3.22.0 → 3.23.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +28 -0
- data/README.rdoc +15 -1
- data/doc/association_basics.rdoc +121 -40
- data/doc/release_notes/3.23.0.txt +172 -0
- data/doc/virtual_rows.rdoc +2 -2
- data/lib/sequel/extensions/columns_introspection.rb +61 -0
- data/lib/sequel/extensions/migration.rb +2 -2
- data/lib/sequel/model/associations.rb +168 -32
- data/lib/sequel/model/base.rb +6 -6
- data/lib/sequel/plugins/identity_map.rb +2 -2
- data/lib/sequel/plugins/many_through_many.rb +28 -3
- data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
- data/lib/sequel/plugins/xml_serializer.rb +1 -1
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/spec_helper.rb +5 -0
- data/spec/core/spec_helper.rb +5 -0
- data/spec/extensions/columns_introspection_spec.rb +91 -0
- data/spec/extensions/many_through_many_spec.rb +21 -0
- data/spec/extensions/serialization_modification_detection_spec.rb +36 -0
- data/spec/extensions/spec_helper.rb +3 -1
- data/spec/extensions/xml_serializer_spec.rb +12 -0
- data/spec/integration/associations_test.rb +58 -0
- data/spec/integration/plugin_test.rb +15 -0
- data/spec/integration/spec_helper.rb +5 -0
- data/spec/model/associations_spec.rb +117 -0
- data/spec/model/eager_loading_spec.rb +269 -1
- data/spec/model/record_spec.rb +6 -0
- data/spec/model/spec_helper.rb +5 -0
- metadata +10 -4
@@ -302,7 +302,7 @@ module Sequel
|
|
302
302
|
#
|
303
303
|
# Part of the +migration+ extension.
|
304
304
|
class Migrator
|
305
|
-
MIGRATION_FILE_PATTERN = /\A\d+_.+\.rb\z/i.freeze
|
305
|
+
MIGRATION_FILE_PATTERN = /\A(\d+)_.+\.rb\z/i.freeze
|
306
306
|
MIGRATION_SPLITTER = '_'.freeze
|
307
307
|
MINIMUM_TIMESTAMP = 20000101
|
308
308
|
|
@@ -589,7 +589,7 @@ module Sequel
|
|
589
589
|
next unless MIGRATION_FILE_PATTERN.match(file)
|
590
590
|
files << File.join(directory, file)
|
591
591
|
end
|
592
|
-
files.
|
592
|
+
files.sort_by{|f| MIGRATION_FILE_PATTERN.match(File.basename(f))[1].to_i}
|
593
593
|
end
|
594
594
|
|
595
595
|
# Returns tuples of migration, filename, and direction
|
@@ -529,8 +529,12 @@ module Sequel
|
|
529
529
|
# set to nil.
|
530
530
|
# - :eager_graph - The associations to eagerly load via +eager_graph+ when loading the associated object(s).
|
531
531
|
# - :eager_grapher - A proc to use to implement eager loading via +eager_graph+, overriding the default.
|
532
|
-
# Takes three arguments, a dataset, an alias to use for
|
533
|
-
# and the alias that was used for the current table
|
532
|
+
# Takes one or three arguments. If three arguments, they are a dataset, an alias to use for
|
533
|
+
# the table to graph for this association, and the alias that was used for the current table
|
534
|
+
# (since you can cascade associations). If one argument, is passed a hash with keys :self,
|
535
|
+
# :table_alias, and :implicit_qualifier, corresponding to the three arguments, and an optional
|
536
|
+
# additional key :eager_block, a callback accepting one argument, the associated dataset. This
|
537
|
+
# is used to customize the association at query time.
|
534
538
|
# Should return a copy of the dataset with the association graphed into it.
|
535
539
|
# - :eager_loader - A proc to use to implement eager loading, overriding the default. Takes one or three arguments.
|
536
540
|
# If three arguments, the first should be a key hash (used solely to enhance performance), the second an array of records,
|
@@ -628,7 +632,8 @@ module Sequel
|
|
628
632
|
raise(Error, 'invalid association type') unless assoc_class = ASSOCIATION_TYPES[type]
|
629
633
|
raise(Error, 'Model.associate name argument must be a symbol') unless Symbol === name
|
630
634
|
raise(Error, ':eager_loader option must have an arity of 1 or 3') if opts[:eager_loader] && ![1, 3].include?(opts[:eager_loader].arity)
|
631
|
-
|
635
|
+
raise(Error, ':eager_grapher option must have an arity of 1 or 3') if opts[:eager_grapher] && ![1, 3].include?(opts[:eager_grapher].arity)
|
636
|
+
|
632
637
|
# dup early so we don't modify opts
|
633
638
|
orig_opts = opts.dup
|
634
639
|
orig_opts = association_reflection(opts[:clone])[:orig_opts].merge(orig_opts) if opts[:clone]
|
@@ -678,16 +683,18 @@ module Sequel
|
|
678
683
|
if opts[:eager_graph]
|
679
684
|
ds = ds.eager_graph(opts[:eager_graph])
|
680
685
|
ds = ds.add_graph_aliases(opts.associated_key_alias=>[opts.associated_class.table_name, opts.associated_key_alias, SQL::QualifiedIdentifier.new(opts.associated_key_table, opts.associated_key_column)]) if opts.eager_loading_use_associated_key?
|
681
|
-
|
686
|
+
end
|
687
|
+
ds = ds.eager(associations) unless Array(associations).empty?
|
688
|
+
ds = opts[:eager_block].call(ds) if opts[:eager_block]
|
689
|
+
ds = eager_options[:eager_block].call(ds) if eager_options[:eager_block]
|
690
|
+
if !opts[:eager_graph] && opts.eager_loading_use_associated_key?
|
682
691
|
ds = if opts[:uses_left_composite_keys]
|
683
692
|
t = opts.associated_key_table
|
684
|
-
ds.
|
693
|
+
ds.select_append(*opts.associated_key_alias.zip(opts.associated_key_column).map{|a, c| SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(t, c), a)})
|
685
694
|
else
|
686
|
-
ds.
|
695
|
+
ds.select_append(SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(opts.associated_key_table, opts.associated_key_column), opts.associated_key_alias))
|
687
696
|
end
|
688
697
|
end
|
689
|
-
ds = ds.eager(associations) unless Array(associations).empty?
|
690
|
-
ds = opts[:eager_block].call(ds) if opts[:eager_block]
|
691
698
|
ds
|
692
699
|
end
|
693
700
|
|
@@ -753,9 +760,20 @@ module Sequel
|
|
753
760
|
def_association_method(opts)
|
754
761
|
end
|
755
762
|
|
756
|
-
# Adds the association method to the association methods module.
|
757
|
-
|
758
|
-
|
763
|
+
# Adds the association method to the association methods module. Be backwards
|
764
|
+
# compatible with ruby 1.8.6, which doesn't support blocks taking block arguments.
|
765
|
+
if RUBY_VERSION >= '1.8.7'
|
766
|
+
class_eval <<-END, __FILE__, __LINE__+1
|
767
|
+
def def_association_method(opts)
|
768
|
+
association_module_def(opts.association_method, opts){|*dynamic_opts, &block| load_associated_objects(opts, dynamic_opts[0], &block)}
|
769
|
+
end
|
770
|
+
END
|
771
|
+
else
|
772
|
+
class_eval <<-END, __FILE__, __LINE__+1
|
773
|
+
def def_association_method(opts)
|
774
|
+
association_module_def(opts.association_method, opts){|*dynamic_opts| load_associated_objects(opts, dynamic_opts[0])}
|
775
|
+
end
|
776
|
+
END
|
759
777
|
end
|
760
778
|
|
761
779
|
# Configures many_to_many association reflection and adds the related association methods
|
@@ -782,8 +800,7 @@ module Sequel
|
|
782
800
|
opts[:graph_join_table_join_type] ||= opts[:graph_join_type]
|
783
801
|
opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
|
784
802
|
opts[:dataset] ||= proc{opts.associated_class.inner_join(join_table, rcks.zip(opts.right_primary_keys) + lcks.zip(lcpks.map{|k| send(k)}))}
|
785
|
-
|
786
|
-
|
803
|
+
|
787
804
|
opts[:eager_loader] ||= proc do |eo|
|
788
805
|
h = eo[:key_hash][left_pk]
|
789
806
|
eo[:rows].each{|object| object.associations[name] = []}
|
@@ -810,9 +827,10 @@ module Sequel
|
|
810
827
|
jt_only_conditions = opts[:graph_join_table_only_conditions]
|
811
828
|
jt_join_type = opts[:graph_join_table_join_type]
|
812
829
|
jt_graph_block = opts[:graph_join_table_block]
|
813
|
-
opts[:eager_grapher] ||= proc do |
|
814
|
-
ds =
|
815
|
-
ds.graph(
|
830
|
+
opts[:eager_grapher] ||= proc do |eo|
|
831
|
+
ds = eo[:self]
|
832
|
+
ds = ds.graph(join_table, use_jt_only_conditions ? jt_only_conditions : lcks.zip(lcpks) + graph_jt_conds, :select=>false, :table_alias=>ds.unused_table_alias(join_table), :join_type=>jt_join_type, :implicit_qualifier=>eo[:implicit_qualifier], :from_self_alias=>ds.opts[:eager_graph][:master], &jt_graph_block)
|
833
|
+
ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : opts.right_primary_keys.zip(rcks) + conditions, :select=>select, :table_alias=>eo[:table_alias], :join_type=>join_type, &graph_block)
|
816
834
|
end
|
817
835
|
|
818
836
|
def_association_dataset_methods(opts)
|
@@ -876,8 +894,9 @@ module Sequel
|
|
876
894
|
only_conditions = opts[:graph_only_conditions]
|
877
895
|
conditions = opts[:graph_conditions]
|
878
896
|
graph_block = opts[:graph_block]
|
879
|
-
opts[:eager_grapher] ||= proc do |
|
880
|
-
ds
|
897
|
+
opts[:eager_grapher] ||= proc do |eo|
|
898
|
+
ds = eo[:self]
|
899
|
+
ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : opts.primary_keys.zip(cks) + conditions, eo.merge(:select=>select, :join_type=>join_type, :from_self_alias=>ds.opts[:eager_graph][:master]), &graph_block)
|
881
900
|
end
|
882
901
|
|
883
902
|
def_association_dataset_methods(opts)
|
@@ -936,10 +955,11 @@ module Sequel
|
|
936
955
|
conditions = opts[:graph_conditions]
|
937
956
|
opts[:cartesian_product_number] ||= one_to_one ? 0 : 1
|
938
957
|
graph_block = opts[:graph_block]
|
939
|
-
opts[:eager_grapher] ||= proc do |
|
940
|
-
ds =
|
958
|
+
opts[:eager_grapher] ||= proc do |eo|
|
959
|
+
ds = eo[:self]
|
960
|
+
ds = ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : cks.zip(cpks) + conditions, eo.merge(:select=>select, :join_type=>join_type, :from_self_alias=>ds.opts[:eager_graph][:master]), &graph_block)
|
941
961
|
# We only load reciprocals for one_to_many associations, as other reciprocals don't make sense
|
942
|
-
ds.opts[:eager_graph][:reciprocals][
|
962
|
+
ds.opts[:eager_graph][:reciprocals][eo[:table_alias]] = opts.reciprocal
|
943
963
|
ds
|
944
964
|
end
|
945
965
|
|
@@ -993,6 +1013,15 @@ module Sequel
|
|
993
1013
|
association_module_def(opts.remove_method, opts){|o,*args| remove_associated_object(opts, o, *args)}
|
994
1014
|
association_module_def(opts.remove_all_method, opts){|*args| remove_all_associated_objects(opts, *args)}
|
995
1015
|
end
|
1016
|
+
|
1017
|
+
# Return dataset to graph into given the association reflection, applying the :callback option if set.
|
1018
|
+
def eager_graph_dataset(opts, eager_options)
|
1019
|
+
ds = opts.associated_class.dataset
|
1020
|
+
if cb = eager_options[:callback]
|
1021
|
+
ds = cb.call(ds)
|
1022
|
+
end
|
1023
|
+
ds
|
1024
|
+
end
|
996
1025
|
end
|
997
1026
|
|
998
1027
|
# Instance methods used to implement the associations support.
|
@@ -1033,6 +1062,15 @@ module Sequel
|
|
1033
1062
|
ds
|
1034
1063
|
end
|
1035
1064
|
|
1065
|
+
# Return a dataset for the association after applying any dynamic callback.
|
1066
|
+
def _associated_dataset(opts, dynamic_opts)
|
1067
|
+
ds = send(opts.dataset_method)
|
1068
|
+
if callback = dynamic_opts[:callback]
|
1069
|
+
ds = callback.call(ds)
|
1070
|
+
end
|
1071
|
+
ds
|
1072
|
+
end
|
1073
|
+
|
1036
1074
|
# Return an association dataset for the given association reflection
|
1037
1075
|
def _dataset(opts)
|
1038
1076
|
raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
|
@@ -1045,13 +1083,14 @@ module Sequel
|
|
1045
1083
|
opts[:join_table_block] ? opts[:join_table_block].call(ds) : ds
|
1046
1084
|
end
|
1047
1085
|
|
1048
|
-
# Return the associated objects from the dataset, without callbacks, reciprocals, and caching.
|
1049
|
-
|
1086
|
+
# Return the associated objects from the dataset, without association callbacks, reciprocals, and caching.
|
1087
|
+
# Still apply the dynamic callback if present.
|
1088
|
+
def _load_associated_objects(opts, dynamic_opts={})
|
1050
1089
|
if opts.returns_array?
|
1051
|
-
opts.can_have_associated_objects?(self) ?
|
1090
|
+
opts.can_have_associated_objects?(self) ? _associated_dataset(opts, dynamic_opts).all : []
|
1052
1091
|
else
|
1053
1092
|
if opts.can_have_associated_objects?(self)
|
1054
|
-
|
1093
|
+
_associated_dataset(opts, dynamic_opts).all.first
|
1055
1094
|
end
|
1056
1095
|
end
|
1057
1096
|
end
|
@@ -1113,12 +1152,20 @@ module Sequel
|
|
1113
1152
|
end
|
1114
1153
|
|
1115
1154
|
# Load the associated objects using the dataset, handling callbacks, reciprocals, and caching.
|
1116
|
-
def load_associated_objects(opts,
|
1155
|
+
def load_associated_objects(opts, dynamic_opts=nil)
|
1156
|
+
if dynamic_opts == true or dynamic_opts == false or dynamic_opts == nil
|
1157
|
+
dynamic_opts = {:reload=>dynamic_opts}
|
1158
|
+
elsif dynamic_opts.respond_to?(:call)
|
1159
|
+
dynamic_opts = {:callback=>dynamic_opts}
|
1160
|
+
end
|
1161
|
+
if block_given?
|
1162
|
+
dynamic_opts = dynamic_opts.merge(:callback=>Proc.new)
|
1163
|
+
end
|
1117
1164
|
name = opts[:name]
|
1118
|
-
if associations.include?(name) and !reload
|
1165
|
+
if associations.include?(name) and !dynamic_opts[:callback] and !dynamic_opts[:reload]
|
1119
1166
|
associations[name]
|
1120
1167
|
else
|
1121
|
-
objs = _load_associated_objects(opts)
|
1168
|
+
objs = _load_associated_objects(opts, dynamic_opts)
|
1122
1169
|
run_association_callbacks(opts, :after_load, objs)
|
1123
1170
|
if opts.set_reciprocal_to_self?
|
1124
1171
|
if opts.returns_array?
|
@@ -1259,6 +1306,24 @@ module Sequel
|
|
1259
1306
|
# Artist.eager_graph(:albums=>:tracks).all
|
1260
1307
|
# Artist.eager(:albums=>{:tracks=>:genre}).all
|
1261
1308
|
# Artist.eager_graph(:albums=>{:tracks=>:genre}).all
|
1309
|
+
#
|
1310
|
+
# You can also pass a callback as a hash value in order to customize the dataset being
|
1311
|
+
# eager loaded at query time, analogous to the way the :eager_block association option
|
1312
|
+
# allows you to customize it at association definition time. For example,
|
1313
|
+
# if you wanted artists with their albums since 1990:
|
1314
|
+
#
|
1315
|
+
# Artist.eager(:albums => proc{|ds| ds.filter{year > 1990}})
|
1316
|
+
#
|
1317
|
+
# Or if you needed albums and their artist's name only, using a single query:
|
1318
|
+
#
|
1319
|
+
# Albums.eager_graph(:artist => proc{|ds| ds.select(:name)})
|
1320
|
+
#
|
1321
|
+
# To cascade eager loading while using a callback, you substitute the cascaded
|
1322
|
+
# associations with a single entry hash that has the proc callback as the key and
|
1323
|
+
# the cascaded associations as the value. This will load artists with their albums
|
1324
|
+
# since 1990, and also the tracks on those albums and the genre for those tracks:
|
1325
|
+
#
|
1326
|
+
# Artist.eager(:albums => {proc{|ds| ds.filter{year > 1990}}=>{:tracks => :genre}})
|
1262
1327
|
module DatasetMethods
|
1263
1328
|
# Add the <tt>eager!</tt> and <tt>eager_graph!</tt> mutation methods to the dataset.
|
1264
1329
|
def self.extended(obj)
|
@@ -1343,6 +1408,33 @@ module Sequel
|
|
1343
1408
|
ds.eager_graph_associations(ds, model, ds.opts[:eager_graph][:master], [], *associations)
|
1344
1409
|
end
|
1345
1410
|
|
1411
|
+
# If the expression is in the form <tt>x = y</tt> where +y+ is a <tt>Sequel::Model</tt>
|
1412
|
+
# instance, assume +x+ is an association symbol and look up the association reflection
|
1413
|
+
# via the dataset's model. From there, return the appropriate SQL based on the type of
|
1414
|
+
# association and the values of the foreign/primary keys of +y+. For most association
|
1415
|
+
# types, this is a simple transformation, but for +many_to_many+ associations this
|
1416
|
+
# creates a subquery to the join table.
|
1417
|
+
def complex_expression_sql(op, args)
|
1418
|
+
if op == :'=' and args.at(1).is_a?(Sequel::Model)
|
1419
|
+
l, r = args
|
1420
|
+
if ar = model.association_reflections[l]
|
1421
|
+
unless r.is_a?(ar.associated_class)
|
1422
|
+
raise Sequel::Error, "invalid association class #{r.class.inspect} for association #{l.inspect} used in dataset filter for model #{model.inspect}, expected class #{ar.associated_class.inspect}"
|
1423
|
+
end
|
1424
|
+
|
1425
|
+
if exp = association_filter_expression(ar, r)
|
1426
|
+
literal(exp)
|
1427
|
+
else
|
1428
|
+
raise Sequel::Error, "invalid association type #{ar[:type].inspect} for association #{l.inspect} used in dataset filter for model #{model.inspect}"
|
1429
|
+
end
|
1430
|
+
else
|
1431
|
+
raise Sequel::Error, "invalid association #{l.inspect} used in dataset filter for model #{model.inspect}"
|
1432
|
+
end
|
1433
|
+
else
|
1434
|
+
super
|
1435
|
+
end
|
1436
|
+
end
|
1437
|
+
|
1346
1438
|
# Do not attempt to split the result set into associations,
|
1347
1439
|
# just return results as simple objects. This is useful if you
|
1348
1440
|
# want to use eager_graph as a shortcut to have all of the joins
|
@@ -1366,10 +1458,23 @@ module Sequel
|
|
1366
1458
|
# r :: association reflection for the current association
|
1367
1459
|
# *associations :: any associations dependent on this one
|
1368
1460
|
def eager_graph_association(ds, model, ta, requirements, r, *associations)
|
1369
|
-
klass = r.associated_class
|
1370
1461
|
assoc_name = r[:name]
|
1371
1462
|
assoc_table_alias = ds.unused_table_alias(assoc_name)
|
1372
|
-
|
1463
|
+
loader = r[:eager_grapher]
|
1464
|
+
if !associations.empty?
|
1465
|
+
if associations.first.respond_to?(:call)
|
1466
|
+
callback = associations.first
|
1467
|
+
associations = {}
|
1468
|
+
elsif associations.length == 1 && (assocs = associations.first).is_a?(Hash) && assocs.length == 1 && (pr_assoc = assocs.to_a.first) && pr_assoc.first.respond_to?(:call)
|
1469
|
+
callback, assoc = pr_assoc
|
1470
|
+
associations = assoc.is_a?(Array) ? assoc : [assoc]
|
1471
|
+
end
|
1472
|
+
end
|
1473
|
+
ds = if loader.arity == 1
|
1474
|
+
loader.call(:self=>ds, :table_alias=>assoc_table_alias, :implicit_qualifier=>ta, :callback=>callback)
|
1475
|
+
else
|
1476
|
+
loader.call(ds, assoc_table_alias, ta)
|
1477
|
+
end
|
1373
1478
|
ds = ds.order_more(*qualified_expression(r[:order], assoc_table_alias)) if r[:order] and r[:order_eager_graph]
|
1374
1479
|
eager_graph = ds.opts[:eager_graph]
|
1375
1480
|
eager_graph[:requirements][assoc_table_alias] = requirements.dup
|
@@ -1471,6 +1576,12 @@ module Sequel
|
|
1471
1576
|
|
1472
1577
|
private
|
1473
1578
|
|
1579
|
+
# Return an expression for filtering by the given association reflection and associated object.
|
1580
|
+
def association_filter_expression(ref, obj)
|
1581
|
+
meth = :"#{ref[:type]}_association_filter_expression"
|
1582
|
+
send(meth, ref, obj) if respond_to?(meth, true)
|
1583
|
+
end
|
1584
|
+
|
1474
1585
|
# Make sure the association is valid for this model, and return the related AssociationReflection.
|
1475
1586
|
def check_association(model, association)
|
1476
1587
|
raise(Sequel::UndefinedAssociation, "Invalid association #{association} for #{model.name}") unless reflection = model.association_reflection(association)
|
@@ -1557,15 +1668,40 @@ module Sequel
|
|
1557
1668
|
|
1558
1669
|
reflections.each do |r|
|
1559
1670
|
loader = r[:eager_loader]
|
1671
|
+
associations = eager_assoc[r[:name]]
|
1672
|
+
if associations.respond_to?(:call)
|
1673
|
+
eager_block = associations
|
1674
|
+
associations = {}
|
1675
|
+
elsif associations.is_a?(Hash) && associations.length == 1 && (pr_assoc = associations.to_a.first) && pr_assoc.first.respond_to?(:call)
|
1676
|
+
eager_block, associations = pr_assoc
|
1677
|
+
end
|
1560
1678
|
if loader.arity == 1
|
1561
|
-
loader.call(:key_hash=>key_hash, :rows=>a, :associations=>
|
1679
|
+
loader.call(:key_hash=>key_hash, :rows=>a, :associations=>associations, :self=>self, :eager_block=>eager_block)
|
1562
1680
|
else
|
1563
|
-
loader.call(key_hash, a,
|
1681
|
+
loader.call(key_hash, a, associations)
|
1564
1682
|
end
|
1565
1683
|
a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} unless r[:after_load].empty?
|
1566
1684
|
end
|
1567
1685
|
end
|
1568
1686
|
|
1687
|
+
# Return a subquery expression for filering by a many_to_many association
|
1688
|
+
def many_to_many_association_filter_expression(ref, obj)
|
1689
|
+
lpks, lks, rks = ref.values_at(:left_primary_keys, :left_keys, :right_keys)
|
1690
|
+
lpks = lpks.first if lpks.length == 1
|
1691
|
+
SQL::BooleanExpression.from_value_pairs(lpks=>model.db[ref[:join_table]].select(*lks).where(rks.zip(ref.right_primary_keys.map{|k| obj.send(k)})))
|
1692
|
+
end
|
1693
|
+
|
1694
|
+
# Return a simple equality expression for filering by a many_to_one association
|
1695
|
+
def many_to_one_association_filter_expression(ref, obj)
|
1696
|
+
SQL::BooleanExpression.from_value_pairs(ref[:keys].zip(ref.primary_keys.map{|k| obj.send(k)}))
|
1697
|
+
end
|
1698
|
+
|
1699
|
+
# Return a simple equality expression for filering by a one_to_* association
|
1700
|
+
def one_to_many_association_filter_expression(ref, obj)
|
1701
|
+
SQL::BooleanExpression.from_value_pairs(ref[:primary_keys].zip(ref[:keys].map{|k| obj.send(k)}))
|
1702
|
+
end
|
1703
|
+
alias one_to_one_association_filter_expression one_to_many_association_filter_expression
|
1704
|
+
|
1569
1705
|
# Build associations from the graph if #eager_graph was used,
|
1570
1706
|
# and/or load other associations if #eager was used.
|
1571
1707
|
def post_load(all_records)
|
data/lib/sequel/model/base.rb
CHANGED
@@ -162,7 +162,7 @@ module Sequel
|
|
162
162
|
def db
|
163
163
|
return @db if @db
|
164
164
|
@db = self == Model ? DATABASES.first : superclass.db
|
165
|
-
raise(Error, "No database associated with #{self}") unless @db
|
165
|
+
raise(Error, "No database associated with #{self}: have you called Sequel.connect or #{self}.db= ?") unless @db
|
166
166
|
@db
|
167
167
|
end
|
168
168
|
|
@@ -1086,10 +1086,10 @@ module Sequel
|
|
1086
1086
|
# For each of the fields in the given array +fields+, call the setter
|
1087
1087
|
# method with the value of that +hash+ entry for the field. Returns self.
|
1088
1088
|
#
|
1089
|
-
# artist.set_fields({:name=>'Jim'}, :name)
|
1089
|
+
# artist.set_fields({:name=>'Jim'}, [:name])
|
1090
1090
|
# artist.name # => 'Jim'
|
1091
1091
|
#
|
1092
|
-
# artist.set_fields({:hometown=>'LA'}, :name)
|
1092
|
+
# artist.set_fields({:hometown=>'LA'}, [:name])
|
1093
1093
|
# artist.name # => nil
|
1094
1094
|
# artist.hometown # => 'Sac'
|
1095
1095
|
def set_fields(hash, fields)
|
@@ -1149,10 +1149,10 @@ module Sequel
|
|
1149
1149
|
# Update the instances values by calling +set_fields+ with the +hash+
|
1150
1150
|
# and +fields+, then save any changes to the record. Returns self.
|
1151
1151
|
#
|
1152
|
-
# artist.update_fields({:name=>'Jim'}, :name)
|
1152
|
+
# artist.update_fields({:name=>'Jim'}, [:name])
|
1153
1153
|
# # UPDATE artists SET name = 'Jim' WHERE (id = 1)
|
1154
1154
|
#
|
1155
|
-
# artist.update_fields({:hometown=>'LA'}, :name)
|
1155
|
+
# artist.update_fields({:hometown=>'LA'}, [:name])
|
1156
1156
|
# # UPDATE artists SET name = NULL WHERE (id = 1)
|
1157
1157
|
def update_fields(hash, fields)
|
1158
1158
|
set_fields(hash, fields)
|
@@ -1230,7 +1230,7 @@ module Sequel
|
|
1230
1230
|
# the record should be refreshed from the database.
|
1231
1231
|
def _insert
|
1232
1232
|
ds = _insert_dataset
|
1233
|
-
if ds.respond_to?(:insert_select) and h = ds.insert_select(@values)
|
1233
|
+
if !ds.opts[:select] and ds.respond_to?(:insert_select) and h = ds.insert_select(@values)
|
1234
1234
|
@values = h
|
1235
1235
|
nil
|
1236
1236
|
else
|
@@ -121,9 +121,9 @@ module Sequel
|
|
121
121
|
# key option has a value and the association uses the primary key of
|
122
122
|
# the associated class as the :primary_key option, check the identity
|
123
123
|
# map for the associated object and return it if present.
|
124
|
-
def _load_associated_objects(opts)
|
124
|
+
def _load_associated_objects(opts, dynamic_opts={})
|
125
125
|
klass = opts.associated_class
|
126
|
-
if klass.respond_to?(:identity_map) && idm = klass.identity_map and opts[:type] == :many_to_one and opts
|
126
|
+
if !dynamic_opts[:callback] && klass.respond_to?(:identity_map) && idm = klass.identity_map and opts[:type] == :many_to_one and opts.primary_key == klass.primary_key and
|
127
127
|
opts[:key] and pk = _associated_object_pk(opts[:key]) and o = idm[klass.identity_map_key(pk)]
|
128
128
|
o
|
129
129
|
else
|
@@ -116,6 +116,7 @@ module Sequel
|
|
116
116
|
nil
|
117
117
|
end
|
118
118
|
end
|
119
|
+
|
119
120
|
module ClassMethods
|
120
121
|
# Create a many_through_many association. Arguments:
|
121
122
|
# * name - Same as associate, the name of the association.
|
@@ -204,19 +205,43 @@ module Sequel
|
|
204
205
|
only_conditions = opts[:graph_only_conditions]
|
205
206
|
use_only_conditions = opts.include?(:graph_only_conditions)
|
206
207
|
conditions = opts[:graph_conditions]
|
207
|
-
opts[:eager_grapher] ||= proc do |
|
208
|
-
|
208
|
+
opts[:eager_grapher] ||= proc do |eo|
|
209
|
+
ds = eo[:self]
|
210
|
+
iq = eo[:implicit_qualifier]
|
209
211
|
opts.edges.each do |t|
|
210
212
|
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], :implicit_qualifier=>iq, &t[:block])
|
211
213
|
iq = nil
|
212
214
|
end
|
213
215
|
fe = opts[:final_edge]
|
214
|
-
ds.graph(opts.associated_class, use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>
|
216
|
+
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], :join_type=>join_type, &graph_block)
|
215
217
|
end
|
216
218
|
|
217
219
|
def_association_dataset_methods(opts)
|
218
220
|
end
|
219
221
|
end
|
222
|
+
|
223
|
+
module DatasetMethods
|
224
|
+
private
|
225
|
+
|
226
|
+
# Use a subquery to filter rows to those related to the given associated object
|
227
|
+
def many_through_many_association_filter_expression(ref, obj)
|
228
|
+
lpks = ref[:left_primary_keys]
|
229
|
+
lpks = lpks.first if lpks.length == 1
|
230
|
+
edges = ref.edges
|
231
|
+
first, rest = edges.first, edges[1..-1]
|
232
|
+
last = edges.last
|
233
|
+
ds = model.db[first[:table]].select(*Array(first[:right]).map{|x| ::Sequel::SQL::QualifiedIdentifier.new(first[:table], x)})
|
234
|
+
rest.each{|e| ds = ds.join(e[:table], e.fetch(:only_conditions, (Array(e[:right]).zip(Array(e[:left])) + e[:conditions])), :table_alias=>ds.unused_table_alias(e[:table]), &e[:block])}
|
235
|
+
last_alias = if rest.empty?
|
236
|
+
first[:table]
|
237
|
+
else
|
238
|
+
last_join = ds.opts[:join].last
|
239
|
+
last_join.table_alias || last_join.table
|
240
|
+
end
|
241
|
+
ds = ds.where(Array(ref[:final_edge][:left]).map{|x| ::Sequel::SQL::QualifiedIdentifier.new(last_alias, x)}.zip(ref.right_primary_keys.map{|k| obj.send(k)}))
|
242
|
+
SQL::BooleanExpression.from_value_pairs(lpks=>ds)
|
243
|
+
end
|
244
|
+
end
|
220
245
|
end
|
221
246
|
end
|
222
247
|
end
|