sequel 3.46.0 → 3.47.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 +96 -0
- data/Rakefile +7 -1
- data/bin/sequel +6 -4
- data/doc/active_record.rdoc +1 -1
- data/doc/advanced_associations.rdoc +14 -35
- data/doc/association_basics.rdoc +66 -4
- data/doc/migration.rdoc +4 -0
- data/doc/opening_databases.rdoc +6 -0
- data/doc/postgresql.rdoc +302 -0
- data/doc/release_notes/3.47.0.txt +270 -0
- data/doc/security.rdoc +6 -0
- data/lib/sequel/adapters/ibmdb.rb +9 -9
- data/lib/sequel/adapters/jdbc.rb +22 -7
- data/lib/sequel/adapters/jdbc/postgresql.rb +7 -2
- data/lib/sequel/adapters/mock.rb +2 -0
- data/lib/sequel/adapters/postgres.rb +44 -13
- data/lib/sequel/adapters/shared/mssql.rb +1 -1
- data/lib/sequel/adapters/shared/mysql.rb +2 -2
- data/lib/sequel/adapters/shared/postgres.rb +94 -55
- data/lib/sequel/adapters/shared/sqlite.rb +3 -1
- data/lib/sequel/adapters/sqlite.rb +2 -2
- data/lib/sequel/adapters/utils/pg_types.rb +1 -14
- data/lib/sequel/adapters/utils/split_alter_table.rb +3 -3
- data/lib/sequel/connection_pool/threaded.rb +1 -1
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database/connecting.rb +2 -2
- data/lib/sequel/database/features.rb +5 -0
- data/lib/sequel/database/misc.rb +47 -5
- data/lib/sequel/database/query.rb +2 -2
- data/lib/sequel/dataset/actions.rb +4 -2
- data/lib/sequel/dataset/misc.rb +1 -1
- data/lib/sequel/dataset/prepared_statements.rb +1 -1
- data/lib/sequel/dataset/query.rb +8 -6
- data/lib/sequel/dataset/sql.rb +8 -6
- data/lib/sequel/extensions/constraint_validations.rb +5 -2
- data/lib/sequel/extensions/migration.rb +10 -8
- data/lib/sequel/extensions/pagination.rb +3 -0
- data/lib/sequel/extensions/pg_array.rb +85 -25
- data/lib/sequel/extensions/pg_hstore.rb +8 -1
- data/lib/sequel/extensions/pg_hstore_ops.rb +4 -1
- data/lib/sequel/extensions/pg_inet.rb +16 -13
- data/lib/sequel/extensions/pg_interval.rb +6 -2
- data/lib/sequel/extensions/pg_json.rb +18 -11
- data/lib/sequel/extensions/pg_range.rb +17 -2
- data/lib/sequel/extensions/pg_range_ops.rb +7 -5
- data/lib/sequel/extensions/pg_row.rb +29 -12
- data/lib/sequel/extensions/pretty_table.rb +3 -0
- data/lib/sequel/extensions/query.rb +3 -0
- data/lib/sequel/extensions/schema_caching.rb +2 -0
- data/lib/sequel/extensions/schema_dumper.rb +3 -1
- data/lib/sequel/extensions/select_remove.rb +3 -0
- data/lib/sequel/model.rb +8 -2
- data/lib/sequel/model/associations.rb +39 -27
- data/lib/sequel/model/base.rb +99 -38
- data/lib/sequel/model/plugins.rb +25 -0
- data/lib/sequel/plugins/association_autoreloading.rb +27 -22
- data/lib/sequel/plugins/association_dependencies.rb +1 -7
- data/lib/sequel/plugins/auto_validations.rb +110 -0
- data/lib/sequel/plugins/boolean_readers.rb +1 -6
- data/lib/sequel/plugins/caching.rb +6 -13
- data/lib/sequel/plugins/class_table_inheritance.rb +1 -0
- data/lib/sequel/plugins/composition.rb +14 -7
- data/lib/sequel/plugins/constraint_validations.rb +2 -13
- data/lib/sequel/plugins/defaults_setter.rb +1 -6
- data/lib/sequel/plugins/dirty.rb +8 -0
- data/lib/sequel/plugins/error_splitter.rb +54 -0
- data/lib/sequel/plugins/force_encoding.rb +1 -5
- data/lib/sequel/plugins/hook_class_methods.rb +1 -6
- data/lib/sequel/plugins/input_transformer.rb +79 -0
- data/lib/sequel/plugins/instance_filters.rb +7 -1
- data/lib/sequel/plugins/instance_hooks.rb +7 -1
- data/lib/sequel/plugins/json_serializer.rb +5 -10
- data/lib/sequel/plugins/lazy_attributes.rb +20 -7
- data/lib/sequel/plugins/list.rb +1 -6
- data/lib/sequel/plugins/many_through_many.rb +1 -2
- data/lib/sequel/plugins/many_to_one_pk_lookup.rb +23 -39
- data/lib/sequel/plugins/optimistic_locking.rb +1 -5
- data/lib/sequel/plugins/pg_row.rb +4 -2
- data/lib/sequel/plugins/pg_typecast_on_load.rb +3 -7
- data/lib/sequel/plugins/prepared_statements.rb +1 -5
- data/lib/sequel/plugins/prepared_statements_safe.rb +2 -11
- data/lib/sequel/plugins/rcte_tree.rb +2 -2
- data/lib/sequel/plugins/serialization.rb +11 -13
- data/lib/sequel/plugins/serialization_modification_detection.rb +13 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +4 -4
- data/lib/sequel/plugins/static_cache.rb +67 -19
- data/lib/sequel/plugins/string_stripper.rb +7 -27
- data/lib/sequel/plugins/subclasses.rb +3 -5
- data/lib/sequel/plugins/tactical_eager_loading.rb +2 -2
- data/lib/sequel/plugins/timestamps.rb +2 -7
- data/lib/sequel/plugins/touch.rb +5 -8
- data/lib/sequel/plugins/tree.rb +1 -6
- data/lib/sequel/plugins/typecast_on_load.rb +1 -5
- data/lib/sequel/plugins/update_primary_key.rb +26 -14
- data/lib/sequel/plugins/validation_class_methods.rb +31 -16
- data/lib/sequel/plugins/validation_helpers.rb +50 -26
- data/lib/sequel/plugins/xml_serializer.rb +3 -6
- data/lib/sequel/sql.rb +1 -1
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +131 -15
- data/spec/adapters/sqlite_spec.rb +1 -1
- data/spec/core/connection_pool_spec.rb +16 -17
- data/spec/core/database_spec.rb +111 -40
- data/spec/core/dataset_spec.rb +65 -74
- data/spec/core/expression_filters_spec.rb +6 -5
- data/spec/core/object_graph_spec.rb +0 -1
- data/spec/core/schema_spec.rb +23 -23
- data/spec/core/spec_helper.rb +5 -1
- data/spec/extensions/association_dependencies_spec.rb +1 -1
- data/spec/extensions/association_proxies_spec.rb +1 -1
- data/spec/extensions/auto_validations_spec.rb +90 -0
- data/spec/extensions/caching_spec.rb +6 -0
- data/spec/extensions/class_table_inheritance_spec.rb +8 -1
- data/spec/extensions/composition_spec.rb +12 -5
- data/spec/extensions/constraint_validations_spec.rb +4 -4
- data/spec/extensions/core_refinements_spec.rb +29 -79
- data/spec/extensions/dirty_spec.rb +14 -0
- data/spec/extensions/error_splitter_spec.rb +18 -0
- data/spec/extensions/identity_map_spec.rb +0 -1
- data/spec/extensions/input_transformer_spec.rb +54 -0
- data/spec/extensions/instance_filters_spec.rb +6 -0
- data/spec/extensions/instance_hooks_spec.rb +12 -1
- data/spec/extensions/json_serializer_spec.rb +0 -1
- data/spec/extensions/lazy_attributes_spec.rb +64 -55
- data/spec/extensions/looser_typecasting_spec.rb +1 -1
- data/spec/extensions/many_through_many_spec.rb +3 -4
- data/spec/extensions/many_to_one_pk_lookup_spec.rb +53 -15
- data/spec/extensions/migration_spec.rb +16 -0
- data/spec/extensions/null_dataset_spec.rb +1 -1
- data/spec/extensions/pg_array_spec.rb +48 -1
- data/spec/extensions/pg_hstore_ops_spec.rb +10 -2
- data/spec/extensions/pg_hstore_spec.rb +5 -0
- data/spec/extensions/pg_inet_spec.rb +5 -0
- data/spec/extensions/pg_interval_spec.rb +7 -3
- data/spec/extensions/pg_json_spec.rb +6 -1
- data/spec/extensions/pg_range_ops_spec.rb +4 -1
- data/spec/extensions/pg_range_spec.rb +5 -0
- data/spec/extensions/pg_row_plugin_spec.rb +13 -0
- data/spec/extensions/pg_row_spec.rb +28 -19
- data/spec/extensions/pg_typecast_on_load_spec.rb +6 -1
- data/spec/extensions/prepared_statements_associations_spec.rb +1 -1
- data/spec/extensions/query_literals_spec.rb +1 -1
- data/spec/extensions/rcte_tree_spec.rb +2 -2
- data/spec/extensions/schema_spec.rb +2 -2
- data/spec/extensions/serialization_modification_detection_spec.rb +8 -0
- data/spec/extensions/serialization_spec.rb +15 -1
- data/spec/extensions/sharding_spec.rb +1 -1
- data/spec/extensions/single_table_inheritance_spec.rb +1 -1
- data/spec/extensions/static_cache_spec.rb +59 -9
- data/spec/extensions/tactical_eager_loading_spec.rb +19 -4
- data/spec/extensions/update_primary_key_spec.rb +17 -1
- data/spec/extensions/validation_class_methods_spec.rb +25 -0
- data/spec/extensions/validation_helpers_spec.rb +59 -3
- data/spec/integration/associations_test.rb +5 -5
- data/spec/integration/eager_loader_test.rb +32 -63
- data/spec/integration/model_test.rb +2 -2
- data/spec/integration/plugin_test.rb +88 -56
- data/spec/integration/prepared_statement_test.rb +1 -1
- data/spec/integration/schema_test.rb +1 -1
- data/spec/integration/timezone_test.rb +0 -1
- data/spec/integration/transaction_test.rb +0 -1
- data/spec/model/association_reflection_spec.rb +1 -1
- data/spec/model/associations_spec.rb +106 -84
- data/spec/model/base_spec.rb +4 -4
- data/spec/model/eager_loading_spec.rb +8 -8
- data/spec/model/model_spec.rb +27 -9
- data/spec/model/plugins_spec.rb +71 -0
- data/spec/model/record_spec.rb +99 -13
- metadata +12 -2
data/lib/sequel/model/base.rb
CHANGED
|
@@ -22,6 +22,10 @@ module Sequel
|
|
|
22
22
|
# with all of these modules.
|
|
23
23
|
attr_reader :dataset_method_modules
|
|
24
24
|
|
|
25
|
+
# The default options to use for Model#set_fields. These are merged with
|
|
26
|
+
# the options given to set_fields.
|
|
27
|
+
attr_accessor :default_set_fields_options
|
|
28
|
+
|
|
25
29
|
# SQL string fragment used for faster DELETE statement creation when deleting/destroying
|
|
26
30
|
# model instances, or nil if the optimization should not be used. For internal use only.
|
|
27
31
|
attr_reader :fast_instance_delete_sql
|
|
@@ -49,10 +53,9 @@ module Sequel
|
|
|
49
53
|
# Whether to raise an error when unable to typecast data for a column
|
|
50
54
|
# (default: true). This should be set to false if you want to use
|
|
51
55
|
# validations to display nice error messages to the user (e.g. most
|
|
52
|
-
# web applications). You can use the
|
|
53
|
-
# (from
|
|
54
|
-
#
|
|
55
|
-
# columns that aren't blobs or strings.
|
|
56
|
+
# web applications). You can use the validates_schema_types validation
|
|
57
|
+
# (from the validation_helpers plugin) in connection with this setting to
|
|
58
|
+
# check for typecast failures during validation.
|
|
56
59
|
attr_accessor :raise_on_typecast_failure
|
|
57
60
|
|
|
58
61
|
# Whether to raise an error if an UPDATE or DELETE query related to
|
|
@@ -245,6 +248,10 @@ module Sequel
|
|
|
245
248
|
#
|
|
246
249
|
# Sequel::Model.db = DB1
|
|
247
250
|
# Artist.db = DB2
|
|
251
|
+
#
|
|
252
|
+
# Note that you should not use this to change the model's database
|
|
253
|
+
# at runtime. If you have that need, you should look into Sequel's
|
|
254
|
+
# sharding support.
|
|
248
255
|
def db=(db)
|
|
249
256
|
@db = db
|
|
250
257
|
set_dataset(db.dataset(@dataset.opts)) if @dataset
|
|
@@ -350,11 +357,22 @@ module Sequel
|
|
|
350
357
|
def inherited(subclass)
|
|
351
358
|
super
|
|
352
359
|
ivs = subclass.instance_variables.collect{|x| x.to_s}
|
|
353
|
-
|
|
354
|
-
INHERITED_INSTANCE_VARIABLES.each do |iv, dup|
|
|
360
|
+
inherited_instance_variables.each do |iv, dup|
|
|
355
361
|
next if ivs.include?(iv.to_s)
|
|
356
|
-
sup_class_value = instance_variable_get(iv)
|
|
357
|
-
|
|
362
|
+
if (sup_class_value = instance_variable_get(iv)) && dup
|
|
363
|
+
sup_class_value = case dup
|
|
364
|
+
when :dup
|
|
365
|
+
sup_class_value.dup
|
|
366
|
+
when :hash_dup
|
|
367
|
+
h = {}
|
|
368
|
+
sup_class_value.each{|k,v| h[k] = v.dup}
|
|
369
|
+
h
|
|
370
|
+
when Proc
|
|
371
|
+
dup.call(sup_class_value)
|
|
372
|
+
else
|
|
373
|
+
raise Error, "bad inherited instance variable type: #{dup.inspect}"
|
|
374
|
+
end
|
|
375
|
+
end
|
|
358
376
|
subclass.instance_variable_set(iv, sup_class_value)
|
|
359
377
|
end
|
|
360
378
|
unless ivs.include?("@dataset")
|
|
@@ -369,7 +387,7 @@ module Sequel
|
|
|
369
387
|
end
|
|
370
388
|
end
|
|
371
389
|
end
|
|
372
|
-
|
|
390
|
+
|
|
373
391
|
# Returns the implicit table name for the model class, which is the demodulized,
|
|
374
392
|
# underscored, pluralized name of the class.
|
|
375
393
|
#
|
|
@@ -497,6 +515,10 @@ module Sequel
|
|
|
497
515
|
#
|
|
498
516
|
# Artist.set_dataset(:tbl_artists)
|
|
499
517
|
# Artist.set_dataset(DB[:artists])
|
|
518
|
+
#
|
|
519
|
+
# Note that you should not use this to change the model's dataset
|
|
520
|
+
# at runtime. If you have that need, you should look into Sequel's
|
|
521
|
+
# sharding support.
|
|
500
522
|
def set_dataset(ds, opts={})
|
|
501
523
|
inherited = opts[:inherited]
|
|
502
524
|
@dataset = case ds
|
|
@@ -647,7 +669,7 @@ module Sequel
|
|
|
647
669
|
# module if the model has a dataset. Add dataset methods to the class for all
|
|
648
670
|
# public dataset methods.
|
|
649
671
|
def dataset_extend(mod)
|
|
650
|
-
@dataset.extend(mod) if @dataset
|
|
672
|
+
@dataset.extend(mod) if defined?(@dataset) && @dataset
|
|
651
673
|
reset_instance_dataset
|
|
652
674
|
dataset_method_modules << mod
|
|
653
675
|
mod.public_instance_methods.each{|meth| def_model_dataset_method(meth)}
|
|
@@ -732,6 +754,14 @@ module Sequel
|
|
|
732
754
|
schema_hash
|
|
733
755
|
end
|
|
734
756
|
|
|
757
|
+
# A hash of instance variables to automatically set up in subclasses.
|
|
758
|
+
# See Sequel::Model::INHERITED_INSTANCE_VARIABLES. It is safe to modify
|
|
759
|
+
# the hash returned by this method, though it may not be safe to modify
|
|
760
|
+
# values of the hash.
|
|
761
|
+
def inherited_instance_variables
|
|
762
|
+
INHERITED_INSTANCE_VARIABLES.dup
|
|
763
|
+
end
|
|
764
|
+
|
|
735
765
|
# For the given opts hash and default name or :class option, add a
|
|
736
766
|
# :class_name option unless already present which contains the name
|
|
737
767
|
# of the class to use as a string. The purpose is to allow late
|
|
@@ -750,7 +780,7 @@ module Sequel
|
|
|
750
780
|
# Module that the class includes that holds methods the class adds for column accessors and
|
|
751
781
|
# associations so that the methods can be overridden with +super+.
|
|
752
782
|
def overridable_methods_module
|
|
753
|
-
include(@overridable_methods_module = Module.new) unless @overridable_methods_module
|
|
783
|
+
include(@overridable_methods_module = Module.new) unless defined?(@overridable_methods_module) && @overridable_methods_module
|
|
754
784
|
@overridable_methods_module
|
|
755
785
|
end
|
|
756
786
|
|
|
@@ -818,7 +848,7 @@ module Sequel
|
|
|
818
848
|
# Reset the instance dataset to a modified copy of the current dataset,
|
|
819
849
|
# should be used whenever the model's dataset is modified.
|
|
820
850
|
def reset_instance_dataset
|
|
821
|
-
@instance_dataset = @dataset.limit(1).naked if @dataset
|
|
851
|
+
@instance_dataset = @dataset.limit(1).naked if defined?(@dataset) && @dataset
|
|
822
852
|
end
|
|
823
853
|
|
|
824
854
|
# Set the columns for this model and create accessor methods for each column.
|
|
@@ -841,7 +871,7 @@ module Sequel
|
|
|
841
871
|
end
|
|
842
872
|
|
|
843
873
|
# Add model methods that call dataset methods
|
|
844
|
-
|
|
874
|
+
Plugins.def_dataset_methods(self, DATASET_METHODS)
|
|
845
875
|
|
|
846
876
|
# Returns a copy of the model's dataset with custom SQL
|
|
847
877
|
#
|
|
@@ -891,7 +921,7 @@ module Sequel
|
|
|
891
921
|
private_class_method :class_attr_overridable, :class_attr_reader
|
|
892
922
|
|
|
893
923
|
class_attr_reader :columns, :db, :primary_key, :db_schema
|
|
894
|
-
class_attr_overridable
|
|
924
|
+
class_attr_overridable(*BOOLEAN_SETTINGS)
|
|
895
925
|
|
|
896
926
|
# The hash of attribute values. Keys are symbols with the names of the
|
|
897
927
|
# underlying database columns.
|
|
@@ -1077,7 +1107,7 @@ module Sequel
|
|
|
1077
1107
|
errors
|
|
1078
1108
|
validate
|
|
1079
1109
|
errors.freeze
|
|
1080
|
-
this.freeze
|
|
1110
|
+
this.freeze if !new? && model.primary_key
|
|
1081
1111
|
super
|
|
1082
1112
|
end
|
|
1083
1113
|
|
|
@@ -1153,7 +1183,18 @@ module Sequel
|
|
|
1153
1183
|
# a.save_changes # No callbacks run, as no changes
|
|
1154
1184
|
# a.modified!
|
|
1155
1185
|
# a.save_changes # Callbacks run, even though no changes made
|
|
1156
|
-
|
|
1186
|
+
#
|
|
1187
|
+
# If a column is given, specifically marked that column as modified,
|
|
1188
|
+
# so that +save_changes+/+update+ will include that column in the
|
|
1189
|
+
# update. This should be used if you plan on mutating the column
|
|
1190
|
+
# value instead of assigning a new column value:
|
|
1191
|
+
#
|
|
1192
|
+
# a.modified!(:name)
|
|
1193
|
+
# a.name.gsub!(/[aeou]/, 'i')
|
|
1194
|
+
def modified!(column=nil)
|
|
1195
|
+
if column && !changed_columns.include?(column)
|
|
1196
|
+
changed_columns << column
|
|
1197
|
+
end
|
|
1157
1198
|
@modified = true
|
|
1158
1199
|
end
|
|
1159
1200
|
|
|
@@ -1165,8 +1206,19 @@ module Sequel
|
|
|
1165
1206
|
# a.modified? # => false
|
|
1166
1207
|
# a.set(:name=>'Jim')
|
|
1167
1208
|
# a.modified? # => true
|
|
1168
|
-
|
|
1169
|
-
|
|
1209
|
+
#
|
|
1210
|
+
# If a column is given, specifically check if the given column has
|
|
1211
|
+
# been modified:
|
|
1212
|
+
#
|
|
1213
|
+
# a.modified?(:num_albums) # => false
|
|
1214
|
+
# a.num_albums = 10
|
|
1215
|
+
# a.modified?(:num_albums) # => true
|
|
1216
|
+
def modified?(column=nil)
|
|
1217
|
+
if column
|
|
1218
|
+
changed_columns.include?(column)
|
|
1219
|
+
else
|
|
1220
|
+
@modified || !changed_columns.empty?
|
|
1221
|
+
end
|
|
1170
1222
|
end
|
|
1171
1223
|
|
|
1172
1224
|
# Returns true if the current instance represents a new record.
|
|
@@ -1331,28 +1383,30 @@ module Sequel
|
|
|
1331
1383
|
# artist.set_fields({}, [:name], :missing=>:raise)
|
|
1332
1384
|
# # Sequel::Error raised
|
|
1333
1385
|
def set_fields(hash, fields, opts=nil)
|
|
1334
|
-
if opts
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1386
|
+
opts = if opts
|
|
1387
|
+
model.default_set_fields_options.merge(opts)
|
|
1388
|
+
else
|
|
1389
|
+
model.default_set_fields_options
|
|
1390
|
+
end
|
|
1391
|
+
|
|
1392
|
+
case opts[:missing]
|
|
1393
|
+
when :skip
|
|
1394
|
+
fields.each do |f|
|
|
1395
|
+
if hash.has_key?(f)
|
|
1396
|
+
send("#{f}=", hash[f])
|
|
1397
|
+
elsif f.is_a?(Symbol) && hash.has_key?(sf = f.to_s)
|
|
1398
|
+
send("#{sf}=", hash[sf])
|
|
1343
1399
|
end
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1400
|
+
end
|
|
1401
|
+
when :raise
|
|
1402
|
+
fields.each do |f|
|
|
1403
|
+
if hash.has_key?(f)
|
|
1404
|
+
send("#{f}=", hash[f])
|
|
1405
|
+
elsif f.is_a?(Symbol) && hash.has_key?(sf = f.to_s)
|
|
1406
|
+
send("#{sf}=", hash[sf])
|
|
1407
|
+
else
|
|
1408
|
+
raise(Sequel::Error, "missing field in hash: #{f.inspect} not in #{hash.inspect}")
|
|
1353
1409
|
end
|
|
1354
|
-
else
|
|
1355
|
-
fields.each{|f| send("#{f}=", hash[f])}
|
|
1356
1410
|
end
|
|
1357
1411
|
else
|
|
1358
1412
|
fields.each{|f| send("#{f}=", hash[f])}
|
|
@@ -1761,6 +1815,13 @@ module Sequel
|
|
|
1761
1815
|
raise HookFailed.new("the #{type} hook failed", self)
|
|
1762
1816
|
end
|
|
1763
1817
|
|
|
1818
|
+
# Get the ruby class or classes related to the given column's type.
|
|
1819
|
+
def schema_type_class(column)
|
|
1820
|
+
if (sch = db_schema[column]) && (type = sch[:type])
|
|
1821
|
+
db.schema_type_class(type)
|
|
1822
|
+
end
|
|
1823
|
+
end
|
|
1824
|
+
|
|
1764
1825
|
# Set the columns, filtered by the only and except arrays.
|
|
1765
1826
|
def set_restricted(hash, only, except)
|
|
1766
1827
|
return self if hash.empty?
|
data/lib/sequel/model/plugins.rb
CHANGED
|
@@ -20,5 +20,30 @@ module Sequel
|
|
|
20
20
|
# every time the Model.plugin method is called, after including/extending
|
|
21
21
|
# any modules.
|
|
22
22
|
module Plugins
|
|
23
|
+
# In the given module +mod+, define methods that are call the same method
|
|
24
|
+
# on the dataset. This is designed for plugins to define dataset methods
|
|
25
|
+
# inside ClassMethods that call the implementations in DatasetMethods.
|
|
26
|
+
def self.def_dataset_methods(mod, meths)
|
|
27
|
+
Array(meths).each do |meth|
|
|
28
|
+
mod.class_eval("def #{meth}(*args, &block); dataset.#{meth}(*args, &block) end", __FILE__, __LINE__)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Add method to +mod+ that overrides inherited_instance_variables to include the
|
|
33
|
+
# values in this hash.
|
|
34
|
+
def self.inherited_instance_variables(mod, hash)
|
|
35
|
+
mod.send(:define_method, :inherited_instance_variables) do ||
|
|
36
|
+
super().merge!(hash)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Add method to +mod+ that overrides set_dataset to call the method afterward.
|
|
41
|
+
def self.after_set_dataset(mod, meth)
|
|
42
|
+
mod.send(:define_method, :set_dataset) do |*a|
|
|
43
|
+
r = super(*a)
|
|
44
|
+
send(meth)
|
|
45
|
+
r
|
|
46
|
+
end
|
|
47
|
+
end
|
|
23
48
|
end
|
|
24
49
|
end
|
|
@@ -12,37 +12,42 @@ module Sequel
|
|
|
12
12
|
# album.artist # reloads associated artist
|
|
13
13
|
#
|
|
14
14
|
module AssociationAutoreloading
|
|
15
|
+
def self.apply(model)
|
|
16
|
+
model.instance_variable_set(:@autoreloading_associations, {})
|
|
17
|
+
end
|
|
18
|
+
|
|
15
19
|
module ClassMethods
|
|
16
|
-
|
|
20
|
+
# Hash with column symbol keys and arrays of many_to_one
|
|
21
|
+
# association symbols that should be cleared when the column
|
|
22
|
+
# value changes.
|
|
23
|
+
attr_reader :autoreloading_associations
|
|
17
24
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def create_autoreloading_association_setter(key, assocs)
|
|
22
|
-
include(@autoreloading_associations_module ||= Module.new) unless @autoreloading_associations_module
|
|
23
|
-
@autoreloading_associations_module.class_eval do
|
|
24
|
-
unless method_defined?("#{key}=")
|
|
25
|
-
define_method("#{key}=") do |v|
|
|
26
|
-
o = send(key)
|
|
27
|
-
super(v)
|
|
28
|
-
assocs.each{|a| associations.delete(a)} if send(key) != o
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
25
|
+
Plugins.inherited_instance_variables(self, :@autoreloading_associations=>:hash_dup)
|
|
26
|
+
|
|
27
|
+
private
|
|
33
28
|
|
|
34
|
-
#
|
|
35
|
-
#
|
|
29
|
+
# Add the association to the array of associations to clear for
|
|
30
|
+
# each of the foreign key columns.
|
|
36
31
|
def def_many_to_one(opts)
|
|
37
32
|
super
|
|
38
|
-
@autoreloading_associations ||= {}
|
|
39
33
|
opts[:keys].each do |key|
|
|
40
|
-
|
|
41
|
-
assocs << opts[:name]
|
|
42
|
-
create_autoreloading_association_setter(key, assocs)
|
|
34
|
+
(@autoreloading_associations[key] ||= []) << opts[:name]
|
|
43
35
|
end
|
|
44
36
|
end
|
|
45
37
|
end
|
|
38
|
+
|
|
39
|
+
module InstanceMethods
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
# If a foreign key column value changes, clear the related
|
|
43
|
+
# cached associations.
|
|
44
|
+
def change_column_value(column, value)
|
|
45
|
+
if assocs = model.autoreloading_associations[column]
|
|
46
|
+
assocs.each{|a| associations.delete(a)}
|
|
47
|
+
end
|
|
48
|
+
super
|
|
49
|
+
end
|
|
50
|
+
end
|
|
46
51
|
end
|
|
47
52
|
end
|
|
48
53
|
end
|
|
@@ -71,13 +71,7 @@ module Sequel
|
|
|
71
71
|
end
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
def inherited(subclass)
|
|
76
|
-
super
|
|
77
|
-
ad = association_dependencies.dup
|
|
78
|
-
ad.keys.each{|k| ad[k] = ad[k].dup}
|
|
79
|
-
subclass.instance_variable_set(:@association_dependencies, ad)
|
|
80
|
-
end
|
|
74
|
+
Plugins.inherited_instance_variables(self, :@association_dependencies=>:hash_dup)
|
|
81
75
|
end
|
|
82
76
|
|
|
83
77
|
module InstanceMethods
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Plugins
|
|
3
|
+
# The auto_validations plugin automatically sets up three types of validations
|
|
4
|
+
# for your model columns:
|
|
5
|
+
#
|
|
6
|
+
# 1. type validations for all columns
|
|
7
|
+
# 2. presence validations on NOT NULL columns
|
|
8
|
+
# 3. unique validations on columns or sets of columns with unique indexes
|
|
9
|
+
#
|
|
10
|
+
# To determine the columns to use for the presence validations and the types for the type validations,
|
|
11
|
+
# the plugin looks at the database schema for the model's table. To determine
|
|
12
|
+
# the unique validations, Sequel looks at the indexes on the table. In order
|
|
13
|
+
# for this plugin to be fully functional, the underlying database adapter needs
|
|
14
|
+
# to support both schema and index parsing.
|
|
15
|
+
#
|
|
16
|
+
# This plugin uses the validation_helpers plugin underneath to implement the
|
|
17
|
+
# validations. It does not allow for any per-column validation message
|
|
18
|
+
# customization, but you can alter the messages for the given type of validation
|
|
19
|
+
# on a per-model basis (see the validation_helpers documentation).
|
|
20
|
+
#
|
|
21
|
+
# You can skip certain types of validations from being automatically added via:
|
|
22
|
+
#
|
|
23
|
+
# Model.skip_auto_validations(:presence)
|
|
24
|
+
#
|
|
25
|
+
# If you want to skip all auto validations (only useful if loading the plugin
|
|
26
|
+
# in a superclass):
|
|
27
|
+
#
|
|
28
|
+
# Model.skip_auto_validations(:all)
|
|
29
|
+
#
|
|
30
|
+
# Usage:
|
|
31
|
+
#
|
|
32
|
+
# # Make all model subclass use auto validations (called before loading subclasses)
|
|
33
|
+
# Sequel::Model.plugin :auto_validations
|
|
34
|
+
#
|
|
35
|
+
# # Make the Album class use auto validations
|
|
36
|
+
# Album.plugin :auto_validations
|
|
37
|
+
module AutoValidations
|
|
38
|
+
# Load the validation_helpers plugin and setup data structures.
|
|
39
|
+
def self.apply(model)
|
|
40
|
+
model.instance_eval do
|
|
41
|
+
plugin :validation_helpers
|
|
42
|
+
@auto_validate_presence_columns = []
|
|
43
|
+
@auto_validate_unique_columns = []
|
|
44
|
+
@auto_validate_types = true
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Setup auto validations for the model if it has a dataset.
|
|
49
|
+
def self.configure(model)
|
|
50
|
+
model.instance_eval do
|
|
51
|
+
setup_auto_validations if @dataset
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
module ClassMethods
|
|
56
|
+
# The columns with automatic presence validations
|
|
57
|
+
attr_reader :auto_validate_presence_columns
|
|
58
|
+
|
|
59
|
+
# The columns or sets of columns with automatic unique validations
|
|
60
|
+
attr_reader :auto_validate_unique_columns
|
|
61
|
+
|
|
62
|
+
Plugins.inherited_instance_variables(self, :@auto_validate_types=>nil, :@auto_validate_presence_columns=>:dup, :@auto_validate_unique_columns=>:dup)
|
|
63
|
+
Plugins.after_set_dataset(self, :setup_auto_validations)
|
|
64
|
+
|
|
65
|
+
# Whether to automatically validate schema types for all columns
|
|
66
|
+
def auto_validate_types?
|
|
67
|
+
@auto_validate_types
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Skip automatic validations for the given validation type (:presence, :types, :unique).
|
|
71
|
+
# If :all is given as the type, skip all auto validations.
|
|
72
|
+
def skip_auto_validations(type)
|
|
73
|
+
if type == :all
|
|
74
|
+
[:presence, :types, :unique].each{|v| skip_auto_validations(v)}
|
|
75
|
+
elsif type == :types
|
|
76
|
+
@auto_validate_types = false
|
|
77
|
+
else
|
|
78
|
+
send("auto_validate_#{type}_columns").clear
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
# Parse the database schema and indexes and record the columns to automatically validate.
|
|
85
|
+
def setup_auto_validations
|
|
86
|
+
@auto_validate_presence_columns = db_schema.select{|col, sch| sch[:allow_null] == false && sch[:ruby_default].nil?}.map{|col, sch| col} - Array(primary_key)
|
|
87
|
+
@auto_validate_unique_columns = if db.respond_to?(:indexes)
|
|
88
|
+
db.indexes(dataset.first_source_table).select{|name, idx| idx[:unique] == true}.map{|name, idx| idx[:columns]}
|
|
89
|
+
else
|
|
90
|
+
[]
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
module InstanceMethods
|
|
96
|
+
# Validate the model's auto validations columns
|
|
97
|
+
def validate
|
|
98
|
+
super
|
|
99
|
+
if presence_columns = model.auto_validate_presence_columns
|
|
100
|
+
validates_not_null(presence_columns)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
validates_schema_types if model.auto_validate_types?
|
|
104
|
+
|
|
105
|
+
model.auto_validate_unique_columns.each{|cols| validates_unique(cols)}
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|