sequel 5.45.0 → 5.46.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b37b49ac4a53cefbee674e57e1b64f3733ee7c330d548c59e370efe6b557cc5a
4
- data.tar.gz: 0f7de640673c6dff3affc0c494609d96502f62d36d7c6b639c4e3dba2d90a89f
3
+ metadata.gz: 8202d77fff48270013e8d06b75c5ab51933ff9e3fda72f99139211c9cb00de65
4
+ data.tar.gz: 15393a6189c83eb324e243d81805da38deced274d3f3a1b64bbf3cee54a27fa6
5
5
  SHA512:
6
- metadata.gz: 342e7413e9a93cea694f1d1062803d4729b28f3d8adfc2f537ecd24dc988c448754c13046709d0a6505a78c0b21fb3383a1e5d55f73ee5e8364dbd0efb84fa7d
7
- data.tar.gz: 9ea9cd35c75b670053804399f27475e38e28ea7b21062cf736e9bbea641c9d0862ef77edab6007d224a3a669a7469cab0c94658c4477e4d97a93c1c0355f031f
6
+ metadata.gz: 35aa602f835100be3a01bec05ecfb3a0d53555774d1dff520963c254d79c8123328f61e2252c8cb9c69b935f015de7c53f5cbeaec378d425666e7c96bcd0dba7
7
+ data.tar.gz: b52d0a0e2ea2fe6e388bd8fc694bac86d66f960cba0ed6c952213ba2414395862d3ba734b1763f13883ec7f7983cf69fe75a39a0f8e9406af46e2a2974f7210e
data/CHANGELOG CHANGED
@@ -1,3 +1,9 @@
1
+ === 5.46.0 (2021-07-01)
2
+
3
+ * Add unused_associations plugin, for determining which associations and association methods are not used (jeremyevans)
4
+
5
+ * Make nil :setter/:adder/:remover/:clearer association options not create related methods (jeremyevans)
6
+
1
7
  === 5.45.0 (2021-06-01)
2
8
 
3
9
  * Fix handling of NULL values in boolean columns in the ODBC adapter (jeremyevans) (#1765)
data/README.rdoc CHANGED
@@ -22,10 +22,9 @@ RDoc Documentation :: http://sequel.jeremyevans.net/rdoc
22
22
  Source Code :: https://github.com/jeremyevans/sequel
23
23
  Bug tracking (GitHub Issues) :: http://github.com/jeremyevans/sequel/issues
24
24
  Discussion Forum (sequel-talk Google Group) :: http://groups.google.com/group/sequel-talk
25
- IRC Channel (#sequel) :: irc://irc.freenode.net/sequel
26
25
 
27
26
  If you have questions about how to use Sequel, please ask on the
28
- sequel-talk Google Group or IRC. Only use the the bug tracker to report
27
+ sequel-talk Google Group. Only use the the bug tracker to report
29
28
  bugs in Sequel, not to ask for help on using Sequel.
30
29
 
31
30
  To check out the source code:
@@ -826,6 +826,8 @@ you also wanted to handle the Artist#add_album method:
826
826
  end)
827
827
  end
828
828
 
829
+ You can set this to +nil+ to not create a add_<i>association</i> method.
830
+
829
831
  === :remover (\_remove_<i>association</i> method)
830
832
 
831
833
  Continuing with the same example, here's how you would handle the same case if
@@ -837,6 +839,8 @@ you also wanted to handle the Artist#remove_album method:
837
839
  end)
838
840
  end
839
841
 
842
+ You can set this to +nil+ to not create a remove_<i>association</i> method.
843
+
840
844
  === :clearer (\_remove_all_<i>association</i> method)
841
845
 
842
846
  Continuing with the same example, here's how you would handle the same case if
@@ -850,6 +854,22 @@ you also wanted to handle the Artist#remove_all_albums method:
850
854
  end)
851
855
  end
852
856
 
857
+ You can set this to +nil+ to not create a remove_all_<i>association</i> method.
858
+
859
+ === :no_dataset_method
860
+
861
+ Setting this to true will not result in the <i>association</i>_dataset method
862
+ not being defined. This can save memory if you only use the <i>association</i>
863
+ method and do not call the <i>association</i>_dataset method directly or
864
+ indirectly.
865
+
866
+ === :no_association_method
867
+
868
+ Setting this to true will not result in the <i>association</i> method
869
+ not being defined. This can save memory if you only use the
870
+ <i>association</i>_dataset method and do not call the <i>association</i> method
871
+ directly or indirectly.
872
+
853
873
  == Association Options
854
874
 
855
875
  Sequel's associations mostly share the same options. For ease of understanding,
@@ -1638,9 +1658,8 @@ For +many_to_one+ and +one_to_one+ associations, do not add a setter method.
1638
1658
  For +one_to_many+ and +many_to_many+, do not add the add_<i>association</i>,
1639
1659
  remove_<i>association</i>, or remove_all_<i>association</i> methods.
1640
1660
 
1641
- If the default modification methods would not do what you want, and you
1642
- don't plan on overriding the internal modification methods to do what you
1643
- want, it may be best to set this option to true.
1661
+ If you are not using the association modification methods, setting this
1662
+ value to true will save memory.
1644
1663
 
1645
1664
  ==== :validate
1646
1665
 
