sequel 5.22.0 → 5.26.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +66 -0
  3. data/README.rdoc +1 -1
  4. data/doc/dataset_filtering.rdoc +15 -0
  5. data/doc/opening_databases.rdoc +3 -0
  6. data/doc/postgresql.rdoc +2 -2
  7. data/doc/release_notes/5.23.0.txt +56 -0
  8. data/doc/release_notes/5.24.0.txt +56 -0
  9. data/doc/release_notes/5.25.0.txt +32 -0
  10. data/doc/release_notes/5.26.0.txt +35 -0
  11. data/doc/testing.rdoc +1 -0
  12. data/lib/sequel/adapters/jdbc.rb +7 -1
  13. data/lib/sequel/adapters/jdbc/postgresql.rb +1 -13
  14. data/lib/sequel/adapters/jdbc/sqlite.rb +29 -0
  15. data/lib/sequel/adapters/mysql2.rb +0 -1
  16. data/lib/sequel/adapters/shared/mssql.rb +9 -8
  17. data/lib/sequel/adapters/shared/postgres.rb +25 -7
  18. data/lib/sequel/adapters/shared/sqlite.rb +16 -2
  19. data/lib/sequel/adapters/tinytds.rb +12 -0
  20. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  21. data/lib/sequel/database/logging.rb +7 -1
  22. data/lib/sequel/database/schema_generator.rb +11 -2
  23. data/lib/sequel/database/schema_methods.rb +2 -0
  24. data/lib/sequel/dataset/actions.rb +3 -2
  25. data/lib/sequel/extensions/named_timezones.rb +51 -9
  26. data/lib/sequel/extensions/pg_array.rb +4 -0
  27. data/lib/sequel/extensions/pg_json.rb +88 -17
  28. data/lib/sequel/extensions/pg_json_ops.rb +124 -0
  29. data/lib/sequel/extensions/pg_range.rb +9 -0
  30. data/lib/sequel/extensions/pg_row.rb +3 -1
  31. data/lib/sequel/extensions/sql_comments.rb +2 -2
  32. data/lib/sequel/model/base.rb +12 -5
  33. data/lib/sequel/plugins/association_multi_add_remove.rb +83 -0
  34. data/lib/sequel/plugins/association_proxies.rb +3 -2
  35. data/lib/sequel/plugins/caching.rb +3 -0
  36. data/lib/sequel/plugins/class_table_inheritance.rb +10 -0
  37. data/lib/sequel/plugins/csv_serializer.rb +26 -9
  38. data/lib/sequel/plugins/dirty.rb +3 -9
  39. data/lib/sequel/plugins/insert_conflict.rb +72 -0
  40. data/lib/sequel/plugins/nested_attributes.rb +7 -0
  41. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +89 -30
  42. data/lib/sequel/plugins/sharding.rb +11 -5
  43. data/lib/sequel/plugins/static_cache.rb +8 -3
  44. data/lib/sequel/plugins/static_cache_cache.rb +53 -0
  45. data/lib/sequel/plugins/typecast_on_load.rb +3 -2
  46. data/lib/sequel/sql.rb +3 -1
  47. data/lib/sequel/timezones.rb +50 -11
  48. data/lib/sequel/version.rb +1 -1
  49. data/spec/adapters/postgres_spec.rb +130 -0
  50. data/spec/bin_spec.rb +2 -2
  51. data/spec/core/database_spec.rb +50 -0
  52. data/spec/core/dataset_spec.rb +23 -1
  53. data/spec/core/expression_filters_spec.rb +7 -2
  54. data/spec/core/schema_spec.rb +18 -0
  55. data/spec/core/spec_helper.rb +1 -1
  56. data/spec/core_extensions_spec.rb +1 -1
  57. data/spec/extensions/association_multi_add_remove_spec.rb +1041 -0
  58. data/spec/extensions/dirty_spec.rb +33 -0
  59. data/spec/extensions/insert_conflict_spec.rb +103 -0
  60. data/spec/extensions/named_timezones_spec.rb +109 -2
  61. data/spec/extensions/nested_attributes_spec.rb +48 -0
  62. data/spec/extensions/pg_auto_constraint_validations_spec.rb +37 -0
  63. data/spec/extensions/pg_json_ops_spec.rb +67 -0
  64. data/spec/extensions/pg_json_spec.rb +12 -0
  65. data/spec/extensions/pg_range_spec.rb +19 -2
  66. data/spec/extensions/sharding_spec.rb +8 -0
  67. data/spec/extensions/spec_helper.rb +9 -2
  68. data/spec/extensions/static_cache_cache_spec.rb +35 -0
  69. data/spec/guards_helper.rb +1 -1
  70. data/spec/integration/plugin_test.rb +27 -0
  71. data/spec/integration/schema_test.rb +16 -2
  72. data/spec/model/spec_helper.rb +1 -1
  73. 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
- # of Hash and Array, so they mostly act like a hash or array, but not
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 # plugin transforms all consecutive
13
- # whitespace in the comment to # a single string:
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
@@ -1069,7 +1069,7 @@ module Sequel
1069
1069
  @new = true
1070
1070
  @modified = true
1071
1071
  initialize_set(values)
1072
- _changed_columns.clear
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
- _changed_columns.clear
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 : _changed_columns.clear
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
- _changed_columns.clear
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
- _changed_columns.clear
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
- CSV.parse(csv, process_csv_serializer_opts(opts)).map do |row|
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
- opts[:headers] ||= Array(opts.delete(:only) || opts_cols || columns) + Array(opts_include) - Array(opts_except)
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 = CSV.parse_line(csv, model.process_csv_serializer_opts(opts)).to_hash
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
- CSV.generate(opts) do |csv|
151
- csv << opts[:headers].map{|k| public_send(k)}
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
- CSV.generate(opts) do |csv|
185
+ CsvSerializer.csv_call(:generate, opts) do |csv|
169
186
  items.each do |object|
170
- csv << opts[:headers].map{|header| object.public_send(header) }
187
+ csv << headers.map{|header| object.public_send(header)}
171
188
  end
172
189
  end
173
190
  end