sequel 4.21.0 → 4.22.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +32 -0
- data/README.rdoc +3 -4
- data/doc/opening_databases.rdoc +10 -75
- data/doc/release_notes/4.22.0.txt +72 -0
- data/lib/sequel/adapters/ado/access.rb +1 -1
- data/lib/sequel/adapters/cubrid.rb +3 -3
- data/lib/sequel/adapters/db2.rb +1 -0
- data/lib/sequel/adapters/dbi.rb +1 -0
- data/lib/sequel/adapters/fdbsql.rb +3 -2
- data/lib/sequel/adapters/firebird.rb +1 -0
- data/lib/sequel/adapters/ibmdb.rb +1 -21
- data/lib/sequel/adapters/informix.rb +1 -0
- data/lib/sequel/adapters/jdbc.rb +37 -49
- data/lib/sequel/adapters/jdbc/fdbsql.rb +1 -0
- data/lib/sequel/adapters/mysql.rb +5 -3
- data/lib/sequel/adapters/mysql2.rb +5 -2
- data/lib/sequel/adapters/odbc.rb +8 -4
- data/lib/sequel/adapters/openbase.rb +1 -0
- data/lib/sequel/adapters/oracle.rb +3 -46
- data/lib/sequel/adapters/postgres.rb +3 -36
- data/lib/sequel/adapters/shared/access.rb +1 -1
- data/lib/sequel/adapters/shared/fdbsql.rb +3 -3
- data/lib/sequel/adapters/shared/mssql.rb +1 -1
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +12 -44
- data/lib/sequel/adapters/shared/oracle.rb +6 -2
- data/lib/sequel/adapters/shared/postgres.rb +6 -6
- data/lib/sequel/adapters/shared/sqlite.rb +1 -1
- data/lib/sequel/adapters/sqlite.rb +3 -46
- data/lib/sequel/adapters/tinytds.rb +12 -28
- data/lib/sequel/adapters/utils/pg_types.rb +1 -1
- data/lib/sequel/connection_pool/sharded_threaded.rb +63 -16
- data/lib/sequel/connection_pool/threaded.rb +72 -18
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database/connecting.rb +2 -2
- data/lib/sequel/database/misc.rb +5 -5
- data/lib/sequel/database/query.rb +3 -2
- data/lib/sequel/database/schema_generator.rb +19 -19
- data/lib/sequel/database/schema_methods.rb +2 -2
- data/lib/sequel/database/transactions.rb +3 -3
- data/lib/sequel/dataset/actions.rb +18 -8
- data/lib/sequel/dataset/graph.rb +2 -2
- data/lib/sequel/dataset/prepared_statements.rb +28 -1
- data/lib/sequel/dataset/query.rb +7 -7
- data/lib/sequel/exceptions.rb +27 -24
- data/lib/sequel/extensions/_pretty_table.rb +1 -1
- data/lib/sequel/extensions/constraint_validations.rb +2 -2
- data/lib/sequel/extensions/date_arithmetic.rb +2 -2
- data/lib/sequel/extensions/pg_array.rb +10 -1
- data/lib/sequel/extensions/pg_row.rb +1 -1
- data/lib/sequel/extensions/pg_static_cache_updater.rb +1 -1
- data/lib/sequel/extensions/schema_dumper.rb +8 -8
- data/lib/sequel/extensions/split_array_nil.rb +1 -1
- data/lib/sequel/model.rb +1 -1
- data/lib/sequel/model/associations.rb +18 -11
- data/lib/sequel/model/base.rb +15 -15
- data/lib/sequel/model/exceptions.rb +11 -2
- data/lib/sequel/plugins/accessed_columns.rb +1 -1
- data/lib/sequel/plugins/auto_validations.rb +1 -1
- data/lib/sequel/plugins/boolean_readers.rb +1 -1
- data/lib/sequel/plugins/class_table_inheritance.rb +4 -7
- data/lib/sequel/plugins/composition.rb +1 -1
- data/lib/sequel/plugins/constraint_validations.rb +2 -2
- data/lib/sequel/plugins/csv_serializer.rb +171 -0
- data/lib/sequel/plugins/dirty.rb +2 -2
- data/lib/sequel/plugins/hook_class_methods.rb +1 -1
- data/lib/sequel/plugins/instance_hooks.rb +1 -1
- data/lib/sequel/plugins/many_through_many.rb +1 -1
- data/lib/sequel/plugins/nested_attributes.rb +5 -5
- data/lib/sequel/plugins/pg_array_associations.rb +4 -4
- data/lib/sequel/plugins/prepared_statements.rb +2 -2
- data/lib/sequel/plugins/prepared_statements_safe.rb +1 -1
- data/lib/sequel/plugins/serialization.rb +6 -6
- data/lib/sequel/plugins/serialization_modification_detection.rb +1 -1
- data/lib/sequel/plugins/sharding.rb +3 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +5 -13
- data/lib/sequel/plugins/static_cache.rb +2 -2
- data/lib/sequel/plugins/tactical_eager_loading.rb +1 -1
- data/lib/sequel/plugins/tree.rb +1 -1
- data/lib/sequel/plugins/validation_class_methods.rb +2 -2
- data/lib/sequel/plugins/validation_helpers.rb +4 -4
- data/lib/sequel/plugins/xml_serializer.rb +3 -3
- data/lib/sequel/sql.rb +1 -1
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +17 -0
- data/spec/core/connection_pool_spec.rb +1 -1
- data/spec/core/dataset_spec.rb +22 -0
- data/spec/extensions/auto_validations_spec.rb +1 -1
- data/spec/extensions/blacklist_security_spec.rb +2 -2
- data/spec/extensions/csv_serializer_spec.rb +173 -0
- data/spec/extensions/json_serializer_spec.rb +2 -2
- data/spec/extensions/nested_attributes_spec.rb +9 -9
- data/spec/extensions/pg_array_spec.rb +5 -0
- data/spec/extensions/single_table_inheritance_spec.rb +21 -0
- data/spec/extensions/touch_spec.rb +1 -1
- data/spec/extensions/tree_spec.rb +4 -0
- data/spec/extensions/xml_serializer_spec.rb +3 -3
- data/spec/integration/prepared_statement_test.rb +1 -1
- data/spec/integration/schema_test.rb +7 -0
- data/spec/integration/type_test.rb +2 -2
- data/spec/model/associations_spec.rb +108 -14
- data/spec/model/base_spec.rb +8 -8
- data/spec/model/record_spec.rb +7 -7
- metadata +6 -2
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
|
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
|
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
|
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
|
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
|
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
|
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")
|
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
|
|
data/lib/sequel/model/base.rb
CHANGED
@@ -489,7 +489,7 @@ module Sequel
|
|
489
489
|
# end
|
490
490
|
def inherited(subclass)
|
491
491
|
super
|
492
|
-
ivs = subclass.instance_variables.collect
|
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
|
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
|
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
|
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
|
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
|
2132
|
-
if Array(model.primary_key).map
|
2133
|
-
raise
|
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
|
2135
|
+
raise MassAssignmentRestriction, "#{k} is a restricted column"
|
2136
2136
|
end
|
2137
2137
|
else
|
2138
|
-
raise
|
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
|
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
|
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
|
-
|
19
|
+
NoExistingObject = Class.new(Error)
|
20
20
|
|
21
21
|
# Raised when an undefined association is used when eager loading.
|
22
|
-
|
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
|
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[:
|
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
|
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
|
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).
|
@@ -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
|
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
|
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
|