sequel 3.46.0 → 3.47.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (170) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +96 -0
  3. data/Rakefile +7 -1
  4. data/bin/sequel +6 -4
  5. data/doc/active_record.rdoc +1 -1
  6. data/doc/advanced_associations.rdoc +14 -35
  7. data/doc/association_basics.rdoc +66 -4
  8. data/doc/migration.rdoc +4 -0
  9. data/doc/opening_databases.rdoc +6 -0
  10. data/doc/postgresql.rdoc +302 -0
  11. data/doc/release_notes/3.47.0.txt +270 -0
  12. data/doc/security.rdoc +6 -0
  13. data/lib/sequel/adapters/ibmdb.rb +9 -9
  14. data/lib/sequel/adapters/jdbc.rb +22 -7
  15. data/lib/sequel/adapters/jdbc/postgresql.rb +7 -2
  16. data/lib/sequel/adapters/mock.rb +2 -0
  17. data/lib/sequel/adapters/postgres.rb +44 -13
  18. data/lib/sequel/adapters/shared/mssql.rb +1 -1
  19. data/lib/sequel/adapters/shared/mysql.rb +2 -2
  20. data/lib/sequel/adapters/shared/postgres.rb +94 -55
  21. data/lib/sequel/adapters/shared/sqlite.rb +3 -1
  22. data/lib/sequel/adapters/sqlite.rb +2 -2
  23. data/lib/sequel/adapters/utils/pg_types.rb +1 -14
  24. data/lib/sequel/adapters/utils/split_alter_table.rb +3 -3
  25. data/lib/sequel/connection_pool/threaded.rb +1 -1
  26. data/lib/sequel/core.rb +1 -1
  27. data/lib/sequel/database/connecting.rb +2 -2
  28. data/lib/sequel/database/features.rb +5 -0
  29. data/lib/sequel/database/misc.rb +47 -5
  30. data/lib/sequel/database/query.rb +2 -2
  31. data/lib/sequel/dataset/actions.rb +4 -2
  32. data/lib/sequel/dataset/misc.rb +1 -1
  33. data/lib/sequel/dataset/prepared_statements.rb +1 -1
  34. data/lib/sequel/dataset/query.rb +8 -6
  35. data/lib/sequel/dataset/sql.rb +8 -6
  36. data/lib/sequel/extensions/constraint_validations.rb +5 -2
  37. data/lib/sequel/extensions/migration.rb +10 -8
  38. data/lib/sequel/extensions/pagination.rb +3 -0
  39. data/lib/sequel/extensions/pg_array.rb +85 -25
  40. data/lib/sequel/extensions/pg_hstore.rb +8 -1
  41. data/lib/sequel/extensions/pg_hstore_ops.rb +4 -1
  42. data/lib/sequel/extensions/pg_inet.rb +16 -13
  43. data/lib/sequel/extensions/pg_interval.rb +6 -2
  44. data/lib/sequel/extensions/pg_json.rb +18 -11
  45. data/lib/sequel/extensions/pg_range.rb +17 -2
  46. data/lib/sequel/extensions/pg_range_ops.rb +7 -5
  47. data/lib/sequel/extensions/pg_row.rb +29 -12
  48. data/lib/sequel/extensions/pretty_table.rb +3 -0
  49. data/lib/sequel/extensions/query.rb +3 -0
  50. data/lib/sequel/extensions/schema_caching.rb +2 -0
  51. data/lib/sequel/extensions/schema_dumper.rb +3 -1
  52. data/lib/sequel/extensions/select_remove.rb +3 -0
  53. data/lib/sequel/model.rb +8 -2
  54. data/lib/sequel/model/associations.rb +39 -27
  55. data/lib/sequel/model/base.rb +99 -38
  56. data/lib/sequel/model/plugins.rb +25 -0
  57. data/lib/sequel/plugins/association_autoreloading.rb +27 -22
  58. data/lib/sequel/plugins/association_dependencies.rb +1 -7
  59. data/lib/sequel/plugins/auto_validations.rb +110 -0
  60. data/lib/sequel/plugins/boolean_readers.rb +1 -6
  61. data/lib/sequel/plugins/caching.rb +6 -13
  62. data/lib/sequel/plugins/class_table_inheritance.rb +1 -0
  63. data/lib/sequel/plugins/composition.rb +14 -7
  64. data/lib/sequel/plugins/constraint_validations.rb +2 -13
  65. data/lib/sequel/plugins/defaults_setter.rb +1 -6
  66. data/lib/sequel/plugins/dirty.rb +8 -0
  67. data/lib/sequel/plugins/error_splitter.rb +54 -0
  68. data/lib/sequel/plugins/force_encoding.rb +1 -5
  69. data/lib/sequel/plugins/hook_class_methods.rb +1 -6
  70. data/lib/sequel/plugins/input_transformer.rb +79 -0
  71. data/lib/sequel/plugins/instance_filters.rb +7 -1
  72. data/lib/sequel/plugins/instance_hooks.rb +7 -1
  73. data/lib/sequel/plugins/json_serializer.rb +5 -10
  74. data/lib/sequel/plugins/lazy_attributes.rb +20 -7
  75. data/lib/sequel/plugins/list.rb +1 -6
  76. data/lib/sequel/plugins/many_through_many.rb +1 -2
  77. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +23 -39
  78. data/lib/sequel/plugins/optimistic_locking.rb +1 -5
  79. data/lib/sequel/plugins/pg_row.rb +4 -2
  80. data/lib/sequel/plugins/pg_typecast_on_load.rb +3 -7
  81. data/lib/sequel/plugins/prepared_statements.rb +1 -5
  82. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -11
  83. data/lib/sequel/plugins/rcte_tree.rb +2 -2
  84. data/lib/sequel/plugins/serialization.rb +11 -13
  85. data/lib/sequel/plugins/serialization_modification_detection.rb +13 -1
  86. data/lib/sequel/plugins/single_table_inheritance.rb +4 -4
  87. data/lib/sequel/plugins/static_cache.rb +67 -19
  88. data/lib/sequel/plugins/string_stripper.rb +7 -27
  89. data/lib/sequel/plugins/subclasses.rb +3 -5
  90. data/lib/sequel/plugins/tactical_eager_loading.rb +2 -2
  91. data/lib/sequel/plugins/timestamps.rb +2 -7
  92. data/lib/sequel/plugins/touch.rb +5 -8
  93. data/lib/sequel/plugins/tree.rb +1 -6
  94. data/lib/sequel/plugins/typecast_on_load.rb +1 -5
  95. data/lib/sequel/plugins/update_primary_key.rb +26 -14
  96. data/lib/sequel/plugins/validation_class_methods.rb +31 -16
  97. data/lib/sequel/plugins/validation_helpers.rb +50 -26
  98. data/lib/sequel/plugins/xml_serializer.rb +3 -6
  99. data/lib/sequel/sql.rb +1 -1
  100. data/lib/sequel/version.rb +1 -1
  101. data/spec/adapters/postgres_spec.rb +131 -15
  102. data/spec/adapters/sqlite_spec.rb +1 -1
  103. data/spec/core/connection_pool_spec.rb +16 -17
  104. data/spec/core/database_spec.rb +111 -40
  105. data/spec/core/dataset_spec.rb +65 -74
  106. data/spec/core/expression_filters_spec.rb +6 -5
  107. data/spec/core/object_graph_spec.rb +0 -1
  108. data/spec/core/schema_spec.rb +23 -23
  109. data/spec/core/spec_helper.rb +5 -1
  110. data/spec/extensions/association_dependencies_spec.rb +1 -1
  111. data/spec/extensions/association_proxies_spec.rb +1 -1
  112. data/spec/extensions/auto_validations_spec.rb +90 -0
  113. data/spec/extensions/caching_spec.rb +6 -0
  114. data/spec/extensions/class_table_inheritance_spec.rb +8 -1
  115. data/spec/extensions/composition_spec.rb +12 -5
  116. data/spec/extensions/constraint_validations_spec.rb +4 -4
  117. data/spec/extensions/core_refinements_spec.rb +29 -79
  118. data/spec/extensions/dirty_spec.rb +14 -0
  119. data/spec/extensions/error_splitter_spec.rb +18 -0
  120. data/spec/extensions/identity_map_spec.rb +0 -1
  121. data/spec/extensions/input_transformer_spec.rb +54 -0
  122. data/spec/extensions/instance_filters_spec.rb +6 -0
  123. data/spec/extensions/instance_hooks_spec.rb +12 -1
  124. data/spec/extensions/json_serializer_spec.rb +0 -1
  125. data/spec/extensions/lazy_attributes_spec.rb +64 -55
  126. data/spec/extensions/looser_typecasting_spec.rb +1 -1
  127. data/spec/extensions/many_through_many_spec.rb +3 -4
  128. data/spec/extensions/many_to_one_pk_lookup_spec.rb +53 -15
  129. data/spec/extensions/migration_spec.rb +16 -0
  130. data/spec/extensions/null_dataset_spec.rb +1 -1
  131. data/spec/extensions/pg_array_spec.rb +48 -1
  132. data/spec/extensions/pg_hstore_ops_spec.rb +10 -2
  133. data/spec/extensions/pg_hstore_spec.rb +5 -0
  134. data/spec/extensions/pg_inet_spec.rb +5 -0
  135. data/spec/extensions/pg_interval_spec.rb +7 -3
  136. data/spec/extensions/pg_json_spec.rb +6 -1
  137. data/spec/extensions/pg_range_ops_spec.rb +4 -1
  138. data/spec/extensions/pg_range_spec.rb +5 -0
  139. data/spec/extensions/pg_row_plugin_spec.rb +13 -0
  140. data/spec/extensions/pg_row_spec.rb +28 -19
  141. data/spec/extensions/pg_typecast_on_load_spec.rb +6 -1
  142. data/spec/extensions/prepared_statements_associations_spec.rb +1 -1
  143. data/spec/extensions/query_literals_spec.rb +1 -1
  144. data/spec/extensions/rcte_tree_spec.rb +2 -2
  145. data/spec/extensions/schema_spec.rb +2 -2
  146. data/spec/extensions/serialization_modification_detection_spec.rb +8 -0
  147. data/spec/extensions/serialization_spec.rb +15 -1
  148. data/spec/extensions/sharding_spec.rb +1 -1
  149. data/spec/extensions/single_table_inheritance_spec.rb +1 -1
  150. data/spec/extensions/static_cache_spec.rb +59 -9
  151. data/spec/extensions/tactical_eager_loading_spec.rb +19 -4
  152. data/spec/extensions/update_primary_key_spec.rb +17 -1
  153. data/spec/extensions/validation_class_methods_spec.rb +25 -0
  154. data/spec/extensions/validation_helpers_spec.rb +59 -3
  155. data/spec/integration/associations_test.rb +5 -5
  156. data/spec/integration/eager_loader_test.rb +32 -63
  157. data/spec/integration/model_test.rb +2 -2
  158. data/spec/integration/plugin_test.rb +88 -56
  159. data/spec/integration/prepared_statement_test.rb +1 -1
  160. data/spec/integration/schema_test.rb +1 -1
  161. data/spec/integration/timezone_test.rb +0 -1
  162. data/spec/integration/transaction_test.rb +0 -1
  163. data/spec/model/association_reflection_spec.rb +1 -1
  164. data/spec/model/associations_spec.rb +106 -84
  165. data/spec/model/base_spec.rb +4 -4
  166. data/spec/model/eager_loading_spec.rb +8 -8
  167. data/spec/model/model_spec.rb +27 -9
  168. data/spec/model/plugins_spec.rb +71 -0
  169. data/spec/model/record_spec.rb +99 -13
  170. metadata +12 -2
