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 | 158 |
1 files changed, 43 insertions, 115 deletions
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 68b8b792ad..1d2e8667e4 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -1,142 +1,70 @@ module ActiveRecord # = Active Record Belongs To Has One Association module Associations - class HasOneAssociation < AssociationProxy #:nodoc: - def initialize(owner, reflection) - super - construct_sql - end + class HasOneAssociation < SingularAssociation #:nodoc: + def replace(record, save = true) + raise_on_type_mismatch(record) if record + load_target - def create(attrs = {}, replace_existing = true) - new_record(replace_existing) do |reflection| - attrs = merge_with_conditions(attrs) - reflection.create_association(attrs) - end - end + reflection.klass.transaction do + if target && target != record + remove_target!(options[:dependent]) + end - def create!(attrs = {}, replace_existing = true) - new_record(replace_existing) do |reflection| - attrs = merge_with_conditions(attrs) - reflection.create_association!(attrs) - end - end + if record + set_inverse_instance(record) + set_owner_attributes(record) - def build(attrs = {}, replace_existing = true) - new_record(replace_existing) do |reflection| - attrs = merge_with_conditions(attrs) - reflection.build_association(attrs) + 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 + end end - end - def replace(obj, dont_save = false) - load_target + self.target = record + end - unless @target.nil? || @target == obj - if dependent? && !dont_save - case @reflection.options[:dependent] + def delete(method = options[:dependent]) + if load_target + case method when :delete - @target.delete unless @target.new_record? - @owner.clear_association_cache + target.delete when :destroy - @target.destroy unless @target.new_record? - @owner.clear_association_cache + target.destroy when :nullify - @target[@reflection.primary_key_name] = nil - @target.save unless @owner.new_record? || @target.new_record? - end - else - @target[@reflection.primary_key_name] = nil - @target.save unless @owner.new_record? || @target.new_record? + target.update_attribute(reflection.foreign_key, nil) 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.new_record? or obj.nil? or dont_save - return (obj.save ? self : false) - else - return (obj.nil? ? nil : self) - end 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 - end - private - def find_target - the_target = @reflection.klass.find(:first, - :conditions => @finder_sql, - :select => @reflection.options[:select], - :order => @reflection.options[:order], - :include => @reflection.options[:include], - :readonly => @reflection.options[:readonly] - ) - set_inverse_instance(the_target, @owner) - the_target - end - def construct_sql - case - when @reflection.options[:as] - @finder_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 - @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}" - end - @finder_sql << " AND (#{conditions})" if conditions + # 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 construct_scope - create_scoping = {} - set_belongs_to_association_for(create_scoping) - { :create => create_scoping } - 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 => construct_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 unless @owner.new_record? - 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 |