sequel 5.83.0 → 5.106.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/MIT-LICENSE +1 -1
- data/lib/sequel/adapters/ado/access.rb +2 -2
- data/lib/sequel/adapters/ado.rb +2 -2
- data/lib/sequel/adapters/amalgalite.rb +16 -1
- data/lib/sequel/adapters/ibmdb.rb +2 -1
- data/lib/sequel/adapters/jdbc/db2.rb +2 -2
- data/lib/sequel/adapters/jdbc/derby.rb +4 -2
- data/lib/sequel/adapters/jdbc/h2.rb +4 -4
- data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -2
- data/lib/sequel/adapters/jdbc/jtds.rb +2 -2
- data/lib/sequel/adapters/jdbc/mysql.rb +3 -3
- data/lib/sequel/adapters/jdbc/oracle.rb +6 -6
- data/lib/sequel/adapters/jdbc/postgresql.rb +5 -5
- data/lib/sequel/adapters/jdbc/sqlanywhere.rb +6 -6
- data/lib/sequel/adapters/jdbc/sqlite.rb +3 -3
- data/lib/sequel/adapters/jdbc/sqlserver.rb +3 -3
- data/lib/sequel/adapters/jdbc.rb +29 -15
- data/lib/sequel/adapters/mysql.rb +1 -1
- data/lib/sequel/adapters/mysql2.rb +10 -3
- data/lib/sequel/adapters/odbc.rb +1 -1
- data/lib/sequel/adapters/oracle.rb +16 -0
- data/lib/sequel/adapters/postgres.rb +2 -1
- data/lib/sequel/adapters/shared/access.rb +1 -0
- data/lib/sequel/adapters/shared/db2.rb +8 -3
- data/lib/sequel/adapters/shared/mssql.rb +9 -8
- data/lib/sequel/adapters/shared/mysql.rb +16 -11
- data/lib/sequel/adapters/shared/oracle.rb +1 -0
- data/lib/sequel/adapters/shared/postgres.rb +438 -47
- data/lib/sequel/adapters/shared/sqlite.rb +9 -7
- data/lib/sequel/adapters/sqlite.rb +20 -2
- data/lib/sequel/adapters/tinytds.rb +2 -2
- data/lib/sequel/adapters/trilogy.rb +2 -3
- data/lib/sequel/ast_transformer.rb +2 -0
- data/lib/sequel/connection_pool/sharded_timed_queue.rb +42 -11
- data/lib/sequel/connection_pool/timed_queue.rb +33 -10
- data/lib/sequel/connection_pool.rb +8 -2
- data/lib/sequel/core.rb +31 -2
- data/lib/sequel/database/connecting.rb +1 -1
- data/lib/sequel/database/dataset_defaults.rb +3 -3
- data/lib/sequel/database/misc.rb +29 -11
- data/lib/sequel/database/query.rb +12 -12
- data/lib/sequel/database/schema_generator.rb +117 -16
- data/lib/sequel/database/schema_methods.rb +85 -30
- data/lib/sequel/dataset/actions.rb +83 -11
- data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +1 -1
- data/lib/sequel/dataset/placeholder_literalizer.rb +3 -0
- data/lib/sequel/dataset/prepared_statements.rb +76 -28
- data/lib/sequel/dataset/query.rb +26 -11
- data/lib/sequel/dataset/sql.rb +40 -17
- data/lib/sequel/extensions/async_thread_pool.rb +1 -1
- data/lib/sequel/extensions/caller_logging.rb +1 -3
- data/lib/sequel/extensions/connection_checkout_event_callback.rb +151 -0
- data/lib/sequel/extensions/connection_expiration.rb +44 -5
- data/lib/sequel/extensions/connection_validator.rb +15 -10
- data/lib/sequel/extensions/constraint_validations.rb +3 -3
- data/lib/sequel/extensions/dataset_run.rb +41 -0
- data/lib/sequel/extensions/date_arithmetic.rb +6 -6
- data/lib/sequel/extensions/empty_array_consider_nulls.rb +7 -0
- data/lib/sequel/extensions/eval_inspect.rb +3 -1
- data/lib/sequel/extensions/inflector.rb +2 -2
- data/lib/sequel/extensions/lit_require_frozen.rb +131 -0
- data/lib/sequel/extensions/looser_typecasting.rb +5 -12
- data/lib/sequel/extensions/migration.rb +38 -21
- data/lib/sequel/extensions/null_dataset.rb +2 -2
- data/lib/sequel/extensions/pg_array_ops.rb +41 -0
- data/lib/sequel/extensions/pg_auto_parameterize.rb +10 -3
- data/lib/sequel/extensions/pg_auto_parameterize_duplicate_query_detection.rb +191 -0
- data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +94 -10
- data/lib/sequel/extensions/pg_enum.rb +4 -4
- data/lib/sequel/extensions/pg_hstore.rb +1 -1
- data/lib/sequel/extensions/pg_hstore_ops.rb +10 -5
- data/lib/sequel/extensions/pg_json_ops.rb +674 -18
- data/lib/sequel/extensions/pg_multirange.rb +1 -1
- data/lib/sequel/extensions/pg_range.rb +14 -2
- data/lib/sequel/extensions/pg_row.rb +5 -3
- data/lib/sequel/extensions/pg_schema_caching.rb +90 -0
- data/lib/sequel/extensions/provenance.rb +1 -3
- data/lib/sequel/extensions/query_blocker.rb +172 -0
- data/lib/sequel/extensions/schema_caching.rb +24 -9
- data/lib/sequel/extensions/schema_dumper.rb +17 -5
- data/lib/sequel/extensions/set_literalizer.rb +20 -39
- data/lib/sequel/extensions/split_array_nil.rb +12 -2
- data/lib/sequel/extensions/sqlite_json_ops.rb +1 -1
- data/lib/sequel/extensions/string_agg.rb +2 -2
- data/lib/sequel/extensions/to_dot.rb +5 -0
- data/lib/sequel/extensions/virtual_row_method_block.rb +1 -0
- data/lib/sequel/model/associations.rb +215 -15
- data/lib/sequel/model/base.rb +198 -35
- data/lib/sequel/model/plugins.rb +12 -1
- data/lib/sequel/model.rb +1 -0
- data/lib/sequel/plugins/accessed_columns.rb +4 -0
- data/lib/sequel/plugins/active_model.rb +5 -6
- data/lib/sequel/plugins/association_pks.rb +2 -0
- data/lib/sequel/plugins/auto_validations.rb +2 -0
- data/lib/sequel/plugins/class_table_inheritance_constraint_validations.rb +82 -0
- data/lib/sequel/plugins/columns_updated.rb +4 -0
- data/lib/sequel/plugins/composition.rb +3 -1
- data/lib/sequel/plugins/constraint_validations.rb +15 -10
- data/lib/sequel/plugins/dataset_associations.rb +20 -1
- data/lib/sequel/plugins/deprecated_associations.rb +151 -0
- data/lib/sequel/plugins/detect_unnecessary_association_options.rb +164 -0
- data/lib/sequel/plugins/dirty.rb +9 -2
- data/lib/sequel/plugins/enum.rb +1 -1
- data/lib/sequel/plugins/forbid_lazy_load.rb +15 -1
- data/lib/sequel/plugins/insert_conflict.rb +4 -0
- data/lib/sequel/plugins/insert_returning_select.rb +10 -1
- data/lib/sequel/plugins/inspect_pk.rb +44 -0
- data/lib/sequel/plugins/instance_filters.rb +8 -1
- data/lib/sequel/plugins/instance_hooks.rb +4 -0
- data/lib/sequel/plugins/inverted_subsets.rb +1 -0
- data/lib/sequel/plugins/json_serializer.rb +6 -10
- data/lib/sequel/plugins/lazy_attributes.rb +1 -1
- data/lib/sequel/plugins/many_through_many.rb +21 -0
- data/lib/sequel/plugins/modification_detection.rb +2 -0
- data/lib/sequel/plugins/mssql_optimistic_locking.rb +1 -1
- data/lib/sequel/plugins/nested_attributes.rb +10 -5
- data/lib/sequel/plugins/paged_operations.rb +5 -2
- data/lib/sequel/plugins/pg_array_associations.rb +2 -2
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +6 -1
- data/lib/sequel/plugins/pg_auto_validate_enums.rb +88 -0
- data/lib/sequel/plugins/pg_eager_any_typed_array.rb +95 -0
- data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +1 -1
- data/lib/sequel/plugins/rcte_tree.rb +6 -6
- data/lib/sequel/plugins/serialization.rb +23 -6
- data/lib/sequel/plugins/serialization_modification_detection.rb +4 -0
- data/lib/sequel/plugins/single_statement_dataset_destroy.rb +49 -0
- data/lib/sequel/plugins/split_values.rb +12 -0
- data/lib/sequel/plugins/sql_comments.rb +7 -2
- data/lib/sequel/plugins/static_cache.rb +13 -0
- data/lib/sequel/plugins/static_cache_cache.rb +50 -13
- data/lib/sequel/plugins/subset_conditions.rb +85 -5
- data/lib/sequel/plugins/subset_static_cache.rb +278 -0
- data/lib/sequel/plugins/table_select.rb +7 -0
- data/lib/sequel/plugins/tactical_eager_loading.rb +4 -0
- data/lib/sequel/plugins/unused_associations.rb +11 -3
- data/lib/sequel/plugins/update_primary_key.rb +4 -0
- data/lib/sequel/plugins/validation_class_methods.rb +1 -1
- data/lib/sequel/plugins/validation_contexts.rb +4 -0
- data/lib/sequel/sql.rb +27 -12
- data/lib/sequel/version.rb +1 -1
- metadata +17 -240
- data/CHANGELOG +0 -1393
- data/README.rdoc +0 -936
- data/doc/advanced_associations.rdoc +0 -884
- data/doc/association_basics.rdoc +0 -1859
- data/doc/bin_sequel.rdoc +0 -146
- data/doc/cheat_sheet.rdoc +0 -255
- data/doc/code_order.rdoc +0 -104
- data/doc/core_extensions.rdoc +0 -405
- data/doc/dataset_basics.rdoc +0 -96
- data/doc/dataset_filtering.rdoc +0 -222
- data/doc/extensions.rdoc +0 -77
- data/doc/fork_safety.rdoc +0 -84
- data/doc/mass_assignment.rdoc +0 -98
- data/doc/migration.rdoc +0 -660
- data/doc/model_dataset_method_design.rdoc +0 -129
- data/doc/model_hooks.rdoc +0 -254
- data/doc/model_plugins.rdoc +0 -270
- data/doc/mssql_stored_procedures.rdoc +0 -43
- data/doc/object_model.rdoc +0 -563
- data/doc/opening_databases.rdoc +0 -439
- data/doc/postgresql.rdoc +0 -611
- data/doc/prepared_statements.rdoc +0 -144
- data/doc/querying.rdoc +0 -1070
- data/doc/reflection.rdoc +0 -120
- data/doc/release_notes/5.0.0.txt +0 -159
- data/doc/release_notes/5.1.0.txt +0 -31
- data/doc/release_notes/5.10.0.txt +0 -84
- data/doc/release_notes/5.11.0.txt +0 -83
- data/doc/release_notes/5.12.0.txt +0 -141
- data/doc/release_notes/5.13.0.txt +0 -27
- data/doc/release_notes/5.14.0.txt +0 -63
- data/doc/release_notes/5.15.0.txt +0 -39
- data/doc/release_notes/5.16.0.txt +0 -110
- data/doc/release_notes/5.17.0.txt +0 -31
- data/doc/release_notes/5.18.0.txt +0 -69
- data/doc/release_notes/5.19.0.txt +0 -28
- data/doc/release_notes/5.2.0.txt +0 -33
- data/doc/release_notes/5.20.0.txt +0 -89
- data/doc/release_notes/5.21.0.txt +0 -87
- data/doc/release_notes/5.22.0.txt +0 -48
- data/doc/release_notes/5.23.0.txt +0 -56
- data/doc/release_notes/5.24.0.txt +0 -56
- data/doc/release_notes/5.25.0.txt +0 -32
- data/doc/release_notes/5.26.0.txt +0 -35
- data/doc/release_notes/5.27.0.txt +0 -21
- data/doc/release_notes/5.28.0.txt +0 -16
- data/doc/release_notes/5.29.0.txt +0 -22
- data/doc/release_notes/5.3.0.txt +0 -121
- data/doc/release_notes/5.30.0.txt +0 -20
- data/doc/release_notes/5.31.0.txt +0 -148
- data/doc/release_notes/5.32.0.txt +0 -46
- data/doc/release_notes/5.33.0.txt +0 -24
- data/doc/release_notes/5.34.0.txt +0 -40
- data/doc/release_notes/5.35.0.txt +0 -56
- data/doc/release_notes/5.36.0.txt +0 -60
- data/doc/release_notes/5.37.0.txt +0 -30
- data/doc/release_notes/5.38.0.txt +0 -28
- data/doc/release_notes/5.39.0.txt +0 -19
- data/doc/release_notes/5.4.0.txt +0 -80
- data/doc/release_notes/5.40.0.txt +0 -40
- data/doc/release_notes/5.41.0.txt +0 -25
- data/doc/release_notes/5.42.0.txt +0 -136
- data/doc/release_notes/5.43.0.txt +0 -98
- data/doc/release_notes/5.44.0.txt +0 -32
- data/doc/release_notes/5.45.0.txt +0 -34
- data/doc/release_notes/5.46.0.txt +0 -87
- data/doc/release_notes/5.47.0.txt +0 -59
- data/doc/release_notes/5.48.0.txt +0 -14
- data/doc/release_notes/5.49.0.txt +0 -59
- data/doc/release_notes/5.5.0.txt +0 -61
- data/doc/release_notes/5.50.0.txt +0 -78
- data/doc/release_notes/5.51.0.txt +0 -47
- data/doc/release_notes/5.52.0.txt +0 -87
- data/doc/release_notes/5.53.0.txt +0 -23
- data/doc/release_notes/5.54.0.txt +0 -27
- data/doc/release_notes/5.55.0.txt +0 -21
- data/doc/release_notes/5.56.0.txt +0 -51
- data/doc/release_notes/5.57.0.txt +0 -23
- data/doc/release_notes/5.58.0.txt +0 -31
- data/doc/release_notes/5.59.0.txt +0 -73
- data/doc/release_notes/5.6.0.txt +0 -31
- data/doc/release_notes/5.60.0.txt +0 -22
- data/doc/release_notes/5.61.0.txt +0 -43
- data/doc/release_notes/5.62.0.txt +0 -132
- data/doc/release_notes/5.63.0.txt +0 -33
- data/doc/release_notes/5.64.0.txt +0 -50
- data/doc/release_notes/5.65.0.txt +0 -21
- data/doc/release_notes/5.66.0.txt +0 -24
- data/doc/release_notes/5.67.0.txt +0 -32
- data/doc/release_notes/5.68.0.txt +0 -61
- data/doc/release_notes/5.69.0.txt +0 -26
- data/doc/release_notes/5.7.0.txt +0 -108
- data/doc/release_notes/5.70.0.txt +0 -35
- data/doc/release_notes/5.71.0.txt +0 -21
- data/doc/release_notes/5.72.0.txt +0 -33
- data/doc/release_notes/5.73.0.txt +0 -66
- data/doc/release_notes/5.74.0.txt +0 -45
- data/doc/release_notes/5.75.0.txt +0 -35
- data/doc/release_notes/5.76.0.txt +0 -86
- data/doc/release_notes/5.77.0.txt +0 -63
- data/doc/release_notes/5.78.0.txt +0 -67
- data/doc/release_notes/5.79.0.txt +0 -28
- data/doc/release_notes/5.8.0.txt +0 -170
- data/doc/release_notes/5.80.0.txt +0 -40
- data/doc/release_notes/5.81.0.txt +0 -31
- data/doc/release_notes/5.82.0.txt +0 -61
- data/doc/release_notes/5.83.0.txt +0 -56
- data/doc/release_notes/5.9.0.txt +0 -99
- data/doc/schema_modification.rdoc +0 -679
- data/doc/security.rdoc +0 -443
- data/doc/sharding.rdoc +0 -286
- data/doc/sql.rdoc +0 -648
- data/doc/testing.rdoc +0 -204
- data/doc/thread_safety.rdoc +0 -15
- data/doc/transactions.rdoc +0 -250
- data/doc/validations.rdoc +0 -558
- data/doc/virtual_rows.rdoc +0 -265
|
@@ -421,6 +421,18 @@ module Sequel
|
|
|
421
421
|
@exclude_end
|
|
422
422
|
end
|
|
423
423
|
|
|
424
|
+
# Support a friendly output
|
|
425
|
+
def inspect
|
|
426
|
+
range = if empty?
|
|
427
|
+
"empty"
|
|
428
|
+
else
|
|
429
|
+
"#{@exclude_begin ? "(" : "["}#{@begin},#{@end}#{@exclude_end ? ")" : "]"}"
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
"#<#{self.class.name} #{range}#{"::#{@db_type}" if @db_type}>"
|
|
433
|
+
end
|
|
434
|
+
alias to_s inspect
|
|
435
|
+
|
|
424
436
|
# Append a literalize version of the receiver to the sql.
|
|
425
437
|
def sql_literal_append(ds, sql)
|
|
426
438
|
if (s = @db_type) && !empty?
|
|
@@ -481,9 +493,9 @@ module Sequel
|
|
|
481
493
|
end
|
|
482
494
|
end
|
|
483
495
|
|
|
484
|
-
# Allow automatic parameterization for ranges with types.
|
|
496
|
+
# Allow automatic parameterization for ranges with types, if both start .
|
|
485
497
|
def sequel_auto_param_type(ds)
|
|
486
|
-
"::#{db_type}" if db_type
|
|
498
|
+
"::#{db_type}" if db_type && (!@begin || ds.send(:auto_param_type, @begin)) && (!@end || ds.send(:auto_param_type, @end))
|
|
487
499
|
end
|
|
488
500
|
|
|
489
501
|
private
|
|
@@ -113,6 +113,7 @@ module Sequel
|
|
|
113
113
|
# automatically casted to the database type when literalizing.
|
|
114
114
|
def self.subclass(db_type)
|
|
115
115
|
Class.new(self) do
|
|
116
|
+
Sequel.set_temp_name(self){"Sequel::Postgres::PGRow::ArrayRow::_Subclass(#{db_type})"}
|
|
116
117
|
@db_type = db_type
|
|
117
118
|
end
|
|
118
119
|
end
|
|
@@ -170,6 +171,7 @@ module Sequel
|
|
|
170
171
|
# type and columns.
|
|
171
172
|
def self.subclass(db_type, columns)
|
|
172
173
|
Class.new(self) do
|
|
174
|
+
Sequel.set_temp_name(self){"Sequel::Postgres::PGRow::HashRow::_Subclass(#{db_type})"}
|
|
173
175
|
@db_type = db_type
|
|
174
176
|
@columns = columns
|
|
175
177
|
end
|
|
@@ -317,8 +319,8 @@ module Sequel
|
|
|
317
319
|
end
|
|
318
320
|
|
|
319
321
|
# Typecast the given object to the appropriate type using the
|
|
320
|
-
# typecaster. Note that this does
|
|
321
|
-
# of the composite type, since those
|
|
322
|
+
# typecaster. Note that this does no conversion for the members
|
|
323
|
+
# of the composite type, since those conversions expect strings and
|
|
322
324
|
# strings may not be provided.
|
|
323
325
|
def typecast(obj)
|
|
324
326
|
case obj
|
|
@@ -391,7 +393,7 @@ module Sequel
|
|
|
391
393
|
db.instance_exec do
|
|
392
394
|
@row_types = {}
|
|
393
395
|
@row_schema_types = {}
|
|
394
|
-
extend(@row_type_method_module = Module.new)
|
|
396
|
+
extend(@row_type_method_module = Sequel.set_temp_name(Module.new){"Sequel::Postgres::PGRow::DatabaseMethods::_RowTypeMethodModule"})
|
|
395
397
|
add_conversion_proc(2249, PGRow::Parser.new(:converter=>PGRow::ArrayRow))
|
|
396
398
|
if respond_to?(:register_array_type)
|
|
397
399
|
register_array_type('record', :oid=>2287, :scalar_oid=>2249)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
#
|
|
3
|
+
# The pg_schema_caching extension builds on top of the schema_caching
|
|
4
|
+
# extension, and allows it to handle custom PostgreSQL types. On
|
|
5
|
+
# PostgreSQL, column schema hashes include an :oid entry for the OID
|
|
6
|
+
# for the column's type. For custom types, this OID is dependent on
|
|
7
|
+
# the PostgreSQL database, so in most cases, test and development
|
|
8
|
+
# versions of the same database, created with the same migrations,
|
|
9
|
+
# will have different OIDs.
|
|
10
|
+
#
|
|
11
|
+
# To fix this case, the pg_schema_caching extension removes custom
|
|
12
|
+
# OIDs from the schema cache when dumping the schema, replacing them
|
|
13
|
+
# with a placeholder. When loading the cached schema, the Database
|
|
14
|
+
# object makes a single query to get the OIDs for all custom types
|
|
15
|
+
# used by the cached schema, and it updates all related column
|
|
16
|
+
# schema hashes to set the correct :oid entry for the current
|
|
17
|
+
# database.
|
|
18
|
+
#
|
|
19
|
+
# Related module: Sequel::Postgres::SchemaCaching
|
|
20
|
+
|
|
21
|
+
require_relative "schema_caching"
|
|
22
|
+
|
|
23
|
+
module Sequel
|
|
24
|
+
module Postgres
|
|
25
|
+
module SchemaCaching
|
|
26
|
+
include Sequel::SchemaCaching
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
# Load custom oids from database when loading schema cache file.
|
|
31
|
+
def load_schema_cache_file(file)
|
|
32
|
+
set_custom_oids_for_cached_schema(super)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Find all column schema hashes that use custom types.
|
|
36
|
+
# Load the oids for custom types in a single query, and update
|
|
37
|
+
# each related column schema hash with the correct oid.
|
|
38
|
+
def set_custom_oids_for_cached_schema(schemas)
|
|
39
|
+
custom_oid_rows = {}
|
|
40
|
+
|
|
41
|
+
schemas.each_value do |cols|
|
|
42
|
+
cols.each do |_, h|
|
|
43
|
+
if h[:oid] == :custom
|
|
44
|
+
(custom_oid_rows[h[:db_type]] ||= []) << h
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
unless custom_oid_rows.empty?
|
|
50
|
+
from(:pg_type).where(:typname=>custom_oid_rows.keys).select_hash(:typname, :oid).each do |name, oid|
|
|
51
|
+
custom_oid_rows.delete(name).each do |row|
|
|
52
|
+
row[:oid] = oid
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
unless custom_oid_rows.empty?
|
|
58
|
+
warn "Could not load OIDs for the following custom types: #{custom_oid_rows.keys.sort.join(", ")}", uplevel: 3
|
|
59
|
+
|
|
60
|
+
schemas.keys.each do |k|
|
|
61
|
+
if schemas[k].any?{|_,h| h[:oid] == :custom}
|
|
62
|
+
# Remove schema entry for table, so it will be queried at runtime to get the correct oids
|
|
63
|
+
schemas.delete(k)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
schemas
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Replace :oid entries for custom types with :custom.
|
|
72
|
+
def dumpable_schema_cache
|
|
73
|
+
sch = super
|
|
74
|
+
|
|
75
|
+
sch.each_value do |cols|
|
|
76
|
+
cols.each do |_, h|
|
|
77
|
+
if (oid = h[:oid]) && oid >= 10000
|
|
78
|
+
h[:oid] = :custom
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
sch
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
Database.register_extension(:pg_schema_caching, Postgres::SchemaCaching)
|
|
89
|
+
end
|
|
90
|
+
|
|
@@ -97,9 +97,7 @@ module Sequel
|
|
|
97
97
|
def provenance_source
|
|
98
98
|
ignore = db.opts[:provenance_caller_ignore]
|
|
99
99
|
caller.find do |line|
|
|
100
|
-
!(line.start_with?(SEQUEL_LIB_PATH) ||
|
|
101
|
-
line.start_with?(RUBY_STDLIB) ||
|
|
102
|
-
line.start_with?(INTERNAL) ||
|
|
100
|
+
!(line.start_with?(SEQUEL_LIB_PATH, RUBY_STDLIB, INTERNAL) ||
|
|
103
101
|
(ignore && line =~ ignore))
|
|
104
102
|
end
|
|
105
103
|
end
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
#
|
|
3
|
+
# The query_blocker extension adds Database#block_queries.
|
|
4
|
+
# Inside the block passed to #block_queries, any attempts to
|
|
5
|
+
# execute a query/statement on the database will raise a
|
|
6
|
+
# Sequel::QueryBlocker::BlockedQuery exception.
|
|
7
|
+
#
|
|
8
|
+
# DB.extension :query_blocker
|
|
9
|
+
# DB.block_queries do
|
|
10
|
+
# ds = DB[:table] # No exception
|
|
11
|
+
# ds = ds.where(column: 1) # No exception
|
|
12
|
+
# ds.all # Exception raised
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# To handle concurrency, you can pass a :scope option:
|
|
16
|
+
#
|
|
17
|
+
# # Current Thread
|
|
18
|
+
# DB.block_queries(scope: :thread){}
|
|
19
|
+
#
|
|
20
|
+
# # Current Fiber
|
|
21
|
+
# DB.block_queries(scope: :fiber){}
|
|
22
|
+
#
|
|
23
|
+
# # Specific Thread
|
|
24
|
+
# DB.block_queries(scope: Thread.current){}
|
|
25
|
+
#
|
|
26
|
+
# # Specific Fiber
|
|
27
|
+
# DB.block_queries(scope: Fiber.current){}
|
|
28
|
+
#
|
|
29
|
+
# Database#block_queries is useful for blocking queries inside
|
|
30
|
+
# the block. However, there may be cases where you want to
|
|
31
|
+
# allow queries in specific places inside a block_queries block.
|
|
32
|
+
# You can use Database#allow_queries for that:
|
|
33
|
+
#
|
|
34
|
+
# DB.block_queries do
|
|
35
|
+
# DB.allow_queries do
|
|
36
|
+
# DB[:table].all # Query allowed
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# DB[:table].all # Exception raised
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
42
|
+
# When mixing block_queries and allow_queries with scopes, the
|
|
43
|
+
# narrowest scope has priority. So if you are blocking with
|
|
44
|
+
# :thread scope, and allowing with :fiber scope, queries in the
|
|
45
|
+
# current fiber will be allowed, but queries in different fibers of
|
|
46
|
+
# the current thread will be blocked.
|
|
47
|
+
#
|
|
48
|
+
# Note that this should catch all queries executed through the
|
|
49
|
+
# Database instance. Whether it catches queries executed directly
|
|
50
|
+
# on a connection object depends on the adapter in use.
|
|
51
|
+
#
|
|
52
|
+
# Related module: Sequel::QueryBlocker
|
|
53
|
+
|
|
54
|
+
require "fiber"
|
|
55
|
+
|
|
56
|
+
#
|
|
57
|
+
module Sequel
|
|
58
|
+
module QueryBlocker
|
|
59
|
+
# Exception class raised if there is an attempt to execute a
|
|
60
|
+
# query/statement on the database inside a block passed to
|
|
61
|
+
# block_queries.
|
|
62
|
+
class BlockedQuery < Sequel::Error
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.extended(db)
|
|
66
|
+
db.instance_exec do
|
|
67
|
+
@blocked_query_scopes ||= {}
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# If checking a connection for validity, and a BlockedQuery exception is
|
|
72
|
+
# raised, treat it as a valid connection. You cannot check whether the
|
|
73
|
+
# connection is valid without issuing a query, and if queries are blocked,
|
|
74
|
+
# you need to assume it is valid or assume it is not. Since it most cases
|
|
75
|
+
# it will be valid, this assumes validity.
|
|
76
|
+
def valid_connection?(conn)
|
|
77
|
+
super
|
|
78
|
+
rescue BlockedQuery
|
|
79
|
+
true
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Check whether queries are blocked before executing them.
|
|
83
|
+
def log_connection_yield(sql, conn, args=nil)
|
|
84
|
+
# All database adapters should be calling this method around
|
|
85
|
+
# query execution (otherwise the queries would not get logged),
|
|
86
|
+
# ensuring the blocking is checked. Any database adapter issuing
|
|
87
|
+
# a query without calling this method is considered buggy.
|
|
88
|
+
check_blocked_queries!
|
|
89
|
+
super
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Whether queries are currently blocked.
|
|
93
|
+
def block_queries?
|
|
94
|
+
b = @blocked_query_scopes
|
|
95
|
+
b.fetch(Fiber.current) do
|
|
96
|
+
b.fetch(Thread.current) do
|
|
97
|
+
b.fetch(:global, false)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Allow queries inside the block. Only useful if they are already blocked
|
|
103
|
+
# for the same scope. Useful for blocking queries generally, and only allowing
|
|
104
|
+
# them in specific places. Takes the same :scope option as #block_queries.
|
|
105
|
+
def allow_queries(opts=OPTS, &block)
|
|
106
|
+
_allow_or_block_queries(false, opts, &block)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Reject (raise an BlockedQuery exception) if there is an attempt to execute
|
|
110
|
+
# a query/statement inside the block.
|
|
111
|
+
#
|
|
112
|
+
# The :scope option indicates which queries are rejected inside the block:
|
|
113
|
+
#
|
|
114
|
+
# :global :: This is the default, and rejects all queries.
|
|
115
|
+
# :thread :: Reject all queries in the current thread.
|
|
116
|
+
# :fiber :: Reject all queries in the current fiber.
|
|
117
|
+
# Thread :: Reject all queries in the given thread.
|
|
118
|
+
# Fiber :: Reject all queries in the given fiber.
|
|
119
|
+
def block_queries(opts=OPTS, &block)
|
|
120
|
+
_allow_or_block_queries(true, opts, &block)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
private
|
|
124
|
+
|
|
125
|
+
# Internals of block_queries and allow_queries.
|
|
126
|
+
def _allow_or_block_queries(value, opts)
|
|
127
|
+
scope = query_blocker_scope(opts)
|
|
128
|
+
prev_value = nil
|
|
129
|
+
scopes = @blocked_query_scopes
|
|
130
|
+
|
|
131
|
+
begin
|
|
132
|
+
Sequel.synchronize do
|
|
133
|
+
prev_value = scopes[scope]
|
|
134
|
+
scopes[scope] = value
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
yield
|
|
138
|
+
ensure
|
|
139
|
+
Sequel.synchronize do
|
|
140
|
+
if prev_value.nil?
|
|
141
|
+
scopes.delete(scope)
|
|
142
|
+
else
|
|
143
|
+
scopes[scope] = prev_value
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# The scope for the query block, either :global, or a Thread or Fiber instance.
|
|
150
|
+
def query_blocker_scope(opts)
|
|
151
|
+
case scope = opts[:scope]
|
|
152
|
+
when nil
|
|
153
|
+
:global
|
|
154
|
+
when :global, Thread, Fiber
|
|
155
|
+
scope
|
|
156
|
+
when :thread
|
|
157
|
+
Thread.current
|
|
158
|
+
when :fiber
|
|
159
|
+
Fiber.current
|
|
160
|
+
else
|
|
161
|
+
raise Sequel::Error, "invalid scope given to block_queries: #{scope.inspect}"
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Raise a BlockQuery exception if queries are currently blocked.
|
|
166
|
+
def check_blocked_queries!
|
|
167
|
+
raise BlockedQuery, "cannot execute query inside a block_queries block" if block_queries?
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
Database.register_extension(:query_blocker, QueryBlocker)
|
|
172
|
+
end
|
|
@@ -51,14 +51,7 @@ module Sequel
|
|
|
51
51
|
module SchemaCaching
|
|
52
52
|
# Dump the cached schema to the filename given in Marshal format.
|
|
53
53
|
def dump_schema_cache(file)
|
|
54
|
-
sch =
|
|
55
|
-
@schemas.sort.each do |k,v|
|
|
56
|
-
sch[k] = v.map do |c, h|
|
|
57
|
-
h = Hash[h]
|
|
58
|
-
h.delete(:callable_default)
|
|
59
|
-
[c, h]
|
|
60
|
-
end
|
|
61
|
-
end
|
|
54
|
+
sch = dumpable_schema_cache
|
|
62
55
|
File.open(file, 'wb'){|f| f.write(Marshal.dump(sch))}
|
|
63
56
|
nil
|
|
64
57
|
end
|
|
@@ -72,7 +65,7 @@ module Sequel
|
|
|
72
65
|
# Replace the schema cache with the data from the given file, which
|
|
73
66
|
# should be in Marshal format.
|
|
74
67
|
def load_schema_cache(file)
|
|
75
|
-
@schemas =
|
|
68
|
+
@schemas = load_schema_cache_file(file)
|
|
76
69
|
@schemas.each_value{|v| schema_post_process(v)}
|
|
77
70
|
nil
|
|
78
71
|
end
|
|
@@ -82,6 +75,28 @@ module Sequel
|
|
|
82
75
|
def load_schema_cache?(file)
|
|
83
76
|
load_schema_cache(file) if File.exist?(file)
|
|
84
77
|
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
# Return the deserialized schema cache file.
|
|
82
|
+
def load_schema_cache_file(file)
|
|
83
|
+
Marshal.load(File.read(file))
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# A dumpable version of the schema cache.
|
|
87
|
+
def dumpable_schema_cache
|
|
88
|
+
sch = {}
|
|
89
|
+
|
|
90
|
+
@schemas.sort.each do |k,v|
|
|
91
|
+
sch[k] = v.map do |c, h|
|
|
92
|
+
h = Hash[h]
|
|
93
|
+
h.delete(:callable_default)
|
|
94
|
+
[c, h]
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
sch
|
|
99
|
+
end
|
|
85
100
|
end
|
|
86
101
|
|
|
87
102
|
Database.register_extension(:schema_caching, SchemaCaching)
|
|
@@ -27,6 +27,11 @@ Sequel.extension :eval_inspect
|
|
|
27
27
|
|
|
28
28
|
module Sequel
|
|
29
29
|
module SchemaDumper
|
|
30
|
+
# :nocov:
|
|
31
|
+
IGNORE_INDEX_ERRORS_KEY = RUBY_VERSION >= '3.4' ? 'ignore_index_errors: ' : ':ignore_index_errors=>'
|
|
32
|
+
# :nocov:
|
|
33
|
+
private_constant :IGNORE_INDEX_ERRORS_KEY
|
|
34
|
+
|
|
30
35
|
# Convert the column schema information to a hash of column options, one of which must
|
|
31
36
|
# be :type. The other options added should modify that type (e.g. :size). If a
|
|
32
37
|
# database type is not recognized, return it as a String type.
|
|
@@ -161,7 +166,7 @@ END_MIG
|
|
|
161
166
|
def dump_table_schema(table, options=OPTS)
|
|
162
167
|
gen = dump_table_generator(table, options)
|
|
163
168
|
commands = [gen.dump_columns, gen.dump_constraints, gen.dump_indexes].reject{|x| x == ''}.join("\n\n")
|
|
164
|
-
"create_table(#{table.inspect}#{
|
|
169
|
+
"create_table(#{table.inspect}#{", #{IGNORE_INDEX_ERRORS_KEY}true" if !options[:same_db] && options[:indexes] != false && !gen.indexes.empty?}) do\n#{commands.gsub(/^/, ' ')}\nend"
|
|
165
170
|
end
|
|
166
171
|
|
|
167
172
|
private
|
|
@@ -216,7 +221,7 @@ END_MIG
|
|
|
216
221
|
else
|
|
217
222
|
col_opts = if options[:same_db]
|
|
218
223
|
h = {:type=>schema[:db_type]}
|
|
219
|
-
if database_type == :mysql && h[:type]
|
|
224
|
+
if database_type == :mysql && h[:type].start_with?("timestamp")
|
|
220
225
|
h[:null] = true
|
|
221
226
|
end
|
|
222
227
|
if database_type == :mssql && schema[:max_length]
|
|
@@ -252,7 +257,7 @@ END_MIG
|
|
|
252
257
|
gen.foreign_key(name, table, col_opts)
|
|
253
258
|
else
|
|
254
259
|
gen.column(name, type, col_opts)
|
|
255
|
-
if [Integer, :Bignum, Float, BigDecimal].include?(type) && schema[:db_type] =~ / unsigned\z/
|
|
260
|
+
if [Integer, :Bignum, Float, BigDecimal].include?(type) && schema[:db_type] =~ / unsigned\z/i
|
|
256
261
|
gen.check(Sequel::SQL::Identifier.new(name) >= 0)
|
|
257
262
|
end
|
|
258
263
|
end
|
|
@@ -425,6 +430,13 @@ END_MIG
|
|
|
425
430
|
|
|
426
431
|
module Schema
|
|
427
432
|
class CreateTableGenerator
|
|
433
|
+
# :nocov:
|
|
434
|
+
DEFAULT_KEY = RUBY_VERSION >= '3.4' ? 'default: ' : ':default=>'
|
|
435
|
+
IGNORE_ERRORS_KEY = RUBY_VERSION >= '3.4' ? 'ignore_errors: ' : ':ignore_errors=>'
|
|
436
|
+
# :nocov:
|
|
437
|
+
private_constant :DEFAULT_KEY
|
|
438
|
+
private_constant :IGNORE_ERRORS_KEY
|
|
439
|
+
|
|
428
440
|
# Dump this generator's columns to a string that could be evaled inside
|
|
429
441
|
# another instance to represent the same columns
|
|
430
442
|
def dump_columns
|
|
@@ -508,7 +520,7 @@ END_MIG
|
|
|
508
520
|
c = c.dup
|
|
509
521
|
cols = c.delete(:columns)
|
|
510
522
|
if table = options[:add_index] || options[:drop_index]
|
|
511
|
-
"#{options[:drop_index] ? 'drop' : 'add'}_index #{table.inspect}, #{cols.inspect}#{
|
|
523
|
+
"#{options[:drop_index] ? 'drop' : 'add'}_index #{table.inspect}, #{cols.inspect}#{", #{IGNORE_ERRORS_KEY}true" if options[:ignore_errors]}#{opts_inspect(c)}"
|
|
512
524
|
else
|
|
513
525
|
"index #{cols.inspect}#{opts_inspect(c)}"
|
|
514
526
|
end
|
|
@@ -526,7 +538,7 @@ END_MIG
|
|
|
526
538
|
if opts[:default]
|
|
527
539
|
opts = opts.dup
|
|
528
540
|
de = Sequel.eval_inspect(opts.delete(:default))
|
|
529
|
-
",
|
|
541
|
+
", #{DEFAULT_KEY}#{de}#{", #{opts.inspect[1...-1]}" if opts.length > 0}"
|
|
530
542
|
else
|
|
531
543
|
", #{opts.inspect[1...-1]}" if opts.length > 0
|
|
532
544
|
end
|
|
@@ -1,56 +1,37 @@
|
|
|
1
1
|
# frozen-string-literal: true
|
|
2
2
|
#
|
|
3
|
-
# The set_literalizer extension
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# DB.extension :set_literalizer
|
|
3
|
+
# The set_literalizer extension should no longer be used, as Sequel
|
|
4
|
+
# now supports Set values by default. For backwards compatibility
|
|
5
|
+
# the set_literalizer extension will treat a set that contains only
|
|
6
|
+
# 2 element arrays as a condition specifier (matching the behavior
|
|
7
|
+
# for arrays where all elements are 2 element arrays). This is not
|
|
8
|
+
# compatible with Sequel's current default behavior. If you are
|
|
9
|
+
# relying on this behavior, it is recommended you convert the set
|
|
10
|
+
# to an array.
|
|
12
11
|
#
|
|
13
12
|
# Related module: Sequel::Dataset::SetLiteralizer
|
|
14
13
|
|
|
15
|
-
require 'set'
|
|
16
|
-
|
|
17
14
|
module Sequel
|
|
15
|
+
# SEQUEL6: Remove
|
|
16
|
+
Sequel::Deprecation.deprecate("The set_literalizer extension", "Sequel now supports set literalization by default")
|
|
17
|
+
|
|
18
18
|
class Dataset
|
|
19
19
|
module SetLiteralizer
|
|
20
|
-
# Try to generate the same SQL for Set instances used in datasets
|
|
21
|
-
# that would be used for equivalent Array instances.
|
|
22
|
-
def complex_expression_sql_append(sql, op, args)
|
|
23
|
-
# Array instances are treated specially by
|
|
24
|
-
# Sequel::SQL::BooleanExpression.from_value_pairs. That cannot
|
|
25
|
-
# be modified by a dataset extension, so this tries to convert
|
|
26
|
-
# the complex expression values generated by default to what would
|
|
27
|
-
# be the complex expression values used for the equivalent array.
|
|
28
|
-
case op
|
|
29
|
-
when :'=', :'!='
|
|
30
|
-
if (set = args[1]).is_a?(Set)
|
|
31
|
-
op = op == :'=' ? :IN : :'NOT IN'
|
|
32
|
-
col = args[0]
|
|
33
|
-
array = set.to_a
|
|
34
|
-
if Sequel.condition_specifier?(array) && col.is_a?(Array)
|
|
35
|
-
array = Sequel.value_list(array)
|
|
36
|
-
end
|
|
37
|
-
args = [col, array]
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
super
|
|
42
|
-
end
|
|
43
|
-
|
|
44
20
|
private
|
|
45
21
|
|
|
46
|
-
#
|
|
47
|
-
def
|
|
48
|
-
if Set
|
|
49
|
-
|
|
22
|
+
# Allow using sets as condition specifiers.
|
|
23
|
+
def filter_expr(expr = nil, &block)
|
|
24
|
+
if expr.is_a?(Set)
|
|
25
|
+
expr
|
|
50
26
|
else
|
|
51
27
|
super
|
|
52
28
|
end
|
|
53
29
|
end
|
|
30
|
+
|
|
31
|
+
# Literalize Set instances by converting the set to array.
|
|
32
|
+
def literal_set_append(sql, v)
|
|
33
|
+
literal_append(sql, v.to_a)
|
|
34
|
+
end
|
|
54
35
|
end
|
|
55
36
|
|
|
56
37
|
register_extension(:set_literalizer, SetLiteralizer)
|
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
# # with split_array_nils extension:
|
|
26
26
|
# # SELECT * FROM table WHERE ((column NOT IN (1)) AND (column IS NOT NULL)))
|
|
27
27
|
#
|
|
28
|
+
# In addition to arrays, this splitting is also done for sets:
|
|
29
|
+
#
|
|
30
|
+
# ds = DB[:table].where(column: Set[1, nil])
|
|
31
|
+
# # SELECT * FROM table WHERE ((column IN (1)) OR (column IS NULL)))
|
|
32
|
+
#
|
|
28
33
|
# To use this extension with a single dataset:
|
|
29
34
|
#
|
|
30
35
|
# ds = ds.extension(:split_array_nil)
|
|
@@ -47,9 +52,14 @@ module Sequel
|
|
|
47
52
|
case op
|
|
48
53
|
when :IN, :"NOT IN"
|
|
49
54
|
vals = args[1]
|
|
50
|
-
if vals.is_a?(Array) && vals.any?(&:nil?)
|
|
55
|
+
if (vals.is_a?(Array) || vals.is_a?(Set)) && vals.any?(&:nil?)
|
|
51
56
|
cols = args[0]
|
|
52
|
-
|
|
57
|
+
if vals.is_a?(Set)
|
|
58
|
+
vals = vals.dup
|
|
59
|
+
vals.delete(nil)
|
|
60
|
+
else
|
|
61
|
+
vals = vals.compact
|
|
62
|
+
end
|
|
53
63
|
c = Sequel::SQL::BooleanExpression
|
|
54
64
|
if op == :IN
|
|
55
65
|
literal_append(sql, c.new(:OR, c.new(:IN, cols, vals), c.new(:IS, cols, nil)))
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
#
|
|
36
36
|
# j[1] # (json_column ->> 1)
|
|
37
37
|
# j.get(1) # (json_column ->> 1)
|
|
38
|
-
# j.
|
|
38
|
+
# j.get_json(1) # (json_column -> 1)
|
|
39
39
|
# j.extract('$.a') # json_extract(json_column, '$.a')
|
|
40
40
|
# jb.extract('$.a') # jsonb_extract(jsonb_column, '$.a')
|
|
41
41
|
#
|
|
@@ -173,7 +173,7 @@ module Sequel
|
|
|
173
173
|
# Return a modified StringAgg that uses distinct expressions
|
|
174
174
|
def distinct
|
|
175
175
|
self.class.new(@expr, @separator) do |sa|
|
|
176
|
-
sa.instance_variable_set(:@order_expr, @order_expr)
|
|
176
|
+
sa.instance_variable_set(:@order_expr, @order_expr)
|
|
177
177
|
sa.instance_variable_set(:@distinct, true)
|
|
178
178
|
end
|
|
179
179
|
end
|
|
@@ -181,8 +181,8 @@ module Sequel
|
|
|
181
181
|
# Return a modified StringAgg with the given order
|
|
182
182
|
def order(*o)
|
|
183
183
|
self.class.new(@expr, @separator) do |sa|
|
|
184
|
-
sa.instance_variable_set(:@distinct, @distinct) if @distinct
|
|
185
184
|
sa.instance_variable_set(:@order_expr, o.empty? ? nil : o.freeze)
|
|
185
|
+
sa.instance_variable_set(:@distinct, @distinct)
|
|
186
186
|
end
|
|
187
187
|
end
|
|
188
188
|
|
|
@@ -14,6 +14,7 @@ module Sequel
|
|
|
14
14
|
module SQL
|
|
15
15
|
class VirtualRow < BasicObject
|
|
16
16
|
include(Module.new do
|
|
17
|
+
Sequel.set_temp_name(self){"Sequel::SQL:VirtualRow::_MethodBlockMethodMissing"}
|
|
17
18
|
# Handle blocks passed to methods and change the behavior.
|
|
18
19
|
def method_missing(m, *args, &block)
|
|
19
20
|
if block
|