sequel 4.31.0 → 4.32.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +24 -0
  3. data/Rakefile +17 -15
  4. data/doc/association_basics.rdoc +7 -3
  5. data/doc/opening_databases.rdoc +7 -0
  6. data/doc/release_notes/4.32.0.txt +132 -0
  7. data/doc/schema_modification.rdoc +1 -1
  8. data/doc/security.rdoc +70 -26
  9. data/doc/testing.rdoc +1 -0
  10. data/lib/sequel/adapters/jdbc.rb +2 -1
  11. data/lib/sequel/adapters/postgres.rb +3 -4
  12. data/lib/sequel/adapters/shared/mysql.rb +14 -1
  13. data/lib/sequel/adapters/shared/sqlite.rb +2 -2
  14. data/lib/sequel/extensions/_pretty_table.rb +2 -0
  15. data/lib/sequel/extensions/arbitrary_servers.rb +2 -0
  16. data/lib/sequel/extensions/columns_introspection.rb +2 -0
  17. data/lib/sequel/extensions/connection_validator.rb +2 -0
  18. data/lib/sequel/extensions/constraint_validations.rb +2 -0
  19. data/lib/sequel/extensions/core_extensions.rb +1 -5
  20. data/lib/sequel/extensions/current_datetime_timestamp.rb +2 -0
  21. data/lib/sequel/extensions/dataset_source_alias.rb +2 -0
  22. data/lib/sequel/extensions/date_arithmetic.rb +2 -0
  23. data/lib/sequel/extensions/empty_array_consider_nulls.rb +3 -1
  24. data/lib/sequel/extensions/error_sql.rb +2 -0
  25. data/lib/sequel/extensions/eval_inspect.rb +2 -0
  26. data/lib/sequel/extensions/filter_having.rb +2 -0
  27. data/lib/sequel/extensions/from_block.rb +2 -0
  28. data/lib/sequel/extensions/graph_each.rb +2 -0
  29. data/lib/sequel/extensions/hash_aliases.rb +2 -0
  30. data/lib/sequel/extensions/inflector.rb +2 -0
  31. data/lib/sequel/extensions/looser_typecasting.rb +3 -1
  32. data/lib/sequel/extensions/meta_def.rb +2 -0
  33. data/lib/sequel/extensions/migration.rb +4 -0
  34. data/lib/sequel/extensions/mssql_emulate_lateral_with_apply.rb +2 -0
  35. data/lib/sequel/extensions/named_timezones.rb +2 -0
  36. data/lib/sequel/extensions/no_auto_literal_strings.rb +84 -0
  37. data/lib/sequel/extensions/null_dataset.rb +2 -0
  38. data/lib/sequel/extensions/pagination.rb +2 -0
  39. data/lib/sequel/extensions/pg_array.rb +2 -4
  40. data/lib/sequel/extensions/pg_array_ops.rb +2 -0
  41. data/lib/sequel/extensions/pg_enum.rb +2 -0
  42. data/lib/sequel/extensions/pg_hstore.rb +2 -4
  43. data/lib/sequel/extensions/pg_hstore_ops.rb +2 -0
  44. data/lib/sequel/extensions/pg_inet.rb +2 -4
  45. data/lib/sequel/extensions/pg_inet_ops.rb +2 -0
  46. data/lib/sequel/extensions/pg_interval.rb +2 -4
  47. data/lib/sequel/extensions/pg_json.rb +4 -4
  48. data/lib/sequel/extensions/pg_json_ops.rb +3 -0
  49. data/lib/sequel/extensions/pg_loose_count.rb +2 -0
  50. data/lib/sequel/extensions/pg_range.rb +2 -4
  51. data/lib/sequel/extensions/pg_range_ops.rb +2 -0
  52. data/lib/sequel/extensions/pg_row.rb +2 -4
  53. data/lib/sequel/extensions/pg_row_ops.rb +2 -0
  54. data/lib/sequel/extensions/pg_static_cache_updater.rb +2 -0
  55. data/lib/sequel/extensions/pretty_table.rb +2 -0
  56. data/lib/sequel/extensions/query.rb +3 -0
  57. data/lib/sequel/extensions/query_literals.rb +7 -5
  58. data/lib/sequel/extensions/round_timestamps.rb +4 -3
  59. data/lib/sequel/extensions/schema_caching.rb +2 -0
  60. data/lib/sequel/extensions/schema_dumper.rb +2 -0
  61. data/lib/sequel/extensions/select_remove.rb +2 -0
  62. data/lib/sequel/extensions/sequel_3_dataset_methods.rb +2 -0
  63. data/lib/sequel/extensions/server_block.rb +3 -0
  64. data/lib/sequel/extensions/set_overrides.rb +2 -0
  65. data/lib/sequel/extensions/split_array_nil.rb +2 -0
  66. data/lib/sequel/extensions/thread_local_timezones.rb +2 -0
  67. data/lib/sequel/extensions/to_dot.rb +2 -0
  68. data/lib/sequel/model/associations.rb +95 -55
  69. data/lib/sequel/plugins/association_pks.rb +58 -33
  70. data/lib/sequel/plugins/eager_each.rb +22 -0
  71. data/lib/sequel/plugins/pg_typecast_on_load.rb +3 -2
  72. data/lib/sequel/plugins/tactical_eager_loading.rb +44 -3
  73. data/lib/sequel/version.rb +2 -2
  74. data/spec/adapters/mysql_spec.rb +34 -6
  75. data/spec/adapters/oracle_spec.rb +1 -1
  76. data/spec/bin_spec.rb +2 -2
  77. data/spec/core/dataset_spec.rb +7 -0
  78. data/spec/extensions/association_pks_spec.rb +38 -0
  79. data/spec/extensions/class_table_inheritance_spec.rb +24 -0
  80. data/spec/extensions/eager_each_spec.rb +25 -1
  81. data/spec/extensions/no_auto_literal_strings_spec.rb +65 -0
  82. data/spec/extensions/pg_range_spec.rb +1 -0
  83. data/spec/extensions/spec_helper.rb +5 -5
  84. data/spec/extensions/tactical_eager_loading_spec.rb +71 -17
  85. data/spec/integration/associations_test.rb +77 -62
  86. data/spec/integration/dataset_test.rb +3 -3
  87. data/spec/integration/plugin_test.rb +22 -0
  88. data/spec/integration/prepared_statement_test.rb +8 -8
  89. data/spec/integration/spec_helper.rb +4 -0
  90. data/spec/model/association_reflection_spec.rb +30 -0
  91. data/spec/model/associations_spec.rb +177 -16
  92. metadata +6 -2
