sequel 3.31.0 → 3.32.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/CHANGELOG +54 -0
  2. data/MIT-LICENSE +1 -1
  3. data/doc/advanced_associations.rdoc +17 -0
  4. data/doc/association_basics.rdoc +74 -30
  5. data/doc/release_notes/3.32.0.txt +202 -0
  6. data/doc/schema_modification.rdoc +1 -1
  7. data/lib/sequel/adapters/jdbc/db2.rb +7 -0
  8. data/lib/sequel/adapters/jdbc/derby.rb +13 -0
  9. data/lib/sequel/adapters/jdbc/h2.rb +10 -1
  10. data/lib/sequel/adapters/jdbc/hsqldb.rb +7 -0
  11. data/lib/sequel/adapters/jdbc/oracle.rb +7 -0
  12. data/lib/sequel/adapters/mock.rb +4 -0
  13. data/lib/sequel/adapters/mysql.rb +3 -0
  14. data/lib/sequel/adapters/oracle.rb +7 -3
  15. data/lib/sequel/adapters/shared/db2.rb +9 -2
  16. data/lib/sequel/adapters/shared/mssql.rb +48 -2
  17. data/lib/sequel/adapters/shared/mysql.rb +24 -4
  18. data/lib/sequel/adapters/shared/oracle.rb +7 -6
  19. data/lib/sequel/adapters/shared/progress.rb +1 -1
  20. data/lib/sequel/adapters/shared/sqlite.rb +16 -10
  21. data/lib/sequel/core.rb +22 -0
  22. data/lib/sequel/database/query.rb +13 -4
  23. data/lib/sequel/dataset/actions.rb +20 -11
  24. data/lib/sequel/dataset/mutation.rb +7 -1
  25. data/lib/sequel/dataset/prepared_statements.rb +11 -0
  26. data/lib/sequel/dataset/sql.rb +21 -24
  27. data/lib/sequel/extensions/query.rb +1 -1
  28. data/lib/sequel/model.rb +5 -2
  29. data/lib/sequel/model/associations.rb +70 -16
  30. data/lib/sequel/model/base.rb +11 -6
  31. data/lib/sequel/plugins/active_model.rb +13 -1
  32. data/lib/sequel/plugins/composition.rb +43 -10
  33. data/lib/sequel/plugins/many_through_many.rb +4 -1
  34. data/lib/sequel/plugins/nested_attributes.rb +65 -10
  35. data/lib/sequel/plugins/serialization.rb +13 -8
  36. data/lib/sequel/plugins/serialization_modification_detection.rb +22 -10
  37. data/lib/sequel/version.rb +1 -1
  38. data/spec/adapters/mssql_spec.rb +33 -10
  39. data/spec/adapters/mysql_spec.rb +111 -91
  40. data/spec/adapters/oracle_spec.rb +18 -0
  41. data/spec/core/database_spec.rb +1 -0
  42. data/spec/core/dataset_spec.rb +110 -15
  43. data/spec/extensions/active_model_spec.rb +13 -0
  44. data/spec/extensions/many_through_many_spec.rb +14 -14
  45. data/spec/extensions/query_spec.rb +6 -0
  46. data/spec/extensions/serialization_modification_detection_spec.rb +36 -1
  47. data/spec/extensions/serialization_spec.rb +9 -0
  48. data/spec/integration/associations_test.rb +278 -154
  49. data/spec/integration/dataset_test.rb +39 -2
  50. data/spec/integration/plugin_test.rb +63 -3
  51. data/spec/integration/prepared_statement_test.rb +10 -3
  52. data/spec/integration/schema_test.rb +61 -14
  53. data/spec/integration/transaction_test.rb +10 -0
  54. data/spec/model/associations_spec.rb +170 -80
  55. data/spec/model/hooks_spec.rb +40 -0
  56. metadata +4 -2
@@ -431,6 +431,10 @@ module Sequel
431
431
  #
