klobuczek/active_node

View on GitHub
lib/active_node/associations/association.rb

Summary

Maintainability
A
45 mins
Test Coverage
require 'active_support/core_ext/array/wrap'

module ActiveNode
  module Associations
    # = Active Record Associations
    #
    # This is the root class of all associations ('+ Foo' signifies an included module Foo):
    #
    #   Association
    #     SingularAssociation
    #       HasOneAssociation
    #         HasOneThroughAssociation + ThroughAssociation
    #       BelongsToAssociation
    #         BelongsToPolymorphicAssociation
    #     CollectionAssociation
    #       HasAndBelongsToManyAssociation
    #       HasManyAssociation
    #         HasManyThroughAssociation + ThroughAssociation
    class Association #:nodoc:
      attr_reader :owner, :target, :rel_target, :reflection

      delegate :options, :to => :reflection

      def initialize(owner, reflection)
        @owner, @reflection = owner, reflection

        reset
      end

      # Resets the \loaded flag to +false+ and sets the \target to +nil+.
      def reset
        @loaded = false
        @target = nil
        @stale_state = nil
      end

      # Returns the class of the target. belongs_to polymorphic overrides this to look at the
      # polymorphic_type field on the owner.
      def klass
        reflection.klass
      end

      def rel(*associations)
        @loaded = true
        owner.includes!(reflection.name => associations)
      end

      # Implements the writer method, e.g. foo.items= for Foo.has_many :items
      def writer(records)
        records = load_ids(records)
        validate_type(records)
        @dirty = true
        @rel_target = nil
        @target = records
      end

      def load_ids(records)
        records.map {|r| ActiveNode::Base === r ? r : reflection.klass.find(r)}
      end

      def validate_type(records)
        unless records.all? { |r| r.is_a?(reflection.klass) }
          raise ArgumentError, "#{reflection.name} can only accept object(s) of class #{reflection.klass}"
        end
      end

      alias :super_writer :writer

      # Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
      def ids_writer(ids)
        @rel_target = nil
        super_writer klass.find(ids.reject(&:blank?))
      end

      def reader(*args)
        @target ||= rels_reader(*args).map &:other
      end

      # Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
      def ids_reader
        reader
        @target.map(&:id)
      end


      def rels_reader(*args)
        rel(*args) unless @loaded
        @rel_target ||= []
      end

      def rels_writer(rels)
        @dirty = true
        rels_loader(rels)
      end

      def rels_loader(rels)
        @target = nil
        @rel_target = rels
        @loaded = true
      end

      def save(fresh=false)
        #return unless @dirty
        #delete all relations missing in new target
        original_rels = fresh ? [] : owner.class.includes(reflection.name).build(owner.id).first.association(reflection.name).rels_reader
        original_rels.each do |r|
          unless ids_reader.include? r.other.id
            Neo.db.delete_relationship(r.id)
            original_rels.delete(r)
          end
        end

        #add relations missing in old target
        #if no rel_target proceed as before + set rel_target from db
        #if rel_target exists update persisted records and insert new records
        if @rel_target
          @rel_target.each { |r| r.save(self) }
        else
          @target.map do |n|
            original_rels.detect { |r| r.other.id == n.id }.tap { |o_r| o_r.try :other=, n } ||
                ActiveNode::Relationship.create!(n, self)
          end
        end
      end
    end
  end
end