sequel 4.21.0 → 4.22.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.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +32 -0
  3. data/README.rdoc +3 -4
  4. data/doc/opening_databases.rdoc +10 -75
  5. data/doc/release_notes/4.22.0.txt +72 -0
  6. data/lib/sequel/adapters/ado/access.rb +1 -1
  7. data/lib/sequel/adapters/cubrid.rb +3 -3
  8. data/lib/sequel/adapters/db2.rb +1 -0
  9. data/lib/sequel/adapters/dbi.rb +1 -0
  10. data/lib/sequel/adapters/fdbsql.rb +3 -2
  11. data/lib/sequel/adapters/firebird.rb +1 -0
  12. data/lib/sequel/adapters/ibmdb.rb +1 -21
  13. data/lib/sequel/adapters/informix.rb +1 -0
  14. data/lib/sequel/adapters/jdbc.rb +37 -49
  15. data/lib/sequel/adapters/jdbc/fdbsql.rb +1 -0
  16. data/lib/sequel/adapters/mysql.rb +5 -3
  17. data/lib/sequel/adapters/mysql2.rb +5 -2
  18. data/lib/sequel/adapters/odbc.rb +8 -4
  19. data/lib/sequel/adapters/openbase.rb +1 -0
  20. data/lib/sequel/adapters/oracle.rb +3 -46
  21. data/lib/sequel/adapters/postgres.rb +3 -36
  22. data/lib/sequel/adapters/shared/access.rb +1 -1
  23. data/lib/sequel/adapters/shared/fdbsql.rb +3 -3
  24. data/lib/sequel/adapters/shared/mssql.rb +1 -1
  25. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +12 -44
  26. data/lib/sequel/adapters/shared/oracle.rb +6 -2
  27. data/lib/sequel/adapters/shared/postgres.rb +6 -6
  28. data/lib/sequel/adapters/shared/sqlite.rb +1 -1
  29. data/lib/sequel/adapters/sqlite.rb +3 -46
  30. data/lib/sequel/adapters/tinytds.rb +12 -28
  31. data/lib/sequel/adapters/utils/pg_types.rb +1 -1
  32. data/lib/sequel/connection_pool/sharded_threaded.rb +63 -16
  33. data/lib/sequel/connection_pool/threaded.rb +72 -18
  34. data/lib/sequel/core.rb +1 -1
  35. data/lib/sequel/database/connecting.rb +2 -2
  36. data/lib/sequel/database/misc.rb +5 -5
  37. data/lib/sequel/database/query.rb +3 -2
  38. data/lib/sequel/database/schema_generator.rb +19 -19
  39. data/lib/sequel/database/schema_methods.rb +2 -2
  40. data/lib/sequel/database/transactions.rb +3 -3
  41. data/lib/sequel/dataset/actions.rb +18 -8
  42. data/lib/sequel/dataset/graph.rb +2 -2
  43. data/lib/sequel/dataset/prepared_statements.rb +28 -1
  44. data/lib/sequel/dataset/query.rb +7 -7
  45. data/lib/sequel/exceptions.rb +27 -24
  46. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  47. data/lib/sequel/extensions/constraint_validations.rb +2 -2
  48. data/lib/sequel/extensions/date_arithmetic.rb +2 -2
  49. data/lib/sequel/extensions/pg_array.rb +10 -1
  50. data/lib/sequel/extensions/pg_row.rb +1 -1
  51. data/lib/sequel/extensions/pg_static_cache_updater.rb +1 -1
  52. data/lib/sequel/extensions/schema_dumper.rb +8 -8
  53. data/lib/sequel/extensions/split_array_nil.rb +1 -1
  54. data/lib/sequel/model.rb +1 -1
  55. data/lib/sequel/model/associations.rb +18 -11
  56. data/lib/sequel/model/base.rb +15 -15
  57. data/lib/sequel/model/exceptions.rb +11 -2
  58. data/lib/sequel/plugins/accessed_columns.rb +1 -1
  59. data/lib/sequel/plugins/auto_validations.rb +1 -1
  60. data/lib/sequel/plugins/boolean_readers.rb +1 -1
  61. data/lib/sequel/plugins/class_table_inheritance.rb +4 -7
  62. data/lib/sequel/plugins/composition.rb +1 -1
  63. data/lib/sequel/plugins/constraint_validations.rb +2 -2
  64. data/lib/sequel/plugins/csv_serializer.rb +171 -0
  65. data/lib/sequel/plugins/dirty.rb +2 -2
  66. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  67. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  68. data/lib/sequel/plugins/many_through_many.rb +1 -1
  69. data/lib/sequel/plugins/nested_attributes.rb +5 -5
  70. data/lib/sequel/plugins/pg_array_associations.rb +4 -4
  71. data/lib/sequel/plugins/prepared_statements.rb +2 -2
  72. data/lib/sequel/plugins/prepared_statements_safe.rb +1 -1
  73. data/lib/sequel/plugins/serialization.rb +6 -6
  74. data/lib/sequel/plugins/serialization_modification_detection.rb +1 -1
  75. data/lib/sequel/plugins/sharding.rb +3 -1
  76. data/lib/sequel/plugins/single_table_inheritance.rb +5 -13
  77. data/lib/sequel/plugins/static_cache.rb +2 -2
  78. data/lib/sequel/plugins/tactical_eager_loading.rb +1 -1
  79. data/lib/sequel/plugins/tree.rb +1 -1
  80. data/lib/sequel/plugins/validation_class_methods.rb +2 -2
  81. data/lib/sequel/plugins/validation_helpers.rb +4 -4
  82. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  83. data/lib/sequel/sql.rb +1 -1
  84. data/lib/sequel/version.rb +1 -1
  85. data/spec/adapters/postgres_spec.rb +17 -0
  86. data/spec/core/connection_pool_spec.rb +1 -1
  87. data/spec/core/dataset_spec.rb +22 -0
  88. data/spec/extensions/auto_validations_spec.rb +1 -1
  89. data/spec/extensions/blacklist_security_spec.rb +2 -2
  90. data/spec/extensions/csv_serializer_spec.rb +173 -0
  91. data/spec/extensions/json_serializer_spec.rb +2 -2
  92. data/spec/extensions/nested_attributes_spec.rb +9 -9
  93. data/spec/extensions/pg_array_spec.rb +5 -0
  94. data/spec/extensions/single_table_inheritance_spec.rb +21 -0
  95. data/spec/extensions/touch_spec.rb +1 -1
  96. data/spec/extensions/tree_spec.rb +4 -0
  97. data/spec/extensions/xml_serializer_spec.rb +3 -3
  98. data/spec/integration/prepared_statement_test.rb +1 -1
  99. data/spec/integration/schema_test.rb +7 -0
  100. data/spec/integration/type_test.rb +2 -2
  101. data/spec/model/associations_spec.rb +108 -14
  102. data/spec/model/base_spec.rb +8 -8
  103. data/spec/model/record_spec.rb +7 -7
  104. metadata +6 -2