432
432
  # DB[:table].select_hash([:id, :foo], [:name, :bar]) # SELECT * FROM table
433
433
  # # {[1, 3]=>['a', 'c'], [2, 4]=>['b', 'd'], ...}
434
+ #
435
+ # When using this method, you must be sure that each expression has an alias
436
+ # that Sequel can determine. Usually you can do this by calling the #as method
437
+ # on the expression and providing an alias.
434
438
  def select_hash(key_column, value_column)
435
439
  if key_column.is_a?(Array)
436
440
  if value_column.is_a?(Array)
@@ -461,6 +465,10 @@ module Sequel
461
465
  #
462
466
  # DB[:table].select_map([:id, :name]) # SELECT id, name FROM table
463
467
  # # => [[1, 'A'], [2, 'B'], [3, 'C'], ...]
468
+ #
469
+ # If you provide an array of expressions, you must be sure that each entry
470
+ # in the array has an alias that Sequel can determine. Usually you can do this
471
+ # by calling the #as method on the expression and providing an alias.
464
472
  def select_map(column=nil, &block)
465
473
  _select_map(column, false, &block)
466
474
  end
@@ -478,6 +486,10 @@ module Sequel
478
486
  #
479
487
  # DB[:table].select_order_map([:id, :name]) # SELECT id, name FROM table ORDER BY id, name
480
488
  # # => [[1, 'A'], [2, 'B'], [3, 'C'], ...]
489
+ #
490
+ # If you provide an array of expressions, you must be sure that each entry
491
+ # in the array has an alias that Sequel can determine. Usually you can do this
492
+ # by calling the #as method on the expression and providing an alias.
481
493
  def select_order_map(column=nil, &block)
482
494
  _select_map(column, true, &block)
483
495
  end
@@ -636,17 +648,12 @@ module Sequel
636
648
  # Internals of +select_map+ and +select_order_map+
637
649
  def _select_map(column, order, &block)
638
650
  ds = naked.ungraphed
639
- if column
640
- raise(Error, ARG_BLOCK_ERROR_MSG) if block
641
- columns = Array(column)
642
- select_cols = order ? columns.map{|c| c.is_a?(SQL::OrderedExpression) ? c.expression : c} : columns
643
- ds = ds.select(*select_cols)
644
- ds = ds.order(*columns.map{|c| unaliased_identifier(c)}) if order
645
- else
646
- ds = ds.select(&block)
647
- ds = ds.order(&block) if order
648
- end
649
- if column.is_a?(Array)
651
+ columns = Array(column)
652
+ virtual_row_columns(columns, block)
653
+ select_cols = order ? columns.map{|c| c.is_a?(SQL::OrderedExpression) ? c.expression : c} : columns
654
+ ds = ds.select(*select_cols)
655
+ ds = ds.order(*columns.map{|c| unaliased_identifier(c)}) if order
656
+ if column.is_a?(Array) || (columns.length > 1)
650
657
  ds._select_map_multiple(select_cols.map{|c| hash_key_symbol(c)})
651
658
  else
652
659
  ds._select_map_single
@@ -694,6 +701,8 @@ module Sequel
694
701
  hash_key_symbol(s.column)
695
702
  when SQL::AliasedExpression
696
703
  hash_key_symbol(s.aliaz)
704
+ when String
705
+ s.to_sym
697
706
  else
698
707
  raise(Error, "#{s.inspect} is not supported, should be a Symbol, String, SQL::Identifier, SQL::QualifiedIdentifier, or SQL::AliasedExpression")
699
708
  end
@@ -6,7 +6,7 @@ module Sequel
6
6
  # ---------------------
7
7
 
8
8
  # All methods that should have a ! method added that modifies the receiver.
9
- MUTATION_METHODS = QUERY_METHODS
9
+ MUTATION_METHODS = QUERY_METHODS - [:paginate, :naked]
10
10
 
11
11
  # Setup mutation (e.g. filter!) methods. These operate the same as the
