sequel 3.47.0 → 3.48.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.
Files changed (243) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +230 -0
  3. data/README.rdoc +31 -40
  4. data/Rakefile +1 -14
  5. data/doc/active_record.rdoc +29 -29
  6. data/doc/association_basics.rdoc +4 -13
  7. data/doc/cheat_sheet.rdoc +8 -6
  8. data/doc/code_order.rdoc +89 -0
  9. data/doc/core_extensions.rdoc +3 -3
  10. data/doc/dataset_basics.rdoc +7 -8
  11. data/doc/dataset_filtering.rdoc +7 -2
  12. data/doc/mass_assignment.rdoc +2 -3
  13. data/doc/migration.rdoc +8 -8
  14. data/doc/model_hooks.rdoc +11 -7
  15. data/doc/object_model.rdoc +2 -2
  16. data/doc/opening_databases.rdoc +5 -14
  17. data/doc/prepared_statements.rdoc +5 -9
  18. data/doc/querying.rdoc +23 -28
  19. data/doc/reflection.rdoc +11 -0
  20. data/doc/release_notes/3.48.0.txt +477 -0
  21. data/doc/schema_modification.rdoc +12 -5
  22. data/doc/security.rdoc +2 -2
  23. data/doc/sharding.rdoc +1 -2
  24. data/doc/sql.rdoc +10 -13
  25. data/doc/testing.rdoc +8 -4
  26. data/doc/transactions.rdoc +2 -2
  27. data/doc/validations.rdoc +40 -17
  28. data/doc/virtual_rows.rdoc +2 -2
  29. data/lib/sequel/adapters/ado.rb +25 -20
  30. data/lib/sequel/adapters/ado/access.rb +1 -0
  31. data/lib/sequel/adapters/ado/mssql.rb +1 -0
  32. data/lib/sequel/adapters/db2.rb +9 -7
  33. data/lib/sequel/adapters/dbi.rb +16 -16
  34. data/lib/sequel/adapters/do.rb +17 -18
  35. data/lib/sequel/adapters/do/mysql.rb +1 -0
  36. data/lib/sequel/adapters/do/postgres.rb +2 -0
  37. data/lib/sequel/adapters/do/sqlite.rb +1 -0
  38. data/lib/sequel/adapters/firebird.rb +5 -7
  39. data/lib/sequel/adapters/ibmdb.rb +23 -20
  40. data/lib/sequel/adapters/informix.rb +8 -2
  41. data/lib/sequel/adapters/jdbc.rb +39 -35
  42. data/lib/sequel/adapters/jdbc/as400.rb +1 -0
  43. data/lib/sequel/adapters/jdbc/cubrid.rb +1 -0
  44. data/lib/sequel/adapters/jdbc/db2.rb +1 -0
  45. data/lib/sequel/adapters/jdbc/derby.rb +1 -0
  46. data/lib/sequel/adapters/jdbc/firebird.rb +1 -0
  47. data/lib/sequel/adapters/jdbc/h2.rb +1 -0
  48. data/lib/sequel/adapters/jdbc/hsqldb.rb +1 -0
  49. data/lib/sequel/adapters/jdbc/informix.rb +1 -0
  50. data/lib/sequel/adapters/jdbc/jtds.rb +1 -0
  51. data/lib/sequel/adapters/jdbc/mssql.rb +1 -0
  52. data/lib/sequel/adapters/jdbc/mysql.rb +1 -0
  53. data/lib/sequel/adapters/jdbc/oracle.rb +1 -0
  54. data/lib/sequel/adapters/jdbc/postgresql.rb +2 -0
  55. data/lib/sequel/adapters/jdbc/progress.rb +1 -0
  56. data/lib/sequel/adapters/jdbc/sqlite.rb +1 -0
  57. data/lib/sequel/adapters/jdbc/sqlserver.rb +1 -0
  58. data/lib/sequel/adapters/mock.rb +30 -31
  59. data/lib/sequel/adapters/mysql.rb +6 -7
  60. data/lib/sequel/adapters/mysql2.rb +5 -6
  61. data/lib/sequel/adapters/odbc.rb +22 -20
  62. data/lib/sequel/adapters/odbc/mssql.rb +1 -0
  63. data/lib/sequel/adapters/openbase.rb +4 -1
  64. data/lib/sequel/adapters/oracle.rb +10 -8
  65. data/lib/sequel/adapters/postgres.rb +12 -10
  66. data/lib/sequel/adapters/shared/access.rb +6 -0
  67. data/lib/sequel/adapters/shared/cubrid.rb +2 -0
  68. data/lib/sequel/adapters/shared/db2.rb +2 -0
  69. data/lib/sequel/adapters/shared/firebird.rb +2 -0
  70. data/lib/sequel/adapters/shared/informix.rb +2 -0
  71. data/lib/sequel/adapters/shared/mssql.rb +14 -8
  72. data/lib/sequel/adapters/shared/mysql.rb +6 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +2 -0
  74. data/lib/sequel/adapters/shared/postgres.rb +14 -4
  75. data/lib/sequel/adapters/shared/progress.rb +1 -0
  76. data/lib/sequel/adapters/shared/sqlite.rb +4 -3
  77. data/lib/sequel/adapters/sqlite.rb +6 -7
  78. data/lib/sequel/adapters/swift.rb +20 -21
  79. data/lib/sequel/adapters/swift/mysql.rb +1 -0
  80. data/lib/sequel/adapters/swift/postgres.rb +2 -0
  81. data/lib/sequel/adapters/swift/sqlite.rb +1 -0
  82. data/lib/sequel/adapters/tinytds.rb +5 -6
  83. data/lib/sequel/adapters/utils/emulate_offset_with_reverse_and_count.rb +68 -0
  84. data/lib/sequel/connection_pool.rb +1 -1
  85. data/lib/sequel/core.rb +57 -50
  86. data/lib/sequel/database/connecting.rb +9 -10
  87. data/lib/sequel/database/dataset.rb +11 -6
  88. data/lib/sequel/database/dataset_defaults.rb +61 -69
  89. data/lib/sequel/database/features.rb +21 -0
  90. data/lib/sequel/database/misc.rb +23 -3
  91. data/lib/sequel/database/query.rb +13 -7
  92. data/lib/sequel/database/schema_methods.rb +6 -6
  93. data/lib/sequel/database/transactions.rb +1 -0
  94. data/lib/sequel/dataset/actions.rb +51 -38
  95. data/lib/sequel/dataset/features.rb +1 -0
  96. data/lib/sequel/dataset/graph.rb +9 -33
  97. data/lib/sequel/dataset/misc.rb +30 -5
  98. data/lib/sequel/dataset/mutation.rb +2 -3
  99. data/lib/sequel/dataset/prepared_statements.rb +1 -1
  100. data/lib/sequel/dataset/query.rb +91 -27
  101. data/lib/sequel/dataset/sql.rb +40 -6
  102. data/lib/sequel/deprecated.rb +74 -0
  103. data/lib/sequel/deprecated_core_extensions.rb +135 -0
  104. data/lib/sequel/extensions/columns_introspection.rb +1 -5
  105. data/lib/sequel/extensions/core_extensions.rb +10 -3
  106. data/lib/sequel/extensions/date_arithmetic.rb +1 -0
  107. data/lib/sequel/extensions/empty_array_ignore_nulls.rb +33 -0
  108. data/lib/sequel/extensions/filter_having.rb +58 -0
  109. data/lib/sequel/extensions/graph_each.rb +63 -0
  110. data/lib/sequel/extensions/hash_aliases.rb +44 -0
  111. data/lib/sequel/extensions/looser_typecasting.rb +14 -3
  112. data/lib/sequel/extensions/migration.rb +2 -3
  113. data/lib/sequel/extensions/named_timezones.rb +14 -1
  114. data/lib/sequel/extensions/null_dataset.rb +7 -1
  115. data/lib/sequel/extensions/pagination.rb +15 -5
  116. data/lib/sequel/extensions/pg_auto_parameterize.rb +1 -0
  117. data/lib/sequel/extensions/pg_hstore_ops.rb +48 -14
  118. data/lib/sequel/extensions/pg_json.rb +7 -7
  119. data/lib/sequel/extensions/pg_range_ops.rb +8 -2
  120. data/lib/sequel/extensions/pg_statement_cache.rb +1 -0
  121. data/lib/sequel/extensions/pretty_table.rb +13 -4
  122. data/lib/sequel/extensions/query.rb +21 -4
  123. data/lib/sequel/extensions/ruby18_symbol_extensions.rb +22 -0
  124. data/lib/sequel/extensions/schema_caching.rb +10 -7
  125. data/lib/sequel/extensions/schema_dumper.rb +35 -48
  126. data/lib/sequel/extensions/select_remove.rb +13 -4
  127. data/lib/sequel/extensions/sequel_3_dataset_methods.rb +117 -0
  128. data/lib/sequel/extensions/set_overrides.rb +43 -0
  129. data/lib/sequel/extensions/to_dot.rb +6 -0
  130. data/lib/sequel/model.rb +12 -6
  131. data/lib/sequel/model/associations.rb +80 -38
  132. data/lib/sequel/model/base.rb +137 -52
  133. data/lib/sequel/model/errors.rb +7 -2
  134. data/lib/sequel/plugins/active_model.rb +13 -0
  135. data/lib/sequel/plugins/after_initialize.rb +43 -0
  136. data/lib/sequel/plugins/association_proxies.rb +63 -7
  137. data/lib/sequel/plugins/auto_validations.rb +56 -16
  138. data/lib/sequel/plugins/blacklist_security.rb +63 -0
  139. data/lib/sequel/plugins/class_table_inheritance.rb +9 -0
  140. data/lib/sequel/plugins/constraint_validations.rb +50 -8
  141. data/lib/sequel/plugins/dataset_associations.rb +2 -0
  142. data/lib/sequel/plugins/hook_class_methods.rb +7 -1
  143. data/lib/sequel/plugins/identity_map.rb +4 -0
  144. data/lib/sequel/plugins/json_serializer.rb +32 -13
  145. data/lib/sequel/plugins/optimistic_locking.rb +1 -1
  146. data/lib/sequel/plugins/rcte_tree.rb +4 -4
  147. data/lib/sequel/plugins/scissors.rb +33 -0
  148. data/lib/sequel/plugins/serialization.rb +1 -1
  149. data/lib/sequel/plugins/single_table_inheritance.rb +6 -0
  150. data/lib/sequel/plugins/tree.rb +5 -1
  151. data/lib/sequel/plugins/validation_class_methods.rb +2 -1
  152. data/lib/sequel/plugins/validation_helpers.rb +15 -11
  153. data/lib/sequel/plugins/xml_serializer.rb +12 -3
  154. data/lib/sequel/sql.rb +12 -2
  155. data/lib/sequel/timezones.rb +1 -1
  156. data/lib/sequel/version.rb +1 -1
  157. data/lib/sequel_core.rb +1 -0
  158. data/lib/sequel_model.rb +1 -0
  159. data/spec/adapters/mssql_spec.rb +24 -57
  160. data/spec/adapters/postgres_spec.rb +27 -55
  161. data/spec/adapters/spec_helper.rb +1 -1
  162. data/spec/adapters/sqlite_spec.rb +1 -1
  163. data/spec/bin_spec.rb +251 -0
  164. data/spec/core/database_spec.rb +46 -32
  165. data/spec/core/dataset_spec.rb +233 -181
  166. data/spec/core/deprecated_spec.rb +78 -0
  167. data/spec/core/expression_filters_spec.rb +3 -4
  168. data/spec/core/mock_adapter_spec.rb +9 -9
  169. data/spec/core/object_graph_spec.rb +9 -19
  170. data/spec/core/schema_spec.rb +3 -1
  171. data/spec/core/spec_helper.rb +19 -0
  172. data/spec/core_extensions_spec.rb +80 -30
  173. data/spec/extensions/after_initialize_spec.rb +24 -0
  174. data/spec/extensions/association_proxies_spec.rb +37 -1
  175. data/spec/extensions/auto_validations_spec.rb +20 -4
  176. data/spec/extensions/blacklist_security_spec.rb +87 -0
  177. data/spec/extensions/boolean_readers_spec.rb +2 -1
  178. data/spec/extensions/class_table_inheritance_spec.rb +7 -0
  179. data/spec/extensions/columns_introspection_spec.rb +3 -3
  180. data/spec/extensions/constraint_validations_plugin_spec.rb +83 -5
  181. data/spec/extensions/core_refinements_spec.rb +7 -7
  182. data/spec/extensions/dataset_associations_spec.rb +2 -2
  183. data/spec/extensions/date_arithmetic_spec.rb +1 -1
  184. data/spec/extensions/defaults_setter_spec.rb +2 -1
  185. data/spec/extensions/empty_array_ignore_nulls_spec.rb +24 -0
  186. data/spec/extensions/filter_having_spec.rb +40 -0
  187. data/spec/extensions/graph_each_spec.rb +109 -0
  188. data/spec/extensions/hash_aliases_spec.rb +16 -0
  189. data/spec/extensions/hook_class_methods_spec.rb +2 -2
  190. data/spec/extensions/identity_map_spec.rb +3 -3
  191. data/spec/extensions/json_serializer_spec.rb +19 -19
  192. data/spec/extensions/lazy_attributes_spec.rb +1 -0
  193. data/spec/extensions/list_spec.rb +13 -13
  194. data/spec/extensions/looser_typecasting_spec.rb +10 -3
  195. data/spec/extensions/many_through_many_spec.rb +1 -1
  196. data/spec/extensions/migration_spec.rb +7 -7
  197. data/spec/extensions/named_timezones_spec.rb +6 -0
  198. data/spec/extensions/nested_attributes_spec.rb +2 -2
  199. data/spec/extensions/null_dataset_spec.rb +1 -1
  200. data/spec/extensions/pagination_spec.rb +2 -2
  201. data/spec/extensions/pg_hstore_ops_spec.rb +75 -0
  202. data/spec/extensions/pg_range_ops_spec.rb +4 -2
  203. data/spec/extensions/pg_row_plugin_spec.rb +1 -1
  204. data/spec/extensions/pretty_table_spec.rb +1 -1
  205. data/spec/extensions/query_literals_spec.rb +1 -1
  206. data/spec/extensions/query_spec.rb +3 -3
  207. data/spec/extensions/schema_caching_spec.rb +3 -3
  208. data/spec/extensions/schema_dumper_spec.rb +27 -2
  209. data/spec/extensions/schema_spec.rb +2 -2
  210. data/spec/extensions/scissors_spec.rb +26 -0
  211. data/spec/extensions/select_remove_spec.rb +1 -1
  212. data/spec/extensions/sequel_3_dataset_methods_spec.rb +102 -0
  213. data/spec/extensions/set_overrides_spec.rb +45 -0
  214. data/spec/extensions/single_table_inheritance_spec.rb +10 -0
  215. data/spec/extensions/spec_helper.rb +24 -1
  216. data/spec/extensions/static_cache_spec.rb +1 -1
  217. data/spec/extensions/string_stripper_spec.rb +2 -1
  218. data/spec/extensions/to_dot_spec.rb +1 -1
  219. data/spec/extensions/typecast_on_load_spec.rb +3 -2
  220. data/spec/extensions/update_primary_key_spec.rb +2 -2
  221. data/spec/extensions/validation_class_methods_spec.rb +19 -19
  222. data/spec/extensions/validation_helpers_spec.rb +30 -21
  223. data/spec/extensions/xml_serializer_spec.rb +5 -5
  224. data/spec/integration/associations_test.rb +10 -30
  225. data/spec/integration/dataset_test.rb +20 -24
  226. data/spec/integration/eager_loader_test.rb +5 -5
  227. data/spec/integration/model_test.rb +3 -3
  228. data/spec/integration/plugin_test.rb +7 -39
  229. data/spec/integration/schema_test.rb +4 -38
  230. data/spec/integration/spec_helper.rb +2 -1
  231. data/spec/model/association_reflection_spec.rb +70 -5
  232. data/spec/model/associations_spec.rb +11 -11
  233. data/spec/model/base_spec.rb +25 -8
  234. data/spec/model/class_dataset_methods_spec.rb +143 -0
  235. data/spec/model/dataset_methods_spec.rb +1 -1
  236. data/spec/model/eager_loading_spec.rb +25 -25
  237. data/spec/model/hooks_spec.rb +1 -1
  238. data/spec/model/model_spec.rb +22 -7
  239. data/spec/model/plugins_spec.rb +1 -6
  240. data/spec/model/record_spec.rb +37 -29
  241. data/spec/model/spec_helper.rb +23 -1
  242. data/spec/model/validations_spec.rb +15 -17
  243. metadata +32 -3