@@ -43,7 +43,7 @@ module Sequel
43
43
  case op
44
44
  when :IN, :"NOT IN"
45
45
  vals = args.at(1)
46
- if vals.is_a?(Array) && vals.any?{|v| v.nil?}
46
+ if vals.is_a?(Array) && vals.any?(&:nil?)
47
47
  cols = args.at(0)
48
48
  vals = vals.compact
49
49
  c = Sequel::SQL::BooleanExpression
data/lib/sequel/model.rb CHANGED
@@ -170,6 +170,6 @@ module Sequel
170
170
 
171
171
  # The setter methods (methods ending with =) that are never allowed
172
172
  # to be called automatically via +set+/+update+/+new+/etc..
173
- RESTRICTED_SETTER_METHODS = instance_methods.map{|x| x.to_s}.grep(SETTER_METHOD_REGEXP)
173
+ RESTRICTED_SETTER_METHODS = instance_methods.map(&:to_s).grep(SETTER_METHOD_REGEXP)
174
174
  end
175
175
  end
@@ -604,7 +604,7 @@ module Sequel
604
604
  key = qualify(associated_class.table_name, associated_class.primary_key)
605
605
  case obj
606
606
  when Array
607
- {key=>obj.map{|o| o.pk}}
607
+ {key=>obj.map(&:pk)}
608
608
  when Sequel::Dataset
609
609
  {key=>obj.select(*Array(qualify(associated_class.table_name, associated_class.primary_key)))}
610
610
  else
@@ -1001,7 +1001,7 @@ module Sequel
1001
1001
  end
1002
1002
 
1003
1003
  def filter_by_associations_limit_aliases
