sequel 3.20.0 → 3.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/CHANGELOG +32 -0
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +1 -1
  4. data/Rakefile +13 -3
  5. data/bin/sequel +18 -5
  6. data/doc/active_record.rdoc +4 -4
  7. data/doc/opening_databases.rdoc +38 -1
  8. data/doc/release_notes/3.21.0.txt +87 -0
  9. data/doc/validations.rdoc +2 -2
  10. data/lib/sequel/adapters/informix.rb +1 -1
  11. data/lib/sequel/adapters/jdbc/h2.rb +2 -5
  12. data/lib/sequel/adapters/jdbc/mssql.rb +1 -4
  13. data/lib/sequel/adapters/jdbc/mysql.rb +1 -4
  14. data/lib/sequel/adapters/jdbc/postgresql.rb +1 -6
  15. data/lib/sequel/adapters/jdbc/sqlite.rb +2 -8
  16. data/lib/sequel/adapters/shared/mssql.rb +8 -0
  17. data/lib/sequel/adapters/shared/mysql.rb +23 -3
  18. data/lib/sequel/adapters/shared/oracle.rb +2 -2
  19. data/lib/sequel/adapters/tinytds.rb +125 -0
  20. data/lib/sequel/database/connecting.rb +1 -1
  21. data/lib/sequel/database/schema_methods.rb +37 -5
  22. data/lib/sequel/dataset/sql.rb +6 -6
  23. data/lib/sequel/extensions/schema_dumper.rb +1 -1
  24. data/lib/sequel/model/base.rb +50 -0
  25. data/lib/sequel/model/plugins.rb +0 -55
  26. data/lib/sequel/plugins/association_autoreloading.rb +48 -0
  27. data/lib/sequel/plugins/validation_class_methods.rb +6 -5
  28. data/lib/sequel/plugins/validation_helpers.rb +2 -2
  29. data/lib/sequel/version.rb +1 -1
  30. data/spec/adapters/firebird_spec.rb +6 -6
  31. data/spec/adapters/informix_spec.rb +2 -2
  32. data/spec/adapters/mssql_spec.rb +18 -13
  33. data/spec/adapters/mysql_spec.rb +47 -20
  34. data/spec/adapters/oracle_spec.rb +40 -4
  35. data/spec/adapters/postgres_spec.rb +14 -14
  36. data/spec/adapters/spec_helper.rb +1 -1
  37. data/spec/adapters/sqlite_spec.rb +8 -8
  38. data/spec/core/connection_pool_spec.rb +18 -17
  39. data/spec/core/core_sql_spec.rb +18 -18
  40. data/spec/core/database_spec.rb +62 -62
  41. data/spec/core/dataset_spec.rb +105 -92
  42. data/spec/core/expression_filters_spec.rb +2 -2
  43. data/spec/core/schema_spec.rb +6 -6
  44. data/spec/core/version_spec.rb +1 -1
  45. data/spec/extensions/association_autoreloading_spec.rb +94 -0
  46. data/spec/extensions/blank_spec.rb +6 -6
  47. data/spec/extensions/looser_typecasting_spec.rb +1 -1
  48. data/spec/extensions/migration_spec.rb +6 -6
  49. data/spec/extensions/pagination_spec.rb +2 -2
  50. data/spec/extensions/pretty_table_spec.rb +2 -2
  51. data/spec/extensions/query_spec.rb +2 -2
  52. data/spec/extensions/schema_dumper_spec.rb +2 -1
  53. data/spec/extensions/single_table_inheritance_spec.rb +1 -1
  54. data/spec/extensions/sql_expr_spec.rb +1 -1
  55. data/spec/extensions/string_date_time_spec.rb +4 -4
  56. data/spec/extensions/validation_class_methods_spec.rb +2 -2
  57. data/spec/integration/dataset_test.rb +8 -3
  58. data/spec/integration/plugin_test.rb +5 -5
  59. data/spec/integration/prepared_statement_test.rb +1 -1
  60. data/spec/integration/schema_test.rb +7 -0
  61. data/spec/integration/spec_helper.rb +14 -1
  62. data/spec/integration/timezone_test.rb +4 -4
  63. data/spec/integration/type_test.rb +1 -1
  64. data/spec/model/model_spec.rb +3 -3
  65. metadata +9 -4
