/
belongs_to_association.rb
130 lines (106 loc) · 3.5 KB
/
belongs_to_association.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# frozen_string_literal: true
module ActiveRecord
module Associations
# = Active Record Belongs To Association
class BelongsToAssociation < SingularAssociation #:nodoc:
def handle_dependency
return unless load_target
case options[:dependent]
when :destroy
target.destroy
raise ActiveRecord::Rollback unless target.destroyed?
else
target.send(options[:dependent])
end
end
def replace(record)
if record
raise_on_type_mismatch!(record)
update_counters_on_replace(record)
set_inverse_instance(record)
@updated = true
else
decrement_counters
end
replace_keys(record)
self.target = record
end
def inversed_from(record)
replace_keys(record)
super
end
def default(&block)
writer(owner.instance_exec(&block)) if reader.nil?
end
def reset
super
@updated = false
end
def updated?
@updated
end
def decrement_counters # :nodoc:
update_counters(-1)
end
def increment_counters # :nodoc:
update_counters(1)
end
def target_changed?
owner.saved_change_to_attribute?(reflection.foreign_key)
end
private
def update_counters(by)
if require_counter_update? && foreign_key_present?
if target && !stale_target?
target.increment!(reflection.counter_cache_column, by, touch: reflection.options[:touch])
else
klass.update_counters(target_id, reflection.counter_cache_column => by, touch: reflection.options[:touch])
end
end
end
def find_target?
!loaded? && foreign_key_present? && klass
end
def require_counter_update?
reflection.counter_cache_column && owner.persisted?
end
def update_counters_on_replace(record)
if require_counter_update? && different_target?(record)
owner.instance_variable_set :@_after_replace_counter_called, true
record.increment!(reflection.counter_cache_column, touch: reflection.options[:touch])
decrement_counters
end
end
# Checks whether record is different to the current target, without loading it
def different_target?(record)
record._read_attribute(primary_key(record)) != owner._read_attribute(reflection.foreign_key)
end
def replace_keys(record)
owner[reflection.foreign_key] = record ? record._read_attribute(primary_key(record)) : nil
end
def primary_key(record)
reflection.association_primary_key(record.class)
end
def foreign_key_present?
owner._read_attribute(reflection.foreign_key)
end
# NOTE - for now, we're only supporting inverse setting from belongs_to back onto
# has_one associations.
def invertible_for?(record)
inverse = inverse_reflection_for(record)
inverse && inverse.has_one?
end
def target_id
if options[:primary_key]
owner.send(reflection.name).try(:id)
else
owner._read_attribute(reflection.foreign_key)
end
end
def stale_state
result = owner._read_attribute(reflection.foreign_key) { |n| owner.send(:missing_attribute, n, caller) }
result && result.to_s
end
end
end
end