diff options
Diffstat (limited to 'activerecord/lib/active_record/associations/association_collection.rb')
-rw-r--r-- | activerecord/lib/active_record/associations/association_collection.rb | 109 |
1 files changed, 47 insertions, 62 deletions
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 3e3237c348..ca350f51c9 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -56,14 +56,21 @@ module ActiveRecord end def build(attributes = {}, &block) - if attributes.is_a?(Array) - attributes.collect { |attr| build(attr, &block) } - else - build_record(attributes) do |record| - block.call(record) if block_given? - set_owner_attributes(record) - end + build_or_create(attributes, :build, &block) + end + + def create(attributes = {}, &block) + unless @owner.persisted? + raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved" end + + build_or_create(attributes, :create, &block) + end + + def create!(attrs = {}, &block) + record = create(attrs, &block) + Array.wrap(record).each(&:save!) + record end # Add +records+ to this association. Returns +self+ so method calls may be chained. @@ -75,7 +82,7 @@ module ActiveRecord transaction do records.flatten.each do |record| raise_on_type_mismatch(record) - add_record_to_target_with_callbacks(record) do |r| + add_to_target(record) do |r| result &&= insert_record(record) unless @owner.new_record? end end @@ -191,24 +198,6 @@ module ActiveRecord delete_or_destroy(records, :destroy) end - def create(attrs = {}) - if attrs.is_a?(Array) - attrs.collect { |attr| create(attr) } - else - create_record(attrs) do |record| - yield(record) if block_given? - insert_record(record, false) - end - end - end - - def create!(attrs = {}) - create_record(attrs) do |record| - yield(record) if block_given? - insert_record(record, true) - end - end - # Returns the size of the collection by executing a SELECT COUNT(*) # query if the collection hasn't been loaded, and calling # <tt>collection.size</tt> if it has. @@ -348,17 +337,21 @@ module ActiveRecord target end - def add_record_to_target_with_callbacks(record) - callback(:before_add, record) - yield(record) if block_given? - @target ||= [] unless loaded? - if @reflection.options[:uniq] && index = @target.index(record) - @target[index] = record - else - @target << record + def add_to_target(record) + transaction do + callback(:before_add, record) + yield(record) if block_given? + + if @reflection.options[:uniq] && index = @target.index(record) + @target[index] = record + else + @target << record + end + + callback(:after_add, record) + set_inverse_instance(record) end - callback(:after_add, record) - set_inverse_instance(record) + record end @@ -374,17 +367,15 @@ module ActiveRecord def custom_counter_sql if @reflection.options[:counter_sql] - counter_sql = @reflection.options[:counter_sql] + interpolate(@reflection.options[:counter_sql]) else # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */ - counter_sql = @reflection.options[:finder_sql].sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" } + interpolate(@reflection.options[:finder_sql]).sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" } end - - interpolate_sql(counter_sql) end def custom_finder_sql - interpolate_sql(@reflection.options[:finder_sql]) + interpolate(@reflection.options[:finder_sql]) end def find_target @@ -421,26 +412,26 @@ module ActiveRecord end + existing end - # Do the relevant stuff to insert the given record into the association collection. The - # force param specifies whether or not an exception should be raised on failure. The - # validate param specifies whether validation should be performed (if force is false). - def insert_record(record, force = true, validate = true) - raise NotImplementedError - end + def build_or_create(attributes, method) + records = Array.wrap(attributes).map do |attrs| + record = build_record(attrs) + + add_to_target(record) do + yield(record) if block_given? + insert_record(record) if method == :create + end + end - def save_record(record, force, validate) - force ? record.save! : record.save(:validate => validate) + attributes.is_a?(Array) ? records : records.first end - def create_record(attributes, &block) - ensure_owner_is_persisted! - transaction { build_record(attributes, &block) } + # Do the relevant stuff to insert the given record into the association collection. + def insert_record(record, validate = true) + raise NotImplementedError end - def build_record(attributes, &block) - attributes = scoped.scope_for_create.merge(attributes) - record = @reflection.build_association(attributes) - add_record_to_target_with_callbacks(record, &block) + def build_record(attributes) + @reflection.build_association(scoped.scope_for_create.merge(attributes)) end def delete_or_destroy(records, method) @@ -482,12 +473,6 @@ module ActiveRecord @owner.class.send(full_callback_name.to_sym) || [] end - def ensure_owner_is_persisted! - unless @owner.persisted? - raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved" - end - end - # Should we deal with assoc.first or assoc.last by issuing an independent query to # the database, or by getting the target, and then taking the first/last item from that? # |