sequel 3.22.0 → 3.23.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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