Froze rails gems
[depot.git] / vendor / rails / activerecord / lib / active_record / associations / has_many_through_association.rb
1 module ActiveRecord
2 module Associations
3 class HasManyThroughAssociation < HasManyAssociation #:nodoc:
4 def initialize(owner, reflection)
5 reflection.check_validity!
6 super
7 end
8
9 alias_method :new, :build
10
11 def create!(attrs = nil)
12 transaction do
13 self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association! } : @reflection.create_association!)
14 object
15 end
16 end
17
18 def create(attrs = nil)
19 transaction do
20 self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association } : @reflection.create_association)
21 object
22 end
23 end
24
25 # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
26 # calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero
27 # and you need to fetch that collection afterwards, it'll take one less SELECT query if you use length.
28 def size
29 return @owner.send(:read_attribute, cached_counter_attribute_name) if has_cached_counter?
30 return @target.size if loaded?
31 return count
32 end
33
34 protected
35 def target_reflection_has_associated_record?
36 if @reflection.through_reflection.macro == :belongs_to && @owner[@reflection.through_reflection.primary_key_name].blank?
37 false
38 else
39 true
40 end
41 end
42
43 def construct_find_options!(options)
44 options[:select] = construct_select(options[:select])
45 options[:from] ||= construct_from
46 options[:joins] = construct_joins(options[:joins])
47 options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil?
48 end
49
50 def insert_record(record, force=true)
51 if record.new_record?
52 if force
53 record.save!
54 else
55 return false unless record.save
56 end
57 end
58 through_reflection = @reflection.through_reflection
59 klass = through_reflection.klass
60 @owner.send(@reflection.through_reflection.name).proxy_target << klass.send(:with_scope, :create => construct_join_attributes(record)) { through_reflection.create_association! }
61 end
62
63 # TODO - add dependent option support
64 def delete_records(records)
65 klass = @reflection.through_reflection.klass
66 records.each do |associate|
67 klass.delete_all(construct_join_attributes(associate))
68 end
69 end
70
71 def find_target
72 return [] unless target_reflection_has_associated_record?
73 @reflection.klass.find(:all,
74 :select => construct_select,
75 :conditions => construct_conditions,
76 :from => construct_from,
77 :joins => construct_joins,
78 :order => @reflection.options[:order],
79 :limit => @reflection.options[:limit],
80 :group => @reflection.options[:group],
81 :readonly => @reflection.options[:readonly],
82 :include => @reflection.options[:include] || @reflection.source_reflection.options[:include]
83 )
84 end
85
86 # Construct attributes for associate pointing to owner.
87 def construct_owner_attributes(reflection)
88 if as = reflection.options[:as]
89 { "#{as}_id" => @owner.id,
90 "#{as}_type" => @owner.class.base_class.name.to_s }
91 else
92 { reflection.primary_key_name => @owner.id }
93 end
94 end
95
96 # Construct attributes for :through pointing to owner and associate.
97 def construct_join_attributes(associate)
98 # TODO: revist this to allow it for deletion, supposing dependent option is supported
99 raise ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection.new(@owner, @reflection) if @reflection.source_reflection.macro == :has_many
100 join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
101 if @reflection.options[:source_type]
102 join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s)
103 end
104 join_attributes
105 end
106
107 # Associate attributes pointing to owner, quoted.
108 def construct_quoted_owner_attributes(reflection)
109 if as = reflection.options[:as]
110 { "#{as}_id" => owner_quoted_id,
111 "#{as}_type" => reflection.klass.quote_value(
112 @owner.class.base_class.name.to_s,
113 reflection.klass.columns_hash["#{as}_type"]) }
114 elsif reflection.macro == :belongs_to
115 { reflection.klass.primary_key => @owner[reflection.primary_key_name] }
116 else
117 { reflection.primary_key_name => owner_quoted_id }
118 end
119 end
120
121 # Build SQL conditions from attributes, qualified by table name.
122 def construct_conditions
123 table_name = @reflection.through_reflection.quoted_table_name
124 conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value|
125 "#{table_name}.#{attr} = #{value}"
126 end
127 conditions << sql_conditions if sql_conditions
128 "(" + conditions.join(') AND (') + ")"
129 end
130
131 def construct_from
132 @reflection.quoted_table_name
133 end
134
135 def construct_select(custom_select = nil)
136 distinct = "DISTINCT " if @reflection.options[:uniq]
137 selected = custom_select || @reflection.options[:select] || "#{distinct}#{@reflection.quoted_table_name}.*"
138 end
139
140 def construct_joins(custom_joins = nil)
141 polymorphic_join = nil
142 if @reflection.source_reflection.macro == :belongs_to
143 reflection_primary_key = @reflection.klass.primary_key
144 source_primary_key = @reflection.source_reflection.primary_key_name
145 if @reflection.options[:source_type]
146 polymorphic_join = "AND %s.%s = %s" % [
147 @reflection.through_reflection.quoted_table_name, "#{@reflection.source_reflection.options[:foreign_type]}",
148 @owner.class.quote_value(@reflection.options[:source_type])
149 ]
150 end
151 else
152 reflection_primary_key = @reflection.source_reflection.primary_key_name
153 source_primary_key = @reflection.klass.primary_key
154 if @reflection.source_reflection.options[:as]
155 polymorphic_join = "AND %s.%s = %s" % [
156 @reflection.quoted_table_name, "#{@reflection.source_reflection.options[:as]}_type",
157 @owner.class.quote_value(@reflection.through_reflection.klass.name)
158 ]
159 end
160 end
161
162 "INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [
163 @reflection.through_reflection.table_name,
164 @reflection.table_name, reflection_primary_key,
165 @reflection.through_reflection.table_name, source_primary_key,
166 polymorphic_join
167 ]
168 end
169
170 def construct_scope
171 { :create => construct_owner_attributes(@reflection),
172 :find => { :from => construct_from,
173 :conditions => construct_conditions,
174 :joins => construct_joins,
175 :include => @reflection.options[:include],
176 :select => construct_select,
177 :order => @reflection.options[:order],
178 :limit => @reflection.options[:limit],
179 :readonly => @reflection.options[:readonly],
180 } }
181 end
182
183 def construct_sql
184 case
185 when @reflection.options[:finder_sql]
186 @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
187
188 @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
189 @finder_sql << " AND (#{conditions})" if conditions
190 else
191 @finder_sql = construct_conditions
192 end
193
194 if @reflection.options[:counter_sql]
195 @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
196 elsif @reflection.options[:finder_sql]
197 # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
198 @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
199 @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
200 else
201 @counter_sql = @finder_sql
202 end
203 end
204
205 def conditions
206 @conditions = build_conditions unless defined?(@conditions)
207 @conditions
208 end
209
210 def build_conditions
211 association_conditions = @reflection.options[:conditions]
212 through_conditions = build_through_conditions
213 source_conditions = @reflection.source_reflection.options[:conditions]
214 uses_sti = !@reflection.through_reflection.klass.descends_from_active_record?
215
216 if association_conditions || through_conditions || source_conditions || uses_sti
217 all = []
218
219 [association_conditions, source_conditions].each do |conditions|
220 all << interpolate_sql(sanitize_sql(conditions)) if conditions
221 end
222
223 all << through_conditions if through_conditions
224 all << build_sti_condition if uses_sti
225
226 all.map { |sql| "(#{sql})" } * ' AND '
227 end
228 end
229
230 def build_through_conditions
231 conditions = @reflection.through_reflection.options[:conditions]
232 if conditions.is_a?(Hash)
233 interpolate_sql(sanitize_sql(conditions)).gsub(
234 @reflection.quoted_table_name,
235 @reflection.through_reflection.quoted_table_name)
236 elsif conditions
237 interpolate_sql(sanitize_sql(conditions))
238 end
239 end
240
241 def build_sti_condition
242 @reflection.through_reflection.klass.send(:type_condition)
243 end
244
245 alias_method :sql_conditions, :conditions
246
247 def has_cached_counter?
248 @owner.attribute_present?(cached_counter_attribute_name)
249 end
250
251 def cached_counter_attribute_name
252 "#{@reflection.name}_count"
253 end
254 end
255 end
256 end