@@ -10,14 +10,19 @@ module Sequel
10
10
  # to add new error messages, and +on+ to check existing
11
11
  # error messages.
12
12
  def [](k)
13
- has_key?(k) ? super : (self[k] = [])
13
+ if has_key?(k)
14
+ super
15
+ else
16
+ Sequel::Deprecation.deprecate('Model::Errors#[] autovivification', 'Please switch to Model::Errors#add to add errors, and Model::Errors#on to get errors')
17
+ self[k] = []
18
+ end
14
19
  end
15
20
 
16
21
  # Adds an error for the given attribute.
17
22
  #
18
23
  # errors.add(:name, 'is not valid') if name == 'invalid'
19
24
  def add(att, msg)
20
- self[att] << msg
25
+ fetch(att){self[att] = []} << msg
21
26
  end
22
27
 
23
28
  # Return the total number of error messages.
@@ -16,6 +16,14 @@ module Sequel
16
16
  # # Make the Album class active_model compliant
17
17
  # Album.plugin :active_model
18
18
  module ActiveModel
19
+ # ActiveModel compliant error class
20
+ class Errors < Sequel::Model::Errors
21
+ # Add autovivification so that #[] always returns an array.
22
+ def [](k)
23
+ fetch(k){self[k] = []}
24
+ end
25
+ end
26
+
19
27
  module ClassMethods