12
12
  # non-! methods, but replace the options of the current dataset with the
@@ -39,6 +39,12 @@ module Sequel
39
39
  instance_eval("def #{meth}!(*args, &block); mutation_method(:#{meth}, *args, &block) end", __FILE__, __LINE__)
40
40
  end
41
41
  end
42
+
43
+ # Remove the row_proc from the current dataset.
44
+ def naked!
45
+ self.row_proc = nil
46
+ self
47
+ end
42
48
 
43
49
  private
44
50
 
@@ -77,6 +77,7 @@ module Sequel
77
77
  def prepared_sql
78
78
  case @prepared_type
79
79
  when :select, :all
80
+ # Most common scenario, so listed first.
80
81
  select_sql
81
82
  when :first
82
83
  clone(:limit=>1).select_sql
@@ -88,6 +89,8 @@ module Sequel
88
89
  update_sql(*@prepared_modify_values)
89
90
  when :delete
90
91
  delete_sql
92
+ else
93
+ select_sql
91
94
  end
92
95
  end
93
96
 
@@ -122,6 +125,7 @@ module Sequel
122
125
  def run(&block)
123
126
  case @prepared_type
124
127
  when :select, :all
128
+ # Most common scenario, so listed first
125
129
  all(&block)
126
130
  when :insert_select
127
131
  with_sql(prepared_sql).first
@@ -133,6 +137,13 @@ module Sequel
133
137
  update(*@prepared_modify_values)
134
138
  when :delete
135
139
  delete
140
+ when Array
141
+ case @prepared_type.at(0)
142
+ when :map, :to_hash
143
+ send(*@prepared_type, &block)
144
+ end
145
+ else
146
+ all(&block)
136
147
  end
137
148
  end
138
149
 
@@ -419,14 +419,10 @@ module Sequel
419
419
  val_array = true
420
420
  empty_val_array = vals == []
421
421
  end
422
- if col_array
423
- if empty_val_array
424
- if op == :IN
425
- literal_append(sql, SQL::BooleanExpression.from_value_pairs(cols.to_a.map{|x| [x, x]}, :AND, true))
426
- else
427
- literal_append(sql, 1=>1)
428
- end
429
- elsif !supports_multiple_column_in?
422
+ if empty_val_array
423
+ literal_append(sql, empty_array_value(op, cols))
424
+ elsif col_array
425
+ if !supports_multiple_column_in?
430
426
  if val_array
431
427
  expr = SQL::BooleanExpression.new(:OR, *vals.to_a.map{|vs| SQL::BooleanExpression.from_value_pairs(cols.to_a.zip(vs).map{|c, v| [c, v]})})
432
428
  literal_append(sql, op == :IN ? expr : ~expr)
@@ -452,19 +448,11 @@ module Sequel
452
448
  sql << PAREN_CLOSE
453
449
  end
454
450
  else
455
- if empty_val_array
456
- if op == :IN
457
- literal_append(sql, SQL::BooleanExpression.from_value_pairs([[cols, cols]], :AND, true))
458
- else
459
- literal_append(sql, 1=>1)
460
- end
461
- else
462
- sql << PAREN_OPEN
463
- literal_append(sql, cols)
464
- sql << SPACE << op.to_s << SPACE
465
- literal_append(sql, vals)
466
- sql << PAREN_CLOSE
467
- end
451
+ sql << PAREN_OPEN
452
+ literal_append(sql, cols)
453
+ sql << SPACE << op.to_s << SPACE
454
+ literal_append(sql, vals)
455
+ sql << PAREN_CLOSE
468
456
  end
469
457
  when *TWO_ARITY_OPERATORS
470
458
  sql << PAREN_OPEN
@@ -856,6 +844,11 @@ module Sequel
856
844
  :"#{DATASET_ALIAS_BASE_NAME}#{number}"
857
845
  end
858
846
 
