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.
- 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
|