1004
- filter_by_associations_limit_alias_key.map{|v| v.column}
1004
+ filter_by_associations_limit_alias_key.map(&:column)
1005
1005
  end
1006
1006
 
1007
1007
  def filter_by_associations_limit_key
@@ -1289,7 +1289,7 @@ module Sequel
1289
1289
  end
1290
1290
 
1291
1291
  def filter_by_associations_limit_aliases
1292
- filter_by_associations_limit_alias_key.map{|v| v.alias}
1292
+ filter_by_associations_limit_alias_key.map(&:alias)
1293
1293
  end
1294
1294
 
1295
1295
  def filter_by_associations_limit_key
@@ -1846,7 +1846,7 @@ module Sequel
1846
1846
  select_all(egds.first_source).
1847
1847
  select_append(*associated_key_array)
1848
1848
  egds = opts.apply_eager_graph_limit_strategy(egls, egds)
1849
- ds.graph(egds, associated_key_array.map{|v| v.alias}.zip(lpkcs) + conditions, :qualify=>:deep, :table_alias=>eo[:table_alias], :implicit_qualifier=>eo[:implicit_qualifier], :join_type=>eo[:join_type]||join_type, :from_self_alias=>eo[:from_self_alias], :join_only=>eo[:join_only], :select=>select||orig_egds.columns, &graph_block)
1849
+ ds.graph(egds, associated_key_array.map(&:alias).zip(lpkcs) + conditions, :qualify=>:deep, :table_alias=>eo[:table_alias], :implicit_qualifier=>eo[:implicit_qualifier], :join_type=>eo[:join_type]||join_type, :from_self_alias=>eo[:from_self_alias], :join_only=>eo[:join_only], :select=>select||orig_egds.columns, &graph_block)
1850
1850
  else
1851
1851
  ds = ds.graph(join_table, use_jt_only_conditions ? jt_only_conditions : lcks.zip(lpkcs) + graph_jt_conds, :select=>false, :table_alias=>ds.unused_table_alias(join_table, [eo[:table_alias]]), :join_type=>eo[:join_type]||jt_join_type, :join_only=>eo[:join_only], :implicit_qualifier=>eo[:implicit_qualifier], :qualify=>:deep, :from_self_alias=>eo[:from_self_alias], &jt_graph_block)
1852
1852
  ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : opts.right_primary_keys.zip(rcks) + conditions, :select=>select, :table_alias=>eo[:table_alias], :qualify=>:deep, :join_type=>eo[:join_type]||join_type, :join_only=>eo[:join_only], &graph_block)
@@ -1920,7 +1920,7 @@ module Sequel
1920
1920
  graph_cks = opts[:graph_keys]
1921
1921
  opts[:eager_grapher] ||= proc do |eo|
1922
1922
  ds = eo[:self]
1923
- ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : opts.primary_keys.zip(graph_cks) + conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep, :from_self_alias=>eo[:from_self_alias]), &graph_block)
1923
+ ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : opts.primary_keys.zip(graph_cks) + conditions, Hash[eo].merge!(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep, :from_self_alias=>eo[:from_self_alias]), &graph_block)
1924
1924
  end
1925
1925
 
1926
1926
  return if opts[:read_only]
@@ -1980,7 +1980,7 @@ module Sequel
1980
1980
  graph_block = opts[:graph_block]
1981
1981
  opts[:eager_grapher] ||= proc do |eo|
1982
1982
  ds = eo[:self]
1983
- ds = ds.graph(opts.apply_eager_graph_limit_strategy(eo[:limit_strategy], eager_graph_dataset(opts, eo)), use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep, :from_self_alias=>eo[:from_self_alias]), &graph_block)
1983
+ ds = ds.graph(opts.apply_eager_graph_limit_strategy(eo[:limit_strategy], eager_graph_dataset(opts, eo)), use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions, Hash[eo].merge!(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep, :from_self_alias=>eo[:from_self_alias]), &graph_block)
1984
1984
  # We only load reciprocals for one_to_many associations, as other reciprocals don't make sense
1985
1985
  ds.opts[:eager_graph][:reciprocals][eo[:table_alias]] = opts.reciprocal
1986
1986
  ds
@@ -2219,7 +2219,7 @@ module Sequel
2219
2219
  # Duplicate the associations hash when duplicating the object.