20
28
  include ::ActiveModel::Naming
21
29
 
@@ -70,6 +78,11 @@ module Sequel
70
78
  end
71
79
 
72
80
  private
81
+
82
+ # Use ActiveModel compliant errors class.
83
+ def errors_class
84
+ Errors
85
+ end
73
86
 
74
87
  # The string to use to join composite primary key param strings.
75
88
  def to_param_joiner
@@ -0,0 +1,43 @@
1
+ module Sequel
2
+ module Plugins
3
+ # Adds an after_initialize hook to models, called after initializing
4
+ # both new objects and ones loaded from the database.
5
+ #
6
+ # Usage:
7
+ #
8
+ # # Make all model subclasses support the after_initialize hook
9
+ # Sequel::Model.plugin :after_initialize
10
+ #
11
+ # # Make the Album class support the after_initialize hook
12
+ # Album.plugin :after_initialize
13
+ module AfterInitialize
14
+ module ClassMethods
15
+ # Call after_initialize for model objects loaded from the database.
16
+ #def call
17
+ # v = super
18
+ # v.after_initialize
19
+ # v
20
+ #end
21
+
22
+ private
23
+
24
+ # REMOVE40
25
+ def check_deprecated_after_initialize(meths)
26
+ end
27
+ end
28
+
29
+ module InstanceMethods
30
+ # Call after_initialize for new model objects.
31
+ #def initialize(h={})
32
+ # super
33
+ # after_initialize
34
+ #end
35
+
36
+ # An empty after_initialize hook, so that plugins that use this
37
+ # can always call super to get the default behavior.
38
+ #def after_initialize
39
+ #end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -6,6 +6,40 @@ module Sequel
6
6
  # that will load the association and call a method on the association array if sent