@@ -0,0 +1,87 @@
1
+ = New Features
2
+
3
+ * An unused_associations plugin has been added, which allows you to
4
+ determine which associations and association methods are not used.
5
+ You can use this to avoid defining the unused associations and
6
+ association methods, which can save memory.
7
+
8
+ This plugin is supported on Ruby 2.5+, and uses method coverage to
9
+ determine if the plugin's methods are called. Because Sequel::Model
10
+ adds association methods to an anonymous module included in the
11
+ class, directly using the method coverage data to determine which
12
+ associations are used is challenging.
13
+
14
+ This plugin is mostly designed for reporting. You can have a
15
+ test suite that runs with method coverage enabled, and use the
16
+ coverage information to get data on unused associations:
17
+
18
+ # Calls Coverage.result
19
+ cov_data = Sequel::Model.update_associations_coverage
20
+ unused_associations_data = Sequel::Model.update_unused_associations_data(coverage_data: cov_data)
21
+ Sequel::Model.unused_associations(unused_associations_data: unused_associations_data)
22
+ # => [["Class1", "assoc1"], ...]
23
+
24
+ unused_associations returns an array of two element arrays, where
25
+ the first element is the class name and the second element is the
26
+ association name. The returned values will be associations where
27
+ all of the association methods are not used.
28
+
29
+ In addition to determining which associations are not used, you can
30
+ also use this to determine if you are defining association methods
31
+ that are not used:
32
+
33
+ Sequel::Model.unused_association_options(unused_associations_data: unused_associations_data)
34
+ # => [["Class2", "assoc2", {:read_only=>true}], ...]
35
+
36
+ unused_association_options is similar to unused_associations, but
37
+ returns an array of three element arrays, where the third element
38
+ is a hash of association options that should be used to avoid
39
+ defining the unused association methods. It's common in Sequel to
40
+ define associations and only use them for reading data and not for
41
+ modifications, and you can use this to easily see which associations
42
+ are only used for reading data.
43
+
44
+ As the determination of whether associations are used is based on
45
+ method coverage, this will report as unused any associations that are
46
+ used but where the association methods are not called. These cases
47
+ are rare, but can happen if you have libraries that use the
48
+ association reflection metadata without calling the association
49
+ methods, or use the association only in combination with another
50
+ plugin such as dataset_associations. You can set the :is_used
51
+ association option to explicitly mark an association as used, and
52
+ have this plugin avoid reporting it as unused.
53
+
54
+ In addition to just reporting on unused associations, you can also
55
+ directly use the unused associations metadata to automatically avoid
56
+ defining unused associations or unused associations methods. You
57
+ can set a :file option when loading the plugin:
58
+
59
+ Sequel::Model.plugin :unused_associations, file: 'unused_associations.json'
60
+
61
+ Then run the method coverage testing. This will save the unused
62
+ associations metadata to the file. Then you can use this metadata
63
+ automatically by also setting the :modify_associations option:
64
+
65
+ Sequel::Model.plugin :unused_associations, file: 'unused_associations.json',
66
+ modify_associations: true
67
+
68
+ With the :modify_associations option, unused associations are
69
+ skipped instead of being defined, and the options returned by
70
+ unused_association_options are automatically used. Note that using
71
+ the :modify_associations option is risky unless you have complete
72
+ coverage and do not have cases where the associations are used
73
+ without calling methods.
74
+
75
+ It is common to have multiple test suites where you need to combine
76
+ coverage. The plugin supports this by using a :coverage_file option:
77
+
78
+ Sequel::Model.plugin :unused_associations, coverage_file: 'unused_associations_coverage.json'
79
+
80
+ In this case, you would run update_associations_coverage after each
81
+ test suite, and update_unused_associations_data only after all test
82
+ suites have been run.
83
+
84
+ * Passing nil as the value of the :setter, :adder, :remover, or
85
+ :clearer association options will cause the related method to not be
86
+ defined, instead of using the default value. This allows you to
87
+ only define the methods you will actually be using.
@@ -1595,6 +1595,7 @@ module Sequel
1595
1595
  # === Multiple Types
1596
1596
  # :adder :: Proc used to define the private _add_* method for doing the database work
1597
1597
  # to associate the given object to the current object (*_to_many assocations).
1598
+ # Set to nil to not define a add_* method for the association.
1598
1599
  # :after_add :: Symbol, Proc, or array of both/either specifying a callback to call
1599
1600
  # after a new item is added to the association.
1600
1601
  # :after_load :: Symbol, Proc, or array of both/either specifying a callback to call
@@ -1623,6 +1624,7 @@ module Sequel
1623
1624
  # the class. <tt>class: 'Foo', class_namespace: 'Bar'</tt> looks for <tt>::Bar::Foo</tt>.)
1624
1625
  # :clearer :: Proc used to define the private _remove_all_* method for doing the database work
1625
1626
  # to remove all objects associated to the current object (*_to_many assocations).
1627
+ # Set to nil to not define a remove_all_* method for the association.
1626
1628
  # :clone :: Merge the current options and block into the options and block used in defining
1627
1629
  # the given association. Can be used to DRY up a bunch of similar associations that
1628
1630
  # all share the same options such as :class and :key, while changing the order and block used.
@@ -1677,7 +1679,7 @@ module Sequel
1677
1679
  # :graph_only_conditions :: The conditions to use on the SQL join when eagerly loading
1678
1680
  # the association via +eager_graph+, instead of the default conditions specified by the
1679
1681
  # foreign/primary keys. This option causes the :graph_conditions option to be ignored.
1680
- # :graph_order :: Over the order to use when using eager_graph, instead of the default order. This should be used
1682
+ # :graph_order :: the order to use when using eager_graph, instead of the default order. This should be used
1681
1683
  # in the case where :order contains an identifier qualified by the table's name, which may not match
1682
1684
  # the alias used when eager graphing. By setting this to the unqualified identifier, it will be
1683
1685
  # automatically qualified when using eager_graph.
@@ -1689,6 +1691,10 @@ module Sequel
1689
1691
  # limit (first element) and an offset (second element).
1690
1692
  # :methods_module :: The module that methods the association creates will be placed into. Defaults
1691
1693
  # to the module containing the model's columns.
1694
+ # :no_association_method :: Do not add a method for the association. This can save memory if the association
1695
+ # method is never used.
1696
+ # :no_dataset_method :: Do not add a method for the association dataset. This can save memory if the dataset
1697
+ # method is never used.
1692
1698
  # :order :: the column(s) by which to order the association dataset. Can be a
1693
1699
  # singular column symbol or an array of column symbols.
1694
1700
  # :order_eager_graph :: Whether to add the association's order to the graphed dataset's order when graphing
@@ -1701,6 +1707,7 @@ module Sequel
1701
1707
  # the current association's key(s). Set to nil to not use a reciprocal.
1702
1708
  # :remover :: Proc used to define the private _remove_* method for doing the database work
1703
1709
  # to remove the association between the given object and the current object (*_to_many assocations).
1710
+ # Set to nil to not define a remove_* method for the association.
1704
1711
  # :select :: the columns to select. Defaults to the associated class's table_name.* in an association
1705
1712
  # that uses joins, which means it doesn't include the attributes from the
1706
1713
  # join table. If you want to include the join table attributes, you can
@@ -1709,6 +1716,7 @@ module Sequel
1709
1716
  # the same name in both the join table and the associated table.