@@ -52,6 +52,8 @@
52
52
  # allows you to use symbols in your model code. Similar, you can provide
53
53
  # the enum values as symbols when creating enums using create_enum or
54
54
  # add_enum_value.
55
+ #
56
+ # Related module: Sequel::Postgres::EnumDatabaseMethods
55
57
 
56
58
  #
57
59
  module Sequel
@@ -80,11 +80,9 @@
80
80
  # See the {schema modification guide}[rdoc-ref:doc/schema_modification.rdoc]
81
81
  # for details on using hstore columns in CREATE/ALTER TABLE statements.
82
82
  #
83
- # If you are not using the native postgres or jdbc/postgresql adapters and are using hstore
84
- # types as model column values you probably should use the
85
- # pg_typecast_on_load plugin if the column values are returned as a string.
86
- #
87
83
  # This extension requires the delegate and strscan libraries.
84
+ #
85
+ # Related module: Sequel::Postgres::HStore
88
86
 
89
87
  require 'delegate'
90
88
  require 'strscan'
@@ -75,6 +75,8 @@
75
75
  # pg_hstore extension is loaded. Methods representing expressions that return
76
76
  # PostgreSQL arrays will have the returned expression automatically wrapped in a
77
77
  # Postgres::ArrayOp if the pg_array_ops extension is loaded.