@@ -101,9 +101,9 @@ module Sequel
101
101
  "DROP SEQUENCE #{quote_identifier(name)}"
102
102
  end
103
103
  end
104
-
104
+
105
105
  module DatasetMethods
106
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with distinct columns from join where group having compounds order limit')
106
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with distinct columns from join where group having compounds order limit lock')
107
107
 
108
108
  # Oracle uses MINUS instead of EXCEPT, and doesn't support EXCEPT ALL
109
109
  def except(dataset, opts={})
@@ -0,0 +1,125 @@
1
+ require 'tiny_tds'
2
+ Sequel.require 'adapters/shared/mssql'
3
+
4
+ module Sequel
5
+ module TinyTDS
6
+ class Database < Sequel::Database
7
+ include Sequel::MSSQL::DatabaseMethods
8
+ set_adapter_scheme :tinytds
9
+
10
+ # Transfer the :host and :user options to the
11
+ # :dataserver and :username options.
12
+ def connect(server)
13
+ opts = server_opts(server)
14
+ opts[:dataserver] = opts[:host]
15
+ opts[:username] = opts[:user]
16
+ TinyTds::Client.new(opts)
17
+ end
18
+
19
+ # Return instance of Sequel::TinyTDS::Dataset with the given options.
20
+ def dataset(opts = nil)
21
+ TinyTDS::Dataset.new(self, opts)
22
+ end
23
+
24
+ # Execute the given +sql+ on the server. If the :return option
25
+ # is present, its value should be a method symbol that is called
26
+ # on the TinyTds::Result object returned from executing the
27
+ # +sql+. The value of such a method is returned to the caller.
28
+ # Otherwise, if a block is given, it is yielded the result object.
29
+ # If no block is given and a :return is not present, +nil+ is returned.
30
+ def execute(sql, opts={})
31
+ synchronize(opts[:server]) do |c|
32
+ begin
33
+ m = opts[:return]
34
+ r = nil
35
+ log_yield(sql) do
36
+ r = c.execute(sql)
37
+ return r.send(m) if m
38
+ end
39
+ yield(r) if block_given?
40
+ rescue TinyTds::Error => e
41
+ raise_error(e)
42
+ ensure
43
+ r.cancel if r && c.sqlsent?
44
+ end
45
+ end
46
+ end
47
+
48
+ # Return the number of rows modified by the given +sql+.
49
+ def execute_dui(sql, opts={})
50
+ execute(sql, opts.merge(:return=>:do))
51
+ end
52
+
53
+ # Return the value of the autogenerated primary key (if any)
54
+ # for the row inserted by the given +sql+.
55
+ def execute_insert(sql, opts={})
56
+ execute(sql, opts.merge(:return=>:insert))
57
+ end
58
+
59
+ # Execute the DDL +sql+ on the database and return nil.
60
+ def execute_ddl(sql, opts={})
61
+ execute(sql, opts.merge(:return=>:each))
62
+ nil
63
+ end
64
+
65
+ private
66
+
67
+ # For some reason, unless you specify a column can be
68
+ # NULL, it assumes NOT NULL, so turn NULL on by default unless
69
+ # the column is a primary key column.
70
+ def column_list_sql(g)
71
+ pks = []
72
+ g.constraints.each{|c| pks = c[:columns] if c[:type] == :primary_key}
73
+ g.columns.each{|c| c[:null] = true if !pks.include?(c[:name]) && !c[:primary_key] && !c.has_key?(:null) && !c.has_key?(:allow_null)}
74
+ super
75
+ end
76
+
77
+ # Close the TinyTds::Client object.
78
+ def disconnect_connection(c)
79
+ c.close
80
+ end
81
+ end
82
+
83
+ class Dataset < Sequel::Dataset
84
+ include Sequel::MSSQL::DatasetMethods
85
+
86
+ # Yield hashes with symbol keys, attempting to optimize for
87
+ # various cases.
88
+ def fetch_rows(sql)
89
+ execute(sql) do |result|
90
+ each_opts = {:cache_rows=>false}
91
+ each_opts[:timezone] = :utc if Sequel.database_timezone == :utc
92
+ offset = @opts[:offset]
93
+ @columns = cols = result.fields.map{|c| output_identifier(c)}
94
+ if identifier_output_method
95
+ each_opts[:as] = :array
96
+ result.each(each_opts) do |r|
97
+ h = {}
98
+ cols.zip(r).each{|k, v| h[k] = v}
99
+ h.delete(row_number_column) if offset
100
+ yield h
101
+ end
102
+ else
103
+ each_opts[:symbolize_keys] = true
104
+ if offset
105
+ result.each(each_opts) do |r|
106
+ r.delete(row_number_column) if offset
107
+ yield r
108
+ end
109
+ else
110
+ result.each(each_opts, &block)
111
+ end
112
+ end
113
+ end
114
+ self
115
+ end
116
+
117
+ private
118
+
119
+ # Properly escape the given string +v+.
120
+ def literal_string(v)
121
+ db.synchronize{|c| "N'#{c.escape(v)}'"}
122
+ end
123
+ end
124
+ end
125
+ end
@@ -6,7 +6,7 @@ module Sequel
6
6
  # ---------------------