1710
1717
  # :setter :: Proc used to define the private _*= method for doing the work to setup the assocation
1711
1718
  # between the given object and the current object (*_to_one associations).
1719
+ # Set to nil to not define a setter method for the association.
1712
1720
  # :subqueries_per_union :: The number of subqueries to use in each UNION query, for eager
1713
1721
  # loading limited associations using the default :union strategy.
1714
1722
  # :validate :: Set to false to not validate when implicitly saving any associated object.
@@ -1841,8 +1849,7 @@ module Sequel
1841
1849
  # Remove :class entry if it exists and is nil, to work with cached_fetch
1842
1850
  opts.delete(:class) unless opts[:class]
1843
1851
 
1844
- send(:"def_#{type}", opts)
1845
- def_association_instance_methods(opts)
1852
+ def_association(opts)
1846
1853
 
1847
1854
  orig_opts.delete(:clone)
1848
1855
  opts[:orig_class] = orig_opts[:class] || orig_opts[:class_name]
@@ -1956,6 +1963,13 @@ module Sequel
1956
1963
  association_module(opts).send(:private, name)
1957
1964
  end
1958
1965
 
1966
+ # Delegate to the type-specific association method to setup the
1967
+ # association, and define the association instance methods.
1968
+ def def_association(opts)
1969
+ send(:"def_#{opts[:type]}", opts)
1970
+ def_association_instance_methods(opts)
1971
+ end
1972
+
1959
1973
  # Adds the association method to the association methods module.
1960
1974
  def def_association_method(opts)
1961
1975
  association_module_def(opts.association_method, opts) do |dynamic_opts=OPTS, &block|
@@ -1981,13 +1995,13 @@ module Sequel
1981
1995
  opts[:setter_method] = :"#{opts[:name]}="
1982
1996
  end
1983
1997
 
1984
- association_module_def(opts.dataset_method, opts){_dataset(opts)}
1998
+ association_module_def(opts.dataset_method, opts){_dataset(opts)} unless opts[:no_dataset_method]
1985
1999
  if opts[:block]
1986
2000
  opts[:block_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_block", 1, &opts[:block])
1987
2001
  end
1988
2002
  opts[:dataset_opt_arity] = opts[:dataset].arity == 0 ? 0 : 1
1989
2003
  opts[:dataset_opt_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_dataset_opt", opts[:dataset_opt_arity], &opts[:dataset])
1990
- def_association_method(opts)
2004
+ def_association_method(opts) unless opts[:no_association_method]
1991
2005
 
1992
2006
  return if opts[:read_only]
1993
2007
 
@@ -2075,50 +2089,60 @@ module Sequel
2075
2089
  return if opts[:read_only]
2076
2090
 
2077
2091
  if one_through_one
2078
- opts[:setter] ||= proc do |o|
2079
- h = {}
2080
- lh = lcks.zip(lcpks.map{|k| get_column_value(k)})
2081
- jtds = _join_table_dataset(opts).where(lh)
2092
+ unless opts.has_key?(:setter)
2093
+ opts[:setter] = proc do |o|
2094
+ h = {}
2095
+ lh = lcks.zip(lcpks.map{|k| get_column_value(k)})
2096
+ jtds = _join_table_dataset(opts).where(lh)
2082
2097
 
2083
- checked_transaction do
2084
- current = jtds.first
2098
+ checked_transaction do
2099
+ current = jtds.first
2085
2100
 
2086
- if o
2087
- new_values = []
2088
- rcks.zip(opts.right_primary_key_methods).each{|k, pk| new_values << (h[k] = o.get_column_value(pk))}
2089
- end
2090
-
2091
- if current
2092
- current_values = rcks.map{|k| current[k]}
2093
- jtds = jtds.where(rcks.zip(current_values))
2094
2101
  if o
2095
- if current_values != new_values
2096
- jtds.update(h)
2102
+ new_values = []
2103
+ rcks.zip(opts.right_primary_key_methods).each{|k, pk| new_values << (h[k] = o.get_column_value(pk))}
2104
+ end
2105
+
2106
+ if current
2107
+ current_values = rcks.map{|k| current[k]}
2108
+ jtds = jtds.where(rcks.zip(current_values))
2109
+ if o
2110
+ if current_values != new_values
2111
+ jtds.update(h)
2112
+ end
2113
+ else
2114
+ jtds.delete
2097
2115
  end
2098
- else
2099
- jtds.delete
2116
+ elsif o
2117
+ lh.each{|k,v| h[k] = v}
2118
+ jtds.insert(h)
2100
2119
  end
2101
- elsif o
2102
- lh.each{|k,v| h[k] = v}
2103
- jtds.insert(h)
2104
2120
  end
2105
2121
  end
2106
2122
  end
2107
- opts[:_setter] = proc{|o| set_one_through_one_associated_object(opts, o)}
2123
+ if opts.fetch(:setter, true)
2124
+ opts[:_setter] = proc{|o| set_one_through_one_associated_object(opts, o)}
2125
+ end
2108
2126
  else
2109
- opts[:adder] ||= proc do |o|
2110
- h = {}
2111
- lcks.zip(lcpks).each{|k, pk| h[k] = get_column_value(pk)}
2112
- rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.get_column_value(pk)}
2113
- _join_table_dataset(opts).insert(h)
2127
+ unless opts.has_key?(:adder)
2128
+ opts[:adder] = proc do |o|
2129
+ h = {}
2130
+ lcks.zip(lcpks).each{|k, pk| h[k] = get_column_value(pk)}
2131
+ rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.get_column_value(pk)}
2132
+ _join_table_dataset(opts).insert(h)
2133
+ end
2114
2134
  end
2115
2135
 
2116
- opts[:remover] ||= proc do |o|
2117
- _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
2136
+ unless opts.has_key?(:remover)
2137
+ opts[:remover] = proc do |o|
2138
+ _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
2139
+ end
2118
2140
  end
2119
2141
 
2120
- opts[:clearer] ||= proc do
2121
- _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
2142
+ unless opts.has_key?(:clearer)
2143
+ opts[:clearer] = proc do
2144
+ _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
2145
+ end
2122
2146
  end
2123
2147
  end
2124
2148
  end
@@ -2175,8 +2199,12 @@ module Sequel
2175
2199
 
2176
2200
  return if opts[:read_only]
2177
2201
 