2220
2220
  def initialize_copy(other)
2221
2221
  super
2222
- @associations = @associations.dup if @associations
2222
+ @associations = Hash[@associations] if @associations
2223
2223
  self
2224
2224
  end
2225
2225
 
@@ -2231,7 +2231,7 @@ module Sequel
2231
2231
  dynamic_opts = {:callback=>dynamic_opts}
2232
2232
  end
2233
2233
  if block_given?
2234
- dynamic_opts = dynamic_opts.merge(:callback=>Proc.new)
2234
+ dynamic_opts = Hash[dynamic_opts].merge!(:callback=>Proc.new)
2235
2235
  end
2236
2236
  name = opts[:name]
2237
2237
  if associations.include?(name) and !dynamic_opts[:callback] and !dynamic_opts[:reload]
@@ -2316,6 +2316,10 @@ module Sequel
2316
2316
 
2317
2317
  # Run the callback for the association with the object.
2318
2318
  def run_association_callbacks(reflection, callback_type, object)
2319
+ # The reason we automatically set raise_error for singular associations is that
2320
+ # assignment in ruby always returns the argument instead of the result of the
2321
+ # method, so we can't return nil to signal that the association callback prevented
2322
+ # the modification
2319
2323
  raise_error = raise_on_save_failure || !reflection.returns_array?
2320
2324
  stop_on_false = [:before_add, :before_remove, :before_set].include?(callback_type)
2321
2325
  reflection[callback_type].each do |cb|
@@ -2327,11 +2331,14 @@ module Sequel
2327
2331
  else
2328
2332
  raise Error, "callbacks should either be Procs or Symbols"
2329
2333
  end
2334
+
2330
2335
  if res == false and stop_on_false
2331
- raise(HookFailed, "Unable to modify association for #{inspect}: one of the #{callback_type} hooks returned false") if raise_error
2332
- return false
2336
+ raise(HookFailed, "Unable to modify association for #{inspect}: one of the #{callback_type} hooks returned false")
2333
2337
  end
2334
2338
  end
2339
+ rescue HookFailed
2340
+ return false unless raise_error
2341
+ raise
2335
2342
  end
2336
2343
 
2337
2344
  # Set the given object as the associated object for the given *_to_one association reflection
@@ -2514,7 +2521,7 @@ END
2514
2521
  def eager(*associations)
2515
2522
  opts = @opts[:eager]
2516
2523
  association_opts = eager_options_for_associations(associations)
2517
- opts = opts ? opts.merge(association_opts) : association_opts
2524
+ opts = opts ? Hash[opts].merge!(association_opts) : association_opts
2518
2525
  clone(:eager=>opts)
2519
2526
  end
2520
2527
 
@@ -489,7 +489,7 @@ module Sequel
489
489
  # end
490
490
  def inherited(subclass)
491
491
  super
492
- ivs = subclass.instance_variables.collect{|x| x.to_s}
492
+ ivs = subclass.instance_variables.collect(&:to_s)
493
493
  inherited_instance_variables.each do |iv, dup|
494
494
  next if ivs.include?(iv.to_s)
495
495
  if (sup_class_value = instance_variable_get(iv)) && dup
@@ -851,7 +851,7 @@ module Sequel
851
851
  clear_setter_methods_cache
852
852
  columns, bad_columns = columns.partition{|x| NORMAL_METHOD_NAME_REGEXP.match(x.to_s)}
853
853
  bad_columns.each{|x| def_bad_column_accessor(x)}
854
- im = instance_methods.collect{|x| x.to_s}
854
+ im = instance_methods.collect(&:to_s)
855
855
  columns.each do |column|
856
856
  meth = "#{column}="
857
857
  overridable_methods_module.module_eval("def #{column}; self[:#{column}] end", __FILE__, __LINE__) unless im.include?(column.to_s)
@@ -948,7 +948,7 @@ module Sequel
948
948
  if allowed_columns
949
949
  allowed_columns.map{|x| "#{x}="}
950
950
  else
951
- meths = instance_methods.collect{|x| x.to_s}.grep(SETTER_METHOD_REGEXP) - RESTRICTED_SETTER_METHODS
951
+ meths = instance_methods.collect(&:to_s).grep(SETTER_METHOD_REGEXP) - RESTRICTED_SETTER_METHODS
952
952
  meths -= Array(primary_key).map{|x| "#{x}="} if primary_key && restrict_primary_key?
