sequel 5.45.0 → 5.46.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.
- checksums.yaml +4 -4
- data/CHANGELOG +6 -0
- data/README.rdoc +1 -2
- data/doc/association_basics.rdoc +22 -3
- data/doc/release_notes/5.46.0.txt +87 -0
- data/lib/sequel/model/associations.rb +104 -66
- data/lib/sequel/plugins/pg_array_associations.rb +46 -34
- data/lib/sequel/plugins/unused_associations.rb +500 -0
- data/lib/sequel/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8202d77fff48270013e8d06b75c5ab51933ff9e3fda72f99139211c9cb00de65
|
4
|
+
data.tar.gz: 15393a6189c83eb324e243d81805da38deced274d3f3a1b64bbf3cee54a27fa6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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:
|
data/doc/association_basics.rdoc
CHANGED
@@ -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
|
1642
|
-
|
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 ::
|
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
|
-
|
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
|
2079
|
-
|
2080
|
-
|
2081
|
-
|
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
|
-
|
2084
|
-
|
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
|
-
|
2096
|
-
|
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
|
-
|
2099
|
-
|
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
|
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
|
2110
|
-
|
2111
|
-
|
2112
|
-
|
2113
|
-
|
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
|
2117
|
-
|
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
|
2121
|
-
|
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
|
-
|
2179
|
-
|
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
|
2247
|
-
|
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
|
-
|
2250
|
-
|
2251
|
-
|
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
|
-
|
2257
|
-
|
2258
|
-
|
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
|
-
|
2264
|
-
|
2265
|
-
|
2266
|
-
|
2267
|
-
|
2268
|
-
|
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
|
-
|
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
|
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
|
2278
|
-
|
2279
|
-
|
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
|
2283
|
-
|
2284
|
-
|
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
|
2288
|
-
|
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
|
388
|
-
|
389
|
-
array
|
390
|
-
|
391
|
-
|
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
|
397
|
-
|
398
|
-
array.
|
399
|
-
|
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
|
404
|
-
|
405
|
-
|
406
|
-
|
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
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
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
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
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
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
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:
|
data/lib/sequel/version.rb
CHANGED
@@ -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 =
|
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.
|
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-
|
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
|