Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

root/tags/rel_2-0-2/activerecord/lib/active_record/associations/has_many_association.rb

Revision 8178, 6.0 kB (checked in by bitsweat, 9 months ago)

Dynamic finders on association collections respect association :limit. Closes #10227 [Jack Danger Canty]

Line 
1 module ActiveRecord
2   module Associations
3     class HasManyAssociation < AssociationCollection #:nodoc:
4       def initialize(owner, reflection)
5         super
6         construct_sql
7       end
8
9       def build(attributes = {})
10         if attributes.is_a?(Array)
11           attributes.collect { |attr| build(attr) }
12         else
13           build_record(attributes) { |record| set_belongs_to_association_for(record) }
14         end
15       end
16
17       # Count the number of associated records. All arguments are optional.
18       def count(*args)
19         if @reflection.options[:counter_sql]
20           @reflection.klass.count_by_sql(@counter_sql)
21         elsif @reflection.options[:finder_sql]
22           @reflection.klass.count_by_sql(@finder_sql)
23         else
24           column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)         
25           options[:conditions] = options[:conditions].nil? ?
26             @finder_sql :
27             @finder_sql + " AND (#{sanitize_sql(options[:conditions])})"
28           options[:include] ||= @reflection.options[:include]
29
30           @reflection.klass.count(column_name, options)
31         end
32       end
33
34       def find(*args)
35         options = args.extract_options!
36
37         # If using a custom finder_sql, scan the entire collection.
38         if @reflection.options[:finder_sql]
39           expects_array = args.first.kind_of?(Array)
40           ids           = args.flatten.compact.uniq.map(&:to_i)
41
42           if ids.size == 1
43             id = ids.first
44             record = load_target.detect { |record| id == record.id }
45             expects_array ? [ record ] : record
46           else
47             load_target.select { |record| ids.include?(record.id) }
48           end
49         else
50           conditions = "#{@finder_sql}"
51           if sanitized_conditions = sanitize_sql(options[:conditions])
52             conditions << " AND (#{sanitized_conditions})"
53           end
54           options[:conditions] = conditions
55
56           if options[:order] && @reflection.options[:order]
57             options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
58           elsif @reflection.options[:order]
59             options[:order] = @reflection.options[:order]
60           end
61
62           merge_options_from_reflection!(options)
63
64           # Pass through args exactly as we received them.
65           args << options
66           @reflection.klass.find(*args)
67         end
68       end
69
70       protected
71         def load_target
72           if !@owner.new_record? || foreign_key_present
73             begin
74               if !loaded?
75                 if @target.is_a?(Array) && @target.any?
76                   @target = (find_target + @target).uniq
77                 else
78                   @target = find_target
79                 end
80               end
81             rescue ActiveRecord::RecordNotFound
82               reset
83             end
84           end
85
86           loaded if target
87           target
88         end
89
90         def count_records
91           count = if has_cached_counter?
92             @owner.send(:read_attribute, cached_counter_attribute_name)
93           elsif @reflection.options[:counter_sql]
94             @reflection.klass.count_by_sql(@counter_sql)
95           else
96             @reflection.klass.count(:conditions => @counter_sql, :include => @reflection.options[:include])
97           end
98          
99           @target = [] and loaded if count == 0
100          
101           if @reflection.options[:limit]
102             count = [ @reflection.options[:limit], count ].min
103           end
104          
105           return count
106         end
107
108         def has_cached_counter?
109           @owner.attribute_present?(cached_counter_attribute_name)
110         end
111
112         def cached_counter_attribute_name
113           "#{@reflection.name}_count"
114         end
115
116         def insert_record(record)
117           set_belongs_to_association_for(record)
118           record.save
119         end
120
121         def delete_records(records)
122           case @reflection.options[:dependent]
123             when :destroy
124               records.each(&:destroy)
125             when :delete_all
126               @reflection.klass.delete(records.map(&:id))
127             else
128               ids = quoted_record_ids(records)
129               @reflection.klass.update_all(
130                 "#{@reflection.primary_key_name} = NULL",
131                 "#{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
132               )
133           end
134         end
135
136         def target_obsolete?
137           false
138         end
139
140         def construct_sql
141           case
142             when @reflection.options[:finder_sql]
143               @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
144
145             when @reflection.options[:as]
146               @finder_sql =
147                 "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
148                 "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
149               @finder_sql << " AND (#{conditions})" if conditions
150            
151             else
152               @finder_sql = "#{@reflection.klass.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
153               @finder_sql << " AND (#{conditions})" if conditions
154           end
155
156           if @reflection.options[:counter_sql]
157             @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
158           elsif @reflection.options[:finder_sql]
159             # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
160             @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
161             @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
162           else
163             @counter_sql = @finder_sql
164           end
165         end
166
167         def construct_scope
168           create_scoping = {}
169           set_belongs_to_association_for(create_scoping)
170           { :find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit] }, :create => create_scoping }
171         end
172     end
173   end
174 end
Note: See TracBrowser for help on using the browser.