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.
@@ -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.sort
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 the table to graph for this association,
533
- # and the alias that was used for the current table (since you can cascade associations),
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
- elsif opts.eager_loading_use_associated_key?
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.select_more(*opts.associated_key_alias.zip(opts.associated_key_column).map{|a, c| SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(t, c), a)})
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.select_more(SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(opts.associated_key_table, opts.associated_key_column), opts.associated_key_alias))
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
- def def_association_method(opts)
758
- association_module_def(opts.association_method, opts){|*reload| load_associated_objects(opts, reload[0])}
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
- database = db
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 |ds, assoc_alias, table_alias|
814
- 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=>table_alias, :from_self_alias=>ds.opts[:eager_graph][:master], &jt_graph_block)
815
- ds.graph(opts.associated_class, use_only_conditions ? only_conditions : opts.right_primary_keys.zip(rcks) + conditions, :select=>select, :table_alias=>assoc_alias, :join_type=>join_type, &graph_block)
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 |ds, assoc_alias, table_alias|
880
- ds.graph(opts.associated_class, use_only_conditions ? only_conditions : opts.primary_keys.zip(cks) + conditions, :select=>select, :table_alias=>assoc_alias, :join_type=>join_type, :implicit_qualifier=>table_alias, :from_self_alias=>ds.opts[:eager_graph][:master], &graph_block)
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 |ds, assoc_alias, table_alias|
940
- ds = ds.graph(opts.associated_class, use_only_conditions ? only_conditions : cks.zip(cpks) + conditions, :select=>select, :table_alias=>assoc_alias, :join_type=>join_type, :implicit_qualifier=>table_alias, :from_self_alias=>ds.opts[:eager_graph][:master], &graph_block)
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][assoc_alias] = opts.reciprocal
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
- def _load_associated_objects(opts)
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) ? send(opts.dataset_method).all : []
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
- send(opts.dataset_method).all.first
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, reload=false)
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
- ds = r[:eager_grapher].call(ds, assoc_table_alias, ta)
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=>eager_assoc[r[:name]], :self=>self)
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, eager_assoc[r[:name]])
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)
@@ -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[:primary_key] == klass.primary_key and
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 |ds, assoc_alias, table_alias|
208
- iq = table_alias
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=>assoc_alias, :join_type=>join_type, &graph_block)
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