sequel 3.22.0 → 3.23.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.
- 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
|