sequel 3.47.0 → 3.48.0

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