sequel 3.48.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +114 -0
- data/Rakefile +10 -7
- data/doc/association_basics.rdoc +25 -23
- data/doc/code_order.rdoc +7 -0
- data/doc/core_extensions.rdoc +0 -10
- data/doc/object_model.rdoc +4 -1
- data/doc/querying.rdoc +3 -3
- data/doc/release_notes/4.0.0.txt +262 -0
- data/doc/security.rdoc +0 -28
- data/doc/testing.rdoc +8 -14
- data/lib/sequel/adapters/ado.rb +7 -11
- data/lib/sequel/adapters/ado/access.rb +8 -8
- data/lib/sequel/adapters/ado/mssql.rb +4 -4
- data/lib/sequel/adapters/amalgalite.rb +6 -6
- data/lib/sequel/adapters/cubrid.rb +7 -7
- data/lib/sequel/adapters/db2.rb +5 -9
- data/lib/sequel/adapters/dbi.rb +2 -6
- data/lib/sequel/adapters/do.rb +4 -4
- data/lib/sequel/adapters/firebird.rb +4 -4
- data/lib/sequel/adapters/ibmdb.rb +8 -8
- data/lib/sequel/adapters/informix.rb +2 -10
- data/lib/sequel/adapters/jdbc.rb +17 -17
- data/lib/sequel/adapters/jdbc/as400.rb +2 -2
- data/lib/sequel/adapters/jdbc/cubrid.rb +1 -1
- data/lib/sequel/adapters/jdbc/db2.rb +1 -1
- data/lib/sequel/adapters/jdbc/derby.rb +1 -1
- data/lib/sequel/adapters/jdbc/h2.rb +2 -2
- data/lib/sequel/adapters/jdbc/hsqldb.rb +1 -1
- data/lib/sequel/adapters/jdbc/informix.rb +1 -1
- data/lib/sequel/adapters/jdbc/mssql.rb +2 -2
- data/lib/sequel/adapters/jdbc/mysql.rb +1 -1
- data/lib/sequel/adapters/jdbc/oracle.rb +5 -1
- data/lib/sequel/adapters/jdbc/postgresql.rb +3 -3
- data/lib/sequel/adapters/jdbc/sqlite.rb +3 -3
- data/lib/sequel/adapters/jdbc/transactions.rb +3 -3
- data/lib/sequel/adapters/mock.rb +7 -7
- data/lib/sequel/adapters/mysql.rb +3 -3
- data/lib/sequel/adapters/mysql2.rb +4 -4
- data/lib/sequel/adapters/odbc.rb +2 -6
- data/lib/sequel/adapters/odbc/mssql.rb +1 -1
- data/lib/sequel/adapters/openbase.rb +1 -5
- data/lib/sequel/adapters/oracle.rb +13 -17
- data/lib/sequel/adapters/postgres.rb +20 -25
- data/lib/sequel/adapters/shared/cubrid.rb +3 -3
- data/lib/sequel/adapters/shared/db2.rb +2 -2
- data/lib/sequel/adapters/shared/firebird.rb +7 -7
- data/lib/sequel/adapters/shared/mssql.rb +9 -9
- data/lib/sequel/adapters/shared/mysql.rb +29 -13
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +7 -7
- data/lib/sequel/adapters/shared/oracle.rb +22 -13
- data/lib/sequel/adapters/shared/postgres.rb +61 -46
- data/lib/sequel/adapters/shared/sqlite.rb +9 -9
- data/lib/sequel/adapters/sqlite.rb +17 -11
- data/lib/sequel/adapters/swift.rb +3 -3
- data/lib/sequel/adapters/swift/mysql.rb +1 -1
- data/lib/sequel/adapters/swift/sqlite.rb +1 -1
- data/lib/sequel/adapters/tinytds.rb +8 -8
- data/lib/sequel/ast_transformer.rb +3 -1
- data/lib/sequel/connection_pool.rb +4 -2
- data/lib/sequel/connection_pool/sharded_single.rb +2 -2
- data/lib/sequel/connection_pool/sharded_threaded.rb +5 -5
- data/lib/sequel/connection_pool/threaded.rb +7 -7
- data/lib/sequel/core.rb +4 -67
- data/lib/sequel/database.rb +1 -0
- data/lib/sequel/database/connecting.rb +2 -8
- data/lib/sequel/database/dataset.rb +2 -7
- data/lib/sequel/database/dataset_defaults.rb +0 -18
- data/lib/sequel/database/features.rb +4 -4
- data/lib/sequel/database/misc.rb +6 -8
- data/lib/sequel/database/query.rb +5 -61
- data/lib/sequel/database/schema_generator.rb +22 -20
- data/lib/sequel/database/schema_methods.rb +48 -20
- data/lib/sequel/database/transactions.rb +7 -17
- data/lib/sequel/dataset.rb +2 -0
- data/lib/sequel/dataset/actions.rb +23 -91
- data/lib/sequel/dataset/features.rb +1 -4
- data/lib/sequel/dataset/graph.rb +3 -47
- data/lib/sequel/dataset/misc.rb +4 -33
- data/lib/sequel/dataset/prepared_statements.rb +3 -1
- data/lib/sequel/dataset/query.rb +116 -240
- data/lib/sequel/dataset/sql.rb +19 -97
- data/lib/sequel/deprecated.rb +0 -16
- data/lib/sequel/exceptions.rb +0 -3
- data/lib/sequel/extensions/_pretty_table.rb +1 -1
- data/lib/sequel/extensions/columns_introspection.rb +1 -12
- data/lib/sequel/extensions/constraint_validations.rb +3 -3
- data/lib/sequel/extensions/core_extensions.rb +0 -9
- data/lib/sequel/extensions/date_arithmetic.rb +1 -2
- data/lib/sequel/extensions/graph_each.rb +11 -0
- data/lib/sequel/extensions/migration.rb +5 -5
- data/lib/sequel/extensions/null_dataset.rb +11 -13
- data/lib/sequel/extensions/pagination.rb +3 -6
- data/lib/sequel/extensions/pg_array.rb +6 -4
- data/lib/sequel/extensions/pg_array_ops.rb +35 -1
- data/lib/sequel/extensions/pg_json.rb +12 -2
- data/lib/sequel/extensions/pg_json_ops.rb +266 -0
- data/lib/sequel/extensions/pg_range.rb +2 -2
- data/lib/sequel/extensions/pg_range_ops.rb +0 -8
- data/lib/sequel/extensions/pg_row.rb +2 -2
- data/lib/sequel/extensions/pretty_table.rb +0 -4
- data/lib/sequel/extensions/query.rb +3 -8
- data/lib/sequel/extensions/schema_caching.rb +0 -7
- data/lib/sequel/extensions/schema_dumper.rb +10 -17
- data/lib/sequel/extensions/select_remove.rb +0 -4
- data/lib/sequel/extensions/set_overrides.rb +28 -0
- data/lib/sequel/extensions/to_dot.rb +6 -10
- data/lib/sequel/model.rb +6 -7
- data/lib/sequel/model/associations.rb +127 -182
- data/lib/sequel/model/base.rb +88 -211
- data/lib/sequel/model/errors.rb +0 -13
- data/lib/sequel/model/plugins.rb +2 -2
- data/lib/sequel/no_core_ext.rb +0 -1
- data/lib/sequel/plugins/after_initialize.rb +11 -17
- data/lib/sequel/plugins/association_autoreloading.rb +1 -47
- data/lib/sequel/plugins/association_dependencies.rb +2 -2
- data/lib/sequel/plugins/auto_validations.rb +2 -8
- data/lib/sequel/plugins/blacklist_security.rb +32 -2
- data/lib/sequel/plugins/caching.rb +1 -1
- data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
- data/lib/sequel/plugins/composition.rb +10 -8
- data/lib/sequel/plugins/constraint_validations.rb +2 -2
- data/lib/sequel/plugins/dataset_associations.rb +4 -0
- data/lib/sequel/plugins/defaults_setter.rb +8 -6
- data/lib/sequel/plugins/dirty.rb +6 -6
- data/lib/sequel/plugins/force_encoding.rb +13 -8
- data/lib/sequel/plugins/hook_class_methods.rb +1 -7
- data/lib/sequel/plugins/json_serializer.rb +13 -74
- data/lib/sequel/plugins/lazy_attributes.rb +2 -4
- data/lib/sequel/plugins/list.rb +1 -1
- data/lib/sequel/plugins/many_through_many.rb +4 -11
- data/lib/sequel/plugins/many_to_one_pk_lookup.rb +1 -49
- data/lib/sequel/plugins/nested_attributes.rb +1 -1
- data/lib/sequel/plugins/optimistic_locking.rb +3 -5
- data/lib/sequel/plugins/pg_array_associations.rb +453 -0
- data/lib/sequel/plugins/pg_typecast_on_load.rb +23 -7
- data/lib/sequel/plugins/prepared_statements.rb +1 -1
- data/lib/sequel/plugins/prepared_statements_associations.rb +20 -14
- data/lib/sequel/plugins/prepared_statements_safe.rb +2 -2
- data/lib/sequel/plugins/rcte_tree.rb +1 -1
- data/lib/sequel/plugins/serialization.rb +5 -4
- data/lib/sequel/plugins/serialization_modification_detection.rb +1 -1
- data/lib/sequel/plugins/sharding.rb +7 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
- data/lib/sequel/plugins/timestamps.rb +1 -1
- data/lib/sequel/plugins/touch.rb +2 -2
- data/lib/sequel/plugins/tree.rb +1 -1
- data/lib/sequel/plugins/typecast_on_load.rb +19 -4
- data/lib/sequel/plugins/validation_class_methods.rb +0 -30
- data/lib/sequel/plugins/validation_helpers.rb +13 -31
- data/lib/sequel/plugins/xml_serializer.rb +18 -57
- data/lib/sequel/sql.rb +20 -22
- data/lib/sequel/version.rb +2 -2
- data/spec/adapters/db2_spec.rb +14 -23
- data/spec/adapters/firebird_spec.rb +25 -29
- data/spec/adapters/informix_spec.rb +11 -14
- data/spec/adapters/mssql_spec.rb +71 -77
- data/spec/adapters/mysql_spec.rb +165 -172
- data/spec/adapters/oracle_spec.rb +36 -39
- data/spec/adapters/postgres_spec.rb +175 -100
- data/spec/adapters/spec_helper.rb +13 -11
- data/spec/adapters/sqlite_spec.rb +36 -44
- data/spec/core/connection_pool_spec.rb +2 -1
- data/spec/core/database_spec.rb +55 -55
- data/spec/core/dataset_spec.rb +45 -249
- data/spec/core/deprecated_spec.rb +0 -8
- data/spec/core/expression_filters_spec.rb +23 -5
- data/spec/core/object_graph_spec.rb +4 -66
- data/spec/core/schema_spec.rb +35 -12
- data/spec/core/spec_helper.rb +3 -2
- data/spec/core_extensions_spec.rb +17 -19
- data/spec/extensions/arbitrary_servers_spec.rb +2 -3
- data/spec/extensions/association_dependencies_spec.rb +14 -14
- data/spec/extensions/auto_validations_spec.rb +7 -0
- data/spec/extensions/blacklist_security_spec.rb +5 -5
- data/spec/extensions/blank_spec.rb +2 -0
- data/spec/extensions/class_table_inheritance_spec.rb +2 -2
- data/spec/extensions/columns_introspection_spec.rb +2 -29
- data/spec/extensions/composition_spec.rb +10 -17
- data/spec/extensions/core_refinements_spec.rb +5 -1
- data/spec/extensions/dataset_associations_spec.rb +18 -0
- data/spec/extensions/date_arithmetic_spec.rb +2 -2
- data/spec/extensions/defaults_setter_spec.rb +9 -9
- data/spec/extensions/dirty_spec.rb +0 -5
- data/spec/extensions/eval_inspect_spec.rb +2 -0
- data/spec/extensions/force_encoding_spec.rb +2 -18
- data/spec/extensions/hash_aliases_spec.rb +8 -0
- data/spec/extensions/hook_class_methods_spec.rb +39 -58
- data/spec/extensions/inflector_spec.rb +2 -0
- data/spec/extensions/instance_filters_spec.rb +8 -8
- data/spec/extensions/json_serializer_spec.rb +1 -41
- data/spec/extensions/list_spec.rb +1 -1
- data/spec/extensions/many_through_many_spec.rb +106 -109
- data/spec/extensions/migration_spec.rb +2 -0
- data/spec/extensions/named_timezones_spec.rb +1 -0
- data/spec/extensions/pg_array_associations_spec.rb +603 -0
- data/spec/extensions/pg_array_ops_spec.rb +25 -0
- data/spec/extensions/pg_array_spec.rb +9 -1
- data/spec/extensions/pg_hstore_ops_spec.rb +13 -0
- data/spec/extensions/pg_hstore_spec.rb +1 -0
- data/spec/extensions/pg_json_ops_spec.rb +131 -0
- data/spec/extensions/pg_json_spec.rb +10 -4
- data/spec/extensions/pg_range_ops_spec.rb +2 -5
- data/spec/extensions/pg_range_spec.rb +6 -2
- data/spec/extensions/pg_row_ops_spec.rb +2 -0
- data/spec/extensions/prepared_statements_associations_spec.rb +26 -5
- data/spec/extensions/rcte_tree_spec.rb +15 -15
- data/spec/extensions/schema_dumper_spec.rb +0 -1
- data/spec/extensions/schema_spec.rb +9 -9
- data/spec/extensions/serialization_modification_detection_spec.rb +1 -1
- data/spec/extensions/serialization_spec.rb +18 -29
- data/spec/extensions/set_overrides_spec.rb +4 -0
- data/spec/extensions/{many_to_one_pk_lookup_spec.rb → shared_caching_spec.rb} +1 -4
- data/spec/extensions/single_table_inheritance_spec.rb +4 -4
- data/spec/extensions/spec_helper.rb +8 -9
- data/spec/extensions/sql_expr_spec.rb +2 -0
- data/spec/extensions/string_date_time_spec.rb +2 -0
- data/spec/extensions/string_stripper_spec.rb +2 -0
- data/spec/extensions/tactical_eager_loading_spec.rb +12 -12
- data/spec/extensions/thread_local_timezones_spec.rb +2 -0
- data/spec/extensions/timestamps_spec.rb +1 -1
- data/spec/extensions/to_dot_spec.rb +1 -1
- data/spec/extensions/touch_spec.rb +24 -24
- data/spec/extensions/tree_spec.rb +7 -7
- data/spec/extensions/typecast_on_load_spec.rb +8 -1
- data/spec/extensions/update_primary_key_spec.rb +10 -10
- data/spec/extensions/validation_class_methods_spec.rb +10 -39
- data/spec/extensions/validation_helpers_spec.rb +29 -47
- data/spec/extensions/xml_serializer_spec.rb +1 -23
- data/spec/integration/associations_test.rb +231 -40
- data/spec/integration/database_test.rb +1 -1
- data/spec/integration/dataset_test.rb +64 -64
- data/spec/integration/eager_loader_test.rb +28 -28
- data/spec/integration/migrator_test.rb +1 -1
- data/spec/integration/model_test.rb +2 -2
- data/spec/integration/plugin_test.rb +21 -21
- data/spec/integration/prepared_statement_test.rb +7 -7
- data/spec/integration/schema_test.rb +115 -110
- data/spec/integration/spec_helper.rb +17 -27
- data/spec/integration/timezone_test.rb +1 -1
- data/spec/integration/transaction_test.rb +10 -10
- data/spec/integration/type_test.rb +2 -2
- data/spec/model/association_reflection_spec.rb +2 -28
- data/spec/model/associations_spec.rb +239 -188
- data/spec/model/base_spec.rb +27 -68
- data/spec/model/dataset_methods_spec.rb +4 -4
- data/spec/model/eager_loading_spec.rb +160 -172
- data/spec/model/hooks_spec.rb +62 -79
- data/spec/model/model_spec.rb +36 -51
- data/spec/model/plugins_spec.rb +5 -19
- data/spec/model/record_spec.rb +125 -151
- data/spec/model/spec_helper.rb +8 -6
- data/spec/model/validations_spec.rb +4 -17
- data/spec/spec_config.rb +2 -10
- metadata +50 -56
- data/lib/sequel/deprecated_core_extensions.rb +0 -135
- data/lib/sequel/extensions/pg_auto_parameterize.rb +0 -185
- data/lib/sequel/extensions/pg_statement_cache.rb +0 -318
- data/lib/sequel/plugins/identity_map.rb +0 -260
- data/lib/sequel_core.rb +0 -2
- data/lib/sequel_model.rb +0 -2
- data/spec/extensions/association_autoreloading_spec.rb +0 -102
- data/spec/extensions/identity_map_spec.rb +0 -337
- data/spec/extensions/pg_auto_parameterize_spec.rb +0 -70
- data/spec/extensions/pg_statement_cache_spec.rb +0 -208
- data/spec/rcov.opts +0 -8
- data/spec/spec_config.rb.example +0 -10
@@ -11,10 +11,8 @@ module Sequel
|
|
11
11
|
# get the reviews for all of those albums:
|
12
12
|
#
|
13
13
|
# Album.plugin :lazy_attributes, :review
|
14
|
-
#
|
15
|
-
#
|
16
|
-
# a.review
|
17
|
-
# end
|
14
|
+
# Album.filter{id<100}.all do |a|
|
15
|
+
# a.review
|
18
16
|
# end
|
19
17
|
#
|
20
18
|
# # You can specify multiple columns to lazily load:
|
data/lib/sequel/plugins/list.rb
CHANGED
@@ -53,7 +53,7 @@ module Sequel
|
|
53
53
|
# The <tt>:scope</tt> option can be a symbol, array of symbols, or a proc that
|
54
54
|
# accepts a model instance and returns a dataset representing the list.
|
55
55
|
# Also, modify the model dataset's order to order by the position and scope fields.
|
56
|
-
def self.configure(model, opts =
|
56
|
+
def self.configure(model, opts = OPTS)
|
57
57
|
model.position_field = opts[:field] || :position
|
58
58
|
model.dataset = model.dataset.order_prepend(model.position_field)
|
59
59
|
|
@@ -157,7 +157,7 @@ module Sequel
|
|
157
157
|
# :join_type :: The join type to use for the join, defaults to :left_outer.
|
158
158
|
# :only_conditions :: Conditions to use for the join instead of the ones specified by the keys.
|
159
159
|
# * opts - The options for the associaion. Takes the same options as many_to_many.
|
160
|
-
def many_through_many(name, through, opts=
|
160
|
+
def many_through_many(name, through, opts=OPTS, &block)
|
161
161
|
associate(:many_through_many, name, opts.merge(through.is_a?(Hash) ? through : {:through=>through}), &block)
|
162
162
|
end
|
163
163
|
|
@@ -198,6 +198,7 @@ module Sequel
|
|
198
198
|
ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + opts.predicate_keys.zip(left_pks.map{|k| send(k)}), :table_alias=>ft[:alias], :qualify=>:deep)
|
199
199
|
end
|
200
200
|
|
201
|
+
slice_range = opts.slice_range
|
201
202
|
left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
|
202
203
|
opts[:eager_loader] ||= lambda do |eo|
|
203
204
|
h = eo[:id_map]
|
@@ -208,17 +209,10 @@ module Sequel
|
|
208
209
|
ft = opts.final_reverse_edge
|
209
210
|
ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + [[opts.predicate_key, h.keys]], :table_alias=>ft[:alias], :qualify=>:deep)
|
210
211
|
ds = model.eager_loading_dataset(opts, ds, nil, eo[:associations], eo)
|
211
|
-
|
212
|
-
when :window_function
|
212
|
+
if opts.eager_limit_strategy == :window_function
|
213
213
|
delete_rn = true
|
214
214
|
rn = ds.row_number_column
|
215
215
|
ds = apply_window_function_eager_limit_strategy(ds, opts)
|
216
|
-
when :correlated_subquery
|
217
|
-
ds = apply_correlated_subquery_eager_limit_strategy(ds, opts) do |xds|
|
218
|
-
dsa = ds.send(:dataset_alias, 2)
|
219
|
-
opts.reverse_edges.each{|t| xds = xds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
|
220
|
-
xds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + left_keys.map{|k| [k, SQL::QualifiedIdentifier.new(ft[:table], k)]}, :table_alias=>dsa, :qualify=>:deep)
|
221
|
-
end
|
222
216
|
end
|
223
217
|
ds.all do |assoc_record|
|
224
218
|
assoc_record.values.delete(rn) if delete_rn
|
@@ -231,8 +225,7 @@ module Sequel
|
|
231
225
|
objects.each{|object| object.associations[name].push(assoc_record)}
|
232
226
|
end
|
233
227
|
if opts.eager_limit_strategy == :ruby
|
234
|
-
|
235
|
-
rows.each{|o| o.associations[name] = o.associations[name].slice(offset||0, limit) || []}
|
228
|
+
rows.each{|o| o.associations[name] = o.associations[name][slice_range] || []}
|
236
229
|
end
|
237
230
|
end
|
238
231
|
|
@@ -1,55 +1,7 @@
|
|
1
1
|
module Sequel
|
2
2
|
module Plugins
|
3
|
-
#
|
4
|
-
# for many_to_one associations to use a simple primary key lookup on the associated
|
5
|
-
# class, which is generally faster as it uses mostly static SQL. Additional, if the
|
6
|
-
# associated class is caching primary key lookups, you get the benefit of a cached
|
7
|
-
# lookup.
|
8
|
-
#
|
9
|
-
# This plugin attempts to determine cases where the primary key lookup would have
|
10
|
-
# different results than the regular lookup, and use the regular lookup in that case.
|
11
|
-
# If you want to explicitly force whether or not to use primary key lookups for
|
12
|
-
# a given association, set the :many_to_one_pk_lookup association option.
|
13
|
-
#
|
14
|
-
# Usage:
|
15
|
-
#
|
16
|
-
# # Make all model subclass instances use primary key lookups for many_to_one
|
17
|
-
# # association loading
|
18
|
-
# Sequel::Model.plugin :many_to_one_pk_lookup
|
19
|
-
#
|
20
|
-
# # Do so for just the album class.
|
21
|
-
# Album.plugin :many_to_one_pk_lookup
|
3
|
+
# Empty plugin module for backwards compatibility
|
22
4
|
module ManyToOnePkLookup
|
23
|
-
module ClassMethods
|
24
|
-
# Disable primary key lookup in cases where it will result in a different
|
25
|
-
# query than the association query.
|
26
|
-
def def_many_to_one(opts)
|
27
|
-
if !opts.has_key?(:many_to_one_pk_lookup) &&
|
28
|
-
(opts[:dataset] || opts[:conditions] || opts[:block] || opts[:select] ||
|
29
|
-
(opts.has_key?(:key) && opts[:key] == nil))
|
30
|
-
opts[:many_to_one_pk_lookup] = false
|
31
|
-
end
|
32
|
-
super
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
module InstanceMethods
|
37
|
-
private
|
38
|
-
|
39
|
-
# If the current association is a simple many_to_one association, use
|
40
|
-
# a simple primary key lookup on the associated model, which can benefit from
|
41
|
-
# caching if the associated model is using caching.
|
42
|
-
def _load_associated_objects(opts, dynamic_opts={})
|
43
|
-
return super unless opts.can_have_associated_objects?(self) && opts[:type] == :many_to_one
|
44
|
-
klass = opts.associated_class
|
45
|
-
if !dynamic_opts[:callback] &&
|
46
|
-
opts.send(:cached_fetch, :many_to_one_pk_lookup){opts.primary_key == klass.primary_key}
|
47
|
-
klass.send(:primary_key_lookup, ((fk = opts[:key]).is_a?(Array) ? fk.map{|c| send(c)} : send(fk)))
|
48
|
-
else
|
49
|
-
super
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
5
|
end
|
54
6
|
end
|
55
7
|
end
|
@@ -196,7 +196,7 @@ module Sequel
|
|
196
196
|
# :destroy option is given, destroy the object after disassociating it
|
197
197
|
# (unless destroying the object would automatically disassociate it).
|
198
198
|
# Returns the object removed.
|
199
|
-
def nested_attributes_remove(reflection, obj, opts=
|
199
|
+
def nested_attributes_remove(reflection, obj, opts=OPTS)
|
200
200
|
if !opts[:destroy] || reflection.remove_before_destroy?
|
201
201
|
before_save_hook do
|
202
202
|
if reflection.returns_array?
|
@@ -1,6 +1,4 @@
|
|
1
1
|
module Sequel
|
2
|
-
require 'plugins/instance_filters'
|
3
|
-
|
4
2
|
module Plugins
|
5
3
|
# This plugin implements a simple database-independent locking mechanism
|
6
4
|
# to ensure that concurrent updates do not override changes. This is
|
@@ -21,16 +19,16 @@ module Sequel
|
|
21
19
|
# This plugin relies on the instance_filters plugin.
|
22
20
|
module OptimisticLocking
|
23
21
|
# Exception class raised when trying to update or destroy a stale object.
|
24
|
-
Error =
|
22
|
+
Error = Sequel::NoExistingObject
|
25
23
|
|
26
24
|
# Load the instance_filters plugin into the model.
|
27
|
-
def self.apply(model, opts=
|
25
|
+
def self.apply(model, opts=OPTS)
|
28
26
|
model.plugin :instance_filters
|
29
27
|
end
|
30
28
|
|
31
29
|
# Set the lock_column to the :lock_column option, or :lock_version if
|
32
30
|
# that option is not given.
|
33
|
-
def self.configure(model, opts=
|
31
|
+
def self.configure(model, opts=OPTS)
|
34
32
|
model.lock_column = opts[:lock_column] || :lock_version
|
35
33
|
end
|
36
34
|
|
@@ -0,0 +1,453 @@
|
|
1
|
+
module Sequel
|
2
|
+
extension :pg_array, :pg_array_ops
|
3
|
+
|
4
|
+
module Plugins
|
5
|
+
# This plugin allows you to create associations where the foreign keys
|
6
|
+
# are stored in a PostgreSQL array column in one of the tables. The
|
7
|
+
# model with the table containing the array column has a
|
8
|
+
# pg_array_to_many association to the associated model, and the
|
9
|
+
# model with the table containing the primary key referenced by
|
10
|
+
# elements in the array column has a many_to_pg_array association
|
11
|
+
# to the associated model.
|
12
|
+
#
|
13
|
+
# # Database schema:
|
14
|
+
# # tags albums
|
15
|
+
# # :id (int4) <--\ :id
|
16
|
+
# # :name \-- :tag_ids (int4[])
|
17
|
+
# # :name
|
18
|
+
#
|
19
|
+
# class Album
|
20
|
+
# plugin :pg_array_associations
|
21
|
+
# pg_array_to_many :tags
|
22
|
+
# end
|
23
|
+
# class Tag
|
24
|
+
# plugin :pg_array_associations
|
25
|
+
# many_to_pg_array :albums
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# These association types work similarly to Sequel's other association
|
29
|
+
# types, so you can use them as you would any other association. Unlike
|
30
|
+
# other associations, they do not support composite keys.
|
31
|
+
#
|
32
|
+
# One thing that is different is that the modification methods for
|
33
|
+
# pg_array_to_many associations do not affect the database, since they
|
34
|
+
# operate purely on the receiver. For example:
|
35
|
+
#
|
36
|
+
# album = Album[1]
|
37
|
+
# album.add_tag(Tag[2])
|
38
|
+
#
|
39
|
+
# does not save the album. This allows you to call add_tag repeatedly
|
40
|
+
# and the save after to combine all changes into a single query. Note
|
41
|
+
# that the many_to_pg_array association modification methods do save, so:
|
42
|
+
#
|
43
|
+
# tag = Tag[2]
|
44
|
+
# tag.add_album(Album[1])
|
45
|
+
#
|
46
|
+
# will save the changes to the album.
|
47
|
+
#
|
48
|
+
# They support some additional options specific to this plugin:
|
49
|
+
#
|
50
|
+
# :array_type :: This allows you to specify the type of the array. This
|
51
|
+
# is only necessary to set in very narrow circumstances,
|
52
|
+
# such as when this plugin needs to create an array type,
|
53
|
+
# and typecasting is turned off or not setup correctly
|
54
|
+
# for the model object.
|
55
|
+
# :save_after_modify :: For pg_array_to_many associations, this makes the
|
56
|
+
# the modification methods save the current object,
|
57
|
+
# so they operate more similarly to the one_to_many
|
58
|
+
# and many_to_many association modification methods.
|
59
|
+
# :uniq :: Similar to many_to_many associations, this can be used to
|
60
|
+
# make sure the returned associated object array has uniq values.
|
61
|
+
#
|
62
|
+
# Note that until PostgreSQL gains the ability to enforce foreign key
|
63
|
+
# constraints in array columns, this plugin is not recommended for
|
64
|
+
# production use unless you plan on emulating referential integrity
|
65
|
+
# constraints via triggers.
|
66
|
+
#
|
67
|
+
# This plugin should work on all supported PostgreSQL versions, except
|
68
|
+
# the remove_all modification method for many_to_pg_array associations, which
|
69
|
+
# requires the array_remove method added in PostgreSQL 9.3.
|
70
|
+
module PgArrayAssociations
|
71
|
+
# The AssociationReflection subclass for many_to_pg_array associations.
|
72
|
+
class ManyToPgArrayAssociationReflection < Sequel::Model::Associations::AssociationReflection
|
73
|
+
Sequel::Model::Associations::ASSOCIATION_TYPES[:many_to_pg_array] = self
|
74
|
+
|
75
|
+
# The array column in the associated model containing foreign keys to
|
76
|
+
# the current model.
|
77
|
+
def associated_object_keys
|
78
|
+
[self[:key]]
|
79
|
+
end
|
80
|
+
|
81
|
+
# many_to_pg_array associations can have associated objects as long as they have
|
82
|
+
# a primary key.
|
83
|
+
def can_have_associated_objects?(obj)
|
84
|
+
obj.send(self[:primary_key])
|
85
|
+
end
|
86
|
+
|
87
|
+
# Assume that the key in the associated table uses a version of the current
|
88
|
+
# model's name suffixed with _ids.
|
89
|
+
def default_key
|
90
|
+
:"#{underscore(demodulize(self[:model].name))}_ids"
|
91
|
+
end
|
92
|
+
|
93
|
+
# The hash key to use for the eager loading predicate (left side of IN (1, 2, 3))
|
94
|
+
def predicate_key
|
95
|
+
cached_fetch(:predicate_key){qualify_assoc(self[:key_column])}
|
96
|
+
end
|
97
|
+
|
98
|
+
# The column in the current table that the keys in the array column in the
|
99
|
+
# associated table reference.
|
100
|
+
def primary_key
|
101
|
+
self[:primary_key]
|
102
|
+
end
|
103
|
+
|
104
|
+
# Destroying the associated object automatically removes the association,
|
105
|
+
# since the association is stored in the associated object.
|
106
|
+
def remove_before_destroy?
|
107
|
+
false
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
# Only consider an association as a reciprocal if it has matching keys
|
113
|
+
# and primary keys.
|
114
|
+
def reciprocal_association?(assoc_reflect)
|
115
|
+
super && self[:key] == assoc_reflect[:key] && primary_key == assoc_reflect.primary_key
|
116
|
+
end
|
117
|
+
|
118
|
+
def reciprocal_type
|
119
|
+
:pg_array_to_many
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# The AssociationReflection subclass for pg_array_to_many associations.
|
124
|
+
class PgArrayToManyAssociationReflection < Sequel::Model::Associations::AssociationReflection
|
125
|
+
Sequel::Model::Associations::ASSOCIATION_TYPES[:pg_array_to_many] = self
|
126
|
+
|
127
|
+
# An array containing the primary key for the associated model.
|
128
|
+
def associated_object_keys
|
129
|
+
Array(primary_key)
|
130
|
+
end
|
131
|
+
|
132
|
+
# pg_array_to_many associations can only have associated objects if
|
133
|
+
# the array field is not nil or empty.
|
134
|
+
def can_have_associated_objects?(obj)
|
135
|
+
v = obj.send(self[:key])
|
136
|
+
v && !v.empty?
|
137
|
+
end
|
138
|
+
|
139
|
+
# pg_array_to_many associations do not need a primary key.
|
140
|
+
def dataset_need_primary_key?
|
141
|
+
false
|
142
|
+
end
|
143
|
+
|
144
|
+
# Use a default key name of *_ids, for similarity to other association types
|
145
|
+
# that use *_id for single keys.
|
146
|
+
def default_key
|
147
|
+
:"#{singularize(self[:name])}_ids"
|
148
|
+
end
|
149
|
+
|
150
|
+
# A qualified version of the associated primary key.
|
151
|
+
def predicate_key
|
152
|
+
cached_fetch(:predicate_key){qualify_assoc(primary_key)}
|
153
|
+
end
|
154
|
+
|
155
|
+
# The primary key of the associated model.
|
156
|
+
def primary_key
|
157
|
+
cached_fetch(:primary_key){associated_class.primary_key}
|
158
|
+
end
|
159
|
+
|
160
|
+
# The method to call to get value of the primary key of the associated model.
|
161
|
+
def primary_key_method
|
162
|
+
cached_fetch(:primary_key_method){primary_key}
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
# Only consider an association as a reciprocal if it has matching keys
|
168
|
+
# and primary keys.
|
169
|
+
def reciprocal_association?(assoc_reflect)
|
170
|
+
super && self[:key] == assoc_reflect[:key] && primary_key == assoc_reflect.primary_key
|
171
|
+
end
|
172
|
+
|
173
|
+
def reciprocal_type
|
174
|
+
:many_to_pg_array
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
module ClassMethods
|
179
|
+
# Create a many_to_pg_array association, for the case where the associated
|
180
|
+
# table contains the array with foreign keys pointing to the current table.
|
181
|
+
# See associate for options.
|
182
|
+
def many_to_pg_array(name, opts=OPTS, &block)
|
183
|
+
associate(:many_to_pg_array, name, opts, &block)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Create a pg_array_to_many association, for the case where the current
|
187
|
+
# table contains the array with foreign keys pointing to the associated table.
|
188
|
+
# See associate for options.
|
189
|
+
def pg_array_to_many(name, opts=OPTS, &block)
|
190
|
+
associate(:pg_array_to_many, name, opts, &block)
|
191
|
+
end
|
192
|
+
|
193
|
+
private
|
194
|
+
|
195
|
+
# Setup the many_to_pg_array-specific datasets, eager loaders, and modification methods.
|
196
|
+
def def_many_to_pg_array(opts)
|
197
|
+
name = opts[:name]
|
198
|
+
model = self
|
199
|
+
pk = opts[:eager_loader_key] = opts[:primary_key] ||= model.primary_key
|
200
|
+
opts[:key] = opts.default_key unless opts.has_key?(:key)
|
201
|
+
key = opts[:key]
|
202
|
+
key_column = opts[:key_column] ||= opts[:key]
|
203
|
+
opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
|
204
|
+
slice_range = opts.slice_range
|
205
|
+
opts[:dataset] ||= lambda do
|
206
|
+
opts.associated_dataset.where(Sequel.pg_array_op(opts.predicate_key).contains([send(pk)]))
|
207
|
+
end
|
208
|
+
opts[:eager_loader] ||= proc do |eo|
|
209
|
+
id_map = eo[:id_map]
|
210
|
+
rows = eo[:rows]
|
211
|
+
rows.each do |object|
|
212
|
+
object.associations[name] = []
|
213
|
+
end
|
214
|
+
|
215
|
+
klass = opts.associated_class
|
216
|
+
ds = model.eager_loading_dataset(opts, klass.where(Sequel.pg_array_op(opts.predicate_key).overlaps(id_map.keys)), nil, eo[:associations], eo)
|
217
|
+
ds.all do |assoc_record|
|
218
|
+
if pks ||= assoc_record.send(key)
|
219
|
+
pks.each do |pkv|
|
220
|
+
next unless objects = id_map[pkv]
|
221
|
+
objects.each do |object|
|
222
|
+
object.associations[name].push(assoc_record)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
if slice_range
|
228
|
+
rows.each{|o| o.associations[name] = o.associations[name][slice_range] || []}
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
join_type = opts[:graph_join_type]
|
233
|
+
select = opts[:graph_select]
|
234
|
+
opts[:cartesian_product_number] ||= 1
|
235
|
+
|
236
|
+
if opts.include?(:graph_only_conditions)
|
237
|
+
conditions = opts[:graph_only_conditions]
|
238
|
+
graph_block = opts[:graph_block]
|
239
|
+
else
|
240
|
+
conditions = opts[:graph_conditions]
|
241
|
+
conditions = nil if conditions.empty?
|
242
|
+
graph_block = proc do |j, lj, js|
|
243
|
+
Sequel.pg_array_op(Sequel.deep_qualify(j, key_column)).contains([Sequel.deep_qualify(lj, opts.primary_key)])
|
244
|
+
end
|
245
|
+
|
246
|
+
if orig_graph_block = opts[:graph_block]
|
247
|
+
pg_array_graph_block = graph_block
|
248
|
+
graph_block = proc do |j, lj, js|
|
249
|
+
Sequel.&(orig_graph_block.call(j,lj,js), pg_array_graph_block.call(j, lj, js))
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
opts[:eager_grapher] ||= proc do |eo|
|
255
|
+
ds = eo[:self]
|
256
|
+
ds = ds.graph(eager_graph_dataset(opts, eo), conditions, eo.merge(:select=>select, :join_type=>join_type, :qualify=>:deep, :from_self_alias=>ds.opts[:eager_graph][:master]), &graph_block)
|
257
|
+
ds
|
258
|
+
end
|
259
|
+
|
260
|
+
def_association_dataset_methods(opts)
|
261
|
+
|
262
|
+
unless opts[:read_only]
|
263
|
+
validate = opts[:validate]
|
264
|
+
|
265
|
+
array_type = opts[:array_type] ||= :integer
|
266
|
+
adder = opts[:adder] || proc do |o|
|
267
|
+
if array = o.send(key)
|
268
|
+
array << send(pk)
|
269
|
+
else
|
270
|
+
o.send("#{key}=", Sequel.pg_array([send(pk)], array_type))
|
271
|
+
end
|
272
|
+
o.save(:validate=>validate) || raise(Sequel::Error, "invalid associated object, cannot save")
|
273
|
+
end
|
274
|
+
association_module_private_def(opts._add_method, opts, &adder)
|
275
|
+
|
276
|
+
remover = opts[:remover] || proc do |o|
|
277
|
+
if (array = o.send(key)) && !array.empty?
|
278
|
+
array.delete(send(pk))
|
279
|
+
o.save(:validate=>validate) || raise(Sequel::Error, "invalid associated object, cannot save")
|
280
|
+
end
|
281
|
+
end
|
282
|
+
association_module_private_def(opts._remove_method, opts, &remover)
|
283
|
+
|
284
|
+
clearer = opts[:clearer] || proc do
|
285
|
+
opts.associated_dataset.where(Sequel.pg_array_op(key).contains([send(pk)])).update(key=>Sequel.function(:array_remove, key, send(pk)))
|
286
|
+
end
|
287
|
+
association_module_private_def(opts._remove_all_method, opts, &clearer)
|
288
|
+
|
289
|
+
def_add_method(opts)
|
290
|
+
def_remove_methods(opts)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# Setup the pg_array_to_many-specific datasets, eager loaders, and modification methods.
|
295
|
+
def def_pg_array_to_many(opts)
|
296
|
+
name = opts[:name]
|
297
|
+
model = self
|
298
|
+
opts[:key] = opts.default_key unless opts.has_key?(:key)
|
299
|
+
key = opts[:key]
|
300
|
+
key_column = opts[:key_column] ||= key
|
301
|
+
opts[:eager_loader_key] = nil
|
302
|
+
opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
|
303
|
+
slice_range = opts.slice_range
|
304
|
+
opts[:dataset] ||= lambda do
|
305
|
+
opts.associated_dataset.where(opts.predicate_key=>send(key).to_a)
|
306
|
+
end
|
307
|
+
opts[:eager_loader] ||= proc do |eo|
|
308
|
+
rows = eo[:rows]
|
309
|
+
id_map = {}
|
310
|
+
pkm = opts.primary_key_method
|
311
|
+
rows.each do |object|
|
312
|
+
object.associations[name] = []
|
313
|
+
if associated_pks = object.send(key)
|
314
|
+
associated_pks.each do |apk|
|
315
|
+
(id_map[apk] ||= []) << object
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
klass = opts.associated_class
|
321
|
+
ds = model.eager_loading_dataset(opts, klass.where(opts.predicate_key=>id_map.keys), nil, eo[:associations], eo)
|
322
|
+
ds.all do |assoc_record|
|
323
|
+
if objects = id_map[assoc_record.send(pkm)]
|
324
|
+
objects.each do |object|
|
325
|
+
object.associations[name].push(assoc_record)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
if slice_range
|
330
|
+
rows.each{|o| o.associations[name] = o.associations[name][slice_range] || []}
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
join_type = opts[:graph_join_type]
|
335
|
+
select = opts[:graph_select]
|
336
|
+
opts[:cartesian_product_number] ||= 1
|
337
|
+
|
338
|
+
if opts.include?(:graph_only_conditions)
|
339
|
+
conditions = opts[:graph_only_conditions]
|
340
|
+
graph_block = opts[:graph_block]
|
341
|
+
else
|
342
|
+
conditions = opts[:graph_conditions]
|
343
|
+
conditions = nil if conditions.empty?
|
344
|
+
graph_block = proc do |j, lj, js|
|
345
|
+
Sequel.pg_array_op(Sequel.deep_qualify(lj, key_column)).contains([Sequel.deep_qualify(j, opts.primary_key)])
|
346
|
+
end
|
347
|
+
|
348
|
+
if orig_graph_block = opts[:graph_block]
|
349
|
+
pg_array_graph_block = graph_block
|
350
|
+
graph_block = proc do |j, lj, js|
|
351
|
+
Sequel.&(orig_graph_block.call(j,lj,js), pg_array_graph_block.call(j, lj, js))
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
opts[:eager_grapher] ||= proc do |eo|
|
357
|
+
ds = eo[:self]
|
358
|
+
ds = ds.graph(eager_graph_dataset(opts, eo), conditions, eo.merge(:select=>select, :join_type=>join_type, :qualify=>:deep, :from_self_alias=>ds.opts[:eager_graph][:master]), &graph_block)
|
359
|
+
ds
|
360
|
+
end
|
361
|
+
|
362
|
+
def_association_dataset_methods(opts)
|
363
|
+
|
364
|
+
unless opts[:read_only]
|
365
|
+
validate = opts[:validate]
|
366
|
+
array_type = opts[:array_type] ||= :integer
|
367
|
+
if opts[:save_after_modify]
|
368
|
+
save_after_modify = proc do |obj|
|
369
|
+
obj.save(:validate=>validate) || raise(Sequel::Error, "invalid associated object, cannot save")
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
adder = opts[:adder] || proc do |o|
|
374
|
+
opk = o.send(opts.primary_key)
|
375
|
+
if array = send(key)
|
376
|
+
modified!(key)
|
377
|
+
array << opk
|
378
|
+
else
|
379
|
+
send("#{key}=", Sequel.pg_array([opk], array_type))
|
380
|
+
end
|
381
|
+
save_after_modify.call(self) if save_after_modify
|
382
|
+
end
|
383
|
+
association_module_private_def(opts._add_method, opts, &adder)
|
384
|
+
|
385
|
+
remover = opts[:remover] || proc do |o|
|
386
|
+
if (array = send(key)) && !array.empty?
|
387
|
+
modified!(key)
|
388
|
+
array.delete(o.send(opts.primary_key))
|
389
|
+
save_after_modify.call(self) if save_after_modify
|
390
|
+
end
|
391
|
+
end
|
392
|
+
association_module_private_def(opts._remove_method, opts, &remover)
|
393
|
+
|
394
|
+
clearer = opts[:clearer] || proc do
|
395
|
+
if (array = send(key)) && !array.empty?
|
396
|
+
modified!(key)
|
397
|
+
array.clear
|
398
|
+
save_after_modify.call(self) if save_after_modify
|
399
|
+
end
|
400
|
+
end
|
401
|
+
association_module_private_def(opts._remove_all_method, opts, &clearer)
|
402
|
+
|
403
|
+
def_add_method(opts)
|
404
|
+
def_remove_methods(opts)
|
405
|
+
end
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
module DatasetMethods
|
410
|
+
private
|
411
|
+
|
412
|
+
# Support filtering by many_to_pg_array associations using a subquery.
|
413
|
+
def many_to_pg_array_association_filter_expression(op, ref, obj)
|
414
|
+
pk = ref.qualify(model.table_name, ref.primary_key)
|
415
|
+
key = ref[:key]
|
416
|
+
expr = case obj
|
417
|
+
when Sequel::Model
|
418
|
+
if (assoc_pks = obj.send(key)) && !assoc_pks.empty?
|
419
|
+
Sequel.expr(pk=>assoc_pks.to_a)
|
420
|
+
end
|
421
|
+
when Array
|
422
|
+
if (assoc_pks = obj.map{|o| o.send(key)}.flatten.compact.uniq) && !assoc_pks.empty?
|
423
|
+
Sequel.expr(pk=>assoc_pks)
|
424
|
+
end
|
425
|
+
when Sequel::Dataset
|
426
|
+
Sequel.expr(pk=>obj.select{Sequel.pg_array_op(ref.qualify(obj.model.table_name, ref[:key_column])).unnest})
|
427
|
+
end
|
428
|
+
expr = Sequel::SQL::Constants::FALSE unless expr
|
429
|
+
association_filter_handle_inversion(op, expr, [pk])
|
430
|
+
end
|
431
|
+
|
432
|
+
# Support filtering by pg_array_to_many associations using a subquery.
|
433
|
+
def pg_array_to_many_association_filter_expression(op, ref, obj)
|
434
|
+
key = ref.qualify(model.table_name, ref[:key_column])
|
435
|
+
expr = case obj
|
436
|
+
when Sequel::Model
|
437
|
+
if pkv = obj.send(ref.primary_key_method)
|
438
|
+
Sequel.pg_array_op(key).contains([pkv])
|
439
|
+
end
|
440
|
+
when Array
|
441
|
+
if (pkvs = obj.map{|o| o.send(ref.primary_key_method)}.compact) && !pkvs.empty?
|
442
|
+
Sequel.pg_array(key).overlaps(pkvs)
|
443
|
+
end
|
444
|
+
when Sequel::Dataset
|
445
|
+
Sequel.function(:coalesce, Sequel.pg_array_op(key).overlaps(obj.select{array_agg(ref.qualify(obj.model.table_name, ref.primary_key))}), Sequel::SQL::Constants::FALSE)
|
446
|
+
end
|
447
|
+
expr = Sequel::SQL::Constants::FALSE unless expr
|
448
|
+
association_filter_handle_inversion(op, expr, [key])
|
449
|
+
end
|
450
|
+
end
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|