7
7
  # an array method, and otherwise send the method to the association's dataset.
8
8
  #
9
+ # You can override which methods to forward to the dataset by passing a block to the plugin:
10
+ #
11
+ # plugin :association_proxies do |opts|
12
+ # [:find, :where, :create].include?(opts[:method])
13
+ # end
14
+ #
15
+ # If the block returns false or nil, the method is sent to the array of associated
16
+ # objects. Otherwise, the method is sent to the association dataset. Here are the entries
17
+ # in the hash passed to the block:
18
+ #
19
+ # :method :: The name of the method
20
+ # :arguments :: The arguments to the method
21
+ # :block :: The block given to the method
22
+ # :instance :: The model instance related to the call
23
+ # :reflection :: The reflection for the association related to the call
24
+ # :proxy_argument :: The argument given to the association method call
25
+ # :proxy_block :: The block given to the association method call
26
+ #
27
+ # For example, in a call like:
28
+ #
29
+ # artist.albums(1){|ds| ds}.foo(2){|x| 3}
30
+ #
31
+ # The opts passed to the block would be:
32
+ #
33
+ # {
34
+ # :method => :foo,
35
+ # :arguments => [2],
36
+ # :block => {|x| 3},
37
+ # :instance => artist,
38
+ # :reflection => {:name=>:albums, ...},
39
+ # :proxy_argument => 1,
40
+ # :proxy_block => {|ds| ds}
41
+ # }
42
+ #
9
43
  # Usage:
10
44
  #
11
45
  # # Use association proxies in all model subclasses (called before loading subclasses)
@@ -14,34 +48,56 @@ module Sequel
14
48
  # # Use association proxies in a specific model subclass
15
49
  # Album.plugin :association_proxies
16
50
  module AssociationProxies
51
+ def self.configure(model, &block)
52
+ model.instance_eval do
53
+ @association_proxy_to_dataset = block if block
54
+ @association_proxy_to_dataset ||= AssociationProxy::DEFAULT_PROXY_TO_DATASET
55
+ end
56
+ end
57
+
17
58
  # A proxy for the association. Calling an array method will load the
18
59
  # associated objects and call the method on the associated object array.
19
60
  # Calling any other method will call that method on the association's dataset.
20
61
  class AssociationProxy < BasicObject
21
- # Empty array used to check if an array responds to the given method.
22
- ARRAY = []
62
+ array = []
63
+
64
+ # Default proc used to determine whether to sent the method to the dataset.
65
+ # If the array would respond to it, sends it to the array instead of the dataset.
66
+ DEFAULT_PROXY_TO_DATASET = proc{|opts| !array.respond_to?(opts[:method])}
23
67
 
24
68
  # Set the association reflection to use, and whether the association should be
25
69
  # reloaded if an array method is called.
26
- def initialize(instance, reflection, reload=nil)
70
+ def initialize(instance, reflection, proxy_argument, &proxy_block)
27
71
  @instance = instance
28
72
  @reflection = reflection
29
- @reload = reload
73
+ @proxy_argument = proxy_argument
74
+ @proxy_block = proxy_block
30
75
  end
31
76
 
32
77
  # Call the method given on the array of associated objects if the method
33
78
  # is an array method, otherwise call the method on the association's dataset.
34
79
  def method_missing(meth, *args, &block)
35
- (ARRAY.respond_to?(meth) ? @instance.send(:load_associated_objects, @reflection, @reload) : @instance.send(@reflection.dataset_method)).
36
- send(meth, *args, &block)
80
+ v = if @instance.model.association_proxy_to_dataset.call(:method=>meth, :arguments=>args, :block=>block, :instance=>@instance, :reflection=>@reflection, :proxy_argument=>@proxy_argument, :proxy_block=>@proxy_block)
81
+ @instance.send(@reflection.dataset_method)
82
+ else
83
+ @instance.send(:load_associated_objects, @reflection, @proxy_argument, &@proxy_block)
84
+ end
85
+ v.send(meth, *args, &block)
37
86
  end
38
87
  end
39
88
 
40
89
  module ClassMethods
90
+ # Proc that accepts a method name, array of arguments, and block and
91
+ # should return a truthy value to send the method to the dataset instead of the
92
+ # array of associated objects.
93
+ attr_reader :association_proxy_to_dataset
94
+
95
+ Plugins.inherited_instance_variables(self, :@association_proxy_to_dataset=>nil)
96
+
41
97
  # Changes the association method to return a proxy instead of the associated objects
42
98
  # directly.
43
99
  def def_association_method(opts)
44
- opts.returns_array? ? association_module_def(opts.association_method, opts){|*r| AssociationProxy.new(self, opts, r[0])} : super
100
+ opts.returns_array? ? association_module_def(opts.association_method, opts){|*r, &block| AssociationProxy.new(self, opts, r[0], &block)} : super
45
101
  end
46
102
  end
47
103
  end
@@ -4,10 +4,10 @@ module Sequel
4
4
  # for your model columns:
5
5
  #
6
6
  # 1. type validations for all columns