2178
- opts[:setter] ||= proc{|o| cks.zip(opts.primary_key_methods).each{|k, pk| set_column_value(:"#{k}=", (o.get_column_value(pk) if o))}}
2179
- opts[:_setter] = proc{|o| set_associated_object(opts, o)}
2202
+ unless opts.has_key?(:setter)
2203
+ opts[:setter] = proc{|o| cks.zip(opts.primary_key_methods).each{|k, pk| set_column_value(:"#{k}=", (o.get_column_value(pk) if o))}}
2204
+ end
2205
+ if opts.fetch(:setter, true)
2206
+ opts[:_setter] = proc{|o| set_associated_object(opts, o)}
2207
+ end
2180
2208
  end
2181
2209
 
2182
2210
  # Configures one_to_many and one_to_one association reflections and adds the related association methods
@@ -2243,49 +2271,59 @@ module Sequel
2243
2271
  cks.each{|k| ck_nil_hash[k] = nil}
2244
2272
 
2245
2273
  if one_to_one
2246
- opts[:setter] ||= proc do |o|
2247
- up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
2274
+ unless opts.has_key?(:setter)
2275
+ opts[:setter] = proc do |o|
2276
+ up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
2248
2277
 
2249
- if (froms = up_ds.opts[:from]) && (from = froms[0]) && (from.is_a?(Sequel::Dataset) || (from.is_a?(Sequel::SQL::AliasedExpression) && from.expression.is_a?(Sequel::Dataset)))
2250
- if old = up_ds.first
2251
- cks.each{|k| old.set_column_value(:"#{k}=", nil)}
2278
+ if (froms = up_ds.opts[:from]) && (from = froms[0]) && (from.is_a?(Sequel::Dataset) || (from.is_a?(Sequel::SQL::AliasedExpression) && from.expression.is_a?(Sequel::Dataset)))
2279
+ if old = up_ds.first
2280
+ cks.each{|k| old.set_column_value(:"#{k}=", nil)}
2281
+ end
2282
+ save_old = true
2252
2283
  end
2253
- save_old = true
2254
- end
2255
2284
 
2256
- if o
2257
- if !o.new? && !save_old
2258
- up_ds = up_ds.exclude(o.pk_hash)
2285
+ if o
2286
+ if !o.new? && !save_old
2287
+ up_ds = up_ds.exclude(o.pk_hash)
2288
+ end
2289
+ cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2259
2290
  end
2260
- cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2261
- end
2262
2291
 
2263
- checked_transaction do
2264
- if save_old
2265
- old.save(save_opts) || raise(Sequel::Error, "invalid previously associated object, cannot save") if old
2266
- else
2267
- up_ds.skip_limit_check.update(ck_nil_hash)
2268
- end
2292
+ checked_transaction do
2293
+ if save_old
2294
+ old.save(save_opts) || raise(Sequel::Error, "invalid previously associated object, cannot save") if old
2295
+ else
2296
+ up_ds.skip_limit_check.update(ck_nil_hash)
2297
+ end
2269
2298
 
2270
- o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
2299
+ o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
2300
+ end
2271
2301
  end
2272
2302
  end
2273
- opts[:_setter] = proc{|o| set_one_to_one_associated_object(opts, o)}
2303
+ if opts.fetch(:setter, true)
2304
+ opts[:_setter] = proc{|o| set_one_to_one_associated_object(opts, o)}
2305
+ end
2274
2306
  else
2275
2307
  save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false
2276
2308
 
2277
- opts[:adder] ||= proc do |o|
2278
- cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2279
- o.save(save_opts)
2309
+ unless opts.has_key?(:adder)
2310
+ opts[:adder] = proc do |o|
2311
+ cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2312
+ o.save(save_opts)
2313
+ end
2280
2314
  end
2281
2315
 
2282
- opts[:remover] ||= proc do |o|
2283
- cks.each{|k| o.set_column_value(:"#{k}=", nil)}
2284
- o.save(save_opts)
2316
+ unless opts.has_key?(:remover)
2317
+ opts[:remover] = proc do |o|
2318
+ cks.each{|k| o.set_column_value(:"#{k}=", nil)}
2319
+ o.save(save_opts)
2320
+ end
2285
2321
  end
2286
2322
 
2287
- opts[:clearer] ||= proc do
2288
- _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)}))).update(ck_nil_hash)
2323
+ unless opts.has_key?(:clearer)
2324
+ opts[:clearer] = proc do
2325
+ _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)}))).update(ck_nil_hash)
2326
+ end
2289
2327
  end
2290
2328
  end
2291
2329
  end
@@ -384,26 +384,32 @@ module Sequel
384
384
  save_opts = {:validate=>opts[:validate]}
385
385
  save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false
386
386
 
387
- opts[:adder] ||= proc do |o|
388
- if array = o.get_column_value(key)
389
- array << get_column_value(pk)
390
- else
391
- o.set_column_value("#{key}=", Sequel.pg_array([get_column_value(pk)], opts.array_type))
387
+ unless opts.has_key?(:adder)
388
+ opts[:adder] = proc do |o|
389
+ if array = o.get_column_value(key)
390
+ array << get_column_value(pk)
391
+ else
392
+ o.set_column_value("#{key}=", Sequel.pg_array([get_column_value(pk)], opts.array_type))
393
+ end
394
+ o.save(save_opts)
392
395
  end
393
- o.save(save_opts)
394
396
  end
395
-
396
- opts[:remover] ||= proc do |o|
397
- if (array = o.get_column_value(key)) && !array.empty?
398
- array.delete(get_column_value(pk))
399
- o.save(save_opts)
397
+
398
+ unless opts.has_key?(:remover)
399
+ opts[:remover] = proc do |o|
400
+ if (array = o.get_column_value(key)) && !array.empty?
401
+ array.delete(get_column_value(pk))
402
+ o.save(save_opts)
403
+ end
400
404
  end
401
405
  end
402
406
 
403
- opts[:clearer] ||= proc do
404
- pk_value = get_column_value(pk)
405
- db_type = opts.array_type
406
- opts.associated_dataset.where(Sequel.pg_array_op(key).contains(Sequel.pg_array([pk_value], db_type))).update(key=>Sequel.function(:array_remove, key, Sequel.cast(pk_value, db_type)))
407
+ unless opts.has_key?(:clearer)
408
+ opts[:clearer] = proc do
409
+ pk_value = get_column_value(pk)
410
+ db_type = opts.array_type
411
+ opts.associated_dataset.where(Sequel.pg_array_op(key).contains(Sequel.pg_array([pk_value], db_type))).update(key=>Sequel.function(:array_remove, key, Sequel.cast(pk_value, db_type)))
412
+ end
407
413
  end
