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.
- data/CHANGELOG +32 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/Rakefile +13 -3
- data/bin/sequel +18 -5
- data/doc/active_record.rdoc +4 -4
- data/doc/opening_databases.rdoc +38 -1
- data/doc/release_notes/3.21.0.txt +87 -0
- data/doc/validations.rdoc +2 -2
- data/lib/sequel/adapters/informix.rb +1 -1
- data/lib/sequel/adapters/jdbc/h2.rb +2 -5
- data/lib/sequel/adapters/jdbc/mssql.rb +1 -4
- data/lib/sequel/adapters/jdbc/mysql.rb +1 -4
- data/lib/sequel/adapters/jdbc/postgresql.rb +1 -6
- data/lib/sequel/adapters/jdbc/sqlite.rb +2 -8
- data/lib/sequel/adapters/shared/mssql.rb +8 -0
- data/lib/sequel/adapters/shared/mysql.rb +23 -3
- data/lib/sequel/adapters/shared/oracle.rb +2 -2
- data/lib/sequel/adapters/tinytds.rb +125 -0
- data/lib/sequel/database/connecting.rb +1 -1
- data/lib/sequel/database/schema_methods.rb +37 -5
- data/lib/sequel/dataset/sql.rb +6 -6
- data/lib/sequel/extensions/schema_dumper.rb +1 -1
- data/lib/sequel/model/base.rb +50 -0
- data/lib/sequel/model/plugins.rb +0 -55
- data/lib/sequel/plugins/association_autoreloading.rb +48 -0
- data/lib/sequel/plugins/validation_class_methods.rb +6 -5
- data/lib/sequel/plugins/validation_helpers.rb +2 -2
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/firebird_spec.rb +6 -6
- data/spec/adapters/informix_spec.rb +2 -2
- data/spec/adapters/mssql_spec.rb +18 -13
- data/spec/adapters/mysql_spec.rb +47 -20
- data/spec/adapters/oracle_spec.rb +40 -4
- data/spec/adapters/postgres_spec.rb +14 -14
- data/spec/adapters/spec_helper.rb +1 -1
- data/spec/adapters/sqlite_spec.rb +8 -8
- data/spec/core/connection_pool_spec.rb +18 -17
- data/spec/core/core_sql_spec.rb +18 -18
- data/spec/core/database_spec.rb +62 -62
- data/spec/core/dataset_spec.rb +105 -92
- data/spec/core/expression_filters_spec.rb +2 -2
- data/spec/core/schema_spec.rb +6 -6
- data/spec/core/version_spec.rb +1 -1
- data/spec/extensions/association_autoreloading_spec.rb +94 -0
- data/spec/extensions/blank_spec.rb +6 -6
- data/spec/extensions/looser_typecasting_spec.rb +1 -1
- data/spec/extensions/migration_spec.rb +6 -6
- data/spec/extensions/pagination_spec.rb +2 -2
- data/spec/extensions/pretty_table_spec.rb +2 -2
- data/spec/extensions/query_spec.rb +2 -2
- data/spec/extensions/schema_dumper_spec.rb +2 -1
- data/spec/extensions/single_table_inheritance_spec.rb +1 -1
- data/spec/extensions/sql_expr_spec.rb +1 -1
- data/spec/extensions/string_date_time_spec.rb +4 -4
- data/spec/extensions/validation_class_methods_spec.rb +2 -2
- data/spec/integration/dataset_test.rb +8 -3
- data/spec/integration/plugin_test.rb +5 -5
- data/spec/integration/prepared_statement_test.rb +1 -1
- data/spec/integration/schema_test.rb +7 -0
- data/spec/integration/spec_helper.rb +14 -1
- data/spec/integration/timezone_test.rb +4 -4
- data/spec/integration/type_test.rb +1 -1
- data/spec/model/model_spec.rb +3 -3
- 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
|
-
|
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
|
-
|
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
|
-
|
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
|
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)
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -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(
|
181
|
-
COLUMN_REF_RE2 = /\A(
|
182
|
-
COLUMN_REF_RE3 = /\A(
|
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
|
937
|
+
[m[1], m[3], m[5]]
|
938
938
|
elsif m = COLUMN_REF_RE2.match(s)
|
939
|
-
[nil, m[1], m[
|
939
|
+
[nil, m[1], m[3]]
|
940
940
|
elsif m = COLUMN_REF_RE3.match(s)
|
941
|
-
[m[1], m[
|
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}
|
data/lib/sequel/model/base.rb
CHANGED
@@ -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
|
data/lib/sequel/model/plugins.rb
CHANGED
@@ -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.
|
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
|
-
|
349
|
-
|
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: #{
|
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
|
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
|
|