78
+ #
79
+ # Related module: Sequel::Postgres::HStoreOp
78
80
 
79
81
  #
80
82
  module Sequel
@@ -11,10 +11,6 @@
11
11
  #
12
12
  # DB.extension :pg_inet
13
13
  #
14
- # If you are not using the native postgres or jdbc/postgresql adapters and are using inet/cidr
15
- # types as model column values you probably should use the
16
- # pg_typecast_on_load plugin if the column values are returned as a string.
17
- #
18
14
  # This extension integrates with the pg_array extension. If you plan
19
15
  # to use the inet[] or cidr[] types, load the pg_array extension before
20
16
  # the pg_inet extension:
@@ -29,6 +25,8 @@
29
25
  #
30
26
  # See the {schema modification guide}[rdoc-ref:doc/schema_modification.rdoc]
31
27
  # for details on using inet/cidr columns in CREATE/ALTER TABLE statements.
28
+ #
29
+ # Related module: Sequel::Postgres::InetDatabaseMethods
32
30
 
33
31
  require 'ipaddr'
34
32
  Sequel.require 'adapters/utils/pg_types'
@@ -56,6 +56,8 @@
56
56
  #
57
57
  # See the PostgreSQL network function and operator documentation for more
58
58
  # details on what these functions and operators do.
59
+ #
60
+ # Related module: Sequel::Postgres::InetOp
59
61
 
60
62
  require 'ipaddr'
61
63
 
@@ -17,10 +17,6 @@
17
17
  #
18
18
  # DB.extension :pg_interval
19
19
  #
20
- # If you are not using the native postgres or jdbc/postgresql adapters and are using interval
21
- # types as model column values you probably should use the
22
- # pg_typecast_on_load plugin if the column values are returned as a string.
23
- #
24
20
  # This extension integrates with the pg_array extension. If you plan
25
21
  # to use arrays of interval types, load the pg_array extension before the
26
22
  # pg_interval extension:
@@ -36,6 +32,8 @@
36
32
  #
37
33
  # See the {schema modification guide}[rdoc-ref:doc/schema_modification.rdoc]
38
34
  # for details on using interval columns in CREATE/ALTER TABLE statements.
35
+ #
36
+ # Related module: Sequel::Postgres::IntervalDatabaseMethods
39
37
 
40
38
  require 'active_support/duration'
41
39
  Sequel.require 'adapters/utils/pg_types'
@@ -45,10 +45,6 @@
45
45
  #
46
46
  # DB.extension :pg_json
47
47
  #
48
- # If you are not using the native postgres adapter and are using json
49
- # types as model column values you probably should use the
50
- # pg_typecast_on_load plugin if the column values are returned as a string.
51
- #
52
48
  # See the {schema modification guide}[rdoc-ref:doc/schema_modification.rdoc]
53
49
  # for details on using json columns in CREATE/ALTER TABLE statements.
54
50
  #
@@ -62,6 +58,10 @@
62
58
  # Attempting to use other values (such as symbols) will not work correctly.
63
59
  #
64
60
  # This extension requires both the json and delegate libraries.
61
+ #
62
+ # Related modules: Sequel::Postgres::JSONArrayBase, Sequel::Postgres::JSONArray,
63
+ # Sequel::Postgres::JSONArray, Sequel::Postgres::JSONBArray, Sequel::Postgres::JSONHashBase,
64
+ # Sequel::Postgres::JSONHash, Sequel::Postgres::JSONBHash, Sequel::Postgres::JSONDatabaseMethods
65
65
 
66
66
  require 'delegate'
67
67
  require 'json'
@@ -80,6 +80,9 @@
80
80
  # In order to get the automatic conversion from a ruby array to a PostgreSQL array
81
81
  # (as shown in the #[] and #get_text examples above), you need to load the pg_array
82
82
  # extension.
83
+ #
84
+ # Related modules: Sequel::Postgres::JSONBaseOp, Sequel::Postgres::JSONOp,
85
+ # Sequel::Postgres::JSONBOp
83
86
 