7
7
 
8
8
  # Array of supported database adapters
9
- ADAPTERS = %w'ado amalgalite db2 dbi do firebird informix jdbc mysql mysql2 odbc openbase oracle postgres sqlite swift'.collect{|x| x.to_sym}
9
+ ADAPTERS = %w'ado amalgalite db2 dbi do firebird informix jdbc mysql mysql2 odbc openbase oracle postgres sqlite swift tinytds'.collect{|x| x.to_sym}
10
10
 
11
11
  # Whether to use the single threaded connection pool by default
12
12
  @@single_threaded = false
@@ -20,6 +20,9 @@ module Sequel
20
20
  UNIQUE = ' UNIQUE'.freeze
21
21
  UNSIGNED = ' UNSIGNED'.freeze
22
22
 
23
+ # The order of column modifiers to use when defining a column.
24
+ COLUMN_DEFINITION_ORDER = [:default, :null, :unique, :primary_key, :auto_increment, :references]
25
+
23
26
  # Adds a column to the specified table. This method expects a column name,
24
27
  # a datatype and optionally a hash with additional constraints and options:
25
28
  #
@@ -256,21 +259,50 @@ module Sequel
256
259
  AUTOINCREMENT
257
260
  end
258
261
 
262
+ # The order of the column definition, as an array of symbols.
263
+ def column_definition_order
264
+ self.class.const_get(:COLUMN_DEFINITION_ORDER)
265
+ end
266
+
259
267
  # SQL DDL fragment containing the column creation SQL for the given column.
260
268
  def column_definition_sql(column)
261
269
  sql = "#{quote_identifier(column[:name])} #{type_literal(column)}"
262
- sql << UNIQUE if column[:unique]
270
+ column_definition_order.each{|m| send(:"column_definition_#{m}_sql", sql, column)}
271
+ sql
272
+ end
273
+
274
+ # Add auto increment SQL fragment to column creation SQL.
275
+ def column_definition_auto_increment_sql(sql, column)
276
+ sql << " #{auto_increment_sql}" if column[:auto_increment]
277
+ end
278
+
279
+ # Add default SQL fragment to column creation SQL.
280
+ def column_definition_default_sql(sql, column)
281
+ sql << " DEFAULT #{literal(column[:default])}" if column.include?(:default)
282
+ end
283
+
284
+ # Add null/not null SQL fragment to column creation SQL.
285
+ def column_definition_null_sql(sql, column)
263
286
  null = column.fetch(:null, column[:allow_null])
264
287
  sql << NOT_NULL if null == false
265
288
  sql << NULL if null == true
266
- sql << " DEFAULT #{literal(column[:default])}" if column.include?(:default)
289
+ end
290
+
291
+ # Add primary key SQL fragment to column creation SQL.
292
+ def column_definition_primary_key_sql(sql, column)
267
293
  sql << PRIMARY_KEY if column[:primary_key]
