sequel 3.9.0 → 3.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/CHANGELOG +56 -0
  2. data/README.rdoc +1 -1
  3. data/Rakefile +1 -1
  4. data/doc/advanced_associations.rdoc +7 -10
  5. data/doc/release_notes/3.10.0.txt +286 -0
  6. data/lib/sequel/adapters/do/mysql.rb +4 -0
  7. data/lib/sequel/adapters/jdbc.rb +5 -0
  8. data/lib/sequel/adapters/jdbc/as400.rb +58 -0
  9. data/lib/sequel/adapters/jdbc/oracle.rb +30 -0
  10. data/lib/sequel/adapters/shared/mssql.rb +23 -9
  11. data/lib/sequel/adapters/shared/mysql.rb +12 -1
  12. data/lib/sequel/adapters/shared/postgres.rb +7 -18
  13. data/lib/sequel/adapters/shared/sqlite.rb +5 -0
  14. data/lib/sequel/adapters/sqlite.rb +5 -0
  15. data/lib/sequel/connection_pool/single.rb +3 -3
  16. data/lib/sequel/database.rb +3 -2
  17. data/lib/sequel/dataset.rb +6 -5
  18. data/lib/sequel/dataset/convenience.rb +3 -3
  19. data/lib/sequel/dataset/query.rb +13 -0
  20. data/lib/sequel/dataset/sql.rb +31 -1
  21. data/lib/sequel/extensions/schema_dumper.rb +3 -3
  22. data/lib/sequel/model.rb +8 -6
  23. data/lib/sequel/model/associations.rb +144 -102
  24. data/lib/sequel/model/base.rb +21 -1
  25. data/lib/sequel/model/plugins.rb +3 -1
  26. data/lib/sequel/plugins/association_dependencies.rb +14 -7
  27. data/lib/sequel/plugins/caching.rb +4 -0
  28. data/lib/sequel/plugins/composition.rb +138 -0
  29. data/lib/sequel/plugins/identity_map.rb +2 -2
  30. data/lib/sequel/plugins/lazy_attributes.rb +1 -1
  31. data/lib/sequel/plugins/nested_attributes.rb +3 -2
  32. data/lib/sequel/plugins/rcte_tree.rb +281 -0
  33. data/lib/sequel/plugins/typecast_on_load.rb +16 -5
  34. data/lib/sequel/sql.rb +18 -1
  35. data/lib/sequel/version.rb +1 -1
  36. data/spec/adapters/mssql_spec.rb +4 -0
  37. data/spec/adapters/mysql_spec.rb +4 -0
  38. data/spec/adapters/postgres_spec.rb +55 -5
  39. data/spec/core/database_spec.rb +5 -3
  40. data/spec/core/dataset_spec.rb +86 -15
  41. data/spec/core/expression_filters_spec.rb +23 -6
  42. data/spec/extensions/association_dependencies_spec.rb +24 -5
  43. data/spec/extensions/association_proxies_spec.rb +3 -0
  44. data/spec/extensions/composition_spec.rb +194 -0
  45. data/spec/extensions/identity_map_spec.rb +16 -0
  46. data/spec/extensions/nested_attributes_spec.rb +44 -1
  47. data/spec/extensions/rcte_tree_spec.rb +205 -0
  48. data/spec/extensions/schema_dumper_spec.rb +6 -0
  49. data/spec/extensions/spec_helper.rb +6 -0
  50. data/spec/extensions/typecast_on_load_spec.rb +9 -0
  51. data/spec/extensions/validation_helpers_spec.rb +5 -5
  52. data/spec/integration/dataset_test.rb +13 -9
  53. data/spec/integration/eager_loader_test.rb +56 -1
  54. data/spec/integration/model_test.rb +8 -0
  55. data/spec/integration/plugin_test.rb +270 -0
  56. data/spec/integration/schema_test.rb +1 -1
  57. data/spec/model/associations_spec.rb +541 -118
  58. data/spec/model/eager_loading_spec.rb +24 -3
  59. data/spec/model/record_spec.rb +34 -0
  60. 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 table_options join where group having order compounds')
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 .nolock on a query
249
+ # Allows you to do a dirty read of uncommitted data using WITH (NOLOCK).
248
250
  def nolock
249
- clone(:table_options => "(NOLOCK)")
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.sql_function, Sequel::SQL::Window.new(:order=>order)).as(rn)]
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
- sql << " TOP (#{literal(@opts[:limit])})" if @opts[:limit]
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
- # MSSQL uses the WITH statement to lock tables
437
- def select_table_options_sql(sql)
438
- sql << " WITH #{@opts[:table_options]}" if @opts[:table_options]
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 with a :share lock type.
649
+ # Return a cloned dataset which will use FOR SHARE to lock returned rows.
651
650
  def for_share
652
- clone(:lock => :share)
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, since the connection
6
- # is always available.
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 and immediately reconnect from the database.
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
@@ -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
- nil
168
+ result
168
169
  else
169
170
  c.new(opts)
170
171
  end
@@ -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 or order order_by order_more paginate qualify query
38
- reverse reverse_order right_outer_join select select_all select_more server
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 (in ascending order). Column aliases
85
- # may be supplied, and will be included in the select clause.
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])).order(: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
@@ -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
  #
@@ -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=>(self.class.adapter_scheme == :mysql && $1 == '1' && Sequel::MySQL.convert_tinyint_to_bool ? TrueClass : Integer)}
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
@@ -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 order order_by
52
- order_more paginate print qualify query range reverse reverse_order right_outer_join
53
- select select_all select_more server set set_defaults set_graph_aliases set_overrides
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
- r_type = reciprocal_type
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] == r_type && assoc_reflect[:keys] == keys && assoc_reflect.associated_class == self[:model]
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 a one_to_many association.
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 a prepended with an
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
- # find class
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(*args, &block)
668
- associate(:many_to_many, *args, &block)
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(*args, &block)
673
- associate(:many_to_one, *args, &block)
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(*args, &block)
678
- associate(:one_to_many, *args, &block)
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
- records.each{|object| object.associations[name] = []}
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
- objects.each do |object|
863
- object.associations[name].push(assoc_record)
864
- assoc_record.associations[reciprocal] = object if reciprocal
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
- association_module_private_def(opts._add_method) do |o|
891
- cks.zip(cpks).each{|k, pk| o.send(:"#{k}=", send(pk))}
892
- o.save(:validate=>validate) || raise(Sequel::Error, "invalid associated object, cannot save")
893
- end
894
- def_add_method(opts)
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
- # Backbone behind association dataset methods
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 !opts[:key]
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
- objs.each{|o| add_reciprocal_object(opts, o)} if opts.set_reciprocal_to_self?
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(Sequel::Error, "associated object #{o.inspect} does not have a primary key") if o && !o.pk
1102
- old_val = send(opts.association_method)
1103
- return o if old_val == o
1104
- return if old_val and run_association_callbacks(opts, :before_remove, old_val) == false
1105
- return if o and run_association_callbacks(opts, :before_add, o) == false
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
- remove_reciprocal_object(opts, old_val) if old_val
1109
- if o
1110
- add_reciprocal_object(opts, o)
1111
- run_association_callbacks(opts, :after_add, o)
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
- run_association_callbacks(opts, :after_remove, old_val) if old_val
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