| 1 |
require 'set' |
|---|
| 2 |
|
|---|
| 3 |
module ActiveRecord |
|---|
| 4 |
module Associations |
|---|
| 5 |
class AssociationCollection < AssociationProxy |
|---|
| 6 |
def to_ary |
|---|
| 7 |
load_target |
|---|
| 8 |
@target.to_ary |
|---|
| 9 |
end |
|---|
| 10 |
|
|---|
| 11 |
def reset |
|---|
| 12 |
reset_target! |
|---|
| 13 |
@loaded = false |
|---|
| 14 |
end |
|---|
| 15 |
|
|---|
| 16 |
|
|---|
| 17 |
|
|---|
| 18 |
def <<(*records) |
|---|
| 19 |
result = true |
|---|
| 20 |
load_target if @owner.new_record? |
|---|
| 21 |
|
|---|
| 22 |
@owner.transaction do |
|---|
| 23 |
flatten_deeper(records).each do |record| |
|---|
| 24 |
raise_on_type_mismatch(record) |
|---|
| 25 |
callback(:before_add, record) |
|---|
| 26 |
result &&= insert_record(record) unless @owner.new_record? |
|---|
| 27 |
@target << record |
|---|
| 28 |
callback(:after_add, record) |
|---|
| 29 |
end |
|---|
| 30 |
end |
|---|
| 31 |
|
|---|
| 32 |
result && self |
|---|
| 33 |
end |
|---|
| 34 |
|
|---|
| 35 |
alias_method :push, :<< |
|---|
| 36 |
alias_method :concat, :<< |
|---|
| 37 |
|
|---|
| 38 |
|
|---|
| 39 |
def delete_all |
|---|
| 40 |
load_target |
|---|
| 41 |
delete(@target) |
|---|
| 42 |
reset_target! |
|---|
| 43 |
end |
|---|
| 44 |
|
|---|
| 45 |
|
|---|
| 46 |
def sum(*args, &block) |
|---|
| 47 |
calculate(:sum, *args, &block) |
|---|
| 48 |
end |
|---|
| 49 |
|
|---|
| 50 |
|
|---|
| 51 |
def delete(*records) |
|---|
| 52 |
records = flatten_deeper(records) |
|---|
| 53 |
records.each { |record| raise_on_type_mismatch(record) } |
|---|
| 54 |
records.reject! { |record| @target.delete(record) if record.new_record? } |
|---|
| 55 |
return if records.empty? |
|---|
| 56 |
|
|---|
| 57 |
@owner.transaction do |
|---|
| 58 |
records.each { |record| callback(:before_remove, record) } |
|---|
| 59 |
delete_records(records) |
|---|
| 60 |
records.each do |record| |
|---|
| 61 |
@target.delete(record) |
|---|
| 62 |
callback(:after_remove, record) |
|---|
| 63 |
end |
|---|
| 64 |
end |
|---|
| 65 |
end |
|---|
| 66 |
|
|---|
| 67 |
|
|---|
| 68 |
def clear |
|---|
| 69 |
return self if length.zero? |
|---|
| 70 |
|
|---|
| 71 |
if @reflection.options[:dependent] && @reflection.options[:dependent] == :destroy |
|---|
| 72 |
destroy_all |
|---|
| 73 |
else |
|---|
| 74 |
delete_all |
|---|
| 75 |
end |
|---|
| 76 |
|
|---|
| 77 |
self |
|---|
| 78 |
end |
|---|
| 79 |
|
|---|
| 80 |
def destroy_all |
|---|
| 81 |
@owner.transaction do |
|---|
| 82 |
each { |record| record.destroy } |
|---|
| 83 |
end |
|---|
| 84 |
|
|---|
| 85 |
reset_target! |
|---|
| 86 |
end |
|---|
| 87 |
|
|---|
| 88 |
def create(attrs = {}) |
|---|
| 89 |
if attrs.is_a?(Array) |
|---|
| 90 |
attrs.collect { |attr| create(attr) } |
|---|
| 91 |
else |
|---|
| 92 |
create_record(attrs) { |record| record.save } |
|---|
| 93 |
end |
|---|
| 94 |
end |
|---|
| 95 |
|
|---|
| 96 |
def create!(attrs = {}) |
|---|
| 97 |
create_record(attrs) { |record| record.save! } |
|---|
| 98 |
end |
|---|
| 99 |
|
|---|
| 100 |
|
|---|
| 101 |
|
|---|
| 102 |
|
|---|
| 103 |
def size |
|---|
| 104 |
if @owner.new_record? || (loaded? && !@reflection.options[:uniq]) |
|---|
| 105 |
@target.size |
|---|
| 106 |
elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array) |
|---|
| 107 |
unsaved_records = Array(@target.detect { |r| r.new_record? }) |
|---|
| 108 |
unsaved_records.size + count_records |
|---|
| 109 |
else |
|---|
| 110 |
count_records |
|---|
| 111 |
end |
|---|
| 112 |
end |
|---|
| 113 |
|
|---|
| 114 |
|
|---|
| 115 |
|
|---|
| 116 |
def length |
|---|
| 117 |
load_target.size |
|---|
| 118 |
end |
|---|
| 119 |
|
|---|
| 120 |
def empty? |
|---|
| 121 |
size.zero? |
|---|
| 122 |
end |
|---|
| 123 |
|
|---|
| 124 |
def any?(&block) |
|---|
| 125 |
if block_given? |
|---|
| 126 |
method_missing(:any?, &block) |
|---|
| 127 |
else |
|---|
| 128 |
!empty? |
|---|
| 129 |
end |
|---|
| 130 |
end |
|---|
| 131 |
|
|---|
| 132 |
def uniq(collection = self) |
|---|
| 133 |
seen = Set.new |
|---|
| 134 |
collection.inject([]) do |kept, record| |
|---|
| 135 |
unless seen.include?(record.id) |
|---|
| 136 |
kept << record |
|---|
| 137 |
seen << record.id |
|---|
| 138 |
end |
|---|
| 139 |
kept |
|---|
| 140 |
end |
|---|
| 141 |
end |
|---|
| 142 |
|
|---|
| 143 |
|
|---|
| 144 |
|
|---|
| 145 |
def replace(other_array) |
|---|
| 146 |
other_array.each { |val| raise_on_type_mismatch(val) } |
|---|
| 147 |
|
|---|
| 148 |
load_target |
|---|
| 149 |
other = other_array.size < 100 ? other_array : other_array.to_set |
|---|
| 150 |
current = @target.size < 100 ? @target : @target.to_set |
|---|
| 151 |
|
|---|
| 152 |
@owner.transaction do |
|---|
| 153 |
delete(@target.select { |v| !other.include?(v) }) |
|---|
| 154 |
concat(other_array.select { |v| !current.include?(v) }) |
|---|
| 155 |
end |
|---|
| 156 |
end |
|---|
| 157 |
|
|---|
| 158 |
|
|---|
| 159 |
protected |
|---|
| 160 |
def method_missing(method, *args, &block) |
|---|
| 161 |
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method)) |
|---|
| 162 |
super |
|---|
| 163 |
else |
|---|
| 164 |
@reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.send(method, *args, &block) } |
|---|
| 165 |
end |
|---|
| 166 |
end |
|---|
| 167 |
|
|---|
| 168 |
|
|---|
| 169 |
def construct_scope |
|---|
| 170 |
{} |
|---|
| 171 |
end |
|---|
| 172 |
|
|---|
| 173 |
def reset_target! |
|---|
| 174 |
@target = Array.new |
|---|
| 175 |
end |
|---|
| 176 |
|
|---|
| 177 |
def find_target |
|---|
| 178 |
records = |
|---|
| 179 |
if @reflection.options[:finder_sql] |
|---|
| 180 |
@reflection.klass.find_by_sql(@finder_sql) |
|---|
| 181 |
else |
|---|
| 182 |
find(:all) |
|---|
| 183 |
end |
|---|
| 184 |
|
|---|
| 185 |
@reflection.options[:uniq] ? uniq(records) : records |
|---|
| 186 |
end |
|---|
| 187 |
|
|---|
| 188 |
private |
|---|
| 189 |
|
|---|
| 190 |
def create_record(attrs, &block) |
|---|
| 191 |
ensure_owner_is_not_new |
|---|
| 192 |
record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) { @reflection.klass.new(attrs) } |
|---|
| 193 |
add_record_to_target_with_callbacks(record, &block) |
|---|
| 194 |
end |
|---|
| 195 |
|
|---|
| 196 |
def build_record(attrs, &block) |
|---|
| 197 |
record = @reflection.klass.new(attrs) |
|---|
| 198 |
add_record_to_target_with_callbacks(record, &block) |
|---|
| 199 |
end |
|---|
| 200 |
|
|---|
| 201 |
def add_record_to_target_with_callbacks(record) |
|---|
| 202 |
callback(:before_add, record) |
|---|
| 203 |
yield(record) if block_given? |
|---|
| 204 |
@target ||= [] unless loaded? |
|---|
| 205 |
@target << record |
|---|
| 206 |
callback(:after_add, record) |
|---|
| 207 |
record |
|---|
| 208 |
end |
|---|
| 209 |
|
|---|
| 210 |
def callback(method, record) |
|---|
| 211 |
callbacks_for(method).each do |callback| |
|---|
| 212 |
case callback |
|---|
| 213 |
when Symbol |
|---|
| 214 |
@owner.send(callback, record) |
|---|
| 215 |
when Proc, Method |
|---|
| 216 |
callback.call(@owner, record) |
|---|
| 217 |
else |
|---|
| 218 |
if callback.respond_to?(method) |
|---|
| 219 |
callback.send(method, @owner, record) |
|---|
| 220 |
else |
|---|
| 221 |
raise ActiveRecordError, "Callbacks must be a symbol denoting the method to call, a string to be evaluated, a block to be invoked, or an object responding to the callback method." |
|---|
| 222 |
end |
|---|
| 223 |
end |
|---|
| 224 |
end |
|---|
| 225 |
end |
|---|
| 226 |
|
|---|
| 227 |
def callbacks_for(callback_name) |
|---|
| 228 |
full_callback_name = "#{callback_name}_for_#{@reflection.name}" |
|---|
| 229 |
@owner.class.read_inheritable_attribute(full_callback_name.to_sym) || [] |
|---|
| 230 |
end |
|---|
| 231 |
|
|---|
| 232 |
def ensure_owner_is_not_new |
|---|
| 233 |
if @owner.new_record? |
|---|
| 234 |
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved" |
|---|
| 235 |
end |
|---|
| 236 |
end |
|---|
| 237 |
|
|---|
| 238 |
end |
|---|
| 239 |
end |
|---|
| 240 |
end |
|---|