408
414
  end
409
415
 
@@ -486,30 +492,36 @@ module Sequel
486
492
  end
487
493
  end
488
494
 
489
- opts[:adder] ||= proc do |o|
490
- opk = o.get_column_value(opts.primary_key)
491
- if array = get_column_value(key)
492
- modified!(key)
493
- array << opk
494
- else
495
- set_column_value("#{key}=", Sequel.pg_array([opk], opts.array_type))
495
+ unless opts.has_key?(:adder)
496
+ opts[:adder] = proc do |o|
497
+ opk = o.get_column_value(opts.primary_key)
498
+ if array = get_column_value(key)
499
+ modified!(key)
500
+ array << opk
501
+ else
502
+ set_column_value("#{key}=", Sequel.pg_array([opk], opts.array_type))
503
+ end
504
+ save_after_modify.call(self) if save_after_modify
496
505
  end
497
- save_after_modify.call(self) if save_after_modify
498
506
  end
499
-
500
- opts[:remover] ||= proc do |o|
501
- if (array = get_column_value(key)) && !array.empty?
502
- modified!(key)
503
- array.delete(o.get_column_value(opts.primary_key))
504
- save_after_modify.call(self) if save_after_modify
507
+
508
+ unless opts.has_key?(:remover)
509
+ opts[:remover] = proc do |o|
510
+ if (array = get_column_value(key)) && !array.empty?
511
+ modified!(key)
512
+ array.delete(o.get_column_value(opts.primary_key))
513
+ save_after_modify.call(self) if save_after_modify
514
+ end
505
515
  end
506
516
  end
507
517
 
508
- opts[:clearer] ||= proc do
509
- if (array = get_column_value(key)) && !array.empty?
510
- modified!(key)
511
- array.clear
512
- save_after_modify.call(self) if save_after_modify
518
+ unless opts.has_key?(:clearer)
519
+ opts[:clearer] = proc do
520
+ if (array = get_column_value(key)) && !array.empty?
521
+ modified!(key)
522
+ array.clear
523
+ save_after_modify.call(self) if save_after_modify
524
+ end
513
525
  end
514
526
  end
515
527
  end