953
953
  meths
954
954
  end
@@ -1559,7 +1559,7 @@ module Sequel
1559
1559
  # a.save_changes # UPDATE artists SET name = 'Bob' WHERE (id = 1)
1560
1560
  # # => #<Artist {:id=>1, :name=>'Jim', ...}
1561
1561
  def save_changes(opts=OPTS)
1562
- save(opts.merge(:changed=>true)) || false if modified?
1562
+ save(Hash[opts].merge!(:changed=>true)) || false if modified?
1563
1563
  end
1564
1564
 
1565
1565
  # Updates the instance with the supplied values with support for virtual
@@ -1611,7 +1611,7 @@ module Sequel
1611
1611
  # # Sequel::Error raised
1612
1612
  def set_fields(hash, fields, opts=nil)
1613
1613
  opts = if opts
1614
- model.default_set_fields_options.merge(opts)
1614
+ Hash[model.default_set_fields_options].merge!(opts)
1615
1615
  else
1616
1616
  model.default_set_fields_options
1617
1617
  end
@@ -1942,7 +1942,7 @@ module Sequel
1942
1942
  # databases don't like you setting primary key values even
1943
1943
  # to their existing values.
1944
1944
  def _save_update_all_columns_hash
1945
- v = @values.dup
1945
+ v = Hash[@values]
1946
1946
  Array(primary_key).each{|x| v.delete(x) unless changed_columns.include?(x)}
1947
1947
  v
1948
1948
  end
@@ -2029,7 +2029,7 @@ module Sequel
2029
2029
 
2030
2030
  # If transactions should be used, wrap the yield in a transaction block.
2031
2031
  def checked_transaction(opts=OPTS)
2032
- use_transaction?(opts) ? db.transaction({:server=>this_server}.merge(opts)){yield} : yield
2032
+ use_transaction?(opts) ? db.transaction({:server=>this_server}.merge!(opts)){yield} : yield
2033
2033
  end
2034
2034
 
2035
2035
  # Change the value of the column to given value, recording the change.
@@ -2067,7 +2067,7 @@ module Sequel
2067
2067
  # Copy constructor -- Duplicate internal data structures.
2068
2068
  def initialize_copy(other)
2069
2069
  super
2070
- @values = @values.dup
2070
+ @values = Hash[@values]
2071
2071
  @changed_columns = @changed_columns.dup if @changed_columns
2072
2072
  @errors = @errors.dup if @errors
2073
2073
  @this = @this.dup if @this
@@ -2128,14 +2128,14 @@ module Sequel
2128
2128
  set_column_value(m, v)
2129
2129
  elsif strict
2130
2130
  # Avoid using respond_to? or creating symbols from user input
2131
- if public_methods.map{|s| s.to_s}.include?(m)
2132
- if Array(model.primary_key).map{|s| s.to_s}.member?(k.to_s) && model.restrict_primary_key?
2133
- raise Error, "#{k} is a restricted primary key"
2131
+ if public_methods.map(&:to_s).include?(m)
2132
+ if Array(model.primary_key).map(&:to_s).member?(k.to_s) && model.restrict_primary_key?
2133
+ raise MassAssignmentRestriction, "#{k} is a restricted primary key"
2134
2134
  else
2135
- raise Error, "#{k} is a restricted column"
2135
+ raise MassAssignmentRestriction, "#{k} is a restricted column"
2136
2136
  end
2137
2137
  else
2138
- raise Error, "method #{m} doesn't exist"
2138
+ raise MassAssignmentRestriction, "method #{m} doesn't exist"
2139
2139
  end
2140
2140
  end
2141
2141
  end
@@ -2158,7 +2158,7 @@ module Sequel
2158
2158
  if type.is_a?(Array)
2159
2159
  type.map{|x| "#{x}="}
2160
2160
  else
2161
- meths = methods.collect{|x| x.to_s}.grep(SETTER_METHOD_REGEXP) - RESTRICTED_SETTER_METHODS
2161
+ meths = methods.collect(&:to_s).grep(SETTER_METHOD_REGEXP) - RESTRICTED_SETTER_METHODS
2162
2162
  meths -= Array(primary_key).map{|x| "#{x}="} if type != :all && primary_key && model.restrict_primary_key?
2163
2163
  meths