7
- # 2. presence validations on NOT NULL columns
7
+ # 2. not_null validations on NOT NULL columns (optionally, presence validations)
8
8
  # 3. unique validations on columns or sets of columns with unique indexes
9
9
  #
10
- # To determine the columns to use for the presence validations and the types for the type validations,
10
+ # To determine the columns to use for the not_null validations and the types for the type validations,
11
11
  # the plugin looks at the database schema for the model's table. To determine
12
12
  # the unique validations, Sequel looks at the indexes on the table. In order
13
13
  # for this plugin to be fully functional, the underlying database adapter needs
@@ -20,13 +20,21 @@ module Sequel
20
20
  #
21
21
  # You can skip certain types of validations from being automatically added via:
22
22
  #
23
- # Model.skip_auto_validations(:presence)
23
+ # Model.skip_auto_validations(:not_null)
24
24
  #
25
25
  # If you want to skip all auto validations (only useful if loading the plugin
26
26
  # in a superclass):
27
27
  #
28
28
  # Model.skip_auto_validations(:all)
29
29
  #
30
+ # By default, the plugin uses a not_null validation for NOT NULL columns, but that
31
+ # can be changed to a presence validation using an option:
32
+ #
33
+ # Model.plugin :auto_validations, :not_null=>:presence
34
+ #
35
+ # This is useful if you want to enforce that NOT NULL string columns do not
36
+ # allow empty values.
37
+ #
30
38
  # Usage:
31
39
  #
32
40
  # # Make all model subclass use auto validations (called before loading subclasses)
@@ -35,43 +43,61 @@ module Sequel
35
43
  # # Make the Album class use auto validations
36
44
  # Album.plugin :auto_validations
37
45
  module AutoValidations
38
- # Load the validation_helpers plugin and setup data structures.
39
- def self.apply(model)
46
+ def self.apply(model, opts={})
40
47
  model.instance_eval do
41
48
  plugin :validation_helpers
42
- @auto_validate_presence_columns = []
49
+ @auto_validate_presence = false
50
+ @auto_validate_not_null_columns = []
51
+ @auto_validate_explicit_not_null_columns = []
43
52
  @auto_validate_unique_columns = []
44
53
  @auto_validate_types = true
45
54
  end
46
55
  end
47
56
 
48
57
  # Setup auto validations for the model if it has a dataset.
49
- def self.configure(model)
58
+ def self.configure(model, opts={})
50
59
  model.instance_eval do
51
60
  setup_auto_validations if @dataset
61
+ if opts[:not_null] == :presence
62
+ @auto_validate_presence = true
63
+ end
52
64
  end
53
65
  end
54
66
 
55
67
  module ClassMethods
56
- # The columns with automatic presence validations
57
- attr_reader :auto_validate_presence_columns
68
+ # The columns with automatic not_null validations
69
+ attr_reader :auto_validate_not_null_columns
70
+
71
+ # The columns with automatic not_null validations for columns present in the values.
72
+ attr_reader :auto_validate_explicit_not_null_columns
58
73
 
59
74
  # The columns or sets of columns with automatic unique validations
60
75
  attr_reader :auto_validate_unique_columns
61
76
 
62
- Plugins.inherited_instance_variables(self, :@auto_validate_types=>nil, :@auto_validate_presence_columns=>:dup, :@auto_validate_unique_columns=>:dup)
77
+ Plugins.inherited_instance_variables(self, :@auto_validate_presence=>nil, :@auto_validate_types=>nil, :@auto_validate_not_null_columns=>:dup, :@auto_validate_explicit_not_null_columns=>:dup, :@auto_validate_unique_columns=>:dup)
63
78
  Plugins.after_set_dataset(self, :setup_auto_validations)
64
79
 
80
+ # REMOVE40
81
+ def auto_validate_presence_columns
82
+ Sequel::Deprecation.deprecate('Model.auto_validate_presence_columns', 'Please switch to auto_validate_not_null_columns')
83
+ auto_validate_not_null_columns
84
+ end
85
+
86
+ # Whether to use a presence validation for not null columns
87
+ def auto_validate_presence?
88
+ @auto_validate_presence
89
+ end
90
+
65
91
  # Whether to automatically validate schema types for all columns
66
92
  def auto_validate_types?
67
93
  @auto_validate_types
68
94
  end
69
95
 
70
- # Skip automatic validations for the given validation type (:presence, :types, :unique).
96
+ # Skip automatic validations for the given validation type (:not_null, :types, :unique).
71
97
  # If :all is given as the type, skip all auto validations.
72
98
  def skip_auto_validations(type)
73
99
  if type == :all
74
- [:presence, :types, :unique].each{|v| skip_auto_validations(v)}
100
+ [:not_null, :types, :unique].each{|v| skip_auto_validations(v)}
75
101
  elsif type == :types
76
102
  @auto_validate_types = false
77
103
  else
@@ -83,8 +109,11 @@ module Sequel
83
109
 
84
110
  # Parse the database schema and indexes and record the columns to automatically validate.
85
111
  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)