@@ -0,0 +1,500 @@
1
+ # frozen-string-literal: true
2
+
3
+ # :nocov:
4
+
5
+ # This entire file is excluded from coverage testing. This is because it
6
+ # requires coverage testing to work, and if you've already loaded Sequel
7
+ # without enabling coverage, then coverage testing won't work correctly
8
+ # for methods defined by Sequel.
9
+ #
10
+ # While automated coverage testing is disabled, manual coverage testing
11
+ # was used during spec development to make sure this code is 100% covered.
12
+
13
+ if RUBY_VERSION < '2.5'
14
+ raise LoadError, "The Sequel unused_associations plugin depends on Ruby 2.5+ method coverage"
15
+ end
16
+
17
+ require 'coverage'
18
+ require 'json'
19
+
20
+ module Sequel
21
+ module Plugins
22
+ # The unused_associations plugin detects which model associations are not
23
+ # used and can be removed, and which model association methods are not used
24
+ # and can skip being defined. The advantage of removing unused associations
25
+ # and unused association methods is decreased memory usage, since each
26
+ # method defined takes memory and adds more work for the garbage collector.
27
+ #
28
+ # In order to detect which associations are used, this relies on the method
29
+ # coverage support added in Ruby 2.5. To allow flexibility to override
30
+ # association methods, the association methods that Sequel defines are
31
+ # defined in a module included in the class instead of directly in the
32
+ # class. Unfortunately, that makes it difficult to directly use the
33
+ # coverage data to find unused associations. The advantage of this plugin
34
+ # is that it is able to figure out from the coverage information whether
35
+ # the association methods Sequel defines are actually used.
36
+ #
37
+ # = Basic Usage
38
+ #
39
+ # The expected usage of the unused_associations plugin is to load it
40
+ # into the base class for models in your application, which will often
41
+ # be Sequel::Model:
42
+ #
43
+ # Sequel::Model.plugin :unused_associations
44
+ #
45
+ # Then you run your test suite with method coverage enabled, passing the
46
+ # coverage result to +update_associations_coverage+.
47
+ # +update_associations_coverage+ returns a data structure containing
48
+ # method coverage information for all subclasses of the base class.
49
+ # You can pass the coverage information to
50
+ # +update_unused_associations_data+, which will return a data structure
51
+ # with information on unused associations.
52
+ #
53
+ # require 'coverage'
54
+ # Coverage.start(methods: true)
55
+ # # load sequel after starting coverage, then run your tests
56
+ # cov_data = Sequel::Model.update_associations_coverage
57
+ # unused_associations_data = Sequel::Model.update_unused_associations_data(coverage_data: cov_data)
58
+ #
59
+ # You can take that unused association data and pass it to the
60
+ # +unused_associations+ method to get a array of information on
61
+ # associations which have not been used. Each entry in the array
62
+ # will contain a class name and association name for each unused
63
+ # association, both as a string:
64
+ #
65
+ # Sequel::Model.unused_associations(unused_associations_data: unused_associations_data)
66
+ # # => [["Class1", "assoc1"], ...]
67
+ #
68
+ # You can use the output of the +unused_associations+ method to determine
69
+ # which associations are not used at all in your application, and can
70
+ # be eliminiated.
71
+ #
72
+ # You can also take that unused association data and pass it to the
73
+ # +unused_association_options+ method, which will return an array of
74
+ # information on associations which are used, but have related methods
75
+ # defined that are not used. The first two entries in each array are
76
+ # the class name and association name as a string, and the third
77
+ # entry is a hash of association options:
78
+ #
79
+ # Sequel::Model.unused_association_options(unused_associations_data: unused_associations_data)
80
+ # # => [["Class2", "assoc2", {:read_only=>true}], ...]
81
+ #
82
+ # You can use the output of the +unused_association_options+ to
83
+ # find out which association options can be provided when defining
84
+ # the association so that the association method will not define
85
+ # methods that are not used.
86
+ #
87
+ # = Combining Coverage Results
88
+ #
89
+ # It is common to want to combine results from multiple separate
90
+ # coverage runs. For example, if you have multiple test suites
91
+ # for your application, one for model or unit tests and one for
92
+ # web or integration tests, you would want to combine the
93
+ # coverage information from all test suites before determining
94
+ # that the associations are not used.
95
+ #
96
+ # The unused_associations plugin supports combining multiple
97
+ # coverage results using the :coverage_file plugin option:
98
+ #
99
+ # Sequel::Model.plugin :unused_associations,
100
+ # coverage_file: 'unused_associations_coverage.json'
101
+ #
102
+ # With the coverage file option, +update_associations_coverage+
103
+ # will look in the given file for existing coverage information,
104
+ # if it exists. If the file exists, the data from it will be
105
+ # merged with the coverage result passed to the method.
106
+ # Before returning, the coverage file will be updated with the
107
+ # merged result. When using the :coverage_file plugin option,
108
+ # you can each of your test suites update the coverage
109
+ # information:
110
+ #
111
+ # require 'coverage'
112
+ # Coverage.start(methods: true)
113
+ # # run this test suite
114
+ # Sequel::Model.update_associations_coverage
115
+ #
116
+ # After all test suites have been run, you can run
117
+ # +update_unused_associations_data+, without an argument:
118
+ #
119
+ # unused_associations_data = Sequel::Model.update_unused_associations_data
120
+ #
121
+ # With no argument, +update_unused_associations_data+ will get
122
+ # the coverage data from the coverage file, and then use that
123
+ # to prepare the information. You can then use the returned
124
+ # value the same as before to get the data on unused associations.
125
+ # To prevent stale coverage information, calling
126
+ # +update_unused_associations_data+ when using the :coverage_file
127
+ # plugin option will remove the coverage file by default (you can
128
+ # use the :keep_coverage option to prevent the deletion of the
129
+ # coverage file).
130
+ #
131
+ # = Automatic Usage of Unused Association Data
132
+ #
133
+ # Since it can be a pain to manually update all of your code
134
+ # to remove unused assocations or add options to prevent the
135
+ # definition of unused associations, the unused_associations
136
+ # plugin comes with support to take previously saved unused
137
+ # association data, and use it to not create unused associations,
138
+ # and to automatically use the appropriate options so that unused
139
+ # association methods are not created.
140
+ #
141
+ # To use this option, you first need to save the unused association
142
+ # data previously prepared. You can do this by passing an
143
+ # :file option when loading the plugin.
144
+ #
145
+ # Sequel::Model.plugin :unused_associations,
146
+ # file: 'unused_associations.json'
147
+ #
148
+ # With the :file option provided, you no longer need to use
149
+ # the return value of +update_unused_associations_data+, as
150
+ # the file will be updated with the information:
151
+ #
152
+ # Sequel::Model.update_unused_associations_data(coverage_data: cov_data)
153
+ #
154
+ # Then, to use the saved unused associations data, add the
155
+ # :modify_associations plugin option:
156
+ #
157
+ # Sequel::Model.plugin :unused_associations,
158
+ # file: 'unused_associations.json',
159
+ # modify_associations: true
160
+ #
161
+ # With the :modify_associations used, and the unused association
162
+ # data file is available, when subclasses attempt to create an
163
+ # unused association, the attempt will be ignored. If the
164
+ # subclasses attempt to create an association where not
165
+ # all association methods are used, the plugin will automatically
166
+ # set the appropriate options so that the unused association
167
+ # methods are not defined.
168
+ #
169
+ # When you are testing which associations are used, make sure
170
+ # not to set the :modify_associations plugin option, or make sure
171
+ # that the unused associations data file does not exist.
172
+ #
173
+ # == Automatic Usage with Combined Coverage Results
174
+ #
175
+ # If you have multiple test suites and want to automatically
176
+ # use the unused association data, you should provide both
177
+ # :file and :coverage_file options when loading the plugin:
178
+ #
179
+ # Sequel::Model.plugin :unused_associations,
180
+ # file: 'unused_associations.json',
181
+ # coverage_file: 'unused_associations_coverage.json'
182
+ #
183
+ # Then each test suite just needs to run
184
+ # +update_associations_coverage+ to update the coverage information:
185
+ #
186
+ # Sequel::Model.update_associations_coverage
187
+ #
188
+ # After all test suites have been run, you can run
189
+ # +update_unused_associations_data+ to update the unused
190
+ # association data file (and remove the coverage file):
191
+ #
192
+ # Sequel::Model.update_unused_associations_data
193
+ #
194
+ # Then you can add the :modify_associations plugin option to
195
+ # automatically use the unused association data.
196
+ #
197
+ # = Caveats
198
+ #
199
+ # Since this plugin is based on coverage information, if you do
200
+ # not have tests that cover all usage of associations in your
201
+ # application, you can end up with coverage that shows the
202
+ # association is not used, when it is used in code that is not
203
+ # covered. The output of plugin can still be useful in such cases,
204
+ # as long as you are manually checking it. However, you should
205
+ # avoid using the :modify_associations unless you have
206
+ # confidence that your tests cover all usage of associations
207
+ # in your application. You can specify the :is_used association
208
+ # option for any association that you know is used. If an
209
+ # association uses the :is_used association option, this plugin
210
+ # will not modify it if the :modify_associations option is used.
211
+ #
212
+ # This plugin does not handle anonymous classes. Any unused
213
+ # associations defined in anonymous classes will not be
214
+ # reported by this plugin.
215
+ #
216
+ # This plugin only considers the public instance methods the
217
+ # association defines to determine if it was used. If an
218
+ # association is used in a way that does not call an instance
219
+ # method (such as only using the association with the
220
+ # dataset_associations plugin), then it would show up as unused
221
+ # by this plugin.
222
+ #
223
+ # As this relies on the method coverage added in Ruby 2.5, it does
224
+ # not work on older versions of Ruby. It also does not work on
225
+ # JRuby, as JRuby does not implement method coverage.
226
+ module UnusedAssociations
227
+ # Load the subclasses plugin, as the unused associations plugin
228
+ # is designed to handle all subclasses of the class it is loaded
229
+ # into.
230
+ def self.apply(mod, opts=OPTS)
231
+ mod.plugin :subclasses
232
+ end
233
+
234
+ # Plugin options:
235
+ # :coverage_file :: The file to store the coverage information,
236
+ # when combining coverage information from
237
+ # multiple test suites.
238
+ # :file :: The file to store and/or load the unused associations data.
239
+ # :modify_associations :: Whether to use the unused associations data
240
+ # to skip defining associations or association
241
+ # methods.
242
+ # :unused_associations_data :: The unused associations data to use if the
243
+ # :modify_associations is used (by default, the
244
+ # :modify_associations option will use the data from
245
+ # the file specified by the :file option). This is
246
+ # same data returned by the
247
+ # +update_unused_associations_data+ method.
248
+ def self.configure(mod, opts=OPTS)
249
+ mod.instance_exec do
250
+ @unused_associations_coverage_file = opts[:coverage_file]
251
+ @unused_associations_file = opts[:file]
252
+ @unused_associations_data = if opts[:modify_associations]
253
+ if opts[:unused_associations_data]
254
+ opts[:unused_associations_data]
255
+ elsif File.file?(opts[:file])
256
+ Sequel.parse_json(File.binread(opts[:file]))
257
+ end
258
+ end
259
+ end
260
+ end
261
+
262
+ module ClassMethods
263
+ # Only the data is copied to subclasses, to allow the :modify_associations
264
+ # plugin option to affect them. The :file and :coverage_file are not copied
265
+ # to subclasses, as users are expected ot call methods such as
266
+ # unused_associations only on the class that is loading the plugin.
267
+ Plugins.inherited_instance_variables(self, :@unused_associations_data=>nil)
268
+
269
+ # If modifying associations, and this association is marked as not used,
270
+ # and the association does not include the specific :is_used option,
271
+ # skip defining the association.
272
+ def associate(type, assoc_name, opts=OPTS)
273
+ if !opts[:is_used] && @unused_associations_data && (data = @unused_associations_data[name]) && data[assoc_name.to_s] == 'unused'
274
+ return
275
+ end
276
+
277
+ super
278
+ end
279
+
280
+ # Parse the coverage result, and return the coverage data for the
281
+ # associations for descendants of this class. If the plugin
282
+ # uses the :coverage_file option, the existing coverage file will be loaded
283
+ # if present, and before the method returns, the coverage file will be updated.
284
+ #
285
+ # Options:
286
+ # :coverage_result :: The coverage result to use. This defaults to +Coverage.result+.
287
+ def update_associations_coverage(opts=OPTS)
288
+ coverage_result = opts[:coverage_result] || Coverage.result
289
+ module_mapping = {}
290
+ file = @unused_associations_coverage_file
291
+
292
+ coverage_data = if file && File.file?(file)
293
+ Sequel.parse_json(File.binread(file))
294
+ else
295
+ {}
296
+ end
297
+
298
+ ([self] + descendents).each do |sc|
299
+ next if sc.associations.empty? || !sc.name
300
+ module_mapping[sc.send(:overridable_methods_module)] = sc
301
+ coverage_data[sc.name] ||= {}
302
+ end
303
+
304
+ coverage_result.each do |file, coverage|
305
+ coverage[:methods].each do |(mod, meth), times|
306
+ next unless sc = module_mapping[mod]
307
+ coverage_data[sc.name][meth.to_s] ||= 0
308
+ coverage_data[sc.name][meth.to_s] += times
309
+ end
310
+ end
311
+
312
+ if file
313
+ File.binwrite(file, Sequel.object_to_json(coverage_data))
314
+ end
315
+
316
+ coverage_data
317
+ end
318
+
319
+ # Parse the coverage data returned by #update_associations_coverage,
320
+ # and return data on unused associations and unused association methods.
321
+ #
322
+ # Options:
323
+ # :coverage_data :: The coverage data to use. If not given, it is taken
324
+ # from the file specified by the :coverage_file plugin option.
325
+ # :keep_coverage :: Do not delete the file specified by the :coverage_file plugin
326
+ # option, even if it exists.
327
+ def update_unused_associations_data(options=OPTS)
328
+ coverage_data = options[:coverage_data] || Sequel.parse_json(File.binread(@unused_associations_coverage_file))
329
+
330
+ unused_associations_data = {}
331
+
332
+ ([self] + descendents).each do |sc|
333
+ next unless cov_data = coverage_data[sc.name]
334
+
335
+ sc.associations.each do |assoc|
336
+ ref = sc.association_reflection(assoc)
337
+
338
+ # Only report associations for the class they are defined in
339
+ next unless ref[:model] == sc
340
+
341
+ # Do not report associations using methods_module option, because this plugin only
342
+ # looks in the class's overridable_methods_module
343
+ next if ref[:methods_module]
344
+
345
+ info = {}
346
+
347
+ _update_association_coverage_info(info, cov_data, ref.dataset_method, :dataset_method)
348
+ _update_association_coverage_info(info, cov_data, ref.association_method, :association_method)
349
+
350
+ unless ref[:orig_opts][:read_only]
351
+ if ref.returns_array?
352
+ _update_association_coverage_info(info, cov_data, ref[:add_method], :adder)
353
+ _update_association_coverage_info(info, cov_data, ref[:remove_method], :remover)
354
+ _update_association_coverage_info(info, cov_data, ref[:remove_all_method], :clearer)
355
+ else
356
+ _update_association_coverage_info(info, cov_data, ref[:setter_method], :setter)
357
+ end
358
+ end
359
+
360
+ next if info.keys == [:missing]
361
+
362
+ if !info[:used]
363
+ (unused_associations_data[sc.name] ||= {})[assoc.to_s] = 'unused'
364
+ elsif unused = info[:unused]
365
+ if unused.include?(:setter) || [:adder, :remover, :clearer].all?{|k| unused.include?(k)}
366
+ [:setter, :adder, :remover, :clearer].each do |k|
367
+ unused.delete(k)
368
+ end
369
+ unused << :read_only
370
+ end
371
+ (unused_associations_data[sc.name] ||= {})[assoc.to_s] = unused.map(&:to_s)
372
+ end
373
+ end
374
+ end
375
+
376
+ if @unused_associations_file
377
+ File.binwrite(@unused_associations_file, Sequel.object_to_json(unused_associations_data))
378
+ end
379
+ unless options[:keep_coverage]
380
+ _delete_unused_associations_file(@unused_associations_coverage_file)
381
+ end
382
+
383
+ unused_associations_data
384
+ end
385
+
386
+ # Return an array of unused associations. These are associations where none of the
387
+ # association methods are used, according to the coverage information. Each entry
388
+ # in the array is an array of two strings, with the first string being the class name
389
+ # and the second string being the association name.
390
+ #
391
+ # Options:
392
+ # :unused_associations_data :: The data to use for determining which associations
393
+ # are unused, which is returned from
394
+ # +update_unused_associations_data+. If not given,
395
+ # loads the data from the file specified by the :file
396
+ # plugin option.
397
+ def unused_associations(opts=OPTS)
398
+ unused_associations_data = opts[:unused_associations_data] || Sequel.parse_json(File.binread(@unused_associations_file))
399
+
400
+ unused_associations = []
401
+ unused_associations_data.each do |sc, associations|
402
+ associations.each do |assoc, unused|
403
+ if unused == 'unused'
404
+ unused_associations << [sc, assoc]
405
+ end
406
+ end
407
+ end
408
+ unused_associations
409
+ end
410
+
411
+ # Return an array of unused association options. These are associations some but not all
412
+ # of the association methods are used, according to the coverage information. Each entry
413
+ # in the array is an array of three elements. The first element is the class name string,
414
+ # the second element is the association name string, and the third element is a hash of
415
+ # association options that can be used in the association so it does not define methods
416
+ # that are not used.
417
+ #
418
+ # Options:
419
+ # :unused_associations_data :: The data to use for determining which associations
420
+ # are unused, which is returned from
421
+ # +update_unused_associations_data+. If not given,
422
+ # loads the data from the file specified by the :file
423
+ # plugin option.
424
+ def unused_association_options(opts=OPTS)
425
+ unused_associations_data = opts[:unused_associations_data] || Sequel.parse_json(File.binread(@unused_associations_file))
426
+
427
+ unused_association_methods = []
428
+ unused_associations_data.each do |sc, associations|
429
+ associations.each do |assoc, unused|
430
+ unless unused == 'unused'
431
+ unused_association_methods << [sc, assoc, set_unused_options_for_association({}, unused)]
432
+ end
433
+ end
434
+ end
435
+ unused_association_methods
436
+ end
437
+
438
+ # Delete the unused associations coverage file and unused associations data file,
439
+ # if either exist.
440
+ def delete_unused_associations_files
441
+ _delete_unused_associations_file(@unused_associations_coverage_file)
442
+ _delete_unused_associations_file(@unused_associations_file)
443
+ end
444
+
445
+ private
446
+
447
+ # Delete the given file if it exists.
448
+ def _delete_unused_associations_file(file)
449
+ if file && File.file?(file)
450
+ File.unlink(file)
451
+ end
452
+ end
453
+
454
+ # Update the info hash with information on whether the given method was
455
+ # called, according to the coverage information.
456
+ def _update_association_coverage_info(info, coverage_data, meth, key)
457
+ type = case coverage_data[meth.to_s]
458
+ when 0
459
+ :unused
460
+ when Integer
461
+ :used
462
+ else
463
+ # Missing here means there is no coverage information for the
464
+ # the method, which indicates the expected method was never
465
+ # defined. In that case, it can be ignored.
466
+ :missing
467
+ end
468
+
469
+ (info[type] ||= []) << key
470
+ end
471
+
472
+ # Based on the value of the unused, update the opts hash with association
473
+ # options that will prevent unused association methods from being
474
+ # defined.
475
+ def set_unused_options_for_association(opts, unused)
476
+ opts[:read_only] = true if unused.include?('read_only')
477
+ opts[:no_dataset_method] = true if unused.include?('dataset_method')
478
+ opts[:no_association_method] = true if unused.include?('association_method')
479
+ opts[:adder] = nil if unused.include?('adder')
480
+ opts[:remover] = nil if unused.include?('remover')
481
+ opts[:clearer] = nil if unused.include?('clearer')
482
+ opts
483
+ end
484
+
485
+ # If modifying associations, and this association has unused association
486
+ # methods, automatically set the appropriate options so the unused association
487
+ # methods are not defined, unless the association explicitly uses the :is_used
488
+ # options.
489
+ def def_association(opts)
490
+ if !opts[:is_used] && @unused_associations_data && (data = @unused_associations_data[name]) && (unused = data[opts[:name].to_s])
491
+ set_unused_options_for_association(opts, unused)
492
+ end
493
+
494
+ super
495
+ end
496
+ end
497
+ end
498
+ end
499
+ end
500
+ # :nocov:
@@ -6,7 +6,7 @@ module Sequel
6
6
 