2164
2164
  end
@@ -2239,7 +2239,7 @@ module Sequel
2239
2239
  # # DELETE FROM artists WHERE (id = 2)
2240
2240
  # # ...
2241
2241
  def destroy
2242
- pr = proc{all{|r| r.destroy}.length}
2242
+ pr = proc{all(&:destroy).length}
2243
2243
  model.use_transactions ? @db.transaction(:server=>opts[:server], &pr) : pr.call
2244
2244
  end
2245
2245
 
@@ -16,10 +16,14 @@ module Sequel
16
16
 
17
17
  # Exception class raised when +require_modification+ is set and an UPDATE or DELETE statement to modify the dataset doesn't
18
18
  # modify a single row.
19
- class NoExistingObject < Error; end
19
+ NoExistingObject = Class.new(Error)
20
20
 
21
21
  # Raised when an undefined association is used when eager loading.
22
- class UndefinedAssociation < Error; end
22
+ UndefinedAssociation = Class.new(Error)
23
+
24
+ # Raised when a mass assignment method is called in strict mode with either a restricted column
25
+ # or a column without a setter method.
26
+ MassAssignmentRestriction = Class.new(Error)
23
27
 
24
28
  # Exception class raised when +raise_on_save_failure+ is set and validation fails
25
29
  class ValidationFailed < Error
@@ -43,4 +47,9 @@ module Sequel
43
47
  end
44
48
  end
45
49
  end
50
+
51
+ # Call name on each class to set the name for the class, so it gets cached.
52
+ constants.map{|c| const_get(c)}.each do |c|
53
+ Class === c && c.name
54
+ end
46
55
  end
@@ -44,7 +44,7 @@ module Sequel
44
44
 
45
45
  # Copy the accessed columns when duping and cloning.
46
46
  def initialize_copy(other)
47
- other.instance_variable_set(:@accessed_columns, @accessed_columns.dup) if @accessed_columns
47
+ other.instance_variable_set(:@accessed_columns, Hash[@accessed_columns]) if @accessed_columns
48
48
  super
49
49
  end
50
50
 
@@ -109,7 +109,7 @@ module Sequel
109
109
 
110
110
  # Parse the database schema and indexes and record the columns to automatically validate.
111
111
  def setup_auto_validations
112
- not_null_cols, explicit_not_null_cols = db_schema.select{|col, sch| sch[:allow_null] == false}.partition{|col, sch| sch[:ruby_default].nil?}.map{|cs| cs.map{|col, sch| col}}
112
+ not_null_cols, explicit_not_null_cols = db_schema.select{|col, sch| sch[:allow_null] == false}.partition{|col, sch| sch[:default].nil?}.map{|cs| cs.map{|col, sch| col}}
113
113
  @auto_validate_not_null_columns = not_null_cols - Array(primary_key)
114
114
  explicit_not_null_cols += Array(primary_key)
115
115
  @auto_validate_explicit_not_null_columns = explicit_not_null_cols.uniq
@@ -46,7 +46,7 @@ module Sequel
46
46
 
47
47
  # Add attribute? methods for all of the boolean attributes for this model.
48
48
  def create_boolean_readers
49
- im = instance_methods.collect{|x| x.to_s}
49
+ im = instance_methods.collect(&:to_s)
50
50
  cs = columns rescue return
51
51
  cs.each{|c| create_boolean_reader(c) if boolean_attribute?(c) && !im.include?("#{c}?")}
52
52
  end
@@ -153,17 +153,17 @@ module Sequel
153
153
  # Specified with the :table_map option to the plugin, and used if
154
154
  # the implicit naming is incorrect.
155
155
  attr_reader :cti_table_map
156
-
156
+
157
+ Plugins.inherited_instance_variables(self, :@cti_key=>nil, :@cti_model_map=>nil, :@cti_table_map=>nil)
158
+
157
159
  # Add the appropriate data structures to the subclass. Does not
158
160
  # allow anonymous subclasses to be created, since they would not
159
161
  # be mappable to a table.
160
162
  def inherited(subclass)
161
163
  cc = cti_columns
162
- ck = cti_key
163
164
  ct = cti_tables.dup
164
- ctm = cti_table_map.dup
165
+ ctm = cti_table_map
165
166
  cbm = cti_base_model
166
- cmm = cti_model_map
167
167
  pk = primary_key
