sequel 4.43.0 → 4.44.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|