diff options
Diffstat (limited to 'activerecord/lib/active_record/associations/has_one_association.rb')
-rw-r--r-- | activerecord/lib/active_record/associations/has_one_association.rb | 146 |
1 files changed, 38 insertions, 108 deletions
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index c49fd6e66a..a0828dcdea 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -1,135 +1,65 @@ module ActiveRecord # = Active Record Belongs To Has One Association module Associations - class HasOneAssociation < AssociationProxy #:nodoc: - def create(attrs = {}, replace_existing = true) - new_record(replace_existing) do |reflection| - attrs = merge_with_conditions(attrs) - reflection.create_association(attrs) - end - end - - def create!(attrs = {}, replace_existing = true) - new_record(replace_existing) do |reflection| - attrs = merge_with_conditions(attrs) - reflection.create_association!(attrs) - end - end + class HasOneAssociation < SingularAssociation #:nodoc: + def replace(record, save = true) + record = check_record(record) + load_target - def build(attrs = {}, replace_existing = true) - new_record(replace_existing) do |reflection| - attrs = merge_with_conditions(attrs) - reflection.build_association(attrs) - end - end + @reflection.klass.transaction do + if @target && @target != record + remove_target!(@reflection.options[:dependent]) + end - def replace(obj, dont_save = false) - load_target + if record + set_inverse_instance(record) + set_owner_attributes(record) - unless @target.nil? || @target == obj - if dependent? && !dont_save - case @reflection.options[:dependent] - when :delete - @target.delete if @target.persisted? - @owner.clear_association_cache - when :destroy - @target.destroy if @target.persisted? - @owner.clear_association_cache - when :nullify - @target[@reflection.primary_key_name] = nil - @target.save if @owner.persisted? && @target.persisted? + if @owner.persisted? && save && !record.save + nullify_owner_attributes(record) + set_owner_attributes(@target) + raise RecordNotSaved, "Failed to save the new associated #{@reflection.name}." end - else - @target[@reflection.primary_key_name] = nil - @target.save if @owner.persisted? && @target.persisted? end end - if obj.nil? - @target = nil - else - raise_on_type_mismatch(obj) - set_belongs_to_association_for(obj) - @target = (AssociationProxy === obj ? obj.target : obj) - end - - set_inverse_instance(obj, @owner) - @loaded = true - - unless !@owner.persisted? || obj.nil? || dont_save - return (obj.save ? self : false) - else - return (obj.nil? ? nil : self) - end + self.target = record end protected - def owner_quoted_id - if @reflection.options[:primary_key] - @owner.class.quote_value(@owner.send(@reflection.options[:primary_key])) - else - @owner.quoted_id - end + + def association_scope + super.order(@reflection.options[:order]) end private - def find_target - options = @reflection.options.dup.slice(:select, :order, :include, :readonly) - - the_target = with_scope(:find => @scope[:find]) do - @reflection.klass.find(:first, options) - end - set_inverse_instance(the_target, @owner) - the_target - end - def construct_find_scope - if @reflection.options[:as] - sql = - "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " + - "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}" - else - sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}" - end - sql << " AND (#{conditions})" if conditions - { :conditions => sql } - end + alias creation_attributes construct_owner_attributes - def construct_create_scope - create_scoping = {} - set_belongs_to_association_for(create_scoping) - create_scoping + # The reason that the save param for replace is false, if for create (not just build), + # is because the setting of the foreign keys is actually handled by the scoping when + # the record is instantiated, and so they are set straight away and do not need to be + # updated within replace. + def set_new_record(record) + replace(record, false) end - def new_record(replace_existing) - # Make sure we load the target first, if we plan on replacing the existing - # instance. Otherwise, if the target has not previously been loaded - # elsewhere, the instance we create will get orphaned. - load_target if replace_existing - record = @reflection.klass.send(:with_scope, :create => @scope[:create]) do - yield @reflection - end - - if replace_existing - replace(record, true) + def remove_target!(method) + if [:delete, :destroy].include?(method) + @target.send(method) else - record[@reflection.primary_key_name] = @owner.id if @owner.persisted? - self.target = record - set_inverse_instance(record, @owner) - end + nullify_owner_attributes(@target) - record - end - - def we_can_set_the_inverse_on_this?(record) - inverse = @reflection.inverse_of - return !inverse.nil? + if @target.persisted? && @owner.persisted? && !@target.save + set_owner_attributes(@target) + raise RecordNotSaved, "Failed to remove the existing associated #{@reflection.name}. " + + "The record failed to save when after its foreign key was set to nil." + end + end end - def merge_with_conditions(attrs={}) - attrs ||= {} - attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash) - attrs + def nullify_owner_attributes(record) + record[@reflection.foreign_key] = nil end end end |