@@ -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 validates_not_string validations
53
- # (from either the validation_helpers or validation_class_methods standard
54
- # plugins) in connection with option to check for typecast failures for
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
- EMPTY_INSTANCE_VARIABLES.each{|iv| subclass.instance_variable_set(iv, nil) unless ivs.include?(iv.to_s)}
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
- sup_class_value = sup_class_value.dup if dup == :dup && sup_class_value
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
- DATASET_METHODS.each{|arg| class_eval("def #{arg}(*args, &block); dataset.#{arg}(*args, &block) end", __FILE__, __LINE__)}
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 *BOOLEAN_SETTINGS
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 unless new?
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
- def modified!
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
- def modified?
1169
- @modified || !changed_columns.empty?
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
- case opts[:missing]
1336
- when :skip
1337
- fields.each do |f|
1338
- if hash.has_key?(f)
1339
- send("#{f}=", hash[f])
1340
- elsif f.is_a?(Symbol) && hash.has_key?(sf = f.to_s)
1341
- send("#{sf}=", hash[sf])
1342
- end
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
- when :raise
1345
- fields.each do |f|
1346
- if hash.has_key?(f)
1347
- send("#{f}=", hash[f])
1348
- elsif f.is_a?(Symbol) && hash.has_key?(sf = f.to_s)
1349
- send("#{sf}=", hash[sf])
1350
- else
1351
- raise(Sequel::Error, "missing field in hash: #{f.inspect} not in #{hash.inspect}")
1352
- end
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?
@@ -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
- private
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
- # Create a setter method for +key+ in an anonymous module included
19
- # in the class that calls super and clears the cache for
20
- # the given array of associations.
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
- # For each of the foreign keys in the association, create
35
- # a setter method that will clear the association cache.
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
- assocs = @autoreloading_associations[key] ||= []
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
- # Copy the current model object's association_dependencies into the subclass.
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