84
87
  #
85
88
  module Sequel
@@ -17,6 +17,8 @@
17
17
  # To load the extension into the database:
18
18
  #
19
19
  # DB.extension :pg_loose_count
20
+ #
21
+ # Related module: Sequel::Postgres::LooseCount
20
22
 
21
23
  #
22
24
  module Sequel
@@ -47,10 +47,6 @@
47
47
  #
48
48
  # DB.extension :pg_range
49
49
  #
50
- # If you are not using the native postgres or jdbc/postgresql adapters and are using range
51
- # types as model column values you probably should use the
52
- # pg_typecast_on_load plugin if the column values are returned as a string.
53
- #
54
50
  # See the {schema modification guide}[rdoc-ref:doc/schema_modification.rdoc]
55
51
  # for details on using range type columns in CREATE/ALTER TABLE statements.
56
52
  #
@@ -59,6 +55,8 @@
59
55
  # pg_range extension:
60
56
  #
61
57
  # DB.extension :pg_array, :pg_range
58
+ #
59
+ # Related module: Sequel::Postgres::PGRange
62
60
 
63
61
  Sequel.require 'adapters/utils/pg_types'
64
62
 
@@ -53,6 +53,8 @@
53
53
  # If you are also using the pg_range extension, you should load it before
54
54
  # loading this extension. Doing so will allow you to use PGArray#op to get
55
55
  # an RangeOp, allowing you to perform range operations on range literals.
56
+ #
57
+ # Related module: Sequel::Postgres::RangeOp
56
58
 
57
59
  #
58
60
  module Sequel
@@ -76,14 +76,12 @@
76
76
  # DB.conversion_procs.select{|k,v| v.is_a?(Sequel::Postgres::PGRow::Parser) && \
77
77
  # v.converter && (v.converter.name.nil? || v.converter.name == '') }.map{|k,v| v}
78
78
  #
79
- # If you are not using the native postgres or jdbc/postgresql adapters and are using composite types
80
- # types as model column values you probably should use the
81
- # pg_typecast_on_load plugin if the column values are returned as a string.
82
- #
83
79
  # See the {schema modification guide}[rdoc-ref:doc/schema_modification.rdoc]
84
80
  # for details on using row type columns in CREATE/ALTER TABLE statements.
85
81
  #
86
82
  # This extension requires both the strscan and delegate libraries.
83
+ #
84
+ # Related module: Sequel::Postgres::PGRow
87
85
 
88
86
  require 'delegate'
89
87
  require 'strscan'
@@ -80,6 +80,8 @@
80
80
  # Sequel.pg_row_op(:b).splat(:b))
81
81
  # # SELECT (a.*)::a, (b.*)::b FROM a INNER JOIN b ON (b.id = a.b_id)
82
82
  # # => {:a=>{:id=>1, :b_id=>2}, :b=>{:id=>2}}
83
+ #
84
+ # Related module: Sequel::Postgres::PGRowOp
83
85
 
84
86
  #
85
87
  module Sequel
@@ -63,6 +63,8 @@
63
63
  # with the pg driver (the model classes do not have to
64
64
  # use the same Database).
65
65
  # * Must be using a thread-safe connection pool (the default).
66
+ #
67
+ # Related module: Sequel::Postgres::StaticCacheUpdater
66
68
 
67
69
  #
68
70
  module Sequel
@@ -20,6 +20,8 @@
20
20
  # is probably the desired behavior if you are using this extension:
21
21
  #
22
22
  # DB.extension(:pretty_table)
23
+ #
24
+ # Related module: Sequel::DatasetPrinter
23
25
 
24
26
  #
25
27
  module Sequel
@@ -19,6 +19,9 @@
19
19
  # is probably the desired behavior if you are using this extension:
20
20
  #
21
21
  # DB.extension(:query)
22
+ #
23
+ # Related modules: Sequel::DatabaseQuery, Sequel::DatasetQuery,
24
+ # Sequel::Dataset::Query
22
25
 
23
26
  #