268
- sql << " #{auto_increment_sql}" if column[:auto_increment]
294
+ end
295
+
296
+ # Add foreign key reference SQL fragment to column creation SQL.
297
+ def column_definition_references_sql(sql, column)
269
298
  sql << column_references_column_constraint_sql(column) if column[:table]
270
- sql
271
299
  end
272
300
 
273
- # SQL DDL fragment containing the column creation
301
+ # Add unique constraint SQL fragment to column creation SQL.
302
+ def column_definition_unique_sql(sql, column)
303
+ sql << UNIQUE if column[:unique]
304
+ end
305
+
274
306
  # SQL for all given columns, used inside a CREATE TABLE block.
275
307
  def column_list_sql(generator)
276
308
  (generator.columns.map{|c| column_definition_sql(c)} + generator.constraints.map{|c| constraint_definition_sql(c)}).join(COMMA_SEPARATOR)
@@ -177,9 +177,9 @@ module Sequel
177
177
  BOOL_FALSE = "'f'".freeze
178
178
  BOOL_TRUE = "'t'".freeze
179
179
  COMMA_SEPARATOR = ', '.freeze
180
- COLUMN_REF_RE1 = /\A([\w ]+)__([\w ]+)___([\w ]+)\z/.freeze
181
- COLUMN_REF_RE2 = /\A([\w ]+)___([\w ]+)\z/.freeze
182
- COLUMN_REF_RE3 = /\A([\w ]+)__([\w ]+)\z/.freeze
180
+ COLUMN_REF_RE1 = /\A(((?!__).)+)__(((?!___).)+)___(.+)\z/.freeze
181
+ COLUMN_REF_RE2 = /\A(((?!___).)+)___(.+)\z/.freeze
182
+ COLUMN_REF_RE3 = /\A(((?!__).)+)__(.+)\z/.freeze
183
183
  COUNT_FROM_SELF_OPTS = [:distinct, :group, :sql, :limit, :compounds]
184
184
  COUNT_OF_ALL_AS_COUNT = SQL::Function.new(:count, LiteralString.new('*'.freeze)).as(:count)
185
185
  DATASET_ALIAS_BASE_NAME = 't'.freeze
@@ -934,11 +934,11 @@ module Sequel
934
934
  def split_symbol(sym)
935
935
  s = sym.to_s
936
936
  if m = COLUMN_REF_RE1.match(s)
937
- m[1..3]
937
+ [m[1], m[3], m[5]]
938
938
  elsif m = COLUMN_REF_RE2.match(s)
939
- [nil, m[1], m[2]]
939
+ [nil, m[1], m[3]]
940
940
  elsif m = COLUMN_REF_RE3.match(s)
941
- [m[1], m[2], nil]
941
+ [m[1], m[3], nil]
942
942
  else
943
943
  [nil, s, nil]
944
944
  end
@@ -122,7 +122,7 @@ END_MIG
122
122
  {:type=>Integer}
123
123
  when /\Atinyint(?:\((\d+)\))?\z/o
124
124
  {:type =>schema[:type] == :boolean ? TrueClass : Integer}
125
- when /\Abigint(?:\((?:\d+)\))?\z/o
125
+ when /\Abigint(?:\((?:\d+)\))?(?: unsigned)?\z/o
126
126
  {:type=>Bignum}
127
127
  when /\A(?:real|float|double(?: precision)?)\z/o
128
128
  {:type=>Float}
@@ -23,6 +23,12 @@ module Sequel
23
23
  # stored so when the dataset changes, methods defined with def_dataset_method
24
24
  # will be applied to the new dataset.
25
25
  attr_reader :dataset_methods
26
+ #
27
+ # Array of plugin modules loaded by this class
28
+ #
29
+ # Sequel::Model.plugins
30
+ # # => [Sequel::Model, Sequel::Model::Associations]
31
+ attr_reader :plugins
26
32
 
27
33
  # The primary key for the class. Sequel can determine this automatically for
28
34
  # many databases, but not all, so you may need to set it manually. If not
@@ -308,6 +314,28 @@ module Sequel
308
314
  clear_setter_methods_cache
309
315
  @simple_pk = @primary_key = nil
310
316
  end
