sequel 3.9.0 → 3.10.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 +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
|