sequel 5.22.0 → 5.26.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 +66 -0
- data/README.rdoc +1 -1
- data/doc/dataset_filtering.rdoc +15 -0
- data/doc/opening_databases.rdoc +3 -0
- data/doc/postgresql.rdoc +2 -2
- data/doc/release_notes/5.23.0.txt +56 -0
- data/doc/release_notes/5.24.0.txt +56 -0
- data/doc/release_notes/5.25.0.txt +32 -0
- data/doc/release_notes/5.26.0.txt +35 -0
- data/doc/testing.rdoc +1 -0
- data/lib/sequel/adapters/jdbc.rb +7 -1
- data/lib/sequel/adapters/jdbc/postgresql.rb +1 -13
- data/lib/sequel/adapters/jdbc/sqlite.rb +29 -0
- data/lib/sequel/adapters/mysql2.rb +0 -1
- data/lib/sequel/adapters/shared/mssql.rb +9 -8
- data/lib/sequel/adapters/shared/postgres.rb +25 -7
- data/lib/sequel/adapters/shared/sqlite.rb +16 -2
- data/lib/sequel/adapters/tinytds.rb +12 -0
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
- data/lib/sequel/database/logging.rb +7 -1
- data/lib/sequel/database/schema_generator.rb +11 -2
- data/lib/sequel/database/schema_methods.rb +2 -0
- data/lib/sequel/dataset/actions.rb +3 -2
- data/lib/sequel/extensions/named_timezones.rb +51 -9
- data/lib/sequel/extensions/pg_array.rb +4 -0
- data/lib/sequel/extensions/pg_json.rb +88 -17
- data/lib/sequel/extensions/pg_json_ops.rb +124 -0
- data/lib/sequel/extensions/pg_range.rb +9 -0
- data/lib/sequel/extensions/pg_row.rb +3 -1
- data/lib/sequel/extensions/sql_comments.rb +2 -2
- data/lib/sequel/model/base.rb +12 -5
- data/lib/sequel/plugins/association_multi_add_remove.rb +83 -0
- data/lib/sequel/plugins/association_proxies.rb +3 -2
- data/lib/sequel/plugins/caching.rb +3 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +10 -0
- data/lib/sequel/plugins/csv_serializer.rb +26 -9
- data/lib/sequel/plugins/dirty.rb +3 -9
- data/lib/sequel/plugins/insert_conflict.rb +72 -0
- data/lib/sequel/plugins/nested_attributes.rb +7 -0
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +89 -30
- data/lib/sequel/plugins/sharding.rb +11 -5
- data/lib/sequel/plugins/static_cache.rb +8 -3
- data/lib/sequel/plugins/static_cache_cache.rb +53 -0
- data/lib/sequel/plugins/typecast_on_load.rb +3 -2
- data/lib/sequel/sql.rb +3 -1
- data/lib/sequel/timezones.rb +50 -11
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +130 -0
- data/spec/bin_spec.rb +2 -2
- data/spec/core/database_spec.rb +50 -0
- data/spec/core/dataset_spec.rb +23 -1
- data/spec/core/expression_filters_spec.rb +7 -2
- data/spec/core/schema_spec.rb +18 -0
- data/spec/core/spec_helper.rb +1 -1
- data/spec/core_extensions_spec.rb +1 -1
- data/spec/extensions/association_multi_add_remove_spec.rb +1041 -0
- data/spec/extensions/dirty_spec.rb +33 -0
- data/spec/extensions/insert_conflict_spec.rb +103 -0
- data/spec/extensions/named_timezones_spec.rb +109 -2
- data/spec/extensions/nested_attributes_spec.rb +48 -0
- data/spec/extensions/pg_auto_constraint_validations_spec.rb +37 -0
- data/spec/extensions/pg_json_ops_spec.rb +67 -0
- data/spec/extensions/pg_json_spec.rb +12 -0
- data/spec/extensions/pg_range_spec.rb +19 -2
- data/spec/extensions/sharding_spec.rb +8 -0
- data/spec/extensions/spec_helper.rb +9 -2
- data/spec/extensions/static_cache_cache_spec.rb +35 -0
- data/spec/guards_helper.rb +1 -1
- data/spec/integration/plugin_test.rb +27 -0
- data/spec/integration/schema_test.rb +16 -2
- data/spec/model/spec_helper.rb +1 -1
- metadata +30 -2
@@ -73,6 +73,23 @@
|
|
73
73
|
# j.pretty # jsonb_pretty(jsonb_column)
|
74
74
|
# j.set(%w'0 a', :h) # jsonb_set(jsonb_column, ARRAY['0','a'], h, true)
|
75
75
|
#
|
76
|
+
# On PostgreSQL 12+ SQL/JSON functions and operators are supported:
|
77
|
+
#
|
78
|
+
# j.path_exists('$.foo') # (jsonb_column @? '$.foo')
|
79
|
+
# j.path_match('$.foo') # (jsonb_column @@ '$.foo')
|
80
|
+
#
|
81
|
+
# j.path_exists!('$.foo') # jsonb_path_exists(jsonb_column, '$.foo')
|
82
|
+
# j.path_match!('$.foo') # jsonb_path_match(jsonb_column, '$.foo')
|
83
|
+
# j.path_query('$.foo') # jsonb_path_query(jsonb_column, '$.foo')
|
84
|
+
# j.path_query_array('$.foo') # jsonb_path_query_array(jsonb_column, '$.foo')
|
85
|
+
# j.path_query_first('$.foo') # jsonb_path_query_first(jsonb_column, '$.foo')
|
86
|
+
#
|
87
|
+
# For the PostgreSQL 12+ SQL/JSON functions, one argument is required (+path+) and
|
88
|
+
# two more arguments are optional (+vars+ and +silent+). +path+ specifies the JSON path.
|
89
|
+
# +vars+ specifies a hash or a string in JSON format of named variables to be
|
90
|
+
# substituted in +path+. +silent+ specifies whether errors are suppressed. By default,
|
91
|
+
# errors are not suppressed.
|
92
|
+
#
|
76
93
|
# If you are also using the pg_json extension, you should load it before
|
77
94
|
# loading this extension. Doing so will allow you to use the #op method on
|
78
95
|
# JSONHash, JSONHarray, JSONBHash, and JSONBArray, allowing you to perform json/jsonb operations
|
@@ -292,6 +309,8 @@ module Sequel
|
|
292
309
|
CONTAINED_BY = ["(".freeze, " <@ ".freeze, ")".freeze].freeze
|
293
310
|
DELETE_PATH = ["(".freeze, " #- ".freeze, ")".freeze].freeze
|
294
311
|
HAS_KEY = ["(".freeze, " ? ".freeze, ")".freeze].freeze
|
312
|
+
PATH_EXISTS = ["(".freeze, " @? ".freeze, ")".freeze].freeze
|
313
|
+
PATH_MATCH = ["(".freeze, " @@ ".freeze, ")".freeze].freeze
|
295
314
|
|
296
315
|
# jsonb expression for deletion of the given argument from the
|
297
316
|
# current jsonb.
|
@@ -362,6 +381,95 @@ module Sequel
|
|
362
381
|
self.class.new(function(:insert, wrap_input_array(path), wrap_input_jsonb(other), insert_after))
|
363
382
|
end
|
364
383
|
|
384
|
+
# Returns whether the JSON path returns any item for the json object.
|
385
|
+
#
|
386
|
+
# json_op.path_exists("$.foo") # (json @? '$.foo')
|
387
|
+
def path_exists(path)
|
388
|
+
bool_op(PATH_EXISTS, path)
|
389
|
+
end
|
390
|
+
|
391
|
+
# Returns whether the JSON path returns any item for the json object.
|
392
|
+
#
|
393
|
+
# json_op.path_exists!("$.foo")
|
394
|
+
# # jsonb_path_exists(json, '$.foo')
|
395
|
+
#
|
396
|
+
# json_op.path_exists!("$.foo ? ($ > $x)", x: 2)
|
397
|
+
# # jsonb_path_exists(json, '$.foo ? ($ > $x)', '{"x":2}')
|
398
|
+
#
|
399
|
+
# json_op.path_exists!("$.foo ? ($ > $x)", {x: 2}, true)
|
400
|
+
# # jsonb_path_exists(json, '$.foo ? ($ > $x)', '{"x":2}', true)
|
401
|
+
def path_exists!(path, vars=nil, silent=nil)
|
402
|
+
Sequel::SQL::BooleanExpression.new(:NOOP, _path_function(:jsonb_path_exists, path, vars, silent))
|
403
|
+
end
|
404
|
+
|
405
|
+
# Returns the first item of the result of JSON path predicate check for the json object.
|
406
|
+
# Returns nil if the first item is not true or false.
|
407
|
+
#
|
408
|
+
# json_op.path_match("$.foo") # (json @@ '$.foo')
|
409
|
+
def path_match(path)
|
410
|
+
bool_op(PATH_MATCH, path)
|
411
|
+
end
|
412
|
+
|
413
|
+
# Returns the first item of the result of JSON path predicate check for the json object.
|
414
|
+
# Returns nil if the first item is not true or false and silent is true.
|
415
|
+
#
|
416
|
+
# json_op.path_match!("$.foo")
|
417
|
+
# # jsonb_path_match(json, '$.foo')
|
418
|
+
#
|
419
|
+
# json_op.path_match!("$.foo ? ($ > $x)", x: 2)
|
420
|
+
# # jsonb_path_match(json, '$.foo ? ($ > $x)', '{"x":2}')
|
421
|
+
#
|
422
|
+
# json_op.path_match!("$.foo ? ($ > $x)", {x: 2}, true)
|
423
|
+
# # jsonb_path_match(json, '$.foo ? ($ > $x)', '{"x":2}', true)
|
424
|
+
def path_match!(path, vars=nil, silent=nil)
|
425
|
+
Sequel::SQL::BooleanExpression.new(:NOOP, _path_function(:jsonb_path_match, path, vars, silent))
|
426
|
+
end
|
427
|
+
|
428
|
+
# Returns a set of all jsonb values specified by the JSON path
|
429
|
+
# for the json object.
|
430
|
+
#
|
431
|
+
# json_op.path_query("$.foo")
|
432
|
+
# # jsonb_path_query(json, '$.foo')
|
433
|
+
#
|
434
|
+
# json_op.path_query("$.foo ? ($ > $x)", x: 2)
|
435
|
+
# # jsonb_path_query(json, '$.foo ? ($ > $x)', '{"x":2}')
|
436
|
+
#
|
437
|
+
# json_op.path_query("$.foo ? ($ > $x)", {x: 2}, true)
|
438
|
+
# # jsonb_path_query(json, '$.foo ? ($ > $x)', '{"x":2}', true)
|
439
|
+
def path_query(path, vars=nil, silent=nil)
|
440
|
+
_path_function(:jsonb_path_query, path, vars, silent)
|
441
|
+
end
|
442
|
+
|
443
|
+
# Returns a jsonb array of all values specified by the JSON path
|
444
|
+
# for the json object.
|
445
|
+
#
|
446
|
+
# json_op.path_query_array("$.foo")
|
447
|
+
# # jsonb_path_query_array(json, '$.foo')
|
448
|
+
#
|
449
|
+
# json_op.path_query_array("$.foo ? ($ > $x)", x: 2)
|
450
|
+
# # jsonb_path_query_array(json, '$.foo ? ($ > $x)', '{"x":2}')
|
451
|
+
#
|
452
|
+
# json_op.path_query_array("$.foo ? ($ > $x)", {x: 2}, true)
|
453
|
+
# # jsonb_path_query_array(json, '$.foo ? ($ > $x)', '{"x":2}', true)
|
454
|
+
def path_query_array(path, vars=nil, silent=nil)
|
455
|
+
JSONBOp.new(_path_function(:jsonb_path_query_array, path, vars, silent))
|
456
|
+
end
|
457
|
+
|
458
|
+
# Returns the first item of the result specified by the JSON path
|
459
|
+
# for the json object.
|
460
|
+
#
|
461
|
+
# json_op.path_query_first("$.foo")
|
462
|
+
# # jsonb_path_query_first(json, '$.foo')
|
463
|
+
#
|
464
|
+
# json_op.path_query_first("$.foo ? ($ > $x)", x: 2)
|
465
|
+
# # jsonb_path_query_first(json, '$.foo ? ($ > $x)', '{"x":2}')
|
466
|
+
#
|
467
|
+
# json_op.path_query_first("$.foo ? ($ > $x)", {x: 2}, true)
|
468
|
+
# # jsonb_path_query_first(json, '$.foo ? ($ > $x)', '{"x":2}', true)
|
469
|
+
def path_query_first(path, vars=nil, silent=nil)
|
470
|
+
JSONBOp.new(_path_function(:jsonb_path_query_first, path, vars, silent))
|
471
|
+
end
|
472
|
+
|
365
473
|
# Return the receiver, since it is already a JSONBOp.
|
366
474
|
def pg_jsonb
|
367
475
|
self
|
@@ -386,6 +494,22 @@ module Sequel
|
|
386
494
|
|
387
495
|
private
|
388
496
|
|
497
|
+
# Internals of the jsonb SQL/JSON path functions.
|
498
|
+
def _path_function(func, path, vars, silent)
|
499
|
+
args = []
|
500
|
+
if vars
|
501
|
+
if vars.is_a?(Hash)
|
502
|
+
vars = vars.to_json
|
503
|
+
end
|
504
|
+
args << vars
|
505
|
+
|
506
|
+
unless silent.nil?
|
507
|
+
args << silent
|
508
|
+
end
|
509
|
+
end
|
510
|
+
SQL::Function.new(func, self, path, *args)
|
511
|
+
end
|
512
|
+
|
389
513
|
# Return a placeholder literal with the given str and args, wrapped
|
390
514
|
# in a boolean expression, used by operators that return booleans.
|
391
515
|
def bool_op(str, other)
|
@@ -402,6 +402,15 @@ module Sequel
|
|
402
402
|
end
|
403
403
|
alias == eql?
|
404
404
|
|
405
|
+
# Make sure equal ranges have the same hash.
|
406
|
+
def hash
|
407
|
+
if @empty
|
408
|
+
@db_type.hash
|
409
|
+
else
|
410
|
+
[@begin, @end, @exclude_begin, @exclude_end, @db_type].hash
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
405
414
|
# Allow PGRange values in case statements, where they return true if they
|
406
415
|
# are equal to each other using eql?, or if this PGRange can be converted
|
407
416
|
# to a Range, delegating to that range.
|
@@ -7,7 +7,7 @@
|
|
7
7
|
# that when composite fields are retrieved, they are parsed and returned
|
8
8
|
# as instances of Sequel::Postgres::PGRow::(HashRow|ArrayRow), or
|
9
9
|
# optionally a custom type. HashRow and ArrayRow are DelegateClasses of
|
10
|
-
#
|
10
|
+
# Hash and Array, so they mostly act like a hash or array, but not
|
11
11
|
# completely (is_a?(Hash) and is_a?(Array) are false). If you want the
|
12
12
|
# actual hash for a HashRow, call HashRow#to_hash, and if you want the
|
13
13
|
# actual array for an ArrayRow, call ArrayRow#to_a. This is done so
|
@@ -228,7 +228,9 @@ module Sequel
|
|
228
228
|
if skip(/\)/)
|
229
229
|
values << nil
|
230
230
|
else
|
231
|
+
# :nocov:
|
231
232
|
until eos?
|
233
|
+
# :nocov:
|
232
234
|
if skip(/"/)
|
233
235
|
values << scan(/(\\.|""|[^"])*/).gsub(/\\(.)|"(")/, '\1\2')
|
234
236
|
skip(/"[,)]/)
|
@@ -9,8 +9,8 @@
|
|
9
9
|
# #
|
10
10
|
#
|
11
11
|
# As you can see, this uses single line SQL comments (--) suffixed
|
12
|
-
# by a newline. This
|
13
|
-
#
|
12
|
+
# by a newline. This plugin transforms all consecutive whitespace
|
13
|
+
# in the comment to a single string:
|
14
14
|
#
|
15
15
|
# ds = DB[:table].comment("Some\r\nComment Here").all
|
16
16
|
# # SELECT * FROM table -- Some Comment Here
|
data/lib/sequel/model/base.rb
CHANGED
@@ -1069,7 +1069,7 @@ module Sequel
|
|
1069
1069
|
@new = true
|
1070
1070
|
@modified = true
|
1071
1071
|
initialize_set(values)
|
1072
|
-
|
1072
|
+
_clear_changed_columns(:initialize)
|
1073
1073
|
yield self if block_given?
|
1074
1074
|
end
|
1075
1075
|
|
@@ -1626,6 +1626,13 @@ module Sequel
|
|
1626
1626
|
def _changed_columns
|
1627
1627
|
@changed_columns ||= []
|
1628
1628
|
end
|
1629
|
+
|
1630
|
+
# Clear the changed columns. Reason is the reason for clearing
|
1631
|
+
# the columns, and should be one of: :initialize, :refresh, :create
|
1632
|
+
# or :update.
|
1633
|
+
def _clear_changed_columns(_reason)
|
1634
|
+
_changed_columns.clear
|
1635
|
+
end
|
1629
1636
|
|
1630
1637
|
# Do the deletion of the object's dataset, and check that the row
|
1631
1638
|
# was actually deleted.
|
@@ -1716,7 +1723,7 @@ module Sequel
|
|
1716
1723
|
# is used for reading newly inserted values from the database
|
1717
1724
|
def _refresh(dataset)
|
1718
1725
|
_refresh_set_values(_refresh_get(dataset) || raise(NoExistingObject, "Record not found"))
|
1719
|
-
|
1726
|
+
_clear_changed_columns(:refresh)
|
1720
1727
|
end
|
1721
1728
|
|
1722
1729
|
# Get the row of column data from the database.
|
@@ -1754,7 +1761,7 @@ module Sequel
|
|
1754
1761
|
@this = nil
|
1755
1762
|
@new = false
|
1756
1763
|
@modified = false
|
1757
|
-
pk ? _save_refresh :
|
1764
|
+
pk ? _save_refresh : _clear_changed_columns(:create)
|
1758
1765
|
after_create
|
1759
1766
|
true
|
1760
1767
|
end
|
@@ -1771,7 +1778,7 @@ module Sequel
|
|
1771
1778
|
cc.clear
|
1772
1779
|
else
|
1773
1780
|
columns_updated = _save_update_all_columns_hash
|
1774
|
-
|
1781
|
+
_clear_changed_columns(:update)
|
1775
1782
|
end
|
1776
1783
|
else # update only the specified columns
|
1777
1784
|
columns = Array(columns)
|
@@ -1798,7 +1805,7 @@ module Sequel
|
|
1798
1805
|
# can be overridden to avoid the refresh.
|
1799
1806
|
def _save_refresh
|
1800
1807
|
_save_set_values(_refresh_get(this.server?(:default)) || raise(NoExistingObject, "Record not found"))
|
1801
|
-
|
1808
|
+
_clear_changed_columns(:create)
|
1802
1809
|
end
|
1803
1810
|
|
1804
1811
|
# Set values to the provided hash. Called after a create,
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Plugins
|
5
|
+
# The association_multi_add_remove plugin allows adding, removing and setting
|
6
|
+
# multiple associated objects in a single method call.
|
7
|
+
# By default Sequel::Model defines singular <tt>add_*</tt> and <tt>remove_*</tt>
|
8
|
+
# methods that operate on a single associated object, this adds plural forms
|
9
|
+
# that operate on multiple associated objects. Example:
|
10
|
+
#
|
11
|
+
# artist.albums # => [album1]
|
12
|
+
# artist.add_albums([album2, album3])
|
13
|
+
# artist.albums # => [album1, album2, album3]
|
14
|
+
# artist.remove_albums([album3, album1])
|
15
|
+
# artist.albums # => [album2]
|
16
|
+
# artist.albums = [album2, album3]
|
17
|
+
# artist.albums # => [album2, album3]
|
18
|
+
#
|
19
|
+
# It can handle all situations that the normal singular methods handle, but there is
|
20
|
+
# no attempt to optimize behavior, so using these methods will not improve performance.
|
21
|
+
#
|
22
|
+
# The add/remove/set methods defined by this plugin use a transaction,
|
23
|
+
# so if one add/remove/set fails and raises an exception, all adds/removes/set
|
24
|
+
# will be rolled back. If you are using database sharding and want to save
|
25
|
+
# to a specific shard, call Model#set_server to set the server for this instance,
|
26
|
+
# as the transaction will be opened on that server.
|
27
|
+
#
|
28
|
+
# You can customize the method names used for adding/removing multiple associated
|
29
|
+
# objects using the :multi_add_method and :multi_remove_method association options.
|
30
|
+
#
|
31
|
+
# Usage:
|
32
|
+
#
|
33
|
+
# # Allow adding/removing/setting multiple associated objects in a single call
|
34
|
+
# # for all model subclass instances (called before loading subclasses):
|
35
|
+
# Sequel::Model.plugin :association_multi_add_remove
|
36
|
+
#
|
37
|
+
# # Allow adding/removing/setting multiple associated objects in a single call
|
38
|
+
# # for Album instances (called before defining associations in the class):
|
39
|
+
# Album.plugin :association_multi_add_remove
|
40
|
+
module AssociationMultiAddRemove
|
41
|
+
module ClassMethods
|
42
|
+
# Define the methods use to add/remove/set multiple associated objects
|
43
|
+
# in a single method call.
|
44
|
+
def def_association_instance_methods(opts)
|
45
|
+
super
|
46
|
+
|
47
|
+
if opts[:adder]
|
48
|
+
add_method = opts[:add_method]
|
49
|
+
multi_add_method = opts[:multi_add_method] || :"add_#{opts[:name]}"
|
50
|
+
multi_add_method = nil if add_method == multi_add_method
|
51
|
+
if multi_add_method
|
52
|
+
association_module_def(multi_add_method, opts) do |objs, *args|
|
53
|
+
db.transaction(:server=>@server){objs.map{|obj| send(add_method, obj, *args)}.compact}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
if opts[:remover]
|
59
|
+
remove_method = opts[:remove_method]
|
60
|
+
multi_remove_method = opts[:multi_remove_method] || :"remove_#{opts[:name]}"
|
61
|
+
multi_remove_method = nil if remove_method == multi_remove_method
|
62
|
+
if multi_remove_method
|
63
|
+
association_module_def(multi_remove_method, opts) do |objs, *args|
|
64
|
+
db.transaction(:server=>@server){objs.map{|obj| send(remove_method, obj, *args)}.compact}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
if multi_add_method && multi_remove_method
|
70
|
+
association_module_def(:"#{opts[:name]}=", opts) do |objs, *args|
|
71
|
+
db.transaction(:server=>@server) do
|
72
|
+
existing_objs = send(opts.association_method)
|
73
|
+
send(multi_remove_method, (existing_objs - objs), *args)
|
74
|
+
send(multi_add_method, (objs - existing_objs), *args)
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -64,6 +64,8 @@ module Sequel
|
|
64
64
|
array = [].freeze
|
65
65
|
|
66
66
|
if RUBY_VERSION < '2.6'
|
67
|
+
# :nocov:
|
68
|
+
|
67
69
|
# Default proc used to determine whether to send the method to the dataset.
|
68
70
|
# If the array would respond to it, sends it to the array instead of the dataset.
|
69
71
|
DEFAULT_PROXY_TO_DATASET = proc do |opts|
|
@@ -73,10 +75,9 @@ module Sequel
|
|
73
75
|
end
|
74
76
|
!array_method
|
75
77
|
end
|
76
|
-
else
|
77
78
|
# :nocov:
|
79
|
+
else
|
78
80
|
DEFAULT_PROXY_TO_DATASET = proc{|opts| !array.respond_to?(opts[:method])}
|
79
|
-
# :nocov:
|
80
81
|
end
|
81
82
|
|
82
83
|
# Set the association reflection to use, and whether the association should be
|
@@ -26,6 +26,9 @@ module Sequel
|
|
26
26
|
# * Model.with_pk!
|
27
27
|
# * Model.[] # when argument is not hash or nil
|
28
28
|
# * many_to_one association method # without dynamic callback, when primary key matches
|
29
|
+
#
|
30
|
+
# You should not use this plugin if you are using sharding and there are different
|
31
|
+
# rows for the same primary key on different shards.
|
29
32
|
#
|
30
33
|
# Usage:
|
31
34
|
#
|
@@ -108,6 +108,16 @@ module Sequel
|
|
108
108
|
# a = Executive.where{{employees[:id]=>1}}.first # works
|
109
109
|
# a = Executive.where{{executives[:id]=>1}}.first # doesn't work
|
110
110
|
#
|
111
|
+
# Note that because subclass datasets select from a subquery, you cannot update,
|
112
|
+
# delete, or insert into them directly. To delete related rows, you need to go
|
113
|
+
# through the related tables and remove the related rows. Code that does this would
|
114
|
+
# be similar to:
|
115
|
+
#
|
116
|
+
# pks = Executive.where{num_staff < 10}.select_map(:id)
|
117
|
+
# Executive.cti_tables.reverse_each do |table|
|
118
|
+
# DB.from(table).where(:id=>pks).delete
|
119
|
+
# end
|
120
|
+
#
|
111
121
|
# = Usage
|
112
122
|
#
|
113
123
|
# # Use the default of storing the class name in the sti_key
|
@@ -65,8 +65,6 @@ module Sequel
|
|
65
65
|
# # Add CSV output capability to Album class instances
|
66
66
|
# Album.plugin :csv_serializer
|
67
67
|
module CsvSerializer
|
68
|
-
CSV = Object.const_defined?(:CSV) ? ::CSV : ::FasterCSV
|
69
|
-
|
70
68
|
# Set up the column readers to do deserialization and the column writers
|
71
69
|
# to save the value in deserialized_values
|
72
70
|
def self.configure(model, opts = OPTS)
|
@@ -75,13 +73,29 @@ module Sequel
|
|
75
73
|
end
|
76
74
|
end
|
77
75
|
|
76
|
+
# Avoid keyword argument separation warnings on Ruby 2.7, while still
|
77
|
+
# being compatible with 1.9.
|
78
|
+
if RUBY_VERSION >= "2.0"
|
79
|
+
instance_eval(<<-END, __FILE__, __LINE__+1)
|
80
|
+
def self.csv_call(*args, opts, &block)
|
81
|
+
CSV.send(*args, **opts, &block)
|
82
|
+
end
|
83
|
+
END
|
84
|
+
else
|
85
|
+
# :nodoc:
|
86
|
+
def self.csv_call(*args, opts, &block)
|
87
|
+
CSV.send(*args, opts, &block)
|
88
|
+
end
|
89
|
+
# :nodoc:
|
90
|
+
end
|
91
|
+
|
78
92
|
module ClassMethods
|
79
93
|
# The default opts to use when serializing model objects to CSV
|
80
94
|
attr_reader :csv_serializer_opts
|
81
95
|
|
82
96
|
# Attempt to parse an array of instances from the given CSV string
|
83
97
|
def array_from_csv(csv, opts = OPTS)
|
84
|
-
|
98
|
+
CsvSerializer.csv_call(:parse, csv, process_csv_serializer_opts(opts)).map do |row|
|
85
99
|
row = row.to_hash
|
86
100
|
row.delete(nil)
|
87
101
|
new(row)
|
@@ -108,7 +122,8 @@ module Sequel
|
|
108
122
|
opts_cols = opts.delete(:columns)
|
109
123
|
opts_include = opts.delete(:include)
|
110
124
|
opts_except = opts.delete(:except)
|
111
|
-
|
125
|
+
only = opts.delete(:only)
|
126
|
+
opts[:headers] ||= Array(only || opts_cols || columns) + Array(opts_include) - Array(opts_except)
|
112
127
|
opts
|
113
128
|
end
|
114
129
|
|
@@ -130,7 +145,7 @@ module Sequel
|
|
130
145
|
# :headers :: The headers to use for the CSV line. Use nil for a header
|
131
146
|
# to specify the column should be ignored.
|
132
147
|
def from_csv(csv, opts = OPTS)
|
133
|
-
row =
|
148
|
+
row = CsvSerializer.csv_call(:parse_line, csv, model.process_csv_serializer_opts(opts)).to_hash
|
134
149
|
row.delete(nil)
|
135
150
|
set(row)
|
136
151
|
end
|
@@ -146,9 +161,10 @@ module Sequel
|
|
146
161
|
# attributes to include in the CSV output.
|
147
162
|
def to_csv(opts = OPTS)
|
148
163
|
opts = model.process_csv_serializer_opts(opts)
|
164
|
+
headers = opts[:headers]
|
149
165
|
|
150
|
-
|
151
|
-
csv <<
|
166
|
+
CsvSerializer.csv_call(:generate, model.process_csv_serializer_opts(opts)) do |csv|
|
167
|
+
csv << headers.map{|k| public_send(k)}
|
152
168
|
end
|
153
169
|
end
|
154
170
|
end
|
@@ -164,10 +180,11 @@ module Sequel
|
|
164
180
|
def to_csv(opts = OPTS)
|
165
181
|
opts = model.process_csv_serializer_opts({:columns=>columns}.merge!(opts))
|
166
182
|
items = opts.delete(:array) || self
|
183
|
+
headers = opts[:headers]
|
167
184
|
|
168
|
-
|
185
|
+
CsvSerializer.csv_call(:generate, opts) do |csv|
|
169
186
|
items.each do |object|
|
170
|
-
csv <<
|
187
|
+
csv << headers.map{|header| object.public_send(header)}
|
171
188
|
end
|
172
189
|
end
|
173
190
|
end
|