has_many has_one belongs_to都有设置dependent属性
has_many的文档里写的dependent是设置为:destroy将调用关联对象destroy方法,如果设置为:delete_all将调用class.delete_all而不调用destroy方法,:nullify就是直接将外键设为null.
has_one的文档里写的dependent是设置为:destroy将调用关联对象destroy方法,如果设置为:delete将调用class.delete而不调用destroy方法,:nullify就是直接将外键设为null.
belongs_to的文档里写的dependent是设置为:destroy将调用关联对象destroy方法,如果设置为:delete将deleted而不调用destroy方法.
继续查源代码了解清楚一点:
1) has_many的源代码:
def has_many(association_id, options = {}, &extension) reflection = create_has_many_reflection(association_id, options, &extension) configure_dependency_for_has_many(reflection) ... end
# delete children, otherwise foreign key is set to NULL. def configure_dependency_for_has_many(reflection) if reflection.options.include?(:dependent) # Add polymorphic type if the :as option is present dependent_conditions = [] dependent_conditions << "#{reflection.primary_key_name} = \#{record.quoted_id}" dependent_conditions << "#{reflection.options[:as]}_type = '#{base_class.name}'" if reflection.options[:as] dependent_conditions << sanitize_sql(reflection.options[:conditions]) if reflection.options[:conditions] dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ") case reflection.options[:dependent] when :destroy # 一个个用destroy删除 method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym define_method(method_name) do send("#{reflection.name}").each { |o| o.destroy } end before_destroy method_name when :delete_all # 用delete_all指令删除,hook proc到before_destroy module_eval "before_destroy { |record| #{reflection.class_name}.delete_all(%(#{dependent_conditions})) }" when :nullify # 用update_all指令更新为null,hook proc到before_destroy module_eval "before_destroy { |record| #{reflection.class_name}.update_all(%(#{reflection.primary_key_name} = NULL), %(#{dependent_conditions})) }" else raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, or :nullify (#{reflection.options[:dependent].inspect})" end end end
如果只是简单的关联,has_many的delete_all方法会比destroy效率高不少,destroy是一个n+1查询。
实践证明其实对于大表的删除,最好不使用dependent属性,性能问题迟早会找上来
2) has_one的源代码:
def has_one(association_id, options = {}) ... configure_dependency_for_has_one(reflection) end
def configure_dependency_for_has_one(reflection) if reflection.options.include?(:dependent) case reflection.options[:dependent] when :destroy # 创建destroy关联方法,hook到before_destroy method_name = "has_one_dependent_destroy_for_#{reflection.name}".to_sym define_method(method_name) do association = send("#{reflection.name}") association.destroy unless association.nil? end before_destroy method_name when :delete # 创建delete关联方法,调用类的delete(id)方法,hook到before_destroy method_name = "has_one_dependent_delete_for_#{reflection.name}".to_sym define_method(method_name) do association = send("#{reflection.name}") association.class.delete(association.id) unless association.nil? end before_destroy method_name when :nullify # 调用record.update(attr,nil),hook到before_destroy method_name = "has_one_dependent_nullify_for_#{reflection.name}".to_sym define_method(method_name) do association = send("#{reflection.name}") association.update_attribute("#{reflection.primary_key_name}", nil) unless association.nil? end before_destroy method_name else raise ArgumentError, "The :dependent option expects either :destroy, :delete or :nullify (#{reflection.options[:dependent].inspect})" end end end
3) belongs_to相关源代码:
def belongs_to(association_id, options = {}) ... configure_dependency_for_belongs_to(reflection) end
def configure_dependency_for_belongs_to(reflection) if reflection.options.include?(:dependent) case reflection.options[:dependent] when :destroy # 直接调用destroy删除,hook到before_destroy method_name = "belongs_to_dependent_destroy_for_#{reflection.name}".to_sym define_method(method_name) do association = send("#{reflection.name}") association.destroy unless association.nil? end before_destroy method_name when :delete #调用类方法的delete(id)删除,hook到before_destroy method_name = "belongs_to_dependent_delete_for_#{reflection.name}".to_sym define_method(method_name) do association = send("#{reflection.name}") association.class.delete(association.id) unless association.nil? end before_destroy method_name else raise ArgumentError, "The :dependent option expects either :destroy or :delete (#{reflection.options[:dependent].inspect})" end end end