sequel 4.43.0 → 4.44.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 +40 -0
- data/doc/active_record.rdoc +2 -2
- data/doc/code_order.rdoc +15 -0
- data/doc/dataset_filtering.rdoc +1 -1
- data/doc/model_dataset_method_design.rdoc +132 -0
- data/doc/opening_databases.rdoc +2 -2
- data/doc/release_notes/4.44.0.txt +125 -0
- data/lib/sequel/adapters/jdbc.rb +5 -1
- data/lib/sequel/adapters/jdbc/as400.rb +1 -1
- data/lib/sequel/adapters/postgres.rb +23 -14
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +27 -0
- data/lib/sequel/dataset/actions.rb +1 -1
- data/lib/sequel/dataset/query.rb +5 -1
- data/lib/sequel/extensions/eval_inspect.rb +4 -4
- data/lib/sequel/extensions/implicit_subquery.rb +48 -0
- data/lib/sequel/extensions/to_dot.rb +1 -1
- data/lib/sequel/model.rb +3 -5
- data/lib/sequel/model/associations.rb +107 -4
- data/lib/sequel/model/base.rb +98 -12
- data/lib/sequel/model/dataset_module.rb +1 -1
- data/lib/sequel/plugins/active_model.rb +11 -3
- data/lib/sequel/plugins/association_dependencies.rb +7 -0
- data/lib/sequel/plugins/auto_validations.rb +10 -0
- data/lib/sequel/plugins/blacklist_security.rb +13 -4
- data/lib/sequel/plugins/class_table_inheritance.rb +11 -0
- data/lib/sequel/plugins/column_conflicts.rb +8 -0
- data/lib/sequel/plugins/composition.rb +12 -2
- data/lib/sequel/plugins/constraint_validations.rb +12 -0
- data/lib/sequel/plugins/csv_serializer.rb +9 -0
- data/lib/sequel/plugins/defaults_setter.rb +6 -0
- data/lib/sequel/plugins/force_encoding.rb +4 -3
- data/lib/sequel/plugins/hook_class_methods.rb +6 -0
- data/lib/sequel/plugins/input_transformer.rb +9 -0
- data/lib/sequel/plugins/insert_returning_select.rb +8 -0
- data/lib/sequel/plugins/instance_hooks.rb +4 -3
- data/lib/sequel/plugins/json_serializer.rb +9 -0
- data/lib/sequel/plugins/lazy_attributes.rb +7 -0
- data/lib/sequel/plugins/many_through_many.rb +13 -2
- data/lib/sequel/plugins/nested_attributes.rb +7 -0
- data/lib/sequel/plugins/pg_array_associations.rb +18 -2
- data/lib/sequel/plugins/pg_row.rb +3 -3
- data/lib/sequel/plugins/pg_typecast_on_load.rb +7 -0
- data/lib/sequel/plugins/prepared_statements.rb +2 -2
- data/lib/sequel/plugins/prepared_statements_safe.rb +7 -0
- data/lib/sequel/plugins/serialization.rb +9 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +13 -1
- data/lib/sequel/plugins/subclasses.rb +15 -1
- data/lib/sequel/plugins/touch.rb +7 -0
- data/lib/sequel/plugins/tree.rb +7 -0
- data/lib/sequel/plugins/typecast_on_load.rb +7 -0
- data/lib/sequel/plugins/update_refresh.rb +24 -13
- data/lib/sequel/plugins/validation_class_methods.rb +13 -0
- data/lib/sequel/sql.rb +28 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +18 -15
- data/spec/core/dataset_spec.rb +5 -0
- data/spec/core/expression_filters_spec.rb +33 -0
- data/spec/extensions/active_model_spec.rb +15 -1
- data/spec/extensions/association_dependencies_spec.rb +8 -0
- data/spec/extensions/auto_validations_spec.rb +8 -0
- data/spec/extensions/blacklist_security_spec.rb +6 -0
- data/spec/extensions/class_table_inheritance_spec.rb +9 -0
- data/spec/extensions/column_conflicts_spec.rb +6 -0
- data/spec/extensions/composition_spec.rb +8 -0
- data/spec/extensions/constraint_validations_plugin_spec.rb +12 -0
- data/spec/extensions/csv_serializer_spec.rb +7 -0
- data/spec/extensions/defaults_setter_spec.rb +7 -0
- data/spec/extensions/force_encoding_spec.rb +14 -0
- data/spec/extensions/hook_class_methods_spec.rb +10 -0
- data/spec/extensions/implicit_subquery_spec.rb +60 -0
- data/spec/extensions/input_transformer_spec.rb +10 -0
- data/spec/extensions/insert_returning_select_spec.rb +6 -0
- data/spec/extensions/json_serializer_spec.rb +7 -0
- data/spec/extensions/lazy_attributes_spec.rb +6 -0
- data/spec/extensions/many_through_many_spec.rb +44 -0
- data/spec/extensions/nested_attributes_spec.rb +5 -0
- data/spec/extensions/pg_array_associations_spec.rb +46 -0
- data/spec/extensions/pg_typecast_on_load_spec.rb +5 -0
- data/spec/extensions/prepared_statements_safe_spec.rb +5 -0
- data/spec/extensions/serialization_spec.rb +7 -0
- data/spec/extensions/single_table_inheritance_spec.rb +19 -2
- data/spec/extensions/subclasses_spec.rb +13 -0
- data/spec/extensions/to_dot_spec.rb +1 -1
- data/spec/extensions/touch_spec.rb +6 -0
- data/spec/extensions/tree_spec.rb +6 -0
- data/spec/extensions/typecast_on_load_spec.rb +6 -0
- data/spec/extensions/update_refresh_spec.rb +7 -1
- data/spec/extensions/validation_class_methods_spec.rb +13 -0
- data/spec/model/association_reflection_spec.rb +177 -0
- data/spec/model/associations_spec.rb +16 -0
- data/spec/model/dataset_methods_spec.rb +59 -0
- data/spec/model/model_spec.rb +59 -0
- metadata +8 -2
data/lib/sequel/core.rb
CHANGED
@@ -414,6 +414,33 @@ module Sequel
|
|
414
414
|
# CreateTableGenerator#index for available options.
|
415
415
|
#
|
416
416
|
# add_index(:artist_id) # CREATE INDEX table_artist_id_index ON table (artist_id)
|
417
|
+
#
|
418
|
+
# Options:
|
419
|
+
#
|
420
|
+
# :name :: Give a specific name for the index. Highly recommended if you plan on
|
421
|
+
# dropping the index later.
|
422
|
+
# :where :: A filter expression, used to setup a partial index (if supported).
|
423
|
+
# :unique :: Create a unique index.
|
424
|
+
#
|
425
|
+
# PostgreSQL specific options:
|
426
|
+
#
|
427
|
+
# :concurrently :: Create the index concurrently, so it doesn't require an exclusive lock
|
428
|
+
# on the table.
|
429
|
+
# :index_type :: The underlying index type to use for a full_text index, gin by default).
|
430
|
+
# :language :: The language to use for a full text index (simple by default).
|
431
|
+
# :opclass :: Set an opclass to use for all columns (per-column opclasses require
|
432
|
+
# custom SQL).
|
433
|
+
# :type :: Set the index type (e.g. full_text, spatial, hash, gin, gist, btree).
|
434
|
+
#
|
435
|
+
# MySQL specific options:
|
436
|
+
#
|
437
|
+
# :type :: Set the index type, with full_text and spatial indexes handled specially.
|
438
|
+
#
|
439
|
+
# Microsoft SQL Server specific options:
|
440
|
+
#
|
441
|
+
# :include :: Includes additional columns in the index.
|
442
|
+
# :key_index :: Sets the KEY INDEX to the given value.
|
443
|
+
# :type :: clustered uses a clustered index, full_text uses a full text index.
|
417
444
|
def add_index(columns, opts = OPTS)
|
418
445
|
@operations << {:op => :add_index, :columns => Array(columns)}.merge!(opts)
|
419
446
|
end
|
@@ -791,7 +791,7 @@ module Sequel
|
|
791
791
|
# # {[1, 3]=>['Jim', 'bo'], [2, 4]=>['Bob', 'be'], ...}
|
792
792
|
#
|
793
793
|
# DB[:table].to_hash([:id, :name]) # SELECT * FROM table
|
794
|
-
# # {[1, 'Jim']=>{:id=>1, :name=>'Jim'}, [2, 'Bob'=>{:id=>2, :name=>'Bob'}, ...}
|
794
|
+
# # {[1, 'Jim']=>{:id=>1, :name=>'Jim'}, [2, 'Bob']=>{:id=>2, :name=>'Bob'}, ...}
|
795
795
|
#
|
796
796
|
# Options:
|
797
797
|
# :all :: Use all instead of each to retrieve the objects
|
data/lib/sequel/dataset/query.rb
CHANGED
@@ -281,7 +281,11 @@ module Sequel
|
|
281
281
|
fs = {}
|
282
282
|
non_sql = non_sql_options
|
283
283
|
@opts.keys.each{|k| fs[k] = nil unless non_sql.include?(k)}
|
284
|
-
clone(fs).from(opts[:alias] ? as(opts[:alias], opts[:column_aliases]) : self)
|
284
|
+
c = clone(fs).from(opts[:alias] ? as(opts[:alias], opts[:column_aliases]) : self)
|
285
|
+
if cols = _columns
|
286
|
+
c.send(:columns=, cols)
|
287
|
+
end
|
288
|
+
c
|
285
289
|
end
|
286
290
|
|
287
291
|
# Match any of the columns to any of the patterns. The terms can be
|
@@ -26,8 +26,10 @@ module Sequel
|
|
26
26
|
# for eval.
|
27
27
|
def eval_inspect(obj)
|
28
28
|
case obj
|
29
|
-
when Sequel::SQL::Blob, Sequel::LiteralString,
|
30
|
-
"#{obj.class}.new(#{obj.inspect})"
|
29
|
+
when Sequel::SQL::Blob, Sequel::LiteralString, BigDecimal
|
30
|
+
"#{obj.class}.new(#{obj.to_s.inspect})"
|
31
|
+
when Sequel::SQL::ValueList
|
32
|
+
"#{obj.class}.new(#{obj.to_a.inspect})"
|
31
33
|
when Array
|
32
34
|
"[#{obj.map{|o| eval_inspect(o)}.join(', ')}]"
|
33
35
|
when Hash
|
@@ -50,8 +52,6 @@ module Sequel
|
|
50
52
|
when Date
|
51
53
|
# Ignore offset and date of calendar reform
|
52
54
|
"Date.new(#{obj.year}, #{obj.month}, #{obj.day})"
|
53
|
-
when BigDecimal
|
54
|
-
"BigDecimal.new(#{obj.to_s.inspect})"
|
55
55
|
else
|
56
56
|
obj.inspect
|
57
57
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
#
|
3
|
+
# The implicit_subquery extension changes most dataset methods that
|
4
|
+
# return modified datasets to implicitly call from_self if the database
|
5
|
+
# currently uses raw SQL. Sequel's by default does not do this:
|
6
|
+
#
|
7
|
+
# DB["SELECT * FROM table"].select(:column).sql
|
8
|
+
# # => "SELECT * FROM table"
|
9
|
+
#
|
10
|
+
# With this extension, datasets that use raw SQL are implicitly wrapped
|
11
|
+
# in a subquery:
|
12
|
+
#
|
13
|
+
# DB["SELECT * FROM table"].select(:column).sql
|
14
|
+
# # => "SELECT column FROM (SELECT * FROM table) AS t1"
|
15
|
+
#
|
16
|
+
# To add this extension to an existing dataset:
|
17
|
+
#
|
18
|
+
# ds = ds.extension(:implicit_subquery)
|
19
|
+
#
|
20
|
+
# To set this as the default behavior for all datasets on a single database:
|
21
|
+
#
|
22
|
+
# DB.extension(:implicit_subquery)
|
23
|
+
#
|
24
|
+
# Related module: Sequel::Dataset::ImplicitSubquery
|
25
|
+
|
26
|
+
#
|
27
|
+
module Sequel
|
28
|
+
class Dataset
|
29
|
+
module ImplicitSubquery
|
30
|
+
exceptions = [:and, :add_graph_aliases, :filter, :from, :from_self, :naked, :or, :order_more,
|
31
|
+
:qualify, :reverse, :reverse_order, :select_all, :select_more, :server,
|
32
|
+
:set_graph_aliases, :unfiltered, :ungraphed, :ungrouped, :unlimited, :unordered,
|
33
|
+
:with_sql]
|
34
|
+
additions = [:join_table]
|
35
|
+
(Dataset::QUERY_METHODS - Dataset::JOIN_METHODS - exceptions + additions).each do |meth|
|
36
|
+
define_method(meth) do |*a, &b|
|
37
|
+
if opts[:sql]
|
38
|
+
from_self.send(meth, *a, &b)
|
39
|
+
else
|
40
|
+
super(*a, &b)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
register_extension(:implicit_subquery, ImplicitSubquery)
|
47
|
+
end
|
48
|
+
end
|
data/lib/sequel/model.rb
CHANGED
@@ -37,8 +37,8 @@ module Sequel
|
|
37
37
|
ANONYMOUS_MODEL_CLASSES_MUTEX = @Model_mutex = Mutex.new
|
38
38
|
|
39
39
|
# Class methods added to model that call the method of the same name on the dataset
|
40
|
-
DATASET_METHODS = (Dataset::ACTION_METHODS + Dataset::QUERY_METHODS +
|
41
|
-
[:
|
40
|
+
DATASET_METHODS = (Dataset::ACTION_METHODS + Dataset::QUERY_METHODS + [:each_server, :where_all, :where_each, :where_single_value]) -
|
41
|
+
[:and, :or, :[], :columns, :columns!, :delete, :update, :add_graph_aliases, :first, :first!]
|
42
42
|
|
43
43
|
# Boolean settings that can be modified at the global, class, or instance level.
|
44
44
|
BOOLEAN_SETTINGS = [:typecast_empty_string_to_nil, :typecast_on_assignment, :strict_param_setting, \
|
@@ -71,8 +71,7 @@ module Sequel
|
|
71
71
|
# If the value is +nil+, the superclass's instance variable is used directly in the subclass.
|
72
72
|
INHERITED_INSTANCE_VARIABLES = {:@allowed_columns=>:dup,
|
73
73
|
:@dataset_method_modules=>:dup, :@primary_key=>nil, :@use_transactions=>nil,
|
74
|
-
:@raise_on_save_failure=>nil, :@require_modification=>nil,
|
75
|
-
:@restricted_columns=>:dup, :@restrict_primary_key=>nil,
|
74
|
+
:@raise_on_save_failure=>nil, :@require_modification=>nil, :@restrict_primary_key=>nil,
|
76
75
|
:@simple_pk=>nil, :@simple_table=>nil, :@strict_param_setting=>nil,
|
77
76
|
:@typecast_empty_string_to_nil=>nil, :@typecast_on_assignment=>nil,
|
78
77
|
:@raise_on_typecast_failure=>nil, :@plugins=>:dup, :@setter_methods=>nil,
|
@@ -110,7 +109,6 @@ module Sequel
|
|
110
109
|
@require_modification = nil
|
111
110
|
@require_valid_table = false
|
112
111
|
@restrict_primary_key = true
|
113
|
-
@restricted_columns = nil
|
114
112
|
@setter_methods = nil
|
115
113
|
@simple_pk = nil
|
116
114
|
@simple_table = nil
|
@@ -7,7 +7,7 @@ module Sequel
|
|
7
7
|
module Associations
|
8
8
|
# Map of association type symbols to association reflection classes.
|
9
9
|
ASSOCIATION_TYPES = {}
|
10
|
-
|
10
|
+
|
11
11
|
# Set an empty association reflection hash in the model
|
12
12
|
def self.apply(model)
|
13
13
|
model.instance_eval do
|
@@ -23,7 +23,7 @@ module Sequel
|
|
23
23
|
# be instantiated by the user.
|
24
24
|
class AssociationReflection < Hash
|
25
25
|
include Sequel::Inflections
|
26
|
-
|
26
|
+
|
27
27
|
# Name symbol for the _add internal association method
|
28
28
|
def _add_method
|
29
29
|
:"_add_#{singularize(self[:name])}"
|
@@ -56,7 +56,13 @@ module Sequel
|
|
56
56
|
|
57
57
|
# The class associated to the current model class via this association
|
58
58
|
def associated_class
|
59
|
-
cached_fetch(:class)
|
59
|
+
cached_fetch(:class) do
|
60
|
+
begin
|
61
|
+
constantize(self[:class_name])
|
62
|
+
rescue NameError => e
|
63
|
+
raise NameError, "#{e.message} (this happened when attempting to find the associated class for #{inspect})", e.backtrace
|
64
|
+
end
|
65
|
+
end
|
60
66
|
end
|
61
67
|
|
62
68
|
# The dataset associated via this association, with the non-instance specific
|
@@ -339,6 +345,38 @@ module Sequel
|
|
339
345
|
{filter_by_associations_conditions_key=>ds}
|
340
346
|
end
|
341
347
|
|
348
|
+
# Finalize the association by first attempting to populate the thread-safe cache,
|
349
|
+
# and then transfering the thread-safe cache value to the association itself,
|
350
|
+
# so that a mutex is not needed to get the value.
|
351
|
+
def finalize
|
352
|
+
return unless cache = self[:cache]
|
353
|
+
|
354
|
+
finalize_settings.each do |meth, key|
|
355
|
+
next if has_key?(key)
|
356
|
+
|
357
|
+
send(meth)
|
358
|
+
self[key] = cache.delete(key) if cache.has_key?(key)
|
359
|
+
end
|
360
|
+
|
361
|
+
nil
|
362
|
+
end
|
363
|
+
|
364
|
+
# Map of methods to cache keys used for finalizing associations.
|
365
|
+
FINALIZE_SETTINGS = {
|
366
|
+
:associated_class=>:class,
|
367
|
+
:associated_dataset=>:_dataset,
|
368
|
+
:associated_eager_dataset=>:associated_eager_dataset,
|
369
|
+
:eager_limit_strategy=>:_eager_limit_strategy,
|
370
|
+
:filter_by_associations_conditions_dataset=>:filter_by_associations_conditions_dataset,
|
371
|
+
:placeholder_loader=>:placeholder_loader,
|
372
|
+
:predicate_key=>:predicate_key,
|
373
|
+
:predicate_keys=>:predicate_keys,
|
374
|
+
:reciprocal=>:reciprocal,
|
375
|
+
}.freeze
|
376
|
+
def finalize_settings
|
377
|
+
FINALIZE_SETTINGS
|
378
|
+
end
|
379
|
+
|
342
380
|
# Whether to handle silent modification failure when adding/removing
|
343
381
|
# associated records, false by default.
|
344
382
|
def handle_silent_modification_failure?
|
@@ -355,6 +393,18 @@ module Sequel
|
|
355
393
|
end
|
356
394
|
end
|
357
395
|
|
396
|
+
# Show which type of reflection this is, and a guess at what line was used to create the
|
397
|
+
# association.
|
398
|
+
def inspect
|
399
|
+
o = self[:orig_opts].dup
|
400
|
+
o.delete(:class)
|
401
|
+
o.delete(:class_name)
|
402
|
+
o.delete(:block) unless o[:block]
|
403
|
+
o[:class] = self[:orig_class] if self[:orig_class]
|
404
|
+
|
405
|
+
"#<#{self.class} #{self[:model]}.#{self[:type]} #{self[:name].inspect}#{", #{o.inspect[1...-1]}" unless o.empty?}>"
|
406
|
+
end
|
407
|
+
|
358
408
|
# The limit and offset for this association (returned as a two element array).
|
359
409
|
def limit_and_offset
|
360
410
|
if (v = self[:limit]).is_a?(Array)
|
@@ -782,6 +832,18 @@ module Sequel
|
|
782
832
|
nil
|
783
833
|
end
|
784
834
|
|
835
|
+
FINALIZE_SETTINGS = superclass::FINALIZE_SETTINGS.merge(
|
836
|
+
:primary_key=>:primary_key,
|
837
|
+
:primary_keys=>:primary_keys,
|
838
|
+
:primary_key_method=>:primary_key_method,
|
839
|
+
:primary_key_methods=>:primary_key_methods,
|
840
|
+
:qualified_primary_key=>:qualified_primary_key,
|
841
|
+
:reciprocal_type=>:reciprocal_type
|
842
|
+
).freeze
|
843
|
+
def finalize_settings
|
844
|
+
FINALIZE_SETTINGS
|
845
|
+
end
|
846
|
+
|
785
847
|
# The expression to use on the left hand side of the IN lookup when eager loading
|
786
848
|
def predicate_key
|
787
849
|
cached_fetch(:predicate_key){qualified_primary_key}
|
@@ -926,6 +988,13 @@ module Sequel
|
|
926
988
|
:"#{underscore(demodulize(self[:model].name))}_id"
|
927
989
|
end
|
928
990
|
|
991
|
+
FINALIZE_SETTINGS = superclass::FINALIZE_SETTINGS.merge(
|
992
|
+
:qualified_primary_key=>:qualified_primary_key
|
993
|
+
).freeze
|
994
|
+
def finalize_settings
|
995
|
+
FINALIZE_SETTINGS
|
996
|
+
end
|
997
|
+
|
929
998
|
# Handle silent failure of add/remove methods if raise_on_save_failure is false.
|
930
999
|
def handle_silent_modification_failure?
|
931
1000
|
self[:raise_on_save_failure] == false
|
@@ -1196,6 +1265,22 @@ module Sequel
|
|
1196
1265
|
:"#{singularize(self[:name])}_id"
|
1197
1266
|
end
|
1198
1267
|
|
1268
|
+
FINALIZE_SETTINGS = superclass::FINALIZE_SETTINGS.merge(
|
1269
|
+
:associated_key_array=>:associated_key_array,
|
1270
|
+
:qualified_right_key=>:qualified_right_key,
|
1271
|
+
:join_table_source=>:join_table_source,
|
1272
|
+
:join_table_alias=>:join_table_alias,
|
1273
|
+
:qualified_right_primary_key=>:qualified_right_primary_key,
|
1274
|
+
:right_primary_key=>:right_primary_key,
|
1275
|
+
:right_primary_keys=>:right_primary_keys,
|
1276
|
+
:right_primary_key_method=>:right_primary_key_method,
|
1277
|
+
:right_primary_key_methods=>:right_primary_key_methods,
|
1278
|
+
:select=>:select
|
1279
|
+
).freeze
|
1280
|
+
def finalize_settings
|
1281
|
+
FINALIZE_SETTINGS
|
1282
|
+
end
|
1283
|
+
|
1199
1284
|
# The hash key to use for the eager loading predicate (left side of IN (1, 2, 3)).
|
1200
1285
|
# The left key qualified by the join table.
|
1201
1286
|
def predicate_key
|
@@ -1668,7 +1753,7 @@ module Sequel
|
|
1668
1753
|
# Defaults to :right_primary_key option.
|
1669
1754
|
# :uniq :: Adds a after_load callback that makes the array of objects unique.
|
1670
1755
|
def associate(type, name, opts = OPTS, &block)
|
1671
|
-
raise(Error, 'invalid association type') unless assoc_class = ASSOCIATION_TYPES[type]
|
1756
|
+
raise(Error, 'invalid association type') unless assoc_class = Sequel.synchronize{ASSOCIATION_TYPES[type]}
|
1672
1757
|
raise(Error, 'Model.associate name argument must be a symbol') unless name.is_a?(Symbol)
|
1673
1758
|
|
1674
1759
|
# dup early so we don't modify opts
|
@@ -1716,6 +1801,7 @@ module Sequel
|
|
1716
1801
|
def_association_instance_methods(opts)
|
1717
1802
|
|
1718
1803
|
orig_opts.delete(:clone)
|
1804
|
+
opts[:orig_class] = orig_opts[:class] || orig_opts[:class_name]
|
1719
1805
|
orig_opts.merge!(:class_name=>opts[:class_name], :class=>opts[:class], :block=>opts[:block])
|
1720
1806
|
opts[:orig_opts] = orig_opts
|
1721
1807
|
# don't add to association_reflections until we are sure there are no errors
|
@@ -1737,6 +1823,23 @@ module Sequel
|
|
1737
1823
|
opts.eager_load_results(eo, &block)
|
1738
1824
|
end
|
1739
1825
|
|
1826
|
+
# Freeze association related metadata when freezing model class.
|
1827
|
+
def freeze
|
1828
|
+
@association_reflections.freeze.each_value(&:freeze)
|
1829
|
+
@autoreloading_associations.freeze.each_value(&:freeze)
|
1830
|
+
@default_association_options.freeze
|
1831
|
+
|
1832
|
+
super
|
1833
|
+
end
|
1834
|
+
|
1835
|
+
# Finalize all associations such that values that are looked up
|
1836
|
+
# dynamically in associated classes are set statically.
|
1837
|
+
# As this modifies the associations, it must be done before
|
1838
|
+
# calling freeze.
|
1839
|
+
def finalize_associations
|
1840
|
+
@association_reflections.each_value(&:finalize)
|
1841
|
+
end
|
1842
|
+
|
1740
1843
|
# Shortcut for adding a many_to_many association, see #associate
|
1741
1844
|
def many_to_many(name, opts=OPTS, &block)
|
1742
1845
|
associate(:many_to_many, name, opts, &block)
|
data/lib/sequel/model/base.rb
CHANGED
@@ -238,7 +238,7 @@ module Sequel
|
|
238
238
|
|
239
239
|
# Clear the setter_methods cache
|
240
240
|
def clear_setter_methods_cache
|
241
|
-
@setter_methods = nil
|
241
|
+
@setter_methods = nil unless frozen?
|
242
242
|
end
|
243
243
|
|
244
244
|
# Returns the columns in the result set in their original order.
|
@@ -249,7 +249,9 @@ module Sequel
|
|
249
249
|
# Artist.columns
|
250
250
|
# # => [:id, :name]
|
251
251
|
def columns
|
252
|
-
@columns
|
252
|
+
return @columns if @columns
|
253
|
+
return nil if frozen?
|
254
|
+
set_columns(dataset.naked.columns)
|
253
255
|
end
|
254
256
|
|
255
257
|
# Creates instance using new with the given values and block, and saves it.
|
@@ -323,7 +325,7 @@ module Sequel
|
|
323
325
|
# Album.released.by_release_date.for_select_options.sql
|
324
326
|
# # => "SELECT id, name, release_date FROM artists WHERE (release_date <= CURRENT_DATE) ORDER BY release_date"
|
325
327
|
#
|
326
|
-
# The following methods are supported: distinct, exclude, exclude_having, grep, group, group_and_count,
|
328
|
+
# The following methods are supported: distinct, eager, exclude, exclude_having, grep, group, group_and_count,
|
327
329
|
# group_append, having, limit, offset, order, order_append, order_prepend, select, select_all,
|
328
330
|
# select_append, select_group, where, and server.
|
329
331
|
#
|
@@ -388,7 +390,9 @@ module Sequel
|
|
388
390
|
# # {:id=>{:type=>:integer, :primary_key=>true, ...},
|
389
391
|
# # :name=>{:type=>:string, :primary_key=>false, ...}}
|
390
392
|
def db_schema
|
391
|
-
@db_schema
|
393
|
+
return @db_schema if @db_schema
|
394
|
+
return nil if frozen?
|
395
|
+
@db_schema = get_db_schema
|
392
396
|
end
|
393
397
|
|
394
398
|
# Create a column alias, where the column methods have one name, but the underlying storage uses a
|
@@ -579,7 +583,7 @@ module Sequel
|
|
579
583
|
end
|
580
584
|
end
|
581
585
|
|
582
|
-
|
586
|
+
@finder_loaders[meth_name] = loader_proc
|
583
587
|
mod = opts[:mod] || (class << self; self; end)
|
584
588
|
if prepare
|
585
589
|
def_prepare_method(mod, meth_name)
|
@@ -605,6 +609,33 @@ module Sequel
|
|
605
609
|
first(*args, &block) || raise(Sequel::NoMatchingRow.new(dataset))
|
606
610
|
end
|
607
611
|
|
612
|
+
# Freeze a model class, disallowing any further changes to it.
|
613
|
+
def freeze
|
614
|
+
dataset_module.freeze
|
615
|
+
overridable_methods_module.freeze
|
616
|
+
|
617
|
+
@finder_loaders.freeze
|
618
|
+
|
619
|
+
if @dataset
|
620
|
+
@dataset.freeze
|
621
|
+
@instance_dataset.freeze
|
622
|
+
db_schema.freeze.each_value(&:freeze)
|
623
|
+
columns.freeze
|
624
|
+
setter_methods.freeze
|
625
|
+
@finder_loaders.each_key{|k| finder_for(k)}
|
626
|
+
else
|
627
|
+
@setter_methods = [].freeze
|
628
|
+
end
|
629
|
+
|
630
|
+
@dataset_method_modules.freeze
|
631
|
+
@default_set_fields_options.freeze
|
632
|
+
@finders.freeze
|
633
|
+
@plugins.freeze
|
634
|
+
@allowed_columns.freeze if @allowed_columns
|
635
|
+
|
636
|
+
super
|
637
|
+
end
|
638
|
+
|
608
639
|
# Clear the setter_methods cache when a module is included, as it
|
609
640
|
# may contain setter methods.
|
610
641
|
def include(*mods)
|
@@ -856,14 +887,16 @@ module Sequel
|
|
856
887
|
end
|
857
888
|
end
|
858
889
|
self.simple_pk = if key && !key.is_a?(Array)
|
859
|
-
(@dataset || db).literal(key)
|
890
|
+
(@dataset || db).literal(key).freeze
|
860
891
|
end
|
861
892
|
@primary_key = key
|
862
893
|
end
|
863
894
|
|
864
895
|
# Cache of setter methods to allow by default, in order to speed up new/set/update instance methods.
|
865
896
|
def setter_methods
|
866
|
-
@setter_methods
|
897
|
+
return @setter_methods if @setter_methods
|
898
|
+
raise if frozen?
|
899
|
+
@setter_methods = get_setter_methods
|
867
900
|
end
|
868
901
|
|
869
902
|
# Sets up a dataset method that returns a filtered dataset.
|
@@ -951,11 +984,11 @@ module Sequel
|
|
951
984
|
def convert_input_dataset(ds)
|
952
985
|
case ds
|
953
986
|
when Symbol, SQL::Identifier, SQL::QualifiedIdentifier, SQL::AliasedExpression, LiteralString
|
954
|
-
self.simple_table = db.literal(ds)
|
987
|
+
self.simple_table = db.literal(ds).freeze
|
955
988
|
ds = db.from(ds)
|
956
989
|
when Dataset
|
957
990
|
self.simple_table = if ds.send(:simple_select_all?)
|
958
|
-
ds.literal(ds.first_source_table)
|
991
|
+
ds.literal(ds.first_source_table).freeze
|
959
992
|
end
|
960
993
|
@db = ds.db
|
961
994
|
else
|
@@ -1028,7 +1061,7 @@ module Sequel
|
|
1028
1061
|
# for the method, load the finder and set correctly in the finders hash, then
|
1029
1062
|
# return the finder.
|
1030
1063
|
def finder_for(meth)
|
1031
|
-
unless finder = Sequel.synchronize{@finders[meth]}
|
1064
|
+
unless finder = (frozen? ? @finders[meth] : Sequel.synchronize{@finders[meth]})
|
1032
1065
|
finder_loader = @finder_loaders.fetch(meth)
|
1033
1066
|
finder = finder_loader.call(self)
|
1034
1067
|
Sequel.synchronize{@finders[meth] = finder}
|
@@ -1219,7 +1252,7 @@ module Sequel
|
|
1219
1252
|
# Reset the instance dataset to a modified copy of the current dataset,
|
1220
1253
|
# should be used whenever the model's dataset is modified.
|
1221
1254
|
def reset_instance_dataset
|
1222
|
-
@finders.clear if @finders
|
1255
|
+
Sequel.synchronize{@finders.clear if @finders}
|
1223
1256
|
@instance_dataset = @dataset.limit(1).naked if @dataset
|
1224
1257
|
end
|
1225
1258
|
|
@@ -2042,7 +2075,7 @@ module Sequel
|
|
2042
2075
|
|
2043
2076
|
# Get the row of column data from the database.
|
2044
2077
|
def _refresh_get(dataset)
|
2045
|
-
if (sql =
|
2078
|
+
if (sql = model.fast_pk_lookup_sql) && !dataset.opts[:lock]
|
2046
2079
|
sql = sql.dup
|
2047
2080
|
ds = use_server(dataset)
|
2048
2081
|
ds.literal_append(sql, pk)
|
@@ -2528,6 +2561,52 @@ module Sequel
|
|
2528
2561
|
end
|
2529
2562
|
end
|
2530
2563
|
|
2564
|
+
# Return an array of all rows matching the given filter condition, also
|
2565
|
+
# yielding each row to the given block. Basically the same as where(cond).all(&block),
|
2566
|
+
# except it can be optimized to not create an intermediate dataset.
|
2567
|
+
#
|
2568
|
+
# Artist.where_all(:id=>[1,2,3])
|
2569
|
+
# # SELECT * FROM artists WHERE (id IN (1, 2, 3))
|
2570
|
+
def where_all(cond, &block)
|
2571
|
+
if loader = _model_where_loader
|
2572
|
+
loader.all(filter_expr(cond), &block)
|
2573
|
+
else
|
2574
|
+
where(cond).all(&block)
|
2575
|
+
end
|
2576
|
+
end
|
2577
|
+
|
2578
|
+
# Iterate over all rows matching the given filter condition,
|
2579
|
+
# yielding each row to the given block. Basically the same as where(cond).each(&block),
|
2580
|
+
# except it can be optimized to not create an intermediate dataset.
|
2581
|
+
#
|
2582
|
+
# Artist.where_each(:id=>[1,2,3]){|row| p row}
|
2583
|
+
# # SELECT * FROM artists WHERE (id IN (1, 2, 3))
|
2584
|
+
def where_each(cond, &block)
|
2585
|
+
if loader = _model_where_loader
|
2586
|
+
loader.each(filter_expr(cond), &block)
|
2587
|
+
else
|
2588
|
+
where(cond).each(&block)
|
2589
|
+
end
|
2590
|
+
end
|
2591
|
+
|
2592
|
+
# Filter the datasets using the given filter condition, then return a single value.
|
2593
|
+
# This assumes that the dataset has already been setup to limit the selection to
|
2594
|
+
# a single column. Basically the same as where(cond).single_value,
|
2595
|
+
# except it can be optimized to not create an intermediate dataset.
|
2596
|
+
#
|
2597
|
+
# Artist.select(:name).where_single_value(:id=>1)
|
2598
|
+
# # SELECT name FROM artists WHERE (id = 1) LIMIT 1
|
2599
|
+
def where_single_value(cond)
|
2600
|
+
if loader = cached_placeholder_literalizer(:_model_where_single_value_loader) do |pl|
|
2601
|
+
single_value_ds.where(pl.arg)
|
2602
|
+
end
|
2603
|
+
|
2604
|
+
loader.get(filter_expr(cond))
|
2605
|
+
else
|
2606
|
+
where(cond).single_value
|
2607
|
+
end
|
2608
|
+
end
|
2609
|
+
|
2531
2610
|
# Given a primary key value, return the first record in the dataset with that primary key
|
2532
2611
|
# value. If no records matches, returns nil.
|
2533
2612
|
#
|
@@ -2554,6 +2633,13 @@ module Sequel
|
|
2554
2633
|
|
2555
2634
|
private
|
2556
2635
|
|
2636
|
+
# Loader used for where_all and where_each.
|
2637
|
+
def _model_where_loader
|
2638
|
+
cached_placeholder_literalizer(:_model_where_loader) do |pl|
|
2639
|
+
where(pl.arg)
|
2640
|
+
end
|
2641
|
+
end
|
2642
|
+
|
2557
2643
|
# If the dataset is not already ordered, and the model has a primary key,
|
2558
2644
|
# return a clone ordered by the primary key.
|
2559
2645
|
def _primary_key_order
|