sequel 4.31.0 → 4.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 (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