24
27
  module Sequel
@@ -17,11 +17,11 @@
17
17
  # SELECT a, b, 2, FROM table GROUP BY a, b ORDER BY c
18
18
  #
19
19
  # This extension makes select, group, and order methods operate
20
- # like filter methods, which support the same interface.
21
- #
22
- # There are very few places where Sequel's default behavior is
23
- # desirable in this area, but for backwards compatibility, the
24
- # defaults won't be changed until the next major release.
20
+ # like filter methods, which support the same interface. Note
21
+ # that this extension can add SQL injection vulnerabilities to existing
22
+ # code if any of the strings passed to one of the supported
23
+ # methods is derived from user input. For that reason, it should
24
+ # be used with caution.
25
25
  #
26
26
  # You can load this extension into specific datasets:
27
27
  #
@@ -32,6 +32,8 @@
32
32
  # is probably the desired behavior if you are using this extension:
33
33
  #
34
34
  # DB.extension(:query_literals)
35
+ #
36
+ # Related module: Sequel::QueryLiterals
35
37
 
36
38
  #
37
39
  module Sequel
@@ -4,9 +4,8 @@
4
4
  # values to the database's supported level of precision before literalizing
5
5
  # them.
6
6
  #
7
- # For example, if the database supports microsecond precision, and you give
8
- # it a Time value with greater than microsecond precision, it will round it
9
- # appropriately:
7
+ # For example, if the database supports millisecond precision, and you give
8
+ # it a Time value with microsecond precision, it will round it appropriately:
10
9
  #
11
10
  # Time.at(1405341161.917999982833862)
12
11
  # # default: 2014-07-14 14:32:41.917999
@@ -23,6 +22,8 @@
23
22
  # To round timestamps for all datasets on a single database:
24
23
  #
25
24
  # DB.extension(:round_timestamps)
25
+ #
26
+ # Related module: Sequel::Dataset::RoundTimestamps
26
27
 
27
28
  unless RUBY_VERSION >= '1.9'
28
29
  # :nocov:
@@ -43,6 +43,8 @@
43
43
  # The cached schema is dumped in Marshal format, since it is the fastest
44
44
  # and it handles all ruby objects used in the schema hash. Because of this,
45
45
  # you should not attempt to load the schema from a untrusted file.
46
+ #
47
+ # Related module: Sequel::SchemaCaching
46
48
 
47
49
  #
48
50
  module Sequel
@@ -9,6 +9,8 @@
9
9
  # To load the extension:
10
10
  #
11
11
  # DB.extension :schema_dumper
12
+ #
13
+ # Related module: Sequel::SchemaDumper
12
14
 
13
15
  Sequel.extension :eval_inspect
14
16
 
@@ -13,6 +13,8 @@
13
13
  # is probably the desired behavior if you are using this extension:
14
14
  #
15
15
  # DB.extension(:select_remove)
16
+ #
17
+ # Related module: Sequel::SelectRemove
16
18
 
17
19
  #
18
20
  module Sequel
@@ -20,6 +20,8 @@
20
20
  # is probably the desired behavior if you are using this extension:
21
21
  #
22
22
  # DB.extension(:sequel_3_dataset_methods)
23
+ #
24
+ # Related module: Sequel::Sequel3DatasetMethods
23
25
 
24
26
  #
25
27
  module Sequel
@@ -38,6 +38,9 @@
38
38
  # DB[:a].server(:default).all # Uses shard1
39
39
  # DB[:a].server(:read_only).all # Uses shard1
40
40
  # end
41
+ #
42
+ # Related modules: Sequel::ServerBlock, Sequel::UnthreadedServerBlock,
43
+ # Sequel::ThreadedServerBlock
41
44
 
42
45
  #
43
46
  module Sequel
@@ -15,6 +15,8 @@
15
15
  # is probably the desired behavior if you are using this extension:
16
16
  #
17
17
  # DB.extension(:set_overrides)
18
+ #
19
+ # Related module: Sequel::SetOverrides
18
20
 