168
168
  ds = dataset
169
169
  table = nil
@@ -172,12 +172,9 @@ module Sequel
172
172
  raise(Error, "cannot create anonymous subclass for model class using class_table_inheritance") if !(n = name) || n.empty?
173
173
  table = ctm[n.to_sym] || implicit_table_name
174
174
  columns = db.from(table).columns
175
- @cti_key = ck
176
175
  @cti_tables = ct + [table]
177
176
  @cti_columns = cc.merge(table=>columns)
178
- @cti_table_map = ctm
179
177
  @cti_base_model = cbm
180
- @cti_model_map = cmm
181
178
  # Need to set dataset and columns before calling super so that
182
179
  # the main column accessor module is included in the class before any
183
180
  # plugin accessor modules (such as the lazy attributes accessor module).
@@ -178,7 +178,7 @@ module Sequel
178
178
  # Duplicate compositions hash when duplicating model instance.
179
179
  def initialize_copy(other)
180
180
  super
181
- @compositions = other.compositions.dup
181
+ @compositions = Hash[other.compositions]
182
182
  self
183
183
  end
184
184
  end
@@ -147,7 +147,7 @@ module Sequel
147
147
  arg = arg.split(',')
148
148
  type = :includes
149
149
  when :includes_int_array
150
- arg = arg.split(',').map{|x| x.to_i}
150
+ arg = arg.split(',').map(&:to_i)
151
151
  type = :includes
152
152
  when :includes_int_range
153
153
  arg = constraint_validation_int_range(arg)
@@ -155,7 +155,7 @@ module Sequel
155
155
  end
156
156
 
157
157
  column = if type == :unique
158
- column.split(',').map{|c| c.to_sym}
158
+ column.split(',').map(&:to_sym)
159
159
  else
160
160
  column.to_sym
161
161
  end
