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