19
21
  #
20
22
  module Sequel
@@ -32,6 +32,8 @@
32
32
  # To use this extension for all of a database's datasets:
33
33
  #
34
34
  # DB.extension(:split_array_nil)
35
+ #
36
+ # Related module: Sequel::Dataset::SplitArrayNil
35
37
 
36
38
  #
37
39
  module Sequel
@@ -35,6 +35,8 @@
35
35
  # Sequel.application_timezone # => :utc
36
36
  # Sequel.thread_application_timezone = :nil
37
37
  # Sequel.application_timezone # => nil
38
+ #
39
+ # Related module: Sequel::ThreadLocalTimezones
38
40
 
39
41
  #
40
42
  module Sequel
@@ -8,6 +8,8 @@
8
8
  # To load the extension:
9
9
  #
10
10
  # Sequel.extension :to_dot
11
+ #
12
+ # Related module: Sequel::ToDot
11
13
 
12
14
  #
13
15
  module Sequel
@@ -14,6 +14,7 @@ module Sequel
14
14
  @association_reflections = {}
15
15
  @autoreloading_associations = {}
16
16
  @cache_associations = true
17
+ @default_association_options = {}
17
18
  end
18
19
  end
19
20
 
@@ -489,40 +490,21 @@ module Sequel
489
490
 
490
491
  private
491
492
 
492
- if defined?(RUBY_ENGINE) && RUBY_ENGINE != 'ruby'
493
- # :nocov:
494
- # On non-GVL rubies, assume the need to synchronize access. Store the key
495
- # in a special sub-hash that always uses this method to synchronize access.
496
- def cached_fetch(key)
497
- fetch(key) do
498
- return yield unless h = self[:cache]
499
- Sequel.synchronize{return h[key] if h.has_key?(key)}
500
- value = yield
501
- Sequel.synchronize{h[key] = value}
502
- end
503
- end
504
-
505
- # Cache the value at the given key, synchronizing access.
506
- def cached_set(key, value)
507
- return unless h = self[:cache]
493
+ # On non-GVL rubies, assume the need to synchronize access. Store the key
494
+ # in a special sub-hash that always uses this method to synchronize access.
495
+ def cached_fetch(key)
496
+ fetch(key) do
497
+ return yield unless h = self[:cache]
498
+ Sequel.synchronize{return h[key] if h.has_key?(key)}
499
+ value = yield
508
500
  Sequel.synchronize{h[key] = value}
509
501
  end
510
- # :nocov:
511
- else
512
- # On MRI, use a plain fetch, since the GVL will synchronize access.
513
- def cached_fetch(key)
514
- fetch(key) do
515
- return yield unless h = self[:cache]
516
- h.fetch(key){h[key] = yield}
517
- end
518
- end
502
+ end
519
503
 
520
- # On MRI, just set the value at the key in the cache, since the GVL
521
- # will synchronize access.
522
- def cached_set(key, value)
523
- return unless h = self[:cache]
524
- h[key] = value
525
- end
504
+ # Cache the value at the given key, synchronizing access.
505
+ def cached_set(key, value)
506
+ return unless h = self[:cache]
507
+ Sequel.synchronize{h[key] = value}
526
508
  end
527
509
 
528
510
  # The base dataset used for the association, before any order/conditions
@@ -1436,11 +1418,14 @@ module Sequel
1436
1418
  attr_reader :autoreloading_associations
1437
1419
 
1438
1420
  # Whether association metadata should be cached in the association reflection. If not cached, it will be computed
1439
- # on demand. In general you only want to set this to default when using code reloading. When using code reloading,
1421
+ # on demand. In general you only want to set this to false when using code reloading. When using code reloading,
1440
1422
  # setting this will make sure that if an associated class is removed or modified, this class will not hang on to
1441
1423
  # the previous class.
1442
1424
  attr_accessor :cache_associations
1443
1425
 
1426
+ # The default options to use for all associations.
1427
+ attr_accessor :default_association_options
1428
+
1444
1429
  # The default :eager_limit_strategy option to use for limited or offset associations (default: true, causing Sequel