847
+ # The strftime format to use when literalizing the time.
848
+ def default_timestamp_format
849
+ requires_sql_standard_datetimes? ? STANDARD_TIMESTAMP_FORMAT : TIMESTAMP_FORMAT
850
+ end
851
+
859
852
  # The order of methods to call to build the DELETE SQL statement
860
853
  def delete_clause_methods
861
854
  DELETE_CLAUSE_METHODS
@@ -877,9 +870,13 @@ module Sequel
877
870
  end
878
871
  end
879
872
 
880
- # The strftime format to use when literalizing the time.
881
- def default_timestamp_format
882
- requires_sql_standard_datetimes? ? STANDARD_TIMESTAMP_FORMAT : TIMESTAMP_FORMAT
873
+ def empty_array_value(op, cols)
874
+ if Sequel.empty_array_handle_nulls
875
+ c = Array(cols)
876
+ SQL::BooleanExpression.from_value_pairs(c.zip(c), :AND, op == :IN)
877
+ else
878
+ {1 => ((op == :IN) ? 0 : 1)}
879
+ end
883
880
  end
884
881
 
885
882
  # Format the timestamp based on the default_timestamp_format, with a couple
@@ -43,7 +43,7 @@ module Sequel
43
43
 
44
44
  # Merge the given options into the receiver's options and return the receiver
45
45
  # instead of cloning the receiver.
46
- def clone(opts = nil)
46
+ def clone(opts = {})
47
47
  @opts.merge!(opts)
48
48
  self
49
49
  end
data/lib/sequel/model.rb CHANGED
@@ -70,7 +70,8 @@ module Sequel
70
70
  EMPTY_INSTANCE_VARIABLES = [:@overridable_methods_module, :@db]
71
71
 
72
72
  # Boolean settings that can be modified at the global, class, or instance level.
73
- BOOLEAN_SETTINGS = [:typecast_empty_string_to_nil, :typecast_on_assignment, :strict_param_setting, :raise_on_save_failure, :raise_on_typecast_failure, :require_modification, :use_transactions]
73
+ BOOLEAN_SETTINGS = [:typecast_empty_string_to_nil, :typecast_on_assignment, :strict_param_setting, \
74
+ :raise_on_save_failure, :raise_on_typecast_failure, :require_modification, :use_after_commit_rollback, :use_transactions]
74
75
 
75
76
  # Hooks that are called before an action. Can return false to not do the action. When
76
77
  # overriding these, it is recommended to call +super+ as the last line of your method,
@@ -103,7 +104,8 @@ module Sequel
103
104
  :@restricted_columns=>:dup, :@restrict_primary_key=>nil,
104
105
  :@simple_pk=>nil, :@simple_table=>nil, :@strict_param_setting=>nil,
105
106
  :@typecast_empty_string_to_nil=>nil, :@typecast_on_assignment=>nil,
106
- :@raise_on_typecast_failure=>nil, :@plugins=>:dup, :@setter_methods=>nil}
107
+ :@raise_on_typecast_failure=>nil, :@plugins=>:dup, :@setter_methods=>nil,
108
+ :@use_after_commit_rollback=>nil}
107
109
 
108
110
  # Regular expression that determines if a method name is normal in the sense that
109
111
  # it could be used literally in ruby code without using send. Used to
@@ -133,6 +135,7 @@ module Sequel
133
135
  @strict_param_setting = true
134
136
  @typecast_empty_string_to_nil = true
135
137
  @typecast_on_assignment = true
138
+ @use_after_commit_rollback = true
136
139
  @use_transactions = true
137
140
 
138
141
  Sequel.require %w"default_inflections inflections plugins base exceptions errors", "model"
@@ -268,6 +268,18 @@ module Sequel
268
268
  end
269
269
  alias associated_object_keys primary_keys
270
270
 
