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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
module ActiveRecord
module Associations
class AssociationProxy #:nodoc:
attr_reader :reflection
alias_method :proxy_respond_to?, :respond_to?
alias_method :proxy_extend, :extend
delegate :to_param, :to => :proxy_target
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_)/ }
def initialize(owner, reflection)
@owner, @reflection = owner, reflection
Array(reflection.options[:extend]).each { |ext| proxy_extend(ext) }
reset
end
def proxy_owner
@owner
end
def proxy_reflection
@reflection
end
def proxy_target
@target
end
def respond_to?(symbol, include_priv = false)
proxy_respond_to?(symbol, include_priv) || (load_target && @target.respond_to?(symbol, include_priv))
end
# Explicitly proxy === because the instance method removal above
# doesn't catch it.
def ===(other)
load_target
other === @target
end
def aliased_table_name
@reflection.klass.table_name
end
def conditions
@conditions ||= interpolate_sql(sanitize_sql(@reflection.options[:conditions])) if @reflection.options[:conditions]
end
alias :sql_conditions :conditions
def reset
@target = nil
@loaded = false
end
def reload
reset
load_target
end
def loaded?
@loaded
end
def loaded
@loaded = true
end
def target
@target
end
def target=(target)
@target = target
loaded
end
def inspect
loaded? ? @target.inspect : "<#{@reflection.name} not loaded yet>"
end
protected
def dependent?
@reflection.options[:dependent] || false
end
def quoted_record_ids(records)
records.map { |record| record.quoted_id }.join(',')
end
def interpolate_sql_options!(options, *keys)
keys.each { |key| options[key] &&= interpolate_sql(options[key]) }
end
def interpolate_sql(sql, record = nil)
@owner.send(:interpolate_sql, sql, record)
end
def sanitize_sql(sql)
@reflection.klass.send(:sanitize_sql, sql)
end
def extract_options_from_args!(args)
@owner.send(:extract_options_from_args!, args)
end
def set_belongs_to_association_for(record)
if @reflection.options[:as]
record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s
else
record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
end
end
def merge_options_from_reflection!(options)
options.reverse_merge!(
:group => @reflection.options[:group],
:limit => @reflection.options[:limit],
:offset => @reflection.options[:offset],
:joins => @reflection.options[:joins],
:include => @reflection.options[:include],
:select => @reflection.options[:select]
)
end
private
def method_missing(method, *args, &block)
if load_target
@target.send(method, *args, &block)
end
end
def load_target
return nil unless defined?(@loaded)
if !loaded? and (!@owner.new_record? || foreign_key_present)
@target = find_target
end
@loaded = true
@target
rescue ActiveRecord::RecordNotFound
reset
end
# Can be overwritten by associations that might have the foreign key available for an association without
# having the object itself (and still being a new record). Currently, only belongs_to present this scenario.
def foreign_key_present
false
end
def raise_on_type_mismatch(record)
unless record.is_a?(@reflection.klass)
raise ActiveRecord::AssociationTypeMismatch, "#{@reflection.class_name} expected, got #{record.class}"
end
end
# Array#flatten has problems with recursive arrays. Going one level deeper solves the majority of the problems.
def flatten_deeper(array)
array.collect { |element| element.respond_to?(:flatten) ? element.flatten : element }.flatten
end
end
end
end
|