1445
1430
  # to use what it considers the most appropriate strategy).
1446
1431
  attr_accessor :default_eager_limit_strategy
@@ -1674,7 +1659,7 @@ module Sequel
1674
1659
  orig_opts = cloned_assoc[:orig_opts].merge(orig_opts)
1675
1660
  end
1676
1661
 
1677
- opts = orig_opts.merge(:type => type, :name => name, :cache=>({} if cache_associations), :model => self)
1662
+ opts = default_association_options.merge(orig_opts).merge(:type => type, :name => name, :cache=>({} if cache_associations), :model => self)
1678
1663
  opts[:block] = block if block
1679
1664
  if !opts.has_key?(:instance_specific) && (block || orig_opts[:block] || orig_opts[:dataset])
1680
1665
  # It's possible the association is instance specific, in that it depends on
@@ -1757,7 +1742,7 @@ module Sequel
1757
1742
  associate(:one_to_one, name, opts, &block)
1758
1743
  end
1759
1744
 
1760
- Plugins.inherited_instance_variables(self, :@association_reflections=>:dup, :@autoreloading_associations=>:hash_dup, :@cache_associations=>nil, :@default_eager_limit_strategy=>nil)
1745
+ Plugins.inherited_instance_variables(self, :@association_reflections=>:dup, :@autoreloading_associations=>:hash_dup, :@default_association_options=>:dup, :@cache_associations=>nil, :@default_eager_limit_strategy=>nil)
1761
1746
  Plugins.def_dataset_methods(self, [:eager, :eager_graph, :eager_graph_with_options, :association_join, :association_full_join, :association_inner_join, :association_left_join, :association_right_join])
1762
1747
 
1763
1748
  private
@@ -1818,7 +1803,6 @@ module Sequel
1818
1803
  # Configures many_to_many and one_through_one association reflection and adds the related association methods
1819
1804
  def def_many_to_many(opts)
1820
1805
  one_through_one = opts[:type] == :one_through_one
1821
- opts[:read_only] = true if one_through_one
1822
1806
  left = (opts[:left_key] ||= opts.default_left_key)
1823
1807
  lcks = opts[:left_keys] = Array(left)
1824
1808
  right = (opts[:right_key] ||= opts.default_right_key)
@@ -1872,21 +1856,54 @@ module Sequel
1872
1856
  end
1873
1857
  end
1874
1858
 
1875
- return if opts[:read_only] || one_through_one
1859
+ return if opts[:read_only]
1876
1860
 
1877
- opts[:adder] ||= proc do |o|
1878
- h = {}
1879
- lcks.zip(lcpks).each{|k, pk| h[k] = get_column_value(pk)}
1880
- rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.get_column_value(pk)}
1881
- _join_table_dataset(opts).insert(h)
1882
- end
1861
+ if one_through_one
1862
+ opts[:setter] ||= proc do |o|
1863
+ h = {}
1864
+ lh = lcks.zip(lcpks.map{|k| get_column_value(k)})
1865
+ jtds = _join_table_dataset(opts).where(lh)
1883
1866
 
1884
- opts[:remover] ||= proc do |o|
1885
- _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)}) + rcks.zip(opts.right_primary_key_methods.map{|k| o.get_column_value(k)})).delete
1886
- end
1867
+ checked_transaction do
1868
+ current = jtds.first
1869
+
1870
+ if o
1871
+ new_values = []
1872
+ rcks.zip(opts.right_primary_key_methods).each{|k, pk| new_values << (h[k] = o.get_column_value(pk))}
1873
+ end
1887
1874
 
