sequel 3.31.0 → 3.32.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.
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