| 1 |
module ActiveRecord |
|---|
| 2 |
module Associations |
|---|
| 3 |
class HasManyAssociation < AssociationCollection |
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 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 |
|
|---|
| 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 |
|---|