sequel 4.21.0 → 4.22.0

Sign up to get free protection for your applications and to get access to all the features.
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