271
+ # The method symbol or array of method symbols to call on the associated object
272
+ # to get the value to use for the foreign keys.
273
+ def primary_key_method
274
+ self[:primary_key_method] ||= primary_key
275
+ end
276
+
277
+ # The array of method symbols to call on the associated object
278
+ # to get the value to use for the foreign keys.
279
+ def primary_key_methods
280
+ self[:primary_key_methods] ||= Array(primary_key_method)
281
+ end
282
+
271
283
  # #primary_key qualified by the associated table
272
284
  def qualified_primary_key
273
285
  self[:qualified_primary_key] ||= self[:qualify] == false ? primary_key : qualify_assoc(primary_key)
@@ -528,6 +540,18 @@ module Sequel
528
540
  self[:right_primary_keys] ||= Array(right_primary_key)
529
541
  end
530
542
 
543
+ # The method symbol or array of method symbols to call on the associated objects
544
+ # to get the foreign key values for the join table.
545
+ def right_primary_key_method
546
+ self[:right_primary_key_method] ||= right_primary_key
547
+ end
548
+
549
+ # The array of method symbols to call on the associated objects
550
+ # to get the foreign key values for the join table.
551
+ def right_primary_key_methods
552
+ self[:right_primary_key_methods] ||= Array(right_primary_key_method)
553
+ end
554
+
531
555
  # The columns to select when loading the association, associated_class.table_name.* by default.
532
556
  def select
533
557
  return self[:select] if include?(:select)
@@ -763,11 +787,13 @@ module Sequel
763
787
  # array of symbols for a composite key association.
764
788
  # :key_column :: Similar to, and usually identical to, :key, but :key refers to the model method
765
789
  # to call, where :key_column refers to the underlying column. Should only be
766
- # used if the association has the same name as the foreign key column, in conjunction
790
+ # used if the the model method differs from the foreign key column, in conjunction
767
791
  # with defining a model alias method for the key column.
768
792
  # :primary_key :: column in the associated table that :key option references, as a symbol.
769
793
  # Defaults to the primary key of the associated table. Can use an
770
794
  # array of symbols for a composite key association.
795
+ # :primary_key_method :: the method symbol or array of method symbols to call on the associated
796
+ # object to get the foreign key values. Defaults to :primary_key option.
771
797
  # :qualify :: Whether to use qualifier primary keys when loading the association. The default
772
798
  # is true, so you must set to false to not qualify. Qualification rarely causes
773
799
  # problems, but it's necessary to disable in some cases, such as when you are doing
@@ -777,9 +803,15 @@ module Sequel
777
803
  # current model's primary key, as a symbol. Defaults to
778
804
  # :"#{self.name.underscore}_id". Can use an
779
805
  # array of symbols for a composite key association.
806
+ # :key_method :: the method symbol or array of method symbols to call on the associated
807
+ # object to get the foreign key values. Defaults to :key option.
780
808
  # :primary_key :: column in the current table that :key option references, as a symbol.
781
809
  # Defaults to primary key of the current table. Can use an
782
810
  # array of symbols for a composite key association.
811
+ # :primary_key_column :: Similar to, and usually identical to, :primary_key, but :primary_key refers
812
+ # to the model method call, where :primary_key_column refers to the underlying column.
813
+ # Should only be used if the the model method differs from the primary key column, in
814
+ # conjunction with defining a model alias method for the primary key column.
783
815
  # === :many_to_many
784
816
  # :graph_join_table_block :: The block to pass to +join_table+ for
785
817
  # the join table when eagerly loading the association via +eager_graph+.
@@ -806,12 +838,19 @@ module Sequel
806
838
  # :left_primary_key :: column in current table that :left_key points to, as a symbol.
807
839
  # Defaults to primary key of current table. Can use an
808
840
  # array of symbols for a composite key association.
841
+ # :left_primary_key_column :: Similar to, and usually identical to, :left_primary_key, but :left_primary_key refers to
842
+ # the model method to call, where :left_primary_key_column refers to the underlying column. Should only
843
+ # be used if the model method differs from the left primary key column, in conjunction
844
+ # with defining a model alias method for the left primary key column.
809
845
  # :right_key :: foreign key in join table that points to associated
