sequel 3.9.0 → 3.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +56 -0
- data/README.rdoc +1 -1
- data/Rakefile +1 -1
- data/doc/advanced_associations.rdoc +7 -10
- data/doc/release_notes/3.10.0.txt +286 -0
- data/lib/sequel/adapters/do/mysql.rb +4 -0
- data/lib/sequel/adapters/jdbc.rb +5 -0
- data/lib/sequel/adapters/jdbc/as400.rb +58 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +30 -0
- data/lib/sequel/adapters/shared/mssql.rb +23 -9
- data/lib/sequel/adapters/shared/mysql.rb +12 -1
- data/lib/sequel/adapters/shared/postgres.rb +7 -18
- data/lib/sequel/adapters/shared/sqlite.rb +5 -0
- data/lib/sequel/adapters/sqlite.rb +5 -0
- data/lib/sequel/connection_pool/single.rb +3 -3
- data/lib/sequel/database.rb +3 -2
- data/lib/sequel/dataset.rb +6 -5
- data/lib/sequel/dataset/convenience.rb +3 -3
- data/lib/sequel/dataset/query.rb +13 -0
- data/lib/sequel/dataset/sql.rb +31 -1
- data/lib/sequel/extensions/schema_dumper.rb +3 -3
- data/lib/sequel/model.rb +8 -6
- data/lib/sequel/model/associations.rb +144 -102
- data/lib/sequel/model/base.rb +21 -1
- data/lib/sequel/model/plugins.rb +3 -1
- data/lib/sequel/plugins/association_dependencies.rb +14 -7
- data/lib/sequel/plugins/caching.rb +4 -0
- data/lib/sequel/plugins/composition.rb +138 -0
- data/lib/sequel/plugins/identity_map.rb +2 -2
- data/lib/sequel/plugins/lazy_attributes.rb +1 -1
- data/lib/sequel/plugins/nested_attributes.rb +3 -2
- data/lib/sequel/plugins/rcte_tree.rb +281 -0
- data/lib/sequel/plugins/typecast_on_load.rb +16 -5
- data/lib/sequel/sql.rb +18 -1
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +4 -0
- data/spec/adapters/mysql_spec.rb +4 -0
- data/spec/adapters/postgres_spec.rb +55 -5
- data/spec/core/database_spec.rb +5 -3
- data/spec/core/dataset_spec.rb +86 -15
- data/spec/core/expression_filters_spec.rb +23 -6
- data/spec/extensions/association_dependencies_spec.rb +24 -5
- data/spec/extensions/association_proxies_spec.rb +3 -0
- data/spec/extensions/composition_spec.rb +194 -0
- data/spec/extensions/identity_map_spec.rb +16 -0
- data/spec/extensions/nested_attributes_spec.rb +44 -1
- data/spec/extensions/rcte_tree_spec.rb +205 -0
- data/spec/extensions/schema_dumper_spec.rb +6 -0
- data/spec/extensions/spec_helper.rb +6 -0
- data/spec/extensions/typecast_on_load_spec.rb +9 -0
- data/spec/extensions/validation_helpers_spec.rb +5 -5
- data/spec/integration/dataset_test.rb +13 -9
- data/spec/integration/eager_loader_test.rb +56 -1
- data/spec/integration/model_test.rb +8 -0
- data/spec/integration/plugin_test.rb +270 -0
- data/spec/integration/schema_test.rb +1 -1
- data/spec/model/associations_spec.rb +541 -118
- data/spec/model/eager_loading_spec.rb +24 -3
- data/spec/model/record_spec.rb +34 -0
- metadata +9 -2
@@ -7,11 +7,41 @@ module Sequel
|
|
7
7
|
# Instance methods for Oracle Database objects accessed via JDBC.
|
8
8
|
module DatabaseMethods
|
9
9
|
include Sequel::Oracle::DatabaseMethods
|
10
|
+
TRANSACTION_BEGIN = 'Transaction.begin'.freeze
|
11
|
+
TRANSACTION_COMMIT = 'Transaction.commit'.freeze
|
12
|
+
TRANSACTION_ROLLBACK = 'Transaction.rollback'.freeze
|
10
13
|
|
11
14
|
# Return Sequel::JDBC::Oracle::Dataset object with the given opts.
|
12
15
|
def dataset(opts=nil)
|
13
16
|
Sequel::JDBC::Oracle::Dataset.new(self, opts)
|
14
17
|
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# Use JDBC connection's setAutoCommit to false to start transactions
|
22
|
+
def begin_transaction(conn)
|
23
|
+
log_info(TRANSACTION_BEGIN)
|
24
|
+
conn.setAutoCommit(false)
|
25
|
+
conn
|
26
|
+
end
|
27
|
+
|
28
|
+
# Use JDBC connection's commit method to commit transactions
|
29
|
+
def commit_transaction(conn)
|
30
|
+
log_info(TRANSACTION_COMMIT)
|
31
|
+
conn.commit
|
32
|
+
end
|
33
|
+
|
34
|
+
# Use JDBC connection's setAutoCommit to true to enable non-transactional behavior
|
35
|
+
def remove_transaction(conn)
|
36
|
+
conn.setAutoCommit(true) if conn
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
# Use JDBC connection's rollback method to rollback transactions
|
41
|
+
def rollback_transaction(conn)
|
42
|
+
log_info(TRANSACTION_ROLLBACK)
|
43
|
+
conn.rollback
|
44
|
+
end
|
15
45
|
end
|
16
46
|
|
17
47
|
# Dataset class for Oracle datasets accessed via JDBC.
|
@@ -183,8 +183,10 @@ module Sequel
|
|
183
183
|
COMMA_SEPARATOR = ', '.freeze
|
184
184
|
DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'with from output from2 where')
|
185
185
|
INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'with into columns output values')
|
186
|
-
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with limit distinct columns into from
|
186
|
+
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with limit distinct columns into from lock join where group having order compounds')
|
187
187
|
UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'with table set output from where')
|
188
|
+
NOLOCK = ' WITH (NOLOCK)'.freeze
|
189
|
+
UPDLOCK = ' WITH (UPDLOCK)'.freeze
|
188
190
|
WILDCARD = LiteralString.new('*').freeze
|
189
191
|
CONSTANT_MAP = {:CURRENT_DATE=>'CAST(CURRENT_TIMESTAMP AS DATE)'.freeze, :CURRENT_TIME=>'CAST(CURRENT_TIMESTAMP AS TIME)'.freeze}
|
190
192
|
|
@@ -244,9 +246,9 @@ module Sequel
|
|
244
246
|
[insert_sql(columns, LiteralString.new(values.map {|r| "SELECT #{expression_list(r)}" }.join(" UNION ALL ")))]
|
245
247
|
end
|
246
248
|
|
247
|
-
# Allows you to do
|
249
|
+
# Allows you to do a dirty read of uncommitted data using WITH (NOLOCK).
|
248
250
|
def nolock
|
249
|
-
|
251
|
+
lock_style(:dirty)
|
250
252
|
end
|
251
253
|
|
252
254
|
# Include an OUTPUT clause in the eventual INSERT, UPDATE, or DELETE query.
|
@@ -299,7 +301,7 @@ module Sequel
|
|
299
301
|
raise(Error, 'MSSQL requires an order be provided if using an offset') unless order = @opts[:order]
|
300
302
|
dsa1 = dataset_alias(1)
|
301
303
|
rn = row_number_column
|
302
|
-
sel = [Sequel::SQL::WindowFunction.new(:ROW_NUMBER
|
304
|
+
sel = [Sequel::SQL::WindowFunction.new(SQL::Function.new(:ROW_NUMBER), Sequel::SQL::Window.new(:order=>order)).as(rn)]
|
303
305
|
sel.unshift(WILDCARD) unless osel = @opts[:select] and !osel.empty?
|
304
306
|
subselect_sql(unlimited.
|
305
307
|
unordered.
|
@@ -428,14 +430,26 @@ module Sequel
|
|
428
430
|
sql << " INTO #{table_ref(@opts[:into])}" if @opts[:into]
|
429
431
|
end
|
430
432
|
|
431
|
-
# MSSQL uses TOP for limit
|
433
|
+
# MSSQL uses TOP N for limit. For MSSQL 2005+ TOP (N) is used
|
434
|
+
# to allow the limit to be a bound variable.
|
432
435
|
def select_limit_sql(sql)
|
433
|
-
|
436
|
+
if l = @opts[:limit]
|
437
|
+
l = literal(l)
|
438
|
+
l = "(#{l})" if server_version >= 9000000
|
439
|
+
sql << " TOP #{l}"
|
440
|
+
end
|
434
441
|
end
|
435
442
|
|
436
|
-
#
|
437
|
-
def
|
438
|
-
|
443
|
+
# Support different types of locking styles
|
444
|
+
def select_lock_sql(sql)
|
445
|
+
case @opts[:lock]
|
446
|
+
when :update
|
447
|
+
sql << UPDLOCK
|
448
|
+
when :dirty
|
449
|
+
sql << NOLOCK
|
450
|
+
else
|
451
|
+
super
|
452
|
+
end
|
439
453
|
end
|
440
454
|
|
441
455
|
# SQL fragment for MSSQL's OUTPUT clause.
|
@@ -227,9 +227,10 @@ module Sequel
|
|
227
227
|
BOOL_TRUE = '1'.freeze
|
228
228
|
BOOL_FALSE = '0'.freeze
|
229
229
|
COMMA_SEPARATOR = ', '.freeze
|
230
|
+
FOR_SHARE = ' LOCK IN SHARE MODE'.freeze
|
230
231
|
DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'from where order limit')
|
231
232
|
INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'ignore into columns values on_duplicate_key_update')
|
232
|
-
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'distinct columns from join where group having compounds order limit')
|
233
|
+
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'distinct columns from join where group having compounds order limit lock')
|
233
234
|
UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'table set where order limit')
|
234
235
|
|
235
236
|
# MySQL specific syntax for LIKE/REGEXP searches, as well as
|
@@ -248,6 +249,11 @@ module Sequel
|
|
248
249
|
super(op, args)
|
249
250
|
end
|
250
251
|
end
|
252
|
+
|
253
|
+
# Return a cloned dataset which will use LOCK IN SHARE MODE to lock returned rows.
|
254
|
+
def for_share
|
255
|
+
lock_style(:share)
|
256
|
+
end
|
251
257
|
|
252
258
|
# Adds full text filter
|
253
259
|
def full_text_search(cols, terms, opts = {})
|
@@ -465,6 +471,11 @@ module Sequel
|
|
465
471
|
def select_clause_methods
|
466
472
|
SELECT_CLAUSE_METHODS
|
467
473
|
end
|
474
|
+
|
475
|
+
# Support FOR SHARE locking when using the :share lock style.
|
476
|
+
def select_lock_sql(sql)
|
477
|
+
@opts[:lock] == :share ? (sql << FOR_SHARE) : super
|
478
|
+
end
|
468
479
|
|
469
480
|
# MySQL supports the ORDER BY and LIMIT clauses for UPDATE statements
|
470
481
|
def update_clause_methods
|
@@ -593,7 +593,6 @@ module Sequel
|
|
593
593
|
EXPLAIN = 'EXPLAIN '.freeze
|
594
594
|
EXPLAIN_ANALYZE = 'EXPLAIN ANALYZE '.freeze
|
595
595
|
FOR_SHARE = ' FOR SHARE'.freeze
|
596
|
-
FOR_UPDATE = ' FOR UPDATE'.freeze
|
597
596
|
LOCK = 'LOCK TABLE %s IN %s MODE'.freeze
|
598
597
|
NULL = LiteralString.new('NULL').freeze
|
599
598
|
PG_TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S".freeze
|
@@ -647,14 +646,9 @@ module Sequel
|
|
647
646
|
with_sql((opts[:analyze] ? EXPLAIN_ANALYZE : EXPLAIN) + select_sql).map(QUERY_PLAN).join("\r\n")
|
648
647
|
end
|
649
648
|
|
650
|
-
# Return a cloned dataset
|
649
|
+
# Return a cloned dataset which will use FOR SHARE to lock returned rows.
|
651
650
|
def for_share
|
652
|
-
|
653
|
-
end
|
654
|
-
|
655
|
-
# Return a cloned dataset with a :update lock type.
|
656
|
-
def for_update
|
657
|
-
clone(:lock => :update)
|
651
|
+
lock_style(:share)
|
658
652
|
end
|
659
653
|
|
660
654
|
# PostgreSQL specific full text search syntax, using tsearch2 (included
|
@@ -786,21 +780,16 @@ module Sequel
|
|
786
780
|
def select_clause_methods
|
787
781
|
server_version >= 80400 ? SELECT_CLAUSE_METHODS_84 : SELECT_CLAUSE_METHODS
|
788
782
|
end
|
783
|
+
|
784
|
+
# Support FOR SHARE locking when using the :share lock style.
|
785
|
+
def select_lock_sql(sql)
|
786
|
+
@opts[:lock] == :share ? (sql << FOR_SHARE) : super
|
787
|
+
end
|
789
788
|
|
790
789
|
# SQL fragment for named window specifications
|
791
790
|
def select_window_sql(sql)
|
792
791
|
sql << " WINDOW #{@opts[:window].map{|name, window| "#{literal(name)} AS #{literal(window)}"}.join(', ')}" if @opts[:window]
|
793
792
|
end
|
794
|
-
|
795
|
-
# Support lock mode, allowing FOR SHARE and FOR UPDATE queries.
|
796
|
-
def select_lock_sql(sql)
|
797
|
-
case @opts[:lock]
|
798
|
-
when :update
|
799
|
-
sql << FOR_UPDATE
|
800
|
-
when :share
|
801
|
-
sql << FOR_SHARE
|
802
|
-
end
|
803
|
-
end
|
804
793
|
|
805
794
|
# Use WITH RECURSIVE instead of WITH if any of the CTEs is recursive
|
806
795
|
def select_with_sql_base
|
@@ -329,6 +329,11 @@ module Sequel
|
|
329
329
|
SELECT_CLAUSE_METHODS
|
330
330
|
end
|
331
331
|
|
332
|
+
# Support FOR SHARE locking when using the :share lock style.
|
333
|
+
def select_lock_sql(sql)
|
334
|
+
super unless @opts[:lock] == :update
|
335
|
+
end
|
336
|
+
|
332
337
|
# SQLite treats a DELETE with no WHERE clause as a TRUNCATE
|
333
338
|
def _truncate_sql(table)
|
334
339
|
"DELETE FROM #{table}"
|
@@ -1,4 +1,9 @@
|
|
1
1
|
require 'sqlite3'
|
2
|
+
begin
|
3
|
+
SQLite3::Database.instance_method(:type_translation)
|
4
|
+
rescue
|
5
|
+
raise(Sequel::Error, "SQLite3::Database#type_translation is not defined. If you are using the sqlite3 gem, please install the sqlite3-ruby gem.")
|
6
|
+
end
|
2
7
|
Sequel.require 'adapters/shared/sqlite'
|
3
8
|
|
4
9
|
module Sequel
|
@@ -2,13 +2,13 @@
|
|
2
2
|
# It is just a wrapper around a single connection that uses the connection pool
|
3
3
|
# API.
|
4
4
|
class Sequel::SingleConnectionPool < Sequel::ConnectionPool
|
5
|
-
# The SingleConnectionPool always has a size of 1
|
6
|
-
#
|
5
|
+
# The SingleConnectionPool always has a size of 1 if connected
|
6
|
+
# and 0 if not.
|
7
7
|
def size
|
8
8
|
@conn ? 1 : 0
|
9
9
|
end
|
10
10
|
|
11
|
-
# Disconnect
|
11
|
+
# Disconnect the connection from the database.
|
12
12
|
def disconnect(opts=nil, &block)
|
13
13
|
block ||= @disconnection_proc
|
14
14
|
block.call(@conn) if block
|
data/lib/sequel/database.rb
CHANGED
@@ -158,13 +158,14 @@ module Sequel
|
|
158
158
|
m
|
159
159
|
end
|
160
160
|
if block
|
161
|
+
result = nil
|
161
162
|
begin
|
162
|
-
yield(db = c.new(opts))
|
163
|
+
result = yield(db = c.new(opts))
|
163
164
|
ensure
|
164
165
|
db.disconnect if db
|
165
166
|
::Sequel::DATABASES.delete(db)
|
166
167
|
end
|
167
|
-
|
168
|
+
result
|
168
169
|
else
|
169
170
|
c.new(opts)
|
170
171
|
end
|
data/lib/sequel/dataset.rb
CHANGED
@@ -31,11 +31,12 @@ module Sequel
|
|
31
31
|
|
32
32
|
# All methods that should have a ! method added that modifies
|
33
33
|
# the receiver.
|
34
|
-
MUTATION_METHODS = %w'add_graph_aliases and distinct except exclude
|
35
|
-
filter from from_self full_outer_join graph
|
36
|
-
group group_and_count group_by having inner_join intersect invert join join_table
|
37
|
-
left_outer_join limit naked
|
38
|
-
|
34
|
+
MUTATION_METHODS = %w'add_graph_aliases and cross_join distinct except exclude
|
35
|
+
filter for_update from from_self full_join full_outer_join graph
|
36
|
+
group group_and_count group_by having inner_join intersect invert join join_table left_join
|
37
|
+
left_outer_join limit lock_style naked natural_full_join natural_join
|
38
|
+
natural_left_join natural_right_join or order order_by order_more paginate qualify query
|
39
|
+
reverse reverse_order right_join right_outer_join select select_all select_more server
|
39
40
|
set_defaults set_graph_aliases set_overrides unfiltered ungraphed ungrouped union
|
40
41
|
unlimited unordered where with with_recursive with_sql'.collect{|x| x.to_sym}
|
41
42
|
|
@@ -81,8 +81,8 @@ module Sequel
|
|
81
81
|
end
|
82
82
|
|
83
83
|
# Returns a dataset grouped by the given column with count by group,
|
84
|
-
# order by the count of records
|
85
|
-
#
|
84
|
+
# order by the count of records. Column aliases may be supplied, and will
|
85
|
+
# be included in the select clause.
|
86
86
|
#
|
87
87
|
# Examples:
|
88
88
|
#
|
@@ -90,7 +90,7 @@ module Sequel
|
|
90
90
|
# ds.group_and_count(:first_name, :last_name).all => [{:first_name=>'a', :last_name=>'b', :count=>1}, ...]
|
91
91
|
# ds.group_and_count(:first_name___name).all => [{:name=>'a', :count=>1}, ...]
|
92
92
|
def group_and_count(*columns)
|
93
|
-
group(*columns.map{|c| unaliased_identifier(c)}).select(*(columns + [COUNT_OF_ALL_AS_COUNT]))
|
93
|
+
group(*columns.map{|c| unaliased_identifier(c)}).select(*(columns + [COUNT_OF_ALL_AS_COUNT]))
|
94
94
|
end
|
95
95
|
|
96
96
|
# Inserts multiple records into the associated table. This method can be
|
data/lib/sequel/dataset/query.rb
CHANGED
@@ -104,6 +104,11 @@ module Sequel
|
|
104
104
|
def filter(*cond, &block)
|
105
105
|
_filter(@opts[:having] ? :having : :where, *cond, &block)
|
106
106
|
end
|
107
|
+
|
108
|
+
# Returns a cloned dataset with a :update lock style.
|
109
|
+
def for_update
|
110
|
+
lock_style(:update)
|
111
|
+
end
|
107
112
|
|
108
113
|
# Returns a copy of the dataset with the source changed.
|
109
114
|
#
|
@@ -236,6 +241,14 @@ module Sequel
|
|
236
241
|
clone(opts)
|
237
242
|
end
|
238
243
|
|
244
|
+
# Returns a cloned dataset with the given lock style. If style is a
|
245
|
+
# string, it will be used directly. Otherwise, a symbol may be used
|
246
|
+
# for database independent locking. Currently :update is respected
|
247
|
+
# by most databases, and :share is supported by some.
|
248
|
+
def lock_style(style)
|
249
|
+
clone(:lock => style)
|
250
|
+
end
|
251
|
+
|
239
252
|
# Adds an alternate filter to an existing filter using OR. If no filter
|
240
253
|
# exists an error is raised.
|
241
254
|
#
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -25,6 +25,7 @@ module Sequel
|
|
25
25
|
COLUMN_REF_RE3 = /\A([\w ]+)__([\w ]+)\z/.freeze
|
26
26
|
COUNT_FROM_SELF_OPTS = [:distinct, :group, :sql, :limit, :compounds]
|
27
27
|
DATASET_ALIAS_BASE_NAME = 't'.freeze
|
28
|
+
FOR_UPDATE = ' FOR UPDATE'.freeze
|
28
29
|
IS_LITERALS = {nil=>'NULL'.freeze, true=>'TRUE'.freeze, false=>'FALSE'.freeze}.freeze
|
29
30
|
IS_OPERATORS = ::Sequel::SQL::ComplexExpression::IS_OPERATORS
|
30
31
|
N_ARITY_OPERATORS = ::Sequel::SQL::ComplexExpression::N_ARITY_OPERATORS
|
@@ -33,7 +34,7 @@ module Sequel
|
|
33
34
|
QUESTION_MARK = '?'.freeze
|
34
35
|
DELETE_CLAUSE_METHODS = clause_methods(:delete, %w'from where')
|
35
36
|
INSERT_CLAUSE_METHODS = clause_methods(:insert, %w'into columns values')
|
36
|
-
SELECT_CLAUSE_METHODS = clause_methods(:select, %w'with distinct columns from join where group having compounds order limit')
|
37
|
+
SELECT_CLAUSE_METHODS = clause_methods(:select, %w'with distinct columns from join where group having compounds order limit lock')
|
37
38
|
UPDATE_CLAUSE_METHODS = clause_methods(:update, %w'table set where')
|
38
39
|
TIMESTAMP_FORMAT = "'%Y-%m-%d %H:%M:%S%N%z'".freeze
|
39
40
|
STANDARD_TIMESTAMP_FORMAT = "TIMESTAMP #{TIMESTAMP_FORMAT}".freeze
|
@@ -189,6 +190,25 @@ module Sequel
|
|
189
190
|
end
|
190
191
|
end
|
191
192
|
alias first_source first_source_alias
|
193
|
+
|
194
|
+
# The first source (primary table) for this dataset. If the dataset doesn't
|
195
|
+
# have a table, raises an error. If the table is aliased, returns the original
|
196
|
+
# table, not the alias
|
197
|
+
def first_source_table
|
198
|
+
source = @opts[:from]
|
199
|
+
if source.nil? || source.empty?
|
200
|
+
raise Error, 'No source specified for query'
|
201
|
+
end
|
202
|
+
case s = source.first
|
203
|
+
when SQL::AliasedExpression
|
204
|
+
s.expression
|
205
|
+
when Symbol
|
206
|
+
sch, table, aliaz = split_symbol(s)
|
207
|
+
aliaz ? (sch ? SQL::QualifiedIdentifier.new(sch, table) : table.to_sym) : s
|
208
|
+
else
|
209
|
+
s
|
210
|
+
end
|
211
|
+
end
|
192
212
|
|
193
213
|
# SQL fragment specifying an SQL function call
|
194
214
|
def function_sql(f)
|
@@ -1074,6 +1094,16 @@ module Sequel
|
|
1074
1094
|
sql << " LIMIT #{literal(@opts[:limit])}" if @opts[:limit]
|
1075
1095
|
sql << " OFFSET #{literal(@opts[:offset])}" if @opts[:offset]
|
1076
1096
|
end
|
1097
|
+
|
1098
|
+
# Modify the sql to support the different types of locking modes.
|
1099
|
+
def select_lock_sql(sql)
|
1100
|
+
case @opts[:lock]
|
1101
|
+
when :update
|
1102
|
+
sql << FOR_UPDATE
|
1103
|
+
when String
|
1104
|
+
sql << " #{@opts[:lock]}"
|
1105
|
+
end
|
1106
|
+
end
|
1077
1107
|
|
1078
1108
|
# Modify the sql to add the expressions to ORDER BY
|
1079
1109
|
def select_order_sql(sql)
|
@@ -86,8 +86,8 @@ END_MIG
|
|
86
86
|
# name and arguments to it to pass to a Schema::Generator to recreate the column.
|
87
87
|
def column_schema_to_generator_opts(name, schema, options)
|
88
88
|
if options[:single_pk] && schema_autoincrementing_primary_key?(schema)
|
89
|
-
type_hash = column_schema_to_ruby_type(schema)
|
90
|
-
if type_hash == {:type=>Integer}
|
89
|
+
type_hash = options[:same_db] ? {:type=>schema[:db_type]} : column_schema_to_ruby_type(schema)
|
90
|
+
if type_hash == {:type=>Integer} || type_hash == {:type=>"integer"}
|
91
91
|
[:primary_key, name]
|
92
92
|
else
|
93
93
|
[:primary_key, name, type_hash]
|
@@ -115,7 +115,7 @@ END_MIG
|
|
115
115
|
when /\A(?:medium|small)?int(?:eger)?(?:\((?:\d+)\))?\z/o
|
116
116
|
{:type=>Integer}
|
117
117
|
when /\Atinyint(?:\((\d+)\))?\z/o
|
118
|
-
{:type=>
|
118
|
+
{:type =>schema[:type] == :boolean ? TrueClass : Integer}
|
119
119
|
when /\Abigint(?:\((?:\d+)\))?\z/o
|
120
120
|
{:type=>Bignum}
|
121
121
|
when /\A(?:real|float|double(?: precision)?)\z/o
|
data/lib/sequel/model.rb
CHANGED
@@ -44,13 +44,15 @@ module Sequel
|
|
44
44
|
ANONYMOUS_MODEL_CLASSES = {}
|
45
45
|
|
46
46
|
# Class methods added to model that call the method of the same name on the dataset
|
47
|
-
DATASET_METHODS = %w'<< add_graph_aliases all avg count delete distinct
|
48
|
-
each each_page eager eager_graph empty? except exclude filter first from from_self
|
49
|
-
full_outer_join get graph grep group group_and_count group_by having import
|
47
|
+
DATASET_METHODS = %w'<< add_graph_aliases all avg count cross_join delete distinct
|
48
|
+
each each_page each_server eager eager_graph empty? except exclude filter first for_update from from_self
|
49
|
+
full_join full_outer_join get graph grep group group_and_count group_by having import
|
50
50
|
inner_join insert insert_multiple intersect interval invert join join_table
|
51
|
-
last left_outer_join limit map max min multi_insert naked
|
52
|
-
|
53
|
-
|
51
|
+
last left_join left_outer_join limit lock_style map max min multi_insert naked
|
52
|
+
natural_full_join natural_join natural_left_join natural_right_join order order_by
|
53
|
+
order_more paginate print qualify query range reverse reverse_order right_join right_outer_join
|
54
|
+
select select_all select_hash select_map select_more select_order_map
|
55
|
+
server set set_defaults set_graph_aliases set_overrides
|
54
56
|
single_value sum to_csv to_hash truncate unfiltered ungraphed ungrouped union unlimited unordered
|
55
57
|
update where with with_recursive with_sql'.map{|x| x.to_sym}
|
56
58
|
|
@@ -106,10 +106,11 @@ module Sequel
|
|
106
106
|
# it sets album.artist to this_artist.
|
107
107
|
def reciprocal
|
108
108
|
return self[:reciprocal] if include?(:reciprocal)
|
109
|
-
|
109
|
+
r_types = Array(reciprocal_type)
|
110
110
|
keys = self[:keys]
|
111
111
|
associated_class.all_association_reflections.each do |assoc_reflect|
|
112
|
-
if assoc_reflect[:type]
|
112
|
+
if r_types.include?(assoc_reflect[:type]) && assoc_reflect[:keys] == keys && assoc_reflect.associated_class == self[:model]
|
113
|
+
self[:reciprocal_type] = assoc_reflect[:type]
|
113
114
|
return self[:reciprocal] = assoc_reflect[:name]
|
114
115
|
end
|
115
116
|
end
|
@@ -188,7 +189,7 @@ module Sequel
|
|
188
189
|
|
189
190
|
# The key to use for the key hash when eager loading
|
190
191
|
def eager_loader_key
|
191
|
-
self[:key]
|
192
|
+
self[:eager_loader_key] ||= self[:key]
|
192
193
|
end
|
193
194
|
|
194
195
|
# The column(s) in the associated table that the key in the current table references (either a symbol or an array).
|
@@ -201,18 +202,30 @@ module Sequel
|
|
201
202
|
self[:primary_keys] ||= Array(primary_key)
|
202
203
|
end
|
203
204
|
alias associated_object_keys primary_keys
|
205
|
+
|
206
|
+
# True only if the reciprocal is a one_to_many association.
|
207
|
+
def reciprocal_array?
|
208
|
+
!set_reciprocal_to_self?
|
209
|
+
end
|
204
210
|
|
205
211
|
# Whether this association returns an array of objects instead of a single object,
|
206
212
|
# false for a many_to_one association.
|
207
213
|
def returns_array?
|
208
214
|
false
|
209
215
|
end
|
216
|
+
|
217
|
+
# True only if the reciprocal is a one_to_one association.
|
218
|
+
def set_reciprocal_to_self?
|
219
|
+
reciprocal
|
220
|
+
self[:reciprocal_type] == :one_to_one
|
221
|
+
end
|
210
222
|
|
211
223
|
private
|
212
224
|
|
213
|
-
# The reciprocal type of a many_to_one association is
|
225
|
+
# The reciprocal type of a many_to_one association is either
|
226
|
+
# a one_to_many or a one_to_one association.
|
214
227
|
def reciprocal_type
|
215
|
-
:one_to_many
|
228
|
+
self[:reciprocal_type] ||= [:one_to_many, :one_to_one]
|
216
229
|
end
|
217
230
|
end
|
218
231
|
|
@@ -235,12 +248,16 @@ module Sequel
|
|
235
248
|
def default_key
|
236
249
|
:"#{underscore(demodulize(self[:model].name))}_id"
|
237
250
|
end
|
251
|
+
|
252
|
+
# The key to use for the key hash when eager loading
|
253
|
+
def eager_loader_key
|
254
|
+
self[:eager_loader_key] ||= primary_key
|
255
|
+
end
|
238
256
|
|
239
257
|
# The column in the current table that the key in the associated table references.
|
240
258
|
def primary_key
|
241
259
|
self[:primary_key] ||= self[:model].primary_key
|
242
260
|
end
|
243
|
-
alias eager_loader_key primary_key
|
244
261
|
|
245
262
|
# One to many associations set the reciprocal to self when loading associated records.
|
246
263
|
def set_reciprocal_to_self?
|
@@ -265,6 +282,15 @@ module Sequel
|
|
265
282
|
:many_to_one
|
266
283
|
end
|
267
284
|
end
|
285
|
+
|
286
|
+
class OneToOneAssociationReflection < OneToManyAssociationReflection
|
287
|
+
ASSOCIATION_TYPES[:one_to_one] = self
|
288
|
+
|
289
|
+
# one_to_one associations return a single object, not an array
|
290
|
+
def returns_array?
|
291
|
+
false
|
292
|
+
end
|
293
|
+
end
|
268
294
|
|
269
295
|
class ManyToManyAssociationReflection < AssociationReflection
|
270
296
|
ASSOCIATION_TYPES[:many_to_many] = self
|
@@ -315,7 +341,7 @@ module Sequel
|
|
315
341
|
|
316
342
|
# The key to use for the key hash when eager loading
|
317
343
|
def eager_loader_key
|
318
|
-
self[:left_primary_key]
|
344
|
+
self[:eager_loader_key] ||= self[:left_primary_key]
|
319
345
|
end
|
320
346
|
|
321
347
|
# many_to_many associations need to select a key in an associated table to eagerly load
|
@@ -378,6 +404,7 @@ module Sequel
|
|
378
404
|
#
|
379
405
|
# class Project < Sequel::Model
|
380
406
|
# many_to_one :portfolio
|
407
|
+
# # or: one_to_one :portfolio
|
381
408
|
# one_to_many :milestones
|
382
409
|
# # or: many_to_many :milestones
|
383
410
|
# end
|
@@ -385,7 +412,7 @@ module Sequel
|
|
385
412
|
# The project class now has the following instance methods:
|
386
413
|
# * portfolio - Returns the associated portfolio.
|
387
414
|
# * portfolio=(obj) - Sets the associated portfolio to the object,
|
388
|
-
# but the change is not persisted until you save the record.
|
415
|
+
# but the change is not persisted until you save the record (for many_to_one associations).
|
389
416
|
# * portfolio_dataset - Returns a dataset that would return the associated
|
390
417
|
# portfolio, only useful in fairly specific circumstances.
|
391
418
|
# * milestones - Returns an array of associated milestones
|
@@ -395,16 +422,16 @@ module Sequel
|
|
395
422
|
# * milestones_dataset - Returns a dataset that would return the associated
|
396
423
|
# milestones, allowing for further filtering/limiting/etc.
|
397
424
|
#
|
398
|
-
# If you want to override the behavior of the add_/remove_/remove_all_ methods
|
399
|
-
# there are private instance methods created that
|
400
|
-
# underscore (e.g. _add_milestone). The private instance methods can be
|
425
|
+
# If you want to override the behavior of the add_/remove_/remove_all_/ methods
|
426
|
+
# or the association setter method, there are private instance methods created that are prepended
|
427
|
+
# with an underscore (e.g. _add_milestone or _portfolio=). The private instance methods can be
|
401
428
|
# easily overridden, but you shouldn't override the public instance methods without
|
402
429
|
# calling super, as they deal with callbacks and caching.
|
403
430
|
#
|
404
431
|
# By default the classes for the associations are inferred from the association
|
405
432
|
# name, so for example the Project#portfolio will return an instance of
|
406
433
|
# Portfolio, and Project#milestones will return an array of Milestone
|
407
|
-
# instances.
|
434
|
+
# instances. You can use the :class option to change which class is used.
|
408
435
|
#
|
409
436
|
# Association definitions are also reflected by the class, e.g.:
|
410
437
|
#
|
@@ -432,20 +459,16 @@ module Sequel
|
|
432
459
|
# model's primary key. Each current model object can be associated with
|
433
460
|
# more than one associated model objects. Each associated model object
|
434
461
|
# can be associated with only one current model object.
|
462
|
+
# * :one_to_one - Similar to one_to_many in terms of foreign keys, but
|
463
|
+
# only one object is associated to the current object through the
|
464
|
+
# association. The methods created are similar to many_to_one, except
|
465
|
+
# that the one_to_one setter method saves the passed object.
|
435
466
|
# * :many_to_many - A join table is used that has a foreign key that points
|
436
467
|
# to this model's primary key and a foreign key that points to the
|
437
468
|
# associated model's primary key. Each current model object can be
|
438
469
|
# associated with many associated model objects, and each associated
|
439
470
|
# model object can be associated with many current model objects.
|
440
471
|
#
|
441
|
-
# A one to one relationship can be set up with a many_to_one association
|
442
|
-
# on the table with the foreign key, and a one_to_many association with the
|
443
|
-
# :one_to_one option specified on the table without the foreign key. The
|
444
|
-
# two associations will operate similarly, except that the many_to_one
|
445
|
-
# association setter doesn't update the database until you call save manually.
|
446
|
-
# Also, in most cases you need to specify the plural association name when using
|
447
|
-
# one_to_many with the :one_to_one option.
|
448
|
-
#
|
449
472
|
# The following options can be supplied:
|
450
473
|
# * *ALL types*:
|
451
474
|
# - :after_add - Symbol, Proc, or array of both/either specifying a callback to call
|
@@ -455,12 +478,16 @@ module Sequel
|
|
455
478
|
# when eager loading via eager_graph, but called when eager loading via eager.
|
456
479
|
# - :after_remove - Symbol, Proc, or array of both/either specifying a callback to call
|
457
480
|
# after an item is removed from the association.
|
481
|
+
# - :after_set - Symbol, Proc, or array of both/either specifying a callback to call
|
482
|
+
# after an item is set using the association setter method.
|
458
483
|
# - :allow_eager - If set to false, you cannot load the association eagerly
|
459
484
|
# via eager or eager_graph
|
460
485
|
# - :before_add - Symbol, Proc, or array of both/either specifying a callback to call
|
461
486
|
# before a new item is added to the association.
|
462
487
|
# - :before_remove - Symbol, Proc, or array of both/either specifying a callback to call
|
463
488
|
# before an item is removed from the association.
|
489
|
+
# - :before_set - Symbol, Proc, or array of both/either specifying a callback to call
|
490
|
+
# before an item is set using the association setter method.
|
464
491
|
# - :cartesian_product_number - the number of joins completed by this association that could cause more
|
465
492
|
# than one row for each row in the current table (default: 0 for many_to_one associations,
|
466
493
|
# 1 for *_to_many associations).
|
@@ -495,6 +522,8 @@ module Sequel
|
|
495
522
|
# and a hash of dependent associations. The associated records should
|
496
523
|
# be queried from the database and the associations cache for each
|
497
524
|
# record should be populated for this to work correctly.
|
525
|
+
# - :eager_loader_key - A symbol for the key column to use to populate the key hash
|
526
|
+
# for the eager loader.
|
498
527
|
# - :extend - A module or array of modules to extend the dataset with.
|
499
528
|
# - :graph_block - The block to pass to join_table when eagerly loading
|
500
529
|
# the association via eager_graph.
|
@@ -540,15 +569,6 @@ module Sequel
|
|
540
569
|
# current model's primary key, as a symbol. Defaults to
|
541
570
|
# :"#{self.name.underscore}_id". Can use an
|
542
571
|
# array of symbols for a composite key association.
|
543
|
-
# - :one_to_one: Create a getter and setter similar to those of many_to_one
|
544
|
-
# associations. The getter returns a singular matching record, or raises an
|
545
|
-
# error if multiple records match. The setter updates the record given and removes
|
546
|
-
# associations with all other records. When this option is used, the other
|
547
|
-
# association methods usually added are either removed or made private,
|
548
|
-
# so using this is similar to using many_to_one, in terms of the methods
|
549
|
-
# it adds, the main difference is that the foreign key is in the associated
|
550
|
-
# table instead of the current table. Note that using this option still requires
|
551
|
-
# you to use a plural name when creating and using the association (e.g. for reflections, eager loading, etc.).
|
552
572
|
# - :primary_key - column in the current table that :key option references, as a symbol.
|
553
573
|
# Defaults to primary key of the current table. Can use an
|
554
574
|
# array of symbols for a composite key association.
|
@@ -583,6 +603,7 @@ module Sequel
|
|
583
603
|
# array of symbols for a composite key association.
|
584
604
|
# - :uniq - Adds a after_load callback that makes the array of objects unique.
|
585
605
|
def associate(type, name, opts = {}, &block)
|
606
|
+
raise(Error, 'one_to_many association type with :one_to_one option removed, used one_to_one association type') if opts[:one_to_one] && type == :one_to_many
|
586
607
|
raise(Error, 'invalid association type') unless assoc_class = ASSOCIATION_TYPES[type]
|
587
608
|
raise(Error, 'Model.associate name argument must be a symbol') unless Symbol === name
|
588
609
|
|
@@ -599,20 +620,11 @@ module Sequel
|
|
599
620
|
opts[:graph_conditions] = conds if !opts.include?(:graph_conditions) and Sequel.condition_specifier?(conds)
|
600
621
|
opts[:graph_conditions] = opts[:graph_conditions] ? opts[:graph_conditions].to_a : []
|
601
622
|
opts[:graph_select] = Array(opts[:graph_select]) if opts[:graph_select]
|
602
|
-
[:before_add, :before_remove, :after_add, :after_remove, :after_load, :extend].each do |cb_type|
|
623
|
+
[:before_add, :before_remove, :after_add, :after_remove, :after_load, :before_set, :after_set, :extend].each do |cb_type|
|
603
624
|
opts[cb_type] = Array(opts[cb_type])
|
604
625
|
end
|
605
|
-
|
606
|
-
|
607
|
-
case opts[:class]
|
608
|
-
when String, Symbol
|
609
|
-
# Delete :class to allow late binding
|
610
|
-
opts[:class_name] ||= opts.delete(:class).to_s
|
611
|
-
when Class
|
612
|
-
opts[:class_name] ||= opts[:class].name
|
613
|
-
end
|
614
|
-
opts[:class_name] ||= ((self.name || '').split("::")[0..-2] + [camelize(opts.returns_array? ? singularize(name) : name)]).join('::')
|
615
|
-
|
626
|
+
late_binding_class_option(opts, opts.returns_array? ? singularize(name) : name)
|
627
|
+
|
616
628
|
send(:"def_#{type}", opts)
|
617
629
|
|
618
630
|
orig_opts.delete(:clone)
|
@@ -664,18 +676,23 @@ module Sequel
|
|
664
676
|
end
|
665
677
|
|
666
678
|
# Shortcut for adding a many_to_many association, see associate
|
667
|
-
def many_to_many(
|
668
|
-
associate(:many_to_many,
|
679
|
+
def many_to_many(name, opts={}, &block)
|
680
|
+
associate(:many_to_many, name, opts, &block)
|
669
681
|
end
|
670
682
|
|
671
683
|
# Shortcut for adding a many_to_one association, see associate
|
672
|
-
def many_to_one(
|
673
|
-
associate(:many_to_one,
|
684
|
+
def many_to_one(name, opts={}, &block)
|
685
|
+
associate(:many_to_one, name, opts, &block)
|
674
686
|
end
|
675
687
|
|
676
688
|
# Shortcut for adding a one_to_many association, see associate
|
677
|
-
def one_to_many(
|
678
|
-
associate(:one_to_many,
|
689
|
+
def one_to_many(name, opts={}, &block)
|
690
|
+
associate(:one_to_many, name, opts, &block)
|
691
|
+
end
|
692
|
+
|
693
|
+
# Shortcut for adding a one_to_one association, see associate.
|
694
|
+
def one_to_one(name, opts={}, &block)
|
695
|
+
associate(:one_to_one, name, opts, &block)
|
679
696
|
end
|
680
697
|
|
681
698
|
private
|
@@ -781,7 +798,7 @@ module Sequel
|
|
781
798
|
database.dataset.from(join_table).filter(lcks.zip(lcpks.map{|k| send(k)}) + rcks.zip(opts.right_primary_keys.map{|k| o.send(k)})).delete
|
782
799
|
end
|
783
800
|
association_module_private_def(opts._remove_all_method) do
|
784
|
-
database.dataset.from(join_table).filter(lcks.zip(lcpks.map{|k| send(k)})).delete
|
801
|
+
_apply_association_options(opts, database.dataset.from(join_table).filter(lcks.zip(lcpks.map{|k| send(k)}))).delete
|
785
802
|
end
|
786
803
|
|
787
804
|
def_add_method(opts)
|
@@ -839,6 +856,7 @@ module Sequel
|
|
839
856
|
|
840
857
|
# Adds one_to_many association instance methods
|
841
858
|
def def_one_to_many(opts)
|
859
|
+
one_to_one = opts[:type] == :one_to_one
|
842
860
|
name = opts[:name]
|
843
861
|
model = self
|
844
862
|
key = (opts[:key] ||= opts.default_key)
|
@@ -853,15 +871,26 @@ module Sequel
|
|
853
871
|
end
|
854
872
|
opts[:eager_loader] ||= proc do |key_hash, records, associations|
|
855
873
|
h = key_hash[primary_key]
|
856
|
-
|
874
|
+
if one_to_one
|
875
|
+
records.each{|object| object.associations[name] = nil}
|
876
|
+
else
|
877
|
+
records.each{|object| object.associations[name] = []}
|
878
|
+
end
|
857
879
|
reciprocal = opts.reciprocal
|
858
880
|
klass = opts.associated_class
|
859
881
|
model.eager_loading_dataset(opts, klass.filter(uses_cks ? {cks.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}=>SQL::SQLArray.new(h.keys)} : {SQL::QualifiedIdentifier.new(klass.table_name, key)=>h.keys}), opts.select, associations).all do |assoc_record|
|
860
882
|
hash_key = uses_cks ? cks.map{|k| assoc_record.send(k)} : assoc_record.send(key)
|
861
883
|
next unless objects = h[hash_key]
|
862
|
-
|
863
|
-
object
|
864
|
-
|
884
|
+
if one_to_one
|
885
|
+
objects.each do |object|
|
886
|
+
object.associations[name] = assoc_record
|
887
|
+
assoc_record.associations[reciprocal] = object if reciprocal
|
888
|
+
end
|
889
|
+
else
|
890
|
+
objects.each do |object|
|
891
|
+
object.associations[name].push(assoc_record)
|
892
|
+
assoc_record.associations[reciprocal] = object if reciprocal
|
893
|
+
end
|
865
894
|
end
|
866
895
|
end
|
867
896
|
end
|
@@ -871,7 +900,7 @@ module Sequel
|
|
871
900
|
use_only_conditions = opts.include?(:graph_only_conditions)
|
872
901
|
only_conditions = opts[:graph_only_conditions]
|
873
902
|
conditions = opts[:graph_conditions]
|
874
|
-
opts[:cartesian_product_number] ||= 1
|
903
|
+
opts[:cartesian_product_number] ||= one_to_one ? 0 : 1
|
875
904
|
graph_block = opts[:graph_block]
|
876
905
|
opts[:eager_grapher] ||= proc do |ds, assoc_alias, table_alias|
|
877
906
|
ds = ds.graph(opts.associated_class, use_only_conditions ? only_conditions : cks.zip(cpks) + conditions, :select=>select, :table_alias=>assoc_alias, :join_type=>join_type, :implicit_qualifier=>table_alias, :from_self_alias=>ds.opts[:eager_graph][:master], &graph_block)
|
@@ -884,48 +913,43 @@ module Sequel
|
|
884
913
|
|
885
914
|
ck_nil_hash ={}
|
886
915
|
cks.each{|k| ck_nil_hash[k] = nil}
|
887
|
-
|
916
|
+
|
888
917
|
unless opts[:read_only]
|
889
918
|
validate = opts[:validate]
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
919
|
+
|
920
|
+
if one_to_one
|
921
|
+
association_module_private_def(opts._setter_method) do |o|
|
922
|
+
up_ds = _apply_association_options(opts, opts.associated_class.filter(cks.zip(cpks.map{|k| send(k)})))
|
923
|
+
if o
|
924
|
+
up_ds = up_ds.exclude(o.pk_hash)
|
925
|
+
cks.zip(cpks).each{|k, pk| o.send(:"#{k}=", send(pk))}
|
926
|
+
end
|
927
|
+
update_database = lambda do
|
928
|
+
up_ds.update(ck_nil_hash)
|
929
|
+
o.save(:validate=>validate) || raise(Sequel::Error, "invalid associated object, cannot save") if o
|
930
|
+
end
|
931
|
+
use_transactions && o ? db.transaction(opts){update_database.call} : update_database.call
|
932
|
+
end
|
933
|
+
association_module_def(opts.setter_method){|o| set_one_to_one_associated_object(opts, o)}
|
934
|
+
else
|
935
|
+
association_module_private_def(opts._add_method) do |o|
|
936
|
+
cks.zip(cpks).each{|k, pk| o.send(:"#{k}=", send(pk))}
|
937
|
+
o.save(:validate=>validate) || raise(Sequel::Error, "invalid associated object, cannot save")
|
938
|
+
end
|
939
|
+
def_add_method(opts)
|
895
940
|
|
896
|
-
unless opts[:one_to_one]
|
897
941
|
association_module_private_def(opts._remove_method) do |o|
|
898
942
|
cks.each{|k| o.send(:"#{k}=", nil)}
|
899
943
|
o.save(:validate=>validate) || raise(Sequel::Error, "invalid associated object, cannot save")
|
900
944
|
end
|
901
945
|
association_module_private_def(opts._remove_all_method) do
|
902
|
-
opts.associated_class.filter(cks.zip(cpks.map{|k| send(k)})).update(ck_nil_hash)
|
946
|
+
_apply_association_options(opts, opts.associated_class.filter(cks.zip(cpks.map{|k| send(k)}))).update(ck_nil_hash)
|
903
947
|
end
|
904
948
|
def_remove_methods(opts)
|
905
949
|
end
|
906
950
|
end
|
907
|
-
if opts[:one_to_one]
|
908
|
-
overridable_methods_module.send(:private, opts.association_method, opts.dataset_method)
|
909
|
-
n = singularize(name).to_sym
|
910
|
-
raise(Sequel::Error, "one_to_many association names should still be plural even when using the :one_to_one option") if n == name
|
911
|
-
association_module_def(n) do |*o|
|
912
|
-
objs = send(name, *o)
|
913
|
-
raise(Sequel::Error, "multiple values found for a one-to-one relationship") if objs.length > 1
|
914
|
-
objs.first
|
915
|
-
end
|
916
|
-
unless opts[:read_only]
|
917
|
-
overridable_methods_module.send(:private, opts.add_method)
|
918
|
-
association_module_def(:"#{n}=") do |o|
|
919
|
-
klass = opts.associated_class
|
920
|
-
update_database = lambda do
|
921
|
-
send(opts.add_method, o)
|
922
|
-
klass.filter(cks.zip(cpks.map{|k| send(k)})).exclude(o.pk_hash).update(ck_nil_hash)
|
923
|
-
end
|
924
|
-
use_transactions ? db.transaction(opts){update_database.call} : update_database.call
|
925
|
-
end
|
926
|
-
end
|
927
|
-
end
|
928
951
|
end
|
952
|
+
alias def_one_to_one def_one_to_many
|
929
953
|
|
930
954
|
# Add the remove_ and remove_all instance methods
|
931
955
|
def def_remove_methods(opts)
|
@@ -944,11 +968,8 @@ module Sequel
|
|
944
968
|
end
|
945
969
|
|
946
970
|
private
|
947
|
-
|
948
|
-
|
949
|
-
def _dataset(opts)
|
950
|
-
raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
|
951
|
-
ds = send(opts._dataset_method)
|
971
|
+
|
972
|
+
def _apply_association_options(opts, ds)
|
952
973
|
ds.extend(AssociationDatasetMethods)
|
953
974
|
ds.model_object = self
|
954
975
|
ds.association_reflection = opts
|
@@ -959,6 +980,7 @@ module Sequel
|
|
959
980
|
end
|
960
981
|
ds = ds.order(*opts[:order]) if opts[:order]
|
961
982
|
ds = ds.limit(*opts[:limit]) if opts[:limit]
|
983
|
+
ds = ds.limit(1) if !opts.returns_array? && opts[:key]
|
962
984
|
ds = ds.eager(*opts[:eager]) if opts[:eager]
|
963
985
|
ds = ds.distinct if opts[:distinct]
|
964
986
|
ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph] && opts.eager_graph_lazy_dataset?
|
@@ -966,15 +988,19 @@ module Sequel
|
|
966
988
|
ds
|
967
989
|
end
|
968
990
|
|
991
|
+
# Backbone behind association dataset methods
|
992
|
+
def _dataset(opts)
|
993
|
+
raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
|
994
|
+
_apply_association_options(opts, send(opts._dataset_method))
|
995
|
+
end
|
996
|
+
|
969
997
|
# Return the associated objects from the dataset, without callbacks, reciprocals, and caching.
|
970
998
|
def _load_associated_objects(opts)
|
971
999
|
if opts.returns_array?
|
972
1000
|
opts.can_have_associated_objects?(self) ? send(opts.dataset_method).all : []
|
973
1001
|
else
|
974
|
-
if
|
1002
|
+
if opts.can_have_associated_objects?(self)
|
975
1003
|
send(opts.dataset_method).all.first
|
976
|
-
elsif opts.can_have_associated_objects?(self)
|
977
|
-
send(opts.dataset_method).first
|
978
1004
|
end
|
979
1005
|
end
|
980
1006
|
end
|
@@ -1028,7 +1054,13 @@ module Sequel
|
|
1028
1054
|
else
|
1029
1055
|
objs = _load_associated_objects(opts)
|
1030
1056
|
run_association_callbacks(opts, :after_load, objs)
|
1031
|
-
|
1057
|
+
if opts.set_reciprocal_to_self?
|
1058
|
+
if opts.returns_array?
|
1059
|
+
objs.each{|o| add_reciprocal_object(opts, o)}
|
1060
|
+
elsif objs
|
1061
|
+
add_reciprocal_object(opts, objs)
|
1062
|
+
end
|
1063
|
+
end
|
1032
1064
|
associations[name] = objs
|
1033
1065
|
end
|
1034
1066
|
end
|
@@ -1079,7 +1111,7 @@ module Sequel
|
|
1079
1111
|
# Run the callback for the association with the object.
|
1080
1112
|
def run_association_callbacks(reflection, callback_type, object)
|
1081
1113
|
raise_error = raise_on_save_failure || !reflection.returns_array?
|
1082
|
-
stop_on_false = [:before_add, :before_remove].include?(callback_type)
|
1114
|
+
stop_on_false = [:before_add, :before_remove, :before_set].include?(callback_type)
|
1083
1115
|
reflection[callback_type].each do |cb|
|
1084
1116
|
res = case cb
|
1085
1117
|
when Symbol
|
@@ -1098,19 +1130,29 @@ module Sequel
|
|
1098
1130
|
|
1099
1131
|
# Set the given object as the associated object for the given association
|
1100
1132
|
def set_associated_object(opts, o)
|
1101
|
-
raise(
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1133
|
+
raise(Error, "associated object #{o.inspect} does not have a primary key") if o && !o.pk
|
1134
|
+
run_association_callbacks(opts, :before_set, o)
|
1135
|
+
if a = associations[opts[:name]]
|
1136
|
+
remove_reciprocal_object(opts, a)
|
1137
|
+
end
|
1106
1138
|
send(opts._setter_method, o)
|
1107
1139
|
associations[opts[:name]] = o
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1140
|
+
add_reciprocal_object(opts, o) if o
|
1141
|
+
run_association_callbacks(opts, :after_set, o)
|
1142
|
+
o
|
1143
|
+
end
|
1144
|
+
|
1145
|
+
# Set the given object as the associated object for the given association
|
1146
|
+
def set_one_to_one_associated_object(opts, o)
|
1147
|
+
raise(Error, "object #{inspect} does not have a primary key") unless pk
|
1148
|
+
run_association_callbacks(opts, :before_set, o)
|
1149
|
+
if a = associations[opts[:name]]
|
1150
|
+
remove_reciprocal_object(opts, a)
|
1112
1151
|
end
|
1113
|
-
|
1152
|
+
send(opts._setter_method, o)
|
1153
|
+
associations[opts[:name]] = o
|
1154
|
+
add_reciprocal_object(opts, o) if o
|
1155
|
+
run_association_callbacks(opts, :after_set, o)
|
1114
1156
|
o
|
1115
1157
|
end
|
1116
1158
|
end
|