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