810
846
  # model's primary key, as a symbol. Defaults to Defaults to :"#{name.to_s.singularize}_id".
811
847
  # Can use an array of symbols for a composite key association.
812
848
  # :right_primary_key :: column in associated table that :right_key points to, as a symbol.
813
849
  # Defaults to primary key of the associated table. Can use an
814
850
  # array of symbols for a composite key association.
851
+ # :right_primary_key_method :: the method symbol or array of method symbols to call on the associated
852
+ # object to get the foreign key values for the join table.
853
+ # Defaults to :right_primary_key option.
815
854
  # :uniq :: Adds a after_load callback that makes the array of objects unique.
816
855
  def associate(type, name, opts = {}, &block)
817
856
  raise(Error, 'one_to_many association type with :one_to_one option removed, used one_to_one association type') if opts[:one_to_one] && type == :one_to_many
@@ -1034,6 +1073,9 @@ module Sequel
1034
1073
  rcks = opts[:right_keys] = Array(right)
1035
1074
  left_pk = (opts[:left_primary_key] ||= self.primary_key)
1036
1075
  lcpks = opts[:left_primary_keys] = Array(left_pk)
1076
+ lpkc = opts[:left_primary_key_column] ||= left_pk
1077
+ lpkcs = opts[:left_primary_key_columns] ||= Array(lpkc)
1078
+ elk = opts.eager_loader_key
1037
1079
  raise(Error, "mismatched number of left composite keys: #{lcks.inspect} vs #{lcpks.inspect}") unless lcks.length == lcpks.length
1038
1080
  if opts[:right_primary_key]
1039
1081
  rcpks = Array(opts[:right_primary_key])
@@ -1050,7 +1092,7 @@ module Sequel
1050
1092
  opts[:dataset] ||= proc{opts.associated_class.inner_join(join_table, rcks.zip(opts.right_primary_keys) + lcks.zip(lcpks.map{|k| send(k)}))}
1051
1093
 
1052
1094
  opts[:eager_loader] ||= proc do |eo|
1053
- h = eo[:key_hash][left_pk]
1095
+ h = eo[:key_hash][elk]
1054
1096
  rows = eo[:rows]
1055
1097
  rows.each{|object| object.associations[name] = []}
1056
1098
  r = rcks.zip(opts.right_primary_keys)
@@ -1095,7 +1137,7 @@ module Sequel
1095
1137
  jt_graph_block = opts[:graph_join_table_block]
1096
1138
  opts[:eager_grapher] ||= proc do |eo|
1097
1139
  ds = eo[:self]
1098
- 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, [eo[:table_alias]]), :join_type=>jt_join_type, :implicit_qualifier=>eo[:implicit_qualifier], :from_self_alias=>ds.opts[:eager_graph][:master], &jt_graph_block)
1140
+ ds = ds.graph(join_table, use_jt_only_conditions ? jt_only_conditions : lcks.zip(lpkcs) + graph_jt_conds, :select=>false, :table_alias=>ds.unused_table_alias(join_table, [eo[:table_alias]]), :join_type=>jt_join_type, :implicit_qualifier=>eo[:implicit_qualifier], :from_self_alias=>ds.opts[:eager_graph][:master], &jt_graph_block)
1099
1141
  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)
1100
1142
  end
1101
1143
 
@@ -1106,11 +1148,11 @@ module Sequel
1106
1148
  association_module_private_def(opts._add_method, opts) do |o|
1107
1149
  h = {}
1108
1150
  lcks.zip(lcpks).each{|k, pk| h[k] = send(pk)}
1109
- rcks.zip(opts.right_primary_keys).each{|k, pk| h[k] = o.send(pk)}
1151
+ rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.send(pk)}
1110
1152
  _join_table_dataset(opts).insert(h)
1111
1153
  end
1112
1154
  association_module_private_def(opts._remove_method, opts) do |o|