317
+
318
+ # Loads a plugin for use with the model class, passing optional arguments
319
+ # to the plugin. If the plugin is a module, load it directly. Otherwise,
320
+ # require the plugin from either sequel/plugins/#{plugin} or
321
+ # sequel_#{plugin}, and then attempt to load the module using a
322
+ # the camelized plugin name under Sequel::Plugins.
323
+ def plugin(plugin, *args, &blk)
324
+ m = plugin.is_a?(Module) ? plugin : plugin_module(plugin)
325
+ unless @plugins.include?(m)
326
+ @plugins << m
327
+ m.apply(self, *args, &blk) if m.respond_to?(:apply)
328
+ include(m::InstanceMethods) if m.const_defined?("InstanceMethods")
329
+ extend(m::ClassMethods)if m.const_defined?("ClassMethods")
330
+ if m.const_defined?("DatasetMethods")
331
+ dataset.extend(m::DatasetMethods) if @dataset
332
+ dataset_method_modules << m::DatasetMethods
333
+ meths = m::DatasetMethods.public_instance_methods.reject{|x| NORMAL_METHOD_NAME_REGEXP !~ x.to_s}
334
+ def_dataset_method(*meths) unless meths.empty?
335
+ end
336
+ end
337
+ m.configure(self, *args, &blk) if m.respond_to?(:configure)
338
+ end
311
339
 
312
340
  # Returns primary key attribute hash. If using a composite primary key
313
341
  # value such be an array with values for each primary key in the correct
@@ -608,6 +636,27 @@ module Sequel
608
636
  @overridable_methods_module
609
637
  end
610
638
 
639
+ # Returns the module for the specified plugin. If the module is not
640
+ # defined, the corresponding plugin required.
641
+ def plugin_module(plugin)
642
+ module_name = plugin.to_s.gsub(/(^|_)(.)/){|x| x[-1..-1].upcase}
643
+ if !Sequel::Plugins.const_defined?(module_name) ||
644
+ (Sequel.const_defined?(module_name) &&
645
+ Sequel::Plugins.const_get(module_name) == Sequel.const_get(module_name))
646
+ begin
647
+ Sequel.tsk_require "sequel/plugins/#{plugin}"
648
+ rescue LoadError => e
649
+ begin
650
+ Sequel.tsk_require "sequel_#{plugin}"
651
+ rescue LoadError => e2
652
+ e.message << "; #{e2.message}"
653
+ raise e
654
+ end
655
+ end
656
+ end
657
+ Sequel::Plugins.const_get(module_name)
658
+ end
659
+
611
660
  # Find the row in the dataset that matches the primary key. Uses
612
661
  # a static SQL optimization if the table and primary key are simple.
613
662
  def primary_key_lookup(pk)
@@ -1444,6 +1493,7 @@ module Sequel
1444
1493
  end
1445
1494
  end
1446
1495
 
1496
+ extend ClassMethods
1447
1497
  plugin self
1448
1498
  end
1449
1499
  end
@@ -21,59 +21,4 @@ module Sequel
21
21
  # any modules.
22
22
  module Plugins
23
23
  end