@@ -0,0 +1,171 @@
1
+ if RUBY_VERSION < "1.9"
2
+ require 'fastercsv'
3
+ else
4
+ require 'csv'
5
+ end
6
+
7
+ module Sequel
8
+ module Plugins
9
+ # csv_serializer handles serializing entire Sequel::Model objects to CSV,
10
+ # as well as support for deserializing CSV directly into Sequel::Model
11
+ # objects. It requires either the csv standard library when usnig ruby 1.9+,
12
+ # or the fastercsv gem when using ruby 1.8.
13
+ #
14
+ # Basic Example:
15
+ #
16
+ # album = Album[1]
17
+ # album.to_csv(:write_headers=>true)
18
+ # # => "id,name,artist_id\n1,RF,2\n"
19
+ #
20
+ # You can provide options to control the CSV output:
21
+ #
22
+ # album.to_csv(:only=>:name)
23
+ # album.to_csv(:except=>[:id, :artist_id])
24
+ # # => "RF\n"
25
+ #
26
+ # +to_csv+ also exists as a class and dataset method, both of which return
27
+ # all objects in the dataset:
28
+ #
29
+ # Album.to_csv
30
+ # Album.filter(:artist_id=>1).to_csv
31
+ #
32
+ # If you have an existing array of model instance you want to convert to
33
+ # CSV, you can call the class to_csv method with the :array option:
34
+ #
35
+ # Album.to_csv(:array=>[Album[1], Album[2]])
36
+ #
37
+ # In addition to creating CSV, this plugin also enables Sequel::Model
38
+ # classes to create instances directly from CSV using the from_csv class
39
+ # method:
40
+ #
41
+ # csv = album.to_csv
42
+ # album = Album.from_csv(csv)
43
+ #
44
+ # The array_from_csv class method exists to parse arrays of model instances
45
+ # from CSV:
46
+ #
47
+ # csv = Album.filter(:artist_id=>1).to_csv
48
+ # albums = Album.array_from_csv(csv)
49
+ #
50
+ # These do not necessarily round trip, since doing so would let users
51
+ # create model objects with arbitrary values. By default, from_csv will
52
+ # call set with the values in the hash. If you want to specify the allowed
53
+ # fields, you can use the :headers option.
54
+ #
55
+ # Album.from_csv(album.to_csv, :headers=>%w'id name')
56
+ #
57
+ # If you want to update an existing instance, you can use the from_csv
58
+ # instance method:
59
+ #
60
+ # album.from_csv(csv)
61
+ #
62
+ # Usage:
63
+ #
64
+ # # Add CSV output capability to all model subclass instances (called
65
+ # # before loading subclasses)
66
+ # Sequel::Model.plugin :csv_serializer
67
+ #
68
+ # # Add CSV output capability to Album class instances
69
+ # Album.plugin :csv_serializer
70
+ module CsvSerializer
71
+ CSV = Object.const_defined?(:CSV) ? ::CSV : ::FasterCSV
72
+
73
+ # Set up the column readers to do deserialization and the column writers
74
+ # to save the value in deserialized_values
75
+ def self.configure(model, opts = {})
76
+ model.instance_eval do
77
+ @csv_serializer_opts = (@csv_serializer_opts || {}).merge(opts)
78
+ end
79
+ end
80
+
81
+ module ClassMethods
82
+ # The default opts to use when serializing model objects to CSV
83
+ attr_reader :csv_serializer_opts
84
+
85
+ # Attempt to parse an array of instances from the given CSV string
86
+ def array_from_csv(csv, opts = {})
87
+ CSV.parse(csv, process_csv_serializer_opts(opts)).map do |row|
88
+ row = row.to_hash
89
+ row.delete(nil)
90
+ new(row)
91
+ end
92
+ end
93
+
94
+ # Attempt to parse a single instance from the given CSV string
95
+ def from_csv(csv, opts = {})
96
+ new.from_csv(csv, opts)
97
+ end
98
+
99
+ # Convert the options hash to one that can be passed to CSV.
100
+ def process_csv_serializer_opts(opts)
101
+ opts = (csv_serializer_opts || {}).merge(opts)
102
+ opts_cols = opts.delete(:columns)
103
+ opts_include = opts.delete(:include)
104
+ opts_except = opts.delete(:except)
105
+ opts[:headers] ||= Array(opts.delete(:only) || opts_cols || columns) + Array(opts_include) - Array(opts_except)
106
+ opts
107
+ end
108
+
109
+ Plugins.inherited_instance_variables(
110
+ self, :@csv_serializer_opts => lambda do |csv_serializer_opts|
111
+ opts = {}
112
+ csv_serializer_opts.each do |k, v|
113
+ opts[k] = (v.is_a?(Array) || v.is_a?(Hash)) ? v.dup : v
114
+ end
115
+ opts
116
+ end)
117
+
118
+ Plugins.def_dataset_methods(self, :to_csv)
119
+ end
120
+
121
+ module InstanceMethods
122
+ # Update the object using the data provided in the first line in CSV. Options:
123
+ #
124
+ # :headers :: The headers to use for the CSV line. Use nil for a header
125
+ # to specify the column should be ignored.
126
+ def from_csv(csv, opts = {})
127
+ row = CSV.parse_line(csv, model.process_csv_serializer_opts(opts)).to_hash
128
+ row.delete(nil)
129
+ set(row)
130
+ end
131
+
132
+ # Return a string in CSV format. Accepts the same options as CSV.new,
133
+ # as well as the following options:
134
+ #
135
+ # :except :: Symbol or Array of Symbols of columns not to include in
136
+ # the CSV output.
137
+ # :only :: Symbol or Array of Symbols of columns to include in the CSV
138
+ # output, ignoring all other columns
139
+ # :include :: Symbol or Array of Symbols specifying non-column
140
+ # attributes to include in the CSV output.
141
+ def to_csv(opts = {})
142
+ opts = model.process_csv_serializer_opts(opts)
143
+
144
+ CSV.generate(opts) do |csv|
145
+ csv << opts[:headers].map{|k| send(k)}
146
+ end
147
+ end
148
+ end
149
+
150
+ module DatasetMethods
151
+ # Return a CSV string representing an array of all objects in this
152
+ # dataset. Takes the same options as the instance method, and passes
153
+ # them to every instance. Accepts the same options as CSV.new, as well
154
+ # as the following options:
155
+ #
156
+ # :array :: An array of instances. If this is not provided, calls #all
157
+ # on the receiver to get the array.
158
+ def to_csv(opts = {})
159
+ opts = model.process_csv_serializer_opts({:columns=>columns}.merge!(opts))
160
+ items = opts.delete(:array) || self
161
+
162
+ CSV.generate(opts) do |csv|
163
+ items.each do |object|
164
+ csv << opts[:headers].map{|header| object[header]}
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end