1113
- _join_table_dataset(opts).filter(lcks.zip(lcpks.map{|k| send(k)}) + rcks.zip(opts.right_primary_keys.map{|k| o.send(k)})).delete
1155
+ _join_table_dataset(opts).filter(lcks.zip(lcpks.map{|k| send(k)}) + rcks.zip(opts.right_primary_key_methods.map{|k| o.send(k)})).delete
1114
1156
  end
1115
1157
  association_module_private_def(opts._remove_all_method, opts) do
1116
1158
  _join_table_dataset(opts).filter(lcks.zip(lcpks.map{|k| send(k)})).delete
@@ -1154,7 +1196,7 @@ module Sequel
1154
1196
  unless keys.empty?
1155
1197
  klass = opts.associated_class
1156
1198
  model.eager_loading_dataset(opts, klass.filter(opts.qualified_primary_key=>keys), nil, eo[:associations], eo).all do |assoc_record|
1157
- hash_key = uses_cks ? opts.primary_keys.map{|k| assoc_record.send(k)} : assoc_record.send(opts.primary_key)
1199
+ hash_key = uses_cks ? opts.primary_key_methods.map{|k| assoc_record.send(k)} : assoc_record.send(opts.primary_key_method)
1158
1200
  next unless objects = h[hash_key]
1159
1201
  objects.each{|object| object.associations[name] = assoc_record}
1160
1202
  end
@@ -1177,7 +1219,7 @@ module Sequel
1177
1219
 
1178
1220
  return if opts[:read_only]
1179
1221
 
1180
- association_module_private_def(opts._setter_method, opts){|o| cks.zip(opts.primary_keys).each{|k, pk| send(:"#{k}=", (o.send(pk) if o))}}
1222
+ association_module_private_def(opts._setter_method, opts){|o| cks.zip(opts.primary_key_methods).each{|k, pk| send(:"#{k}=", (o.send(pk) if o))}}
1181
1223
  association_module_def(opts.setter_method, opts){|o| set_associated_object(opts, o)}
1182
1224
  end
1183
1225
 
@@ -1187,16 +1229,20 @@ module Sequel
1187
1229
  name = opts[:name]
1188
1230
  model = self
1189
1231
  key = (opts[:key] ||= opts.default_key)
1232
+ km = opts[:key_method] ||= opts[:key]
1190
1233
  cks = opts[:keys] = Array(key)
1191
1234
  primary_key = (opts[:primary_key] ||= self.primary_key)
1192
1235
  cpks = opts[:primary_keys] = Array(primary_key)
1236
+ pkc = opts[:primary_key_column] ||= primary_key
1237
+ pkcs = opts[:primary_key_columns] ||= Array(pkc)
1238
+ elk = opts.eager_loader_key
1193
1239
  raise(Error, "mismatched number of composite keys: #{cks.inspect} vs #{cpks.inspect}") unless cks.length == cpks.length
1194
1240
  uses_cks = opts[:uses_composite_keys] = cks.length > 1
1195
1241
  opts[:dataset] ||= proc do
1196
1242
  opts.associated_class.filter(Array(opts.qualified_key).zip(cpks.map{|k| send(k)}))
1197
1243
  end
1198
1244
  opts[:eager_loader] ||= proc do |eo|
1199
- h = eo[:key_hash][primary_key]
1245
+ h = eo[:key_hash][elk]
1200
1246
  rows = eo[:rows]
1201
1247
  if one_to_one
1202
1248
  rows.each{|object| object.associations[name] = nil}
@@ -1221,7 +1267,7 @@ module Sequel
1221
1267
  end
1222
1268
  ds.all do |assoc_record|
1223
1269
  assoc_record.values.delete(rn) if delete_rn
1224
- hash_key = uses_cks ? cks.map{|k| assoc_record.send(k)} : assoc_record.send(key)
1270
+ hash_key = uses_cks ? km.map{|k| assoc_record.send(k)} : assoc_record.send(km)
1225
1271
  next unless objects = h[hash_key]