112
+ not_null_cols, explicit_not_null_cols = db_schema.select{|col, sch| sch[:allow_null] == false}.partition{|col, sch| sch[:ruby_default].nil?}.map{|cs| cs.map{|col, sch| col}}
113
+ @auto_validate_not_null_columns = not_null_cols - Array(primary_key)
114
+ explicit_not_null_cols += Array(primary_key)
115
+ @auto_validate_explicit_not_null_columns = explicit_not_null_cols.uniq
116
+ @auto_validate_unique_columns = if db.supports_index_parsing?
88
117
  db.indexes(dataset.first_source_table).select{|name, idx| idx[:unique] == true}.map{|name, idx| idx[:columns]}
89
118
  else
90
119
  []
@@ -96,8 +125,19 @@ module Sequel
96
125
  # Validate the model's auto validations columns
97
126
  def validate
98
127
  super
99
- if presence_columns = model.auto_validate_presence_columns
100
- validates_not_null(presence_columns)
128
+ unless (not_null_columns = model.auto_validate_not_null_columns).empty?
129
+ if model.auto_validate_presence?
130
+ validates_presence(not_null_columns)
131
+ else
132
+ validates_not_null(not_null_columns)
133
+ end
134
+ end
135
+ unless (not_null_columns = model.auto_validate_explicit_not_null_columns).empty?
136
+ if model.auto_validate_presence?
137
+ validates_presence(not_null_columns, :allow_missing=>true)
138
+ else
139
+ validates_not_null(not_null_columns, :allow_missing=>true)
140
+ end
101
141
  end
102
142
 
103
143
  validates_schema_types if model.auto_validate_types?
@@ -0,0 +1,63 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The blacklist_security plugin contains blacklist-based support for
4
+ # mass assignment, specifying which columns to not allow mass assignment for,
5
+ # implicitly allowing mass assignment for columns not listed. This is only
6
+ # for backwards compatibility, it should not be used by new code.
7
+ #
8
+ # Usage:
9
+ #
10
+ # # Make all model subclasses support the blacklist security features.
11
+ # Sequel::Model.plugin :blacklist_security
12
+ #
13
+ # # Make the Album class support the blacklist security features.
14
+ # Album.plugin :blacklist_security
15
+ module BlacklistSecurity
16
+ module ClassMethods
17
+ # Which columns are specifically restricted in a call to set/update/new/etc.
18
+ # (default: not set). Some columns are restricted regardless of
19
+ # this setting, such as the primary key column and columns in Model::RESTRICTED_SETTER_METHODS.
20
+ attr_reader :restricted_columns
21
+
22
+ # Set the columns to restrict when using mass assignment (e.g. +set+). Using this means that
23
+ # attempts to call setter methods for the columns listed here will cause an
24
+ # exception or be silently skipped (based on the +strict_param_setting+ setting).
25
+ # If you have any virtual setter methods (methods that end in =) that you
26
+ # want not to be used during mass assignment, they need to be listed here as well (without the =).
27
+ #
28
+ # It's generally a bad idea to rely on a blacklist approach for security. Using a whitelist
29
+ # approach such as set_allowed_columns or the instance level set_only or set_fields methods
30
+ # is usually a better choice. So use of this method is generally a bad idea.
31
+ #
32
+ # Artist.set_restricted_columns(:records_sold)
33
+ # Artist.set(:name=>'Bob', :hometown=>'Sactown') # No Error
34
+ # Artist.set(:name=>'Bob', :records_sold=>30000) # Error
35
+ def set_restricted_columns(*cols)
36
+ clear_setter_methods_cache
37
+ @restricted_columns = cols
38
+ end
39
+ end
40
+
41
+ module InstanceMethods
42
+ # Set all values using the entries in the hash, except for the keys
43
+ # given in except. You should probably use +set_fields+ or +set_only+
44
+ # instead of this method, as blacklist approaches to security are a bad idea.
45
+ #
46
+ # artist.set_except({:name=>'Jim'}, :hometown)
47
+ # artist.name # => 'Jim'
48
+ def set_except(hash, *except)
49
+ set_restricted(hash, false, except.flatten)
50
+ end
51
+
52
+ # Update all values using the entries in the hash, except for the keys
53
+ # given in except. You should probably use +update_fields+ or +update_only+
54
+ # instead of this method, as blacklist approaches to security are a bad idea.
55
+ #
56
+ # artist.update_except({:name=>'Jim'}, :hometown) # UPDATE artists SET name = 'Jim' WHERE (id = 1)
57
+ def update_except(hash, *except)
58
+ update_restricted(hash, false, except.flatten)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -175,6 +175,15 @@ module Sequel
175
175
  def table_name
176
176
  self == cti_base_model ? super : cti_tables.last
177
177
  end
178
+
179
+ private
180
+
181
+ # If calling set_dataset manually, make sure to set the dataset
182
+ # row proc to one that handles inheritance correctly.
183
+ def set_dataset_row_proc(ds)
184
+ ds.row_proc = @dataset.row_proc if @dataset
185
+ end
186
+
178
187
  end
179
188
 
180
189
  module InstanceMethods
@@ -33,7 +33,11 @@ module Sequel
33
33
 