24
-
25
- class Model
26
- # Loads a plugin for use with the model class, passing optional arguments
27
- # to the plugin. If the plugin is a module, load it directly. Otherwise,
28
- # require the plugin from either sequel/plugins/#{plugin} or
29
- # sequel_#{plugin}, and then attempt to load the module using a
30
- # the camelized plugin name under Sequel::Plugins.
31
- def self.plugin(plugin, *args, &blk)
32
- m = plugin.is_a?(Module) ? plugin : plugin_module(plugin)
33
- unless @plugins.include?(m)
34
- @plugins << m
35
- m.apply(self, *args, &blk) if m.respond_to?(:apply)
36
- include(m::InstanceMethods) if m.const_defined?("InstanceMethods")
37
- extend(m::ClassMethods)if m.const_defined?("ClassMethods")
38
- if m.const_defined?("DatasetMethods")
39
- dataset.extend(m::DatasetMethods) if @dataset
40
- dataset_method_modules << m::DatasetMethods
41
- meths = m::DatasetMethods.public_instance_methods.reject{|x| NORMAL_METHOD_NAME_REGEXP !~ x.to_s}
42
- def_dataset_method(*meths) unless meths.empty?
43
- end
44
- end
45
- m.configure(self, *args, &blk) if m.respond_to?(:configure)
46
- end
47
-
48
- module ClassMethods
49
- # Array of plugin modules loaded by this class
50
- #
51
- # Sequel::Model.plugins
52
- # # => [Sequel::Model, Sequel::Model::Associations]
53
- attr_reader :plugins
54
-
55
- private
56
-
57
- # Returns the module for the specified plugin. If the module is not
58
- # defined, the corresponding plugin required.
59
- def plugin_module(plugin)
60
- module_name = plugin.to_s.gsub(/(^|_)(.)/){|x| x[-1..-1].upcase}
61
- if !Sequel::Plugins.const_defined?(module_name) ||
62
- (Sequel.const_defined?(module_name) &&
63
- Sequel::Plugins.const_get(module_name) == Sequel.const_get(module_name))
64
- begin
65
- Sequel.tsk_require "sequel/plugins/#{plugin}"
66
- rescue LoadError => e
67
- begin
68
- Sequel.tsk_require "sequel_#{plugin}"
69
- rescue LoadError => e2
70
- e.message << "; #{e2.message}"
71
- raise e
72
- end
73
- end
74
- end
75
- Sequel::Plugins.const_get(module_name)
76
- end
77
- end
78
- end
79
24
  end
@@ -0,0 +1,48 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The AssociationAutoreloading plugin makes many_to_one association
4
+ # accessor methods automatically reload the cached object whenever
5
+ # the association's foreign key is modified:
6
+ #
7
+ # Album.many_to_one :artists
8
+ # album = Album.first
9
+ # album.artist_id #=> 1
10
+ # album.artist # caches associated artist
11
+ # album.artist_id = 2
12
+ # album.artist # reloads associated artist
13
+ #
14
+ module AssociationAutoreloading
15
+ module ClassMethods
16
+ private
17
+
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
33
+
34
+ # For each of the foreign keys in the association, create
35
+ # a setter method that will clear the association cache.
36
+ def def_many_to_one(opts)
37
+ super
38
+ @autoreloading_associations ||= {}
39
+ opts[:keys].each do |key|
40
+ assocs = @autoreloading_associations[key] ||= []
41
+ assocs << opts[:name]
42
+ create_autoreloading_association_setter(key, assocs)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -262,7 +262,7 @@ module Sequel
262
262
  o.errors.add(a, opts[:message] || opts[:wrong_length]) unless v && v.size == i
263
263
  end
264
264
  if w = opts[:within]
265
- o.errors.add(a, opts[:message] || opts[:wrong_length]) unless v && w.include?(v.size)
265
+ o.errors.add(a, opts[:message] || opts[:wrong_length]) unless v && w.send(w.respond_to?(:cover?) ? :cover? : :include?, v.size)
266
266
  end
267
267
  end
268
268
  end
@@ -345,14 +345,15 @@ module Sequel
345
345
  # * :message - The message to use (default: 'is not in range or set: <specified range>')
346
346
  def validates_inclusion_of(*atts)
347
347
  opts = extract_options!(atts)
348
- unless opts[:in] && opts[:in].respond_to?(:include?)
349
- raise ArgumentError, "The :in parameter is required, and respond to include?"
348
+ n = opts[:in]
349
+ unless n && (n.respond_to?(:cover?) || n.respond_to?(:include?))
350
+ raise ArgumentError, "The :in parameter is required, and must respond to cover? or include?"
350
351
  end
351
- opts[:message] ||= "is not in range or set: #{opts[:in].inspect}"
352
+ opts[:message] ||= "is not in range or set: #{n.inspect}"
352
353
  reflect_validation(:inclusion, opts, atts)
353
354
  atts << opts
354
355
  validates_each(*atts) do |o, a, v|
355
- o.errors.add(a, opts[:message]) unless opts[:in].include?(v)
356
+ o.errors.add(a, opts[:message]) unless n.send(n.respond_to?(:cover?) ? :cover? : :include?, v)
356
357
  end
357
358
  end
358
359