1226
1272
  if one_to_one
1227
1273
  objects.each do |object|
@@ -1252,7 +1298,7 @@ module Sequel
1252
1298
  graph_block = opts[:graph_block]
1253
1299
  opts[:eager_grapher] ||= proc do |eo|
1254
1300
  ds = eo[:self]
1255
- 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)
1301
+ ds = ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions, eo.merge(:select=>select, :join_type=>join_type, :from_self_alias=>ds.opts[:eager_graph][:master]), &graph_block)
1256
1302
  # We only load reciprocals for one_to_many associations, as other reciprocals don't make sense
1257
1303
  ds.opts[:eager_graph][:reciprocals][eo[:table_alias]] = opts.reciprocal
1258
1304
  ds
@@ -1939,25 +1985,33 @@ module Sequel
1939
1985
  # Return a subquery expression for filering by a many_to_many association
1940
1986
  def many_to_many_association_filter_expression(op, ref, obj)
1941
1987
  lpks, lks, rks = ref.values_at(:left_primary_keys, :left_keys, :right_keys)
1988
+ jt = ref.join_table_alias
1942
1989
  lpks = lpks.first if lpks.length == 1
1943
- exp = association_filter_key_expression(rks, ref.right_primary_keys, obj)
1990
+ lpks = ref.qualify(model.table_name, lpks)
1991
+ meths = ref.right_primary_keys
1992
+ meths = ref.qualify(obj.model.table_name, meths) if obj.is_a?(Sequel::Dataset)
1993
+ exp = association_filter_key_expression(ref.qualify(jt, rks), meths, obj)
1944
1994
  if exp == SQL::Constants::FALSE
1945
1995
  association_filter_handle_inversion(op, exp, Array(lpks))
1946
1996
  else
1947
- association_filter_handle_inversion(op, SQL::BooleanExpression.from_value_pairs(lpks=>model.db.from(ref[:join_table]).select(*lks).where(exp).exclude(SQL::BooleanExpression.from_value_pairs(lks.zip([]), :OR))), Array(lpks))
1997
+ association_filter_handle_inversion(op, SQL::BooleanExpression.from_value_pairs(lpks=>model.db.from(ref[:join_table]).select(*ref.qualify(jt, lks)).where(exp).exclude(SQL::BooleanExpression.from_value_pairs(ref.qualify(jt, lks).zip([]), :OR))), Array(lpks))
1948
1998
  end
1949
1999
  end
1950
2000
 
1951
2001
  # Return a simple equality expression for filering by a many_to_one association
1952
2002
  def many_to_one_association_filter_expression(op, ref, obj)
1953
- keys = ref[:keys]
1954
- association_filter_handle_inversion(op, association_filter_key_expression(keys, ref.primary_keys, obj), keys)
2003
+ keys = ref.qualify(model.table_name, ref[:keys])
2004
+ meths = ref.primary_keys
2005
+ meths = ref.qualify(obj.model.table_name, meths) if obj.is_a?(Sequel::Dataset)
2006
+ association_filter_handle_inversion(op, association_filter_key_expression(keys, meths, obj), keys)
1955
2007
  end
1956
2008
 
1957
2009
  # Return a simple equality expression for filering by a one_to_* association
1958
2010
  def one_to_many_association_filter_expression(op, ref, obj)
1959
- keys = ref[:primary_keys]
1960
- association_filter_handle_inversion(op, association_filter_key_expression(keys, ref[:keys], obj), keys)
2011
+ keys = ref.qualify(model.table_name, ref[:primary_keys])
2012
+ meths = ref[:keys]
2013
+ meths = ref.qualify(obj.model.table_name, meths) if obj.is_a?(Sequel::Dataset)
2014
+ association_filter_handle_inversion(op, association_filter_key_expression(keys, meths, obj), keys)
1961
2015
  end
1962
2016
  alias one_to_one_association_filter_expression one_to_many_association_filter_expression
1963
2017