7
7
  # The minor version of Sequel. Bumped for every non-patch level
8
8
  # release, generally around once a month.
9
- MINOR = 45
9
+ MINOR = 46
10
10
 
11
11
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
12
12
  # releases that fix regressions from previous versions.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.45.0
4
+ version: 5.46.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-01 00:00:00.000000000 Z
11
+ date: 2021-07-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -189,6 +189,7 @@ extra_rdoc_files:
189
189
  - doc/release_notes/5.43.0.txt
190
190
  - doc/release_notes/5.44.0.txt
191
191
  - doc/release_notes/5.45.0.txt
192
+ - doc/release_notes/5.46.0.txt
192
193
  - doc/release_notes/5.5.0.txt
193
194
  - doc/release_notes/5.6.0.txt
194
195
  - doc/release_notes/5.7.0.txt
@@ -262,6 +263,7 @@ files:
262
263
  - doc/release_notes/5.43.0.txt
263
264
  - doc/release_notes/5.44.0.txt
264
265
  - doc/release_notes/5.45.0.txt
266
+ - doc/release_notes/5.46.0.txt
265
267
  - doc/release_notes/5.5.0.txt
266
268
  - doc/release_notes/5.6.0.txt
267
269
  - doc/release_notes/5.7.0.txt
@@ -528,6 +530,7 @@ files:
528
530
  - lib/sequel/plugins/tree.rb
529
531
  - lib/sequel/plugins/typecast_on_load.rb
530
532
  - lib/sequel/plugins/unlimited_update.rb
533
+ - lib/sequel/plugins/unused_associations.rb
531
534
  - lib/sequel/plugins/update_or_create.rb
532
535
  - lib/sequel/plugins/update_primary_key.rb
533
536
  - lib/sequel/plugins/update_refresh.rb