sequel 3.20.0 → 3.21.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 (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