34
34
  # Automatically load the validation_helpers plugin to run the actual validations.
35
35
  def self.apply(model, opts={})
36
- model.plugin :validation_helpers
36
+ model.instance_eval do
37
+ plugin :validation_helpers
38
+ @constraint_validations_table = DEFAULT_CONSTRAINT_VALIDATIONS_TABLE
39
+ @constraint_validation_options = {}
40
+ end
37
41
  end
38
42
 
39
43
  # Parse the constraint validations metadata from the database. Options:
@@ -41,9 +45,25 @@ module Sequel
41
45
  # metadata table. Should only be used if the table
42
46
  # name was overridden when creating the constraint
43
47
  # validations.
48
+ # :validation_options :: Override/augment the options stored in the database with the
49
+ # given options. Keys should be validation type symbols (e.g.
50
+ # :presence) and values should be hashes of options specific
51
+ # to that validation type.
44
52
  def self.configure(model, opts={})
45
- model.instance_variable_set(:@constraint_validations_table, opts[:constraint_validations_table] || DEFAULT_CONSTRAINT_VALIDATIONS_TABLE)
46
- model.send(:parse_constraint_validations)
53
+ model.instance_eval do
54
+ if table = opts[:constraint_validations_table]
55
+ @constraint_validations_table = table
56
+ end
57
+ if vos = opts[:validation_options]
58
+ vos.each do |k, v|
59
+ if existing_options = @constraint_validation_options[k]
60
+ v = existing_options.merge(v)
61
+ end
62
+ @constraint_validation_options[k] = v
63
+ end
64
+ end
65
+ parse_constraint_validations
66
+ end
47
67
  end
48
68
 
49
69
  module DatabaseMethods
@@ -58,10 +78,17 @@ module Sequel
58
78
  # is splatted to send to perform a validation via validation_helpers.
59
79
  attr_reader :constraint_validations
60
80
 
81
+ # A hash of reflections of constraint validations. Keys are type name
82
+ # symbols. Each value is an array of pairs, with the first element being
83
+ # the validation type symbol (e.g. :presence) and the second element being
84
+ # options for the validation. If the validation takes an argument, it appears
85
+ # as the :argument entry in the validation option hash.
86
+ attr_reader :constraint_validation_reflections
87
+
61
88
  # The name of the table containing the constraint validations metadata.
62
89
  attr_reader :constraint_validations_table
63
90
 
64
- Plugins.inherited_instance_variables(self, :@constraint_validations_table=>nil)
91
+ Plugins.inherited_instance_variables(self, :@constraint_validations_table=>nil, :@constraint_validation_options=>:hash_dup)
65
92
  Plugins.after_set_dataset(self, :parse_constraint_validations)
66
93
 
67
94
  private
@@ -78,7 +105,7 @@ module Sequel
78
105
  unless hash = Sequel.synchronize{db.constraint_validations}
79
106
  hash = {}
80
107
  db.from(constraint_validations_table).each do |r|
81
- (hash[r[:table]] ||= []) << constraint_validation_array(r)
108
+ (hash[r[:table]] ||= []) << r
82
109
  end
83
110
  Sequel.synchronize{db.constraint_validations = hash}
84
111
  end
@@ -86,14 +113,16 @@ module Sequel
86
113
  if @dataset
87
114
  ds = @dataset.clone
88
115
  ds.quote_identifiers = false
89
- table_name = ds.literal(model.table_name)
90
- @constraint_validations = Sequel.synchronize{hash[table_name]} || []
116
+ table_name = ds.literal(ds.first_source_table)
117
+ reflections = {}
118
+ @constraint_validations = (Sequel.synchronize{hash[table_name]} || []).map{|r| constraint_validation_array(r, reflections)}
119
+ @constraint_validation_reflections = reflections
91
120
  end
92
121
  end
93
122
 
94
123
  # Given a specific database constraint validation metadata row hash, transform
95
124
  # it in an validation method call array suitable for splatting to send.
96
- def constraint_validation_array(r)
125
+ def constraint_validation_array(r, reflections)
97
126
  opts = {}
98
127
  opts[:message] = r[:message] if r[:message]
99
128
  opts[:allow_nil] = true if db.typecast_value(:boolean, r[:allow_nil])
@@ -131,14 +160,27 @@ module Sequel
131
160
  column.to_sym
132
161
  end
133
162
 
163
+ if type_opts = @constraint_validation_options[type]
164
+ opts = opts.merge(type_opts)
165
+ end
166
+
167
+ reflection_opts = opts
134
168
  a = [:"validates_#{type}"]
169
+
135
170
  if arg
136
171
  a << arg
172
+ reflection_opts = reflection_opts.merge(:argument=>arg)
137
173
  end
138
174
  a << column
139
175
  unless opts.empty?
140
176
  a << opts
141
177
  end
178
+
179
+ if column.is_a?(Array) && column.length == 1
180
+ column = column.first
181
+ end
182
+ (reflections[column] ||= []) << [type, reflection_opts]
183
+
142
184
  a
143
185
  end
144
186