sequel 5.39.0 → 5.72.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +408 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +59 -27
- data/bin/sequel +11 -3
- data/doc/advanced_associations.rdoc +16 -14
- data/doc/association_basics.rdoc +119 -24
- data/doc/cheat_sheet.rdoc +11 -3
- data/doc/mass_assignment.rdoc +1 -1
- data/doc/migration.rdoc +13 -6
- data/doc/model_hooks.rdoc +1 -1
- data/doc/object_model.rdoc +8 -8
- data/doc/opening_databases.rdoc +26 -12
- data/doc/postgresql.rdoc +16 -8
- data/doc/querying.rdoc +5 -3
- data/doc/release_notes/5.40.0.txt +40 -0
- data/doc/release_notes/5.41.0.txt +25 -0
- data/doc/release_notes/5.42.0.txt +136 -0
- data/doc/release_notes/5.43.0.txt +98 -0
- data/doc/release_notes/5.44.0.txt +32 -0
- data/doc/release_notes/5.45.0.txt +34 -0
- data/doc/release_notes/5.46.0.txt +87 -0
- data/doc/release_notes/5.47.0.txt +59 -0
- data/doc/release_notes/5.48.0.txt +14 -0
- data/doc/release_notes/5.49.0.txt +59 -0
- data/doc/release_notes/5.50.0.txt +78 -0
- data/doc/release_notes/5.51.0.txt +47 -0
- data/doc/release_notes/5.52.0.txt +87 -0
- data/doc/release_notes/5.53.0.txt +23 -0
- data/doc/release_notes/5.54.0.txt +27 -0
- data/doc/release_notes/5.55.0.txt +21 -0
- data/doc/release_notes/5.56.0.txt +51 -0
- data/doc/release_notes/5.57.0.txt +23 -0
- data/doc/release_notes/5.58.0.txt +31 -0
- data/doc/release_notes/5.59.0.txt +73 -0
- data/doc/release_notes/5.60.0.txt +22 -0
- data/doc/release_notes/5.61.0.txt +43 -0
- data/doc/release_notes/5.62.0.txt +132 -0
- data/doc/release_notes/5.63.0.txt +33 -0
- data/doc/release_notes/5.64.0.txt +50 -0
- data/doc/release_notes/5.65.0.txt +21 -0
- data/doc/release_notes/5.66.0.txt +24 -0
- data/doc/release_notes/5.67.0.txt +32 -0
- data/doc/release_notes/5.68.0.txt +61 -0
- data/doc/release_notes/5.69.0.txt +26 -0
- data/doc/release_notes/5.70.0.txt +35 -0
- data/doc/release_notes/5.71.0.txt +21 -0
- data/doc/release_notes/5.72.0.txt +33 -0
- data/doc/schema_modification.rdoc +1 -1
- data/doc/security.rdoc +9 -9
- data/doc/sharding.rdoc +3 -1
- data/doc/sql.rdoc +28 -16
- data/doc/testing.rdoc +22 -11
- data/doc/transactions.rdoc +6 -6
- data/doc/virtual_rows.rdoc +2 -2
- data/lib/sequel/adapters/ado/access.rb +1 -1
- data/lib/sequel/adapters/ado.rb +17 -17
- data/lib/sequel/adapters/amalgalite.rb +3 -5
- data/lib/sequel/adapters/ibmdb.rb +2 -2
- data/lib/sequel/adapters/jdbc/derby.rb +8 -0
- data/lib/sequel/adapters/jdbc/h2.rb +60 -10
- data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +7 -4
- data/lib/sequel/adapters/jdbc.rb +16 -18
- data/lib/sequel/adapters/mysql.rb +92 -67
- data/lib/sequel/adapters/mysql2.rb +54 -49
- data/lib/sequel/adapters/odbc.rb +6 -2
- data/lib/sequel/adapters/oracle.rb +4 -3
- data/lib/sequel/adapters/postgres.rb +83 -40
- data/lib/sequel/adapters/shared/access.rb +11 -1
- data/lib/sequel/adapters/shared/db2.rb +30 -0
- data/lib/sequel/adapters/shared/mssql.rb +90 -9
- data/lib/sequel/adapters/shared/mysql.rb +47 -2
- data/lib/sequel/adapters/shared/oracle.rb +82 -1
- data/lib/sequel/adapters/shared/postgres.rb +496 -178
- data/lib/sequel/adapters/shared/sqlanywhere.rb +11 -1
- data/lib/sequel/adapters/shared/sqlite.rb +116 -11
- data/lib/sequel/adapters/sqlanywhere.rb +1 -1
- data/lib/sequel/adapters/sqlite.rb +60 -18
- data/lib/sequel/adapters/tinytds.rb +1 -1
- data/lib/sequel/adapters/trilogy.rb +117 -0
- data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
- data/lib/sequel/ast_transformer.rb +6 -0
- data/lib/sequel/connection_pool/sharded_single.rb +5 -7
- data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
- data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
- data/lib/sequel/connection_pool/single.rb +6 -8
- data/lib/sequel/connection_pool/threaded.rb +14 -8
- data/lib/sequel/connection_pool/timed_queue.rb +270 -0
- data/lib/sequel/connection_pool.rb +55 -31
- data/lib/sequel/core.rb +28 -18
- data/lib/sequel/database/connecting.rb +27 -3
- data/lib/sequel/database/dataset.rb +16 -6
- data/lib/sequel/database/misc.rb +69 -14
- data/lib/sequel/database/query.rb +73 -2
- data/lib/sequel/database/schema_generator.rb +46 -53
- data/lib/sequel/database/schema_methods.rb +18 -2
- data/lib/sequel/dataset/actions.rb +108 -14
- data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
- data/lib/sequel/dataset/features.rb +20 -0
- data/lib/sequel/dataset/misc.rb +12 -2
- data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
- data/lib/sequel/dataset/prepared_statements.rb +2 -0
- data/lib/sequel/dataset/query.rb +171 -44
- data/lib/sequel/dataset/sql.rb +182 -47
- data/lib/sequel/dataset.rb +4 -0
- data/lib/sequel/extensions/_model_pg_row.rb +0 -12
- data/lib/sequel/extensions/_pretty_table.rb +1 -1
- data/lib/sequel/extensions/any_not_empty.rb +1 -1
- data/lib/sequel/extensions/async_thread_pool.rb +439 -0
- data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
- data/lib/sequel/extensions/blank.rb +8 -0
- data/lib/sequel/extensions/connection_expiration.rb +15 -9
- data/lib/sequel/extensions/connection_validator.rb +16 -11
- data/lib/sequel/extensions/constraint_validations.rb +1 -1
- data/lib/sequel/extensions/core_refinements.rb +36 -11
- data/lib/sequel/extensions/date_arithmetic.rb +71 -31
- data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
- data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
- data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
- data/lib/sequel/extensions/eval_inspect.rb +2 -0
- data/lib/sequel/extensions/index_caching.rb +5 -1
- data/lib/sequel/extensions/inflector.rb +9 -1
- data/lib/sequel/extensions/is_distinct_from.rb +141 -0
- data/lib/sequel/extensions/looser_typecasting.rb +3 -0
- data/lib/sequel/extensions/migration.rb +11 -2
- data/lib/sequel/extensions/named_timezones.rb +26 -6
- data/lib/sequel/extensions/pagination.rb +1 -1
- data/lib/sequel/extensions/pg_array.rb +32 -4
- data/lib/sequel/extensions/pg_array_ops.rb +2 -2
- data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
- data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
- data/lib/sequel/extensions/pg_enum.rb +2 -3
- data/lib/sequel/extensions/pg_extended_date_support.rb +38 -27
- data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
- data/lib/sequel/extensions/pg_hstore.rb +6 -1
- data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
- data/lib/sequel/extensions/pg_inet.rb +10 -11
- data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
- data/lib/sequel/extensions/pg_interval.rb +45 -19
- data/lib/sequel/extensions/pg_json.rb +13 -15
- data/lib/sequel/extensions/pg_json_ops.rb +73 -2
- data/lib/sequel/extensions/pg_loose_count.rb +3 -1
- data/lib/sequel/extensions/pg_multirange.rb +367 -0
- data/lib/sequel/extensions/pg_range.rb +11 -24
- data/lib/sequel/extensions/pg_range_ops.rb +37 -9
- data/lib/sequel/extensions/pg_row.rb +21 -19
- data/lib/sequel/extensions/pg_row_ops.rb +1 -1
- data/lib/sequel/extensions/query.rb +2 -0
- data/lib/sequel/extensions/s.rb +2 -1
- data/lib/sequel/extensions/schema_caching.rb +1 -1
- data/lib/sequel/extensions/schema_dumper.rb +45 -11
- data/lib/sequel/extensions/server_block.rb +10 -13
- data/lib/sequel/extensions/set_literalizer.rb +58 -0
- data/lib/sequel/extensions/sql_comments.rb +110 -3
- data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
- data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
- data/lib/sequel/extensions/string_agg.rb +1 -1
- data/lib/sequel/extensions/string_date_time.rb +19 -23
- data/lib/sequel/extensions/symbol_aref.rb +2 -0
- data/lib/sequel/model/associations.rb +345 -101
- data/lib/sequel/model/base.rb +51 -27
- data/lib/sequel/model/dataset_module.rb +3 -0
- data/lib/sequel/model/errors.rb +10 -1
- data/lib/sequel/model/inflections.rb +1 -1
- data/lib/sequel/model/plugins.rb +5 -0
- data/lib/sequel/plugins/association_proxies.rb +2 -0
- data/lib/sequel/plugins/async_thread_pool.rb +39 -0
- data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
- data/lib/sequel/plugins/auto_validations.rb +87 -15
- data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
- data/lib/sequel/plugins/column_encryption.rb +728 -0
- data/lib/sequel/plugins/composition.rb +10 -4
- data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
- data/lib/sequel/plugins/constraint_validations.rb +10 -6
- data/lib/sequel/plugins/dataset_associations.rb +4 -1
- data/lib/sequel/plugins/defaults_setter.rb +16 -0
- data/lib/sequel/plugins/dirty.rb +1 -1
- data/lib/sequel/plugins/enum.rb +124 -0
- data/lib/sequel/plugins/finder.rb +4 -2
- data/lib/sequel/plugins/insert_conflict.rb +4 -0
- data/lib/sequel/plugins/instance_specific_default.rb +1 -1
- data/lib/sequel/plugins/json_serializer.rb +39 -24
- data/lib/sequel/plugins/lazy_attributes.rb +3 -0
- data/lib/sequel/plugins/list.rb +3 -1
- data/lib/sequel/plugins/many_through_many.rb +109 -10
- data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
- data/lib/sequel/plugins/nested_attributes.rb +12 -7
- data/lib/sequel/plugins/optimistic_locking.rb +9 -42
- data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
- data/lib/sequel/plugins/pg_array_associations.rb +56 -38
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +11 -3
- data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
- data/lib/sequel/plugins/prepared_statements.rb +12 -2
- data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
- data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
- data/lib/sequel/plugins/rcte_tree.rb +27 -19
- data/lib/sequel/plugins/require_valid_schema.rb +67 -0
- data/lib/sequel/plugins/serialization.rb +9 -3
- data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
- data/lib/sequel/plugins/sql_comments.rb +189 -0
- data/lib/sequel/plugins/static_cache.rb +39 -1
- data/lib/sequel/plugins/static_cache_cache.rb +5 -1
- data/lib/sequel/plugins/subclasses.rb +28 -11
- data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
- data/lib/sequel/plugins/timestamps.rb +1 -1
- data/lib/sequel/plugins/unused_associations.rb +521 -0
- data/lib/sequel/plugins/update_or_create.rb +1 -1
- data/lib/sequel/plugins/validate_associated.rb +22 -12
- data/lib/sequel/plugins/validation_helpers.rb +46 -12
- data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
- data/lib/sequel/plugins/xml_serializer.rb +1 -1
- data/lib/sequel/sql.rb +1 -1
- data/lib/sequel/timezones.rb +12 -14
- data/lib/sequel/version.rb +1 -1
- metadata +132 -38
data/lib/sequel/model/base.rb
CHANGED
@@ -508,7 +508,9 @@ module Sequel
|
|
508
508
|
|
509
509
|
m.configure(self, *args, &block) if m.respond_to?(:configure)
|
510
510
|
end
|
511
|
+
# :nocov:
|
511
512
|
ruby2_keywords(:plugin) if respond_to?(:ruby2_keywords, true)
|
513
|
+
# :nocov:
|
512
514
|
|
513
515
|
# Returns primary key attribute hash. If using a composite primary key
|
514
516
|
# value such be an array with values for each primary key in the correct
|
@@ -604,6 +606,7 @@ module Sequel
|
|
604
606
|
@db_schema = get_db_schema
|
605
607
|
end
|
606
608
|
|
609
|
+
@fast_pk_lookup_sql = @fast_instance_delete_sql = nil unless @dataset.supports_placeholder_literalizer?
|
607
610
|
reset_instance_dataset
|
608
611
|
self
|
609
612
|
end
|
@@ -678,24 +681,26 @@ module Sequel
|
|
678
681
|
|
679
682
|
private
|
680
683
|
|
681
|
-
# Yield to the passed block and if do_raise is false, swallow
|
684
|
+
# Yield to the passed block and if do_raise is false, swallow Sequel::Errors other than DatabaseConnectionError
|
685
|
+
# and DatabaseDisconnectError.
|
682
686
|
def check_non_connection_error(do_raise=require_valid_table)
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
raise if do_raise
|
689
|
-
end
|
687
|
+
db.transaction(:savepoint=>:only){yield}
|
688
|
+
rescue Sequel::DatabaseConnectionError, Sequel::DatabaseDisconnectError
|
689
|
+
raise
|
690
|
+
rescue Sequel::Error
|
691
|
+
raise if do_raise
|
690
692
|
end
|
691
693
|
|
692
694
|
# Convert the given object to a Dataset that should be used as
|
693
695
|
# this model's dataset.
|
694
696
|
def convert_input_dataset(ds)
|
695
697
|
case ds
|
696
|
-
when Symbol, SQL::Identifier, SQL::QualifiedIdentifier
|
698
|
+
when Symbol, SQL::Identifier, SQL::QualifiedIdentifier
|
697
699
|
self.simple_table = db.literal(ds).freeze
|
698
700
|
ds = db.from(ds)
|
701
|
+
when SQL::AliasedExpression, LiteralString
|
702
|
+
self.simple_table = nil
|
703
|
+
ds = db.from(ds)
|
699
704
|
when Dataset
|
700
705
|
ds = ds.from_self(:alias=>ds.first_source) if ds.joined_dataset?
|
701
706
|
|
@@ -727,8 +732,14 @@ module Sequel
|
|
727
732
|
im = instance_methods
|
728
733
|
overridable_methods_module.module_eval do
|
729
734
|
meth = :"#{column}="
|
730
|
-
|
731
|
-
|
735
|
+
unless im.include?(column)
|
736
|
+
define_method(column){self[column]}
|
737
|
+
alias_method(column, column)
|
738
|
+
end
|
739
|
+
unless im.include?(meth)
|
740
|
+
define_method(meth){|v| self[column] = v}
|
741
|
+
alias_method(meth, meth)
|
742
|
+
end
|
732
743
|
end
|
733
744
|
end
|
734
745
|
|
@@ -741,8 +752,14 @@ module Sequel
|
|
741
752
|
im = instance_methods
|
742
753
|
columns.each do |column|
|
743
754
|
meth = :"#{column}="
|
744
|
-
|
745
|
-
|
755
|
+
unless im.include?(column)
|
756
|
+
overridable_methods_module.module_eval("def #{column}; self[:#{column}] end", __FILE__, __LINE__)
|
757
|
+
overridable_methods_module.send(:alias_method, column, column)
|
758
|
+
end
|
759
|
+
unless im.include?(meth)
|
760
|
+
overridable_methods_module.module_eval("def #{meth}(v); self[:#{column}] = v end", __FILE__, __LINE__)
|
761
|
+
overridable_methods_module.send(:alias_method, meth, meth)
|
762
|
+
end
|
746
763
|
end
|
747
764
|
end
|
748
765
|
|
@@ -757,7 +774,10 @@ module Sequel
|
|
757
774
|
else
|
758
775
|
define_singleton_method(meth){|*args, &block| dataset.public_send(meth, *args, &block)}
|
759
776
|
end
|
777
|
+
singleton_class.send(:alias_method, meth, meth)
|
778
|
+
# :nocov:
|
760
779
|
singleton_class.send(:ruby2_keywords, meth) if respond_to?(:ruby2_keywords, true)
|
780
|
+
# :nocov:
|
761
781
|
end
|
762
782
|
|
763
783
|
# Get the schema from the database, fall back on checking the columns
|
@@ -769,7 +789,7 @@ module Sequel
|
|
769
789
|
schema_hash = {}
|
770
790
|
ds_opts = dataset.opts
|
771
791
|
get_columns = proc{check_non_connection_error{columns} || []}
|
772
|
-
schema_array =
|
792
|
+
schema_array = get_db_schema_array(reload) if db.supports_schema_parsing?
|
773
793
|
if schema_array
|
774
794
|
schema_array.each{|k,v| schema_hash[k] = v}
|
775
795
|
|
@@ -806,6 +826,12 @@ module Sequel
|
|
806
826
|
schema_hash
|
807
827
|
end
|
808
828
|
|
829
|
+
# Get the array of schema information for the dataset. Returns nil if
|
830
|
+
# the schema information cannot be determined.
|
831
|
+
def get_db_schema_array(reload)
|
832
|
+
check_non_connection_error(false){db.schema(dataset, :reload=>reload)}
|
833
|
+
end
|
834
|
+
|
809
835
|
# Uncached version of setter_methods, to be overridden by plugins
|
810
836
|
# that want to modify the methods used.
|
811
837
|
def get_setter_methods
|
@@ -1076,7 +1102,7 @@ module Sequel
|
|
1076
1102
|
@modified = true
|
1077
1103
|
initialize_set(values)
|
1078
1104
|
_clear_changed_columns(:initialize)
|
1079
|
-
yield self if
|
1105
|
+
yield self if defined?(yield)
|
1080
1106
|
end
|
1081
1107
|
|
1082
1108
|
# Returns value of the column's attribute.
|
@@ -1116,7 +1142,7 @@ module Sequel
|
|
1116
1142
|
#
|
1117
1143
|
# Artist[1] === Artist[1] # => true
|
1118
1144
|
# Artist.new === Artist.new # => false
|
1119
|
-
# Artist[1].set(:
|
1145
|
+
# Artist[1].set(name: 'Bob') === Artist[1] # => true
|
1120
1146
|
def ===(obj)
|
1121
1147
|
case pkv = pk
|
1122
1148
|
when nil
|
@@ -1135,7 +1161,7 @@ module Sequel
|
|
1135
1161
|
#
|
1136
1162
|
# Artist[1].pk_equal?(Artist[1]) # => true
|
1137
1163
|
# Artist.new.pk_equal?(Artist.new) # => false
|
1138
|
-
# Artist[1].set(:
|
1164
|
+
# Artist[1].set(name: 'Bob').pk_equal?(Artist[1]) # => true
|
1139
1165
|
alias pk_equal? ===
|
1140
1166
|
|
1141
1167
|
# class is defined in Object, but it is also a keyword,
|
@@ -1207,7 +1233,7 @@ module Sequel
|
|
1207
1233
|
#
|
1208
1234
|
# Artist[1] == Artist[1] # => true
|
1209
1235
|
# Artist.new == Artist.new # => true
|
1210
|
-
# Artist[1].set(:
|
1236
|
+
# Artist[1].set(name: 'Bob') == Artist[1] # => false
|
1211
1237
|
def eql?(obj)
|
1212
1238
|
(obj.class == model) && (obj.values == @values)
|
1213
1239
|
end
|
@@ -1243,12 +1269,12 @@ module Sequel
|
|
1243
1269
|
# Once an object is frozen, you cannot modify it's values, changed_columns,
|
1244
1270
|
# errors, or dataset.
|
1245
1271
|
def freeze
|
1246
|
-
values.freeze
|
1247
|
-
_changed_columns.freeze
|
1248
1272
|
unless errors.frozen?
|
1249
1273
|
validate
|
1250
1274
|
errors.freeze
|
1251
1275
|
end
|
1276
|
+
values.freeze
|
1277
|
+
_changed_columns.freeze
|
1252
1278
|
this if !new? && model.primary_key
|
1253
1279
|
super
|
1254
1280
|
end
|
@@ -1309,13 +1335,13 @@ module Sequel
|
|
1309
1335
|
# a = Artist[1]
|
1310
1336
|
# Artist.db.transaction do
|
1311
1337
|
# a.lock!
|
1312
|
-
# a.update(:
|
1338
|
+
# a.update(name: 'A')
|
1313
1339
|
# end
|
1314
1340
|
#
|
1315
1341
|
# a = Artist[2]
|
1316
1342
|
# Artist.db.transaction do
|
1317
1343
|
# a.lock!('FOR NO KEY UPDATE')
|
1318
|
-
# a.update(:
|
1344
|
+
# a.update(name: 'B')
|
1319
1345
|
# end
|
1320
1346
|
def lock!(style=:update)
|
1321
1347
|
_refresh(this.lock_style(style)) unless new?
|
@@ -1613,11 +1639,9 @@ module Sequel
|
|
1613
1639
|
# artist.set(name: 'Invalid').valid? # => false
|
1614
1640
|
# artist.errors.full_messages # => ['name cannot be Invalid']
|
1615
1641
|
def valid?(opts = OPTS)
|
1616
|
-
|
1617
|
-
|
1618
|
-
|
1619
|
-
false
|
1620
|
-
end
|
1642
|
+
_valid?(opts)
|
1643
|
+
rescue HookFailed
|
1644
|
+
false
|
1621
1645
|
end
|
1622
1646
|
|
1623
1647
|
private
|
@@ -8,6 +8,9 @@ module Sequel
|
|
8
8
|
# automatically creates class methods for public dataset
|
9
9
|
# methods.
|
10
10
|
class DatasetModule < Dataset::DatasetModule
|
11
|
+
# The model class related to this dataset module.
|
12
|
+
attr_reader :model
|
13
|
+
|
11
14
|
# Store the model related to this dataset module.
|
12
15
|
def initialize(model)
|
13
16
|
@model = model
|
data/lib/sequel/model/errors.rb
CHANGED
@@ -38,7 +38,7 @@ module Sequel
|
|
38
38
|
def full_messages
|
39
39
|
inject([]) do |m, kv|
|
40
40
|
att, errors = *kv
|
41
|
-
errors.each {|e| m << (e.is_a?(LiteralString) ? e :
|
41
|
+
errors.each {|e| m << (e.is_a?(LiteralString) ? e : full_message(att, e))}
|
42
42
|
m
|
43
43
|
end
|
44
44
|
end
|
@@ -53,6 +53,15 @@ module Sequel
|
|
53
53
|
v
|
54
54
|
end
|
55
55
|
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# Create full error message to use for the given attribute (or array of attributes)
|
60
|
+
# and error message. This can be overridden for easier internalization.
|
61
|
+
def full_message(att, error_msg)
|
62
|
+
att = att.join(' and ') if att.is_a?(Array)
|
63
|
+
"#{att} #{error_msg}"
|
64
|
+
end
|
56
65
|
end
|
57
66
|
end
|
58
67
|
end
|
data/lib/sequel/model/plugins.rb
CHANGED
@@ -31,7 +31,9 @@ module Sequel
|
|
31
31
|
def self.def_dataset_methods(mod, meths)
|
32
32
|
Array(meths).each do |meth|
|
33
33
|
mod.class_eval("def #{meth}(*args, &block); dataset.#{meth}(*args, &block) end", __FILE__, __LINE__)
|
34
|
+
# :nocov:
|
34
35
|
mod.send(:ruby2_keywords, meth) if respond_to?(:ruby2_keywords, true)
|
36
|
+
# :nocov:
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
@@ -120,6 +122,7 @@ module Sequel
|
|
120
122
|
|
121
123
|
model.send(:define_method, meth, &block)
|
122
124
|
model.send(:private, meth)
|
125
|
+
model.send(:alias_method, meth, meth)
|
123
126
|
call_meth
|
124
127
|
end
|
125
128
|
|
@@ -141,6 +144,8 @@ module Sequel
|
|
141
144
|
keyword = :required
|
142
145
|
when :key, :keyrest
|
143
146
|
keyword ||= true
|
147
|
+
else
|
148
|
+
raise Error, "invalid arg_type passed to _define_sequel_method_arg_numbers: #{arg_type}"
|
144
149
|
end
|
145
150
|
end
|
146
151
|
arity = callable.arity
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
extension 'async_thread_pool'
|
5
|
+
|
6
|
+
module Plugins
|
7
|
+
# The async_thread_pool plugin makes it slightly easier to use the async_thread_pool
|
8
|
+
# Database extension with models. It makes Model.async return an async dataset for the
|
9
|
+
# model, and support async behavior for #destroy, #with_pk, and #with_pk! for model
|
10
|
+
# datasets:
|
11
|
+
#
|
12
|
+
# # Will load the artist with primary key 1 asynchronously
|
13
|
+
# artist = Artist.async.with_pk(1)
|
14
|
+
#
|
15
|
+
# You must load the async_thread_pool Database extension into the Database object the
|
16
|
+
# model class uses in order for async behavior to work.
|
17
|
+
#
|
18
|
+
# Usage:
|
19
|
+
#
|
20
|
+
# # Make all model subclass datasets support support async class methods and additional
|
21
|
+
# # async dataset methods
|
22
|
+
# Sequel::Model.plugin :async_thread_pool
|
23
|
+
#
|
24
|
+
# # Make the Album class support async class method and additional async dataset methods
|
25
|
+
# Album.plugin :async_thread_pool
|
26
|
+
module AsyncThreadPool
|
27
|
+
module ClassMethods
|
28
|
+
Plugins.def_dataset_methods(self, :async)
|
29
|
+
end
|
30
|
+
|
31
|
+
module DatasetMethods
|
32
|
+
[:destroy, :with_pk, :with_pk!].each do |meth|
|
33
|
+
::Sequel::Database::AsyncThreadPool::DatasetMethods.define_async_method(self, meth)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Plugins
|
5
|
+
# The auto_restrict_eager_graph plugin will automatically disallow the use
|
6
|
+
# of eager_graph for associations that have associated blocks but no :graph_*
|
7
|
+
# association options. The reason for this is the block will have an effect
|
8
|
+
# during regular and eager loading, but not loading via eager_graph, and it
|
9
|
+
# is likely that whatever the block is doing should have an equivalent done
|
10
|
+
# when eager_graphing. Most likely, not including a :graph_* option was either
|
11
|
+
# an oversight (and one should be added), or use with eager_graph was never
|
12
|
+
# intended (and usage should be forbidden). Disallowing eager_graph in this
|
13
|
+
# case prevents likely unexpected behavior during eager_graph.
|
14
|
+
#
|
15
|
+
# As an example of this, consider the following code:
|
16
|
+
#
|
17
|
+
# Album.one_to_many :popular_tracks, class: :Track do |ds|
|
18
|
+
# ds = ds.where(popular: true)
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# Album.eager(:popular_tracks).all
|
22
|
+
# # SELECT * FROM albums
|
23
|
+
# # SELECT * FROM tracks WHERE ((popular IS TRUE) AND (album_id IN (...)))
|
24
|
+
#
|
25
|
+
# # Notice that no condition for tracks.popular is added.
|
26
|
+
# Album.eager_graph(:popular_tracks).all
|
27
|
+
# # SELECT ... FROM albums LEFT JOIN tracks ON (tracks.album_id = albums.id)
|
28
|
+
#
|
29
|
+
# With the auto_restrict_eager_graph plugin, the eager_graph call above will
|
30
|
+
# raise an error, alerting you to the fact that you either should not be
|
31
|
+
# using eager_graph with the association, or that you should be adding an
|
32
|
+
# appropriate :graph_* option, such as:
|
33
|
+
#
|
34
|
+
# Album.one_to_many :popular_tracks, class: :Track, graph_conditions: {popular: true} do |ds|
|
35
|
+
# ds = ds.where(popular: true)
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# Usage:
|
39
|
+
#
|
40
|
+
# # Automatically restrict eager_graph for associations if appropriate for all
|
41
|
+
# # model subclasses (called before loading subclasses)
|
42
|
+
# Sequel::Model.plugin :auto_restrict_eager_graph
|
43
|
+
#
|
44
|
+
# # Automatically restrict eager_graph for associations in Album class
|
45
|
+
# Album.plugin :auto_restrict_eager_graph
|
46
|
+
module AutoRestrictEagerGraph
|
47
|
+
module ClassMethods
|
48
|
+
# When defining an association, if a block is given for the association, but
|
49
|
+
# a :graph_* option is not used, disallow the use of eager_graph.
|
50
|
+
def associate(type, name, opts = OPTS, &block)
|
51
|
+
opts = super
|
52
|
+
|
53
|
+
if opts[:block] && !opts.has_key?(:allow_eager_graph) && !opts[:orig_opts].any?{|k,| /\Agraph_/ =~ k}
|
54
|
+
opts[:allow_eager_graph] = false
|
55
|
+
end
|
56
|
+
|
57
|
+
opts
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -9,12 +9,16 @@ module Sequel
|
|
9
9
|
# 2. not_null validations on NOT NULL columns (optionally, presence validations)
|
10
10
|
# 3. unique validations on columns or sets of columns with unique indexes
|
11
11
|
# 4. max length validations on string columns
|
12
|
+
# 5. no null byte validations on string columns
|
13
|
+
# 6. minimum and maximum values on columns
|
12
14
|
#
|
13
|
-
# To determine the columns to use for the type/not_null/max_length validations,
|
15
|
+
# To determine the columns to use for the type/not_null/max_length/no_null_byte/max_value/min_value validations,
|
14
16
|
# the plugin looks at the database schema for the model's table. To determine
|
15
17
|
# the unique validations, Sequel looks at the indexes on the table. In order
|
16
18
|
# for this plugin to be fully functional, the underlying database adapter needs
|
17
|
-
# to support both schema and index parsing.
|
19
|
+
# to support both schema and index parsing. Additionally, unique validations are
|
20
|
+
# only added for models that select from a simple table, they are not added for models
|
21
|
+
# that select from a subquery.
|
18
22
|
#
|
19
23
|
# This plugin uses the validation_helpers plugin underneath to implement the
|
20
24
|
# validations. It does not allow for any per-column validation message
|
@@ -48,9 +52,14 @@ module Sequel
|
|
48
52
|
#
|
49
53
|
# Model.plugin :auto_validations, unique_opts: {only_if_modified: true}
|
50
54
|
#
|
51
|
-
# This works for unique_opts, max_length_opts, schema_types_opts,
|
55
|
+
# This works for unique_opts, max_length_opts, schema_types_opts, max_value_opts, min_value_opts, no_null_byte_opts,
|
52
56
|
# explicit_not_null_opts, and not_null_opts.
|
53
57
|
#
|
58
|
+
# If you only want auto_validations to add validations to columns that do not already
|
59
|
+
# have an error associated with them, you can use the skip_invalid option:
|
60
|
+
#
|
61
|
+
# Model.plugin :auto_validations, skip_invalid: true
|
62
|
+
#
|
54
63
|
# Usage:
|
55
64
|
#
|
56
65
|
# # Make all model subclass use auto validations (called before loading subclasses)
|
@@ -64,25 +73,35 @@ module Sequel
|
|
64
73
|
MAX_LENGTH_OPTIONS = {:from=>:values, :allow_nil=>true}.freeze
|
65
74
|
SCHEMA_TYPES_OPTIONS = NOT_NULL_OPTIONS
|
66
75
|
UNIQUE_OPTIONS = NOT_NULL_OPTIONS
|
76
|
+
NO_NULL_BYTE_OPTIONS = MAX_LENGTH_OPTIONS
|
77
|
+
MAX_VALUE_OPTIONS = {:from=>:values, :allow_nil=>true, :skip_invalid=>true}.freeze
|
78
|
+
MIN_VALUE_OPTIONS = MAX_VALUE_OPTIONS
|
79
|
+
AUTO_VALIDATE_OPTIONS = {
|
80
|
+
:no_null_byte=>NO_NULL_BYTE_OPTIONS,
|
81
|
+
:not_null=>NOT_NULL_OPTIONS,
|
82
|
+
:explicit_not_null=>EXPLICIT_NOT_NULL_OPTIONS,
|
83
|
+
:max_length=>MAX_LENGTH_OPTIONS,
|
84
|
+
:max_value=>MAX_VALUE_OPTIONS,
|
85
|
+
:min_value=>MIN_VALUE_OPTIONS,
|
86
|
+
:schema_types=>SCHEMA_TYPES_OPTIONS,
|
87
|
+
:unique=>UNIQUE_OPTIONS
|
88
|
+
}.freeze
|
89
|
+
|
67
90
|
EMPTY_ARRAY = [].freeze
|
68
91
|
|
69
92
|
def self.apply(model, opts=OPTS)
|
70
93
|
model.instance_exec do
|
71
94
|
plugin :validation_helpers
|
72
95
|
@auto_validate_presence = false
|
96
|
+
@auto_validate_no_null_byte_columns = []
|
73
97
|
@auto_validate_not_null_columns = []
|
74
98
|
@auto_validate_explicit_not_null_columns = []
|
75
99
|
@auto_validate_max_length_columns = []
|
100
|
+
@auto_validate_max_value_columns = []
|
101
|
+
@auto_validate_min_value_columns = []
|
76
102
|
@auto_validate_unique_columns = []
|
77
103
|
@auto_validate_types = true
|
78
|
-
|
79
|
-
@auto_validate_options = {
|
80
|
-
:not_null=>NOT_NULL_OPTIONS,
|
81
|
-
:explicit_not_null=>EXPLICIT_NOT_NULL_OPTIONS,
|
82
|
-
:max_length=>MAX_LENGTH_OPTIONS,
|
83
|
-
:schema_types=>SCHEMA_TYPES_OPTIONS,
|
84
|
-
:unique=>UNIQUE_OPTIONS
|
85
|
-
}.freeze
|
104
|
+
@auto_validate_options = AUTO_VALIDATE_OPTIONS
|
86
105
|
end
|
87
106
|
end
|
88
107
|
|
@@ -95,16 +114,26 @@ module Sequel
|
|
95
114
|
end
|
96
115
|
|
97
116
|
h = @auto_validate_options.dup
|
98
|
-
[:not_null, :explicit_not_null, :max_length, :schema_types, :unique].each do |type|
|
117
|
+
[:not_null, :explicit_not_null, :max_length, :max_value, :min_value, :no_null_byte, :schema_types, :unique].each do |type|
|
99
118
|
if type_opts = opts[:"#{type}_opts"]
|
100
119
|
h[type] = h[type].merge(type_opts).freeze
|
101
120
|
end
|
102
121
|
end
|
122
|
+
|
123
|
+
if opts[:skip_invalid]
|
124
|
+
[:not_null, :explicit_not_null, :no_null_byte, :max_length, :schema_types].each do |type|
|
125
|
+
h[type] = h[type].merge(:skip_invalid=>true).freeze
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
103
129
|
@auto_validate_options = h.freeze
|
104
130
|
end
|
105
131
|
end
|
106
132
|
|
107
133
|
module ClassMethods
|
134
|
+
# The columns with automatic no_null_byte validations
|
135
|
+
attr_reader :auto_validate_no_null_byte_columns
|
136
|
+
|
108
137
|
# The columns with automatic not_null validations
|
109
138
|
attr_reader :auto_validate_not_null_columns
|
110
139
|
|
@@ -115,13 +144,31 @@ module Sequel
|
|
115
144
|
# pairs, with the first entry being the column name and second entry being the maximum length.
|
116
145
|
attr_reader :auto_validate_max_length_columns
|
117
146
|
|
147
|
+
# The columns with automatch max value validations, as an array of
|
148
|
+
# pairs, with the first entry being the column name and second entry being the maximum value.
|
149
|
+
attr_reader :auto_validate_max_value_columns
|
150
|
+
|
151
|
+
# The columns with automatch min value validations, as an array of
|
152
|
+
# pairs, with the first entry being the column name and second entry being the minimum value.
|
153
|
+
attr_reader :auto_validate_min_value_columns
|
154
|
+
|
118
155
|
# The columns or sets of columns with automatic unique validations
|
119
156
|
attr_reader :auto_validate_unique_columns
|
120
157
|
|
121
158
|
# Inherited options
|
122
159
|
attr_reader :auto_validate_options
|
123
160
|
|
124
|
-
Plugins.inherited_instance_variables(self,
|
161
|
+
Plugins.inherited_instance_variables(self,
|
162
|
+
:@auto_validate_presence=>nil,
|
163
|
+
:@auto_validate_types=>nil,
|
164
|
+
:@auto_validate_no_null_byte_columns=>:dup,
|
165
|
+
:@auto_validate_not_null_columns=>:dup,
|
166
|
+
:@auto_validate_explicit_not_null_columns=>:dup,
|
167
|
+
:@auto_validate_max_length_columns=>:dup,
|
168
|
+
:@auto_validate_max_value_columns=>:dup,
|
169
|
+
:@auto_validate_min_value_columns=>:dup,
|
170
|
+
:@auto_validate_unique_columns=>:dup,
|
171
|
+
:@auto_validate_options => :dup)
|
125
172
|
Plugins.after_set_dataset(self, :setup_auto_validations)
|
126
173
|
|
127
174
|
# Whether to use a presence validation for not null columns
|
@@ -136,20 +183,27 @@ module Sequel
|
|
136
183
|
|
137
184
|
# Freeze auto_validation settings when freezing model class.
|
138
185
|
def freeze
|
186
|
+
@auto_validate_no_null_byte_columns.freeze
|
139
187
|
@auto_validate_not_null_columns.freeze
|
140
188
|
@auto_validate_explicit_not_null_columns.freeze
|
141
189
|
@auto_validate_max_length_columns.freeze
|
190
|
+
@auto_validate_max_value_columns.freeze
|
191
|
+
@auto_validate_min_value_columns.freeze
|
142
192
|
@auto_validate_unique_columns.freeze
|
143
193
|
|
144
194
|
super
|
145
195
|
end
|
146
196
|
|
147
|
-
# Skip automatic validations for the given validation type
|
197
|
+
# Skip automatic validations for the given validation type
|
198
|
+
# (:not_null, :no_null_byte, :types, :unique, :max_length, :max_value, :min_value).
|
148
199
|
# If :all is given as the type, skip all auto validations.
|
200
|
+
#
|
201
|
+
# Skipping types validation automatically skips max_value and min_value validations,
|
202
|
+
# since those validations require valid types.
|
149
203
|
def skip_auto_validations(type)
|
150
204
|
case type
|
151
205
|
when :all
|
152
|
-
[:not_null, :types, :unique, :max_length].each{|v| skip_auto_validations(v)}
|
206
|
+
[:not_null, :no_null_byte, :types, :unique, :max_length, :max_value, :min_value].each{|v| skip_auto_validations(v)}
|
153
207
|
when :not_null
|
154
208
|
auto_validate_not_null_columns.clear
|
155
209
|
auto_validate_explicit_not_null_columns.clear
|
@@ -169,6 +223,9 @@ module Sequel
|
|
169
223
|
explicit_not_null_cols += Array(primary_key)
|
170
224
|
@auto_validate_explicit_not_null_columns = explicit_not_null_cols.uniq
|
171
225
|
@auto_validate_max_length_columns = db_schema.select{|col, sch| sch[:type] == :string && sch[:max_length].is_a?(Integer)}.map{|col, sch| [col, sch[:max_length]]}
|
226
|
+
@auto_validate_max_value_columns = db_schema.select{|col, sch| sch[:max_value]}.map{|col, sch| [col, sch[:max_value]]}
|
227
|
+
@auto_validate_min_value_columns = db_schema.select{|col, sch| sch[:min_value]}.map{|col, sch| [col, sch[:min_value]]}
|
228
|
+
@auto_validate_no_null_byte_columns = db_schema.select{|_, sch| sch[:type] == :string}.map{|col, _| col}
|
172
229
|
table = dataset.first_source_table
|
173
230
|
@auto_validate_unique_columns = if db.supports_index_parsing? && [Symbol, SQL::QualifiedIdentifier, SQL::Identifier, String].any?{|c| table.is_a?(c)}
|
174
231
|
db.indexes(table).select{|name, idx| idx[:unique] == true}.map{|name, idx| idx[:columns].length == 1 ? idx[:columns].first : idx[:columns]}
|
@@ -195,6 +252,9 @@ module Sequel
|
|
195
252
|
return if skip.include?(:all)
|
196
253
|
opts = model.auto_validate_options
|
197
254
|
|
255
|
+
unless skip.include?(:no_null_byte) || (no_null_byte_columns = model.auto_validate_no_null_byte_columns).empty?
|
256
|
+
validates_no_null_byte(no_null_byte_columns, opts[:no_null_byte])
|
257
|
+
end
|
198
258
|
|
199
259
|
unless skip.include?(:not_null)
|
200
260
|
not_null_method = model.auto_validate_presence? ? :validates_presence : :validates_not_null
|
@@ -214,6 +274,18 @@ module Sequel
|
|
214
274
|
|
215
275
|
unless skip.include?(:types) || !model.auto_validate_types?
|
216
276
|
validates_schema_types(keys, opts[:schema_types])
|
277
|
+
|
278
|
+
unless skip.include?(:max_value) || ((max_value_columns = model.auto_validate_max_value_columns).empty?)
|
279
|
+
max_value_columns.each do |col, max|
|
280
|
+
validates_max_value(max, col, opts[:max_value])
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
unless skip.include?(:min_value) || ((min_value_columns = model.auto_validate_min_value_columns).empty?)
|
285
|
+
min_value_columns.each do |col, min|
|
286
|
+
validates_min_value(min, col, opts[:min_value])
|
287
|
+
end
|
288
|
+
end
|
217
289
|
end
|
218
290
|
|
219
291
|
unless skip.include?(:unique)
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Plugins
|
5
|
+
# The auto_validations_constraint_validations_presence_message plugin provides
|
6
|
+
# integration for the auto_validations and constraint_validations plugins in
|
7
|
+
# the following situation:
|
8
|
+
#
|
9
|
+
# * A column has a NOT NULL constraint in the database
|
10
|
+
# * A constraint validation for presence exists on the column, with a :message
|
11
|
+
# option to set a column-specific message, and with the :allow_nil option set
|
12
|
+
# to true because the CHECK constraint doesn't need to check for NULL values
|
13
|
+
# as the column itself is NOT NULL
|
14
|
+
#
|
15
|
+
# In this case, by default the validation error message on the column will
|
16
|
+
# use the more specific constraint validation error message if the column
|
17
|
+
# has a non-NULL empty value, but will use the default auto_validations
|
18
|
+
# message if the column has a NULL value. With this plugin, the column-specific
|
19
|
+
# constraint validation error message will be used in both cases.
|
20
|
+
#
|
21
|
+
# Usage:
|
22
|
+
#
|
23
|
+
# # Make all model subclasses use this auto_validations/constraint_validations
|
24
|
+
# # integration (called before loading subclasses)
|
25
|
+
# Sequel::Model.plugin :auto_validations_constraint_validations_presence_message
|
26
|
+
#
|
27
|
+
# # Make the Album class use this auto_validations/constraint_validations integration
|
28
|
+
# Album.plugin :auto_validations_constraint_validations_presence_message
|
29
|
+
module AutoValidationsConstraintValidationsPresenceMessage
|
30
|
+
def self.apply(model)
|
31
|
+
model.plugin :auto_validations
|
32
|
+
model.plugin :constraint_validations
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.configure(model, opts=OPTS)
|
36
|
+
model.send(:_adjust_auto_validations_constraint_validations_presence_message)
|
37
|
+
end
|
38
|
+
|
39
|
+
module ClassMethods
|
40
|
+
Plugins.after_set_dataset(self, :_adjust_auto_validations_constraint_validations_presence_message)
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def _adjust_auto_validations_constraint_validations_presence_message
|
45
|
+
if @dataset &&
|
46
|
+
!@auto_validate_options[:not_null][:message] &&
|
47
|
+
!@auto_validate_options[:explicit_not_null][:message]
|
48
|
+
|
49
|
+
@constraint_validations.each do |array|
|
50
|
+
meth, column, opts = array
|
51
|
+
|
52
|
+
if meth == :validates_presence &&
|
53
|
+
opts &&
|
54
|
+
opts[:message] &&
|
55
|
+
opts[:allow_nil] &&
|
56
|
+
(@auto_validate_not_null_columns.include?(column) || @auto_validate_explicit_not_null_columns.include?(column))
|
57
|
+
|
58
|
+
@auto_validate_not_null_columns.delete(column)
|
59
|
+
@auto_validate_explicit_not_null_columns.delete(column)
|
60
|
+
array[2] = array[2].merge(:allow_nil=>false)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -104,7 +104,7 @@ module Sequel
|
|
104
104
|
# should reference the subquery alias (and qualified identifiers should not be needed
|
105
105
|
# unless joining to another table):
|
106
106
|
#
|
107
|
-
# a = Executive.where(:
|
107
|
+
# a = Executive.where(id: 1).first # works
|
108
108
|
# a = Executive.where{{employees[:id]=>1}}.first # works
|
109
109
|
# a = Executive.where{{executives[:id]=>1}}.first # doesn't work
|
110
110
|
#
|
@@ -115,7 +115,7 @@ module Sequel
|
|
115
115
|
#
|
116
116
|
# pks = Executive.where{num_staff < 10}.select_map(:id)
|
117
117
|
# Executive.cti_tables.reverse_each do |table|
|
118
|
-
# DB.from(table).where(:
|
118
|
+
# DB.from(table).where(id: pks).delete
|
119
119
|
# end
|
120
120
|
#
|
121
121
|
# = Usage
|