1888
- opts[:clearer] ||= proc do
1889
- _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
1875
+ if current
1876
+ current_values = rcks.map{|k| current[k]}
1877
+ jtds = jtds.where(rcks.zip(current_values))
1878
+ if o
1879
+ if current_values != new_values
1880
+ jtds.update(h)
1881
+ end
1882
+ else
1883
+ jtds.delete
1884
+ end
1885
+ elsif o
1886
+ lh.each{|k,v| h[k] = v}
1887
+ jtds.insert(h)
1888
+ end
1889
+ end
1890
+ end
1891
+ opts[:_setter] = proc{|o| set_one_through_one_associated_object(opts, o)}
1892
+ else
1893
+ opts[:adder] ||= proc do |o|
1894
+ h = {}
1895
+ lcks.zip(lcpks).each{|k, pk| h[k] = get_column_value(pk)}
1896
+ rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.get_column_value(pk)}
1897
+ _join_table_dataset(opts).insert(h)
1898
+ end
1899
+
1900
+ opts[:remover] ||= proc do |o|
1901
+ _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)}) + rcks.zip(opts.right_primary_key_methods.map{|k| o.get_column_value(k)})).delete
1902
+ end
1903
+
1904
+ opts[:clearer] ||= proc do
1905
+ _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
1906
+ end
1890
1907
  end
1891
1908
  end
1892
1909
 
@@ -2242,18 +2259,34 @@ module Sequel
2242
2259
  self
2243
2260
  end
2244
2261
 
2245
- # Load the associated objects using the dataset, handling callbacks, reciprocals, and caching.
2246
- def load_associated_objects(opts, dynamic_opts=nil)
2247
- if dynamic_opts == true or dynamic_opts == false or dynamic_opts == nil
2248
- dynamic_opts = {:reload=>dynamic_opts}
2249
- elsif dynamic_opts.respond_to?(:call)
2250
- dynamic_opts = {:callback=>dynamic_opts}
2262
+ # Handle parsing of options when loading associations. For historical
2263
+ # reasons, you can pass true/false/nil or a callable argument to
2264
+ # associations. That will be going away in Sequel 5, but we'll still
2265
+ # support it until then.
2266
+ def load_association_objects_options(dynamic_opts, &block)
2267
+ dynamic_opts = case dynamic_opts
2268
+ when true, false, nil
2269
+ {:reload=>dynamic_opts}
2270
+ when Hash
2271
+ Hash[dynamic_opts]
2272
+ else
2273
+ if dynamic_opts.respond_to?(:call)
2274
+ {:callback=>dynamic_opts}
2275
+ end
2251
2276
  end
2277
+
2252
2278
  if block_given?
2253
- dynamic_opts = Hash[dynamic_opts].merge!(:callback=>Proc.new)
2279
+ dynamic_opts[:callback] = block
2254
2280
  end
2281
+
2282
+ dynamic_opts
2283
+ end
2284
+
2285
+ # Load the associated objects using the dataset, handling callbacks, reciprocals, and caching.
2286
+ def load_associated_objects(opts, dynamic_opts=nil, &block)
2287
+ dynamic_opts = load_association_objects_options(dynamic_opts, &block)
2255
2288
  name = opts[:name]
2256
- if associations.include?(name) and !dynamic_opts[:callback] and !dynamic_opts[:reload]
2289
+ if associations.include?(name) && !dynamic_opts[:callback] && !dynamic_opts[:reload]
2257
2290
  associations[name]
2258
2291
  else
2259
2292
  objs = _load_associated_objects(opts, dynamic_opts)
@@ -2386,6 +2419,13 @@ module Sequel
2386
2419
  _set_associated_object(opts, o)
2387
2420
  end
2388
2421
 
2422
+ # Set the given object as the associated object for the given one_through_one association reflection
2423
+ def set_one_through_one_associated_object(opts, o)
2424
+ raise(Error, "object #{inspect} does not have a primary key") unless pk
2425
+ raise(Error, "associated object #{o.inspect} does not have a primary key") if o && !o.pk
2426
+ _set_associated_object(opts, o)
2427
+ end
2428
+
2389
2429
  # Set the given object as the associated object for the given one_to_one association reflection
2390
2430
  def set_one_to_one_associated_object(opts, o)
2391
2431
  raise(Error, "object #{inspect} does not have a primary key") unless pk