ActiveRecord / Dissecting a has many association / 1 - Manual dissection before I had automatic

From WhyNotWiki
Jump to: navigation, search
   has_many :children, :class_name => name, :foreign_key => 'parent_id', :order => 'position', :dependent => :destroy  | has_many_test.rb:8
 + calling ActiveRecord::Associations::ClassMethods::has_many
 / def has_many(association_id, options = {}, &extension)               | ...y/gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:555
 |    (association_id = :children; options = {:class_name=>"Person", :foreign_key=>"parent_id", :order=>"position", :dependent=>:destroy}; extension = nil; reflection = nil)
 |    reflection = create_has_many_reflection(association_id, options, &extension)  | ...ems/activerecord-1.15.3/lib/active_record/associations.rb:556
 |  + calling ActiveRecord::Associations::ClassMethods::create_has_many_reflection
 |  / def create_has_many_reflection(association_id, options, &extension)  | ...ms/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:1093
 |  |    (association_id = :children; options = {:class_name=>"Person", :foreign_key=>"parent_id", :order=>"position", :dependent=>:destroy}; extension = nil)
 |  |    options.assert_valid_keys(                                     | .../gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:1094
 |  |    options[:extend] = create_extension_module(association_id, extension) if block_given?  | ...ord-1.15.3/lib/active_record/associations.rb:1105
 |  |    create_reflection(:has_many, association_id, options, self)    | .../gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:1107
 |  |  + calling ActiveRecord::Reflection::ClassMethods::create_reflection
 |  |  / def create_reflection(macro, name, options, active_record)     | ...ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/reflection.rb:13
 |  |  |    (macro = :has_many; name = :children; options = {:class_name=>"Person", :foreign_key=>"parent_id", :order=>"position", :dependent=>:destroy}; active_record = Person; reflection = nil)
 |  |  |    case macro                                                  | ...ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/reflection.rb:14
 |  |  |    when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many  | ...gems/1.8/gems/activerecord-1.15.3/lib/active_record/reflection.rb:15
 |  |  |    reflection = AssociationReflection.new(macro, name, options, active_record)  | ...s/activerecord-1.15.3/lib/active_record/reflection.rb:16
 |  |  |  + calling ActiveRecord::Reflection::MacroReflection::initialize
 |  |  |  / def initialize(macro, name, options, active_record)         | ...ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/reflection.rb:74
 |  |  |  |    (macro = :has_many; name = :children; options = {:class_name=>"Person", :foreign_key=>"parent_id", :order=>"position", :dependent=>:destroy}; active_record = Person)
 |  |  |  |    @macro, @name, @options, @active_record = macro, name, options, active_record  | ...iverecord-1.15.3/lib/active_record/reflection.rb:75
 |  |  |  \ end (returning from ActiveRecord::Reflection::MacroReflection::initialize)  | ...ms/activerecord-1.15.3/lib/active_record/reflection.rb:76
 |  |  |    write_inheritable_hash :reflections, name => reflection     | ...ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/reflection.rb:20
 |  |  |    reflection                                                  | ...ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/reflection.rb:21
 |  |  \ (returning from ActiveRecord::Reflection::ClassMethods::create_reflection)  | ...erecord-1.15.3/lib/active_record/reflection.rb:14
 |  \ def create_has_many_reflection(association_id, options, &extension) (returning from ActiveRecord::Associations::ClassMe...  | ...iations.rb:1093
 |    configure_dependency_for_has_many(reflection)                     | ...y/gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:558
 |  + calling ActiveRecord::Associations::ClassMethods::configure_dependency_for_has_many
 |  / def configure_dependency_for_has_many(reflection)                 | .../gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:1029
 |  |    (reflection = #<ActiveRecord::Reflection::AssociationReflection:0xb78d405c @macro=:has_many, @name=:children, @options={:class_name=>"Person", :foreign_key=>"parent_id", :order=>"position", :dependent=>:destroy}, @active_record=Person>; dependent_conditions = nil)
 |  |    if reflection.options[:dependent] == true                      | .../gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:1030
 |  \ (returning from ActiveRecord::Associations::ClassMethods::configure_dependency_for_has_many)  | ...1030

 |  + calling ActiveRecord::Associations::ClassMethods::add_multiple_associated_save_callbacks
 |  / def add_multiple_associated_save_callbacks(association_name)      | ...y/gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:964
 |  |    (association_name = :children; method_name = nil; after_callback = nil)
 |  |    method_name = "validate_associated_records_for_#{association_name}".to_sym  | ...ms/activerecord-1.15.3/lib/active_record/associations.rb:965
 |  |    define_method(method_name) do                                  | ...y/gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:966
...
 |  \ (returning from ActiveRecord::Associations::...  | ...ciations.rb:965


 |    add_association_callbacks(reflection.name, reflection.options)    | ...y/gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:564
 |  + calling ActiveRecord::Associations::ClassMethods::add_association_callbacks
 |  / def add_association_callbacks(association_name, options)          | .../gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:1267
 |  |    (association_name = :children; options = {:class_name=>"Person", :foreign_key=>"parent_id", :order=>"position", :dependent=>:destroy}; callbacks = nil)
 |  |    callbacks = %w(before_add after_add before_remove after_remove)  | ...ems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:1268
 |  |    callbacks.each do |callback_name|                              | .../gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:1269
 |  |    full_callback_name = "#{callback_name}_for_#{association_name}"  | ...ems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:1270
 |  |    defined_callbacks = options[callback_name.to_sym]              | .../gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:1271
 |  |    if options.has_key?(callback_name.to_sym)                      | .../gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:1272
 |  |    full_callback_name = "#{callback_name}_for_#{association_name}"  | ...ems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:1270
 |  |    defined_callbacks = options[callback_name.to_sym]              | .../gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:1271
 |  |    if options.has_key?(callback_name.to_sym)                      | .../gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:1272
 |  |    full_callback_name = "#{callback_name}_for_#{association_name}"  | ...ems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:1270
 |  |    defined_callbacks = options[callback_name.to_sym]              | .../gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:1271
 |  |    if options.has_key?(callback_name.to_sym)                      | .../gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:1272
 |  |    full_callback_name = "#{callback_name}_for_#{association_name}"  | ...ems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:1270
 |  |    defined_callbacks = options[callback_name.to_sym]              | .../gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:1271
 |  |    if options.has_key?(callback_name.to_sym)                      | .../gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:1272
...

 |    collection_accessor_methods(reflection, HasManyAssociation)       | ...y/gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:565
 |  + calling ActiveRecord::Associations::ClassMethods::collection_accessor_methods
 |  / def collection_accessor_methods(reflection, association_proxy_class)  | ...ms/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:944
 |  |    (reflection = #<ActiveRecord::Reflection::AssociationReflection:0xb78d405c @macro=:has_many, @name=:children, @options={:class_name=>"Person", :foreign_key=>"parent_id", :order=>"position", :dependent=>:destroy}, @active_record=Person, @primary_key_name="parent_id">; association_proxy_class = ActiveRecord::Associations::HasManyAssociation)
 |  |    collection_reader_method(reflection, association_proxy_class)  | ...y/gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:945
 
...
 \ def has_many(association_id, options = {}, &extension) (returning from ActiveRecord::Associations::ClassMethods::has_many)  | ...ssociations.rb:555
      def has_many(association_id, options = {}, &extension)
        reflection = create_has_many_reflection(association_id, options, &extension)

        configure_dependency_for_has_many(reflection)

        if options[:through]
          ...
        else
          add_multiple_associated_save_callbacks(reflection.name)
          add_association_callbacks(reflection.name, reflection.options)
          collection_accessor_methods(reflection, HasManyAssociation)
        end

        add_deprecated_api_for_has_many(reflection.name)
      end


        def create_has_many_reflection(association_id, options, &extension)
          ...
          create_reflection(:has_many, association_id, options, self)
        end


      def create_reflection(macro, name, options, active_record)
        case macro
          when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
            reflection = AssociationReflection.new(macro, name, options, active_record)
          when :composed_of
            ...
        end
        write_inheritable_hash :reflections, name => reflection
        reflection
      end

    class AssociationReflection < MacroReflection
      ...
    end
 
    class MacroReflection
      attr_reader :active_record

      def initialize(macro, name, options, active_record)
        @macro, @name, @options, @active_record = macro, name, options, active_record
      end

      ...
    end

        def configure_dependency_for_has_many(reflection)
          ...

          # See HasManyAssociation#delete_records.  Dependent associations
          # delete children, otherwise foreign key is set to NULL.

          ...

          case reflection.options[:dependent]
            when :destroy, true
              module_eval "before_destroy '#{reflection.name}.each { |o| o.destroy }'"
            ...
          end
        end



        def add_multiple_associated_save_callbacks(association_name)
          method_name = "validate_associated_records_for_#{association_name}".to_sym
          define_method(method_name) do
            association = instance_variable_get("@#{association_name}")
            if association.respond_to?(:loaded?)
              if new_record?
                association
              else
                association.select { |record| record.new_record? }
              end.each do |record|
                errors.add "#{association_name}" unless record.valid?
              end
            end
          end

          validate method_name
          before_save("@new_record_before_save = new_record?; true")

          after_callback = <<-end_eval
            association = instance_variable_get("@#{association_name}")

            if association.respond_to?(:loaded?)
              if @new_record_before_save
                records_to_save = association
              else
                records_to_save = association.select { |record| record.new_record? }
              end
              records_to_save.each { |record| association.send(:insert_record, record) }
              association.send(:construct_sql)   # reconstruct the SQL queries now that we know the owner's id
            end
          end_eval

          # Doesn't use after_save as that would save associations added in after_create/after_update twice
          after_create(after_callback)
          after_update(after_callback)
        end


        def add_association_callbacks(association_name, options)
          callbacks = %w(before_add after_add before_remove after_remove)
          callbacks.each do |callback_name|
            full_callback_name = "#{callback_name}_for_#{association_name}"
            defined_callbacks = options[callback_name.to_sym]
            if options.has_key?(callback_name.to_sym)
              class_inheritable_reader full_callback_name.to_sym
              write_inheritable_array(full_callback_name.to_sym, [defined_callbacks].flatten)
            end
          end
        end

        def collection_accessor_methods(reflection, association_proxy_class)
          collection_reader_method(reflection, association_proxy_class)

          define_method("#{reflection.name}=") do |new_value|
            # Loads proxy class instance (defined in collection_reader_method) if not already loaded
            association = send(reflection.name)
            association.replace(new_value)
            association
          end

          define_method("#{reflection.name.to_s.singularize}_ids") do
            send(reflection.name).map(&:id)
          end

          define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
            ids = (new_value || []).reject { |nid| nid.blank? }
            send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
          end
        end
Ads
Personal tools