alvinsj/flatten_record

View on GitHub
lib/flatten_record/flattener.rb

Summary

Maintainability
A
25 mins
Test Coverage
module FlattenRecord
  
  class Config
    cattr_accessor :included_models
  end

  module Flattener
    def self.included(base)
      base.extend ClassMethods

      Config.included_models ||= []
      Config.included_models << base.to_s

      base.class_eval do
        cattr_reader :root_node, :normal_model, :definition_hash
      end
    end

    module ClassMethods
      def denormalize(normal_model, definition_hash)
        self.class_variable_set("@@definition_hash", definition_hash)
        self.class_variable_set("@@normal_model", normal_model.to_s.camelize.constantize)
      end

      def flattener_meta
       
        node = self.class_variable_get(:@@root_node)
        if node.blank?
          definition = Definition.new(self.definition_hash)

          root_node = Meta::RootNode.new(self.normal_model, self)
          root_node.build(definition)
 
          node = self.class_variable_set("@@root_node", root_node)
        end
        node
      end

      def create_with(normal)
        if normal_model.eql?(normal.class)
          destroy_with(normal)
          records = flattener_meta.denormalize(normal.reload, self.new)
          records.respond_to?(:each) ? records.each(&:save) : records.save
          records
        else
          destroy_with(normal)
          find_normals(normal).each do |n|
            create_with(n)
          end
        end
      end
      alias_method :update_with, :create_with

      def destroy_with(normal)
        if normal_model.eql?(normal.class)
          records = find_with(normal)
          records.each(&:destroy)
        else
          # update associated model
          find_normals(normal).each do |n|
            update_with(n)
          end
        end
        records
      end

      def destroy_with_id(normal_id, normal_class=normal_model)
        if normal_model.eql?(normal_class)
          records = find_with_id(normal_id, normal_class)
          records.each(&:destroy)
        else
          # update associated model
          find_normals_with_id(normal_id, normal_class).each do |n|
            update_with(n)
          end
        end
        records
      end

      def find_normals(normal)
        return normal if normal_model.eql?(normal.class)
        
        records = find_with(normal)
        id_name = flattener_meta.id_column.name
        normal_id_name = flattener_meta.id_column.column.name
        
        ids = records.collect{|c| c.send(id_name.to_sym) }
        normal_model.where(normal_id_name => ids)
      end

      def find_normals_with_id(normal_id, normal_class=normal_model)
        return normal_class.find(normal_id) if normal_model.eql?(normal_class)
        
        records = find_with_id(normal_id, normal_class)
        id_name = flattener_meta.id_column.name
        normal_id_name = flattener_meta.id_column.column.name
        
        ids = records.collect{|c| c.send(id_name.to_sym) }
        normal_model.where(normal_id_name => ids)
      end

      def find_with(normal)
        node = find_node(:target_model, normal.class)
        
        raise "#{normal.class} was not defined in the denormalization" if node.nil?
        id_name = node.id_column.name 
        normal_id_name = node.id_column.column.name

        self.where(id_name => normal.send(normal_id_name))
      end

      def find_with_id(normal_id, normal_class=normal_model)
        node = find_node(:target_model, normal_class)
        
        raise "#{normal_class} was not defined in the denormalization" if node.nil?
        id_name = node.id_column.name 
        normal_id_name = node.id_column.column.name

        self.where(id_name => normal_id)
      end

      def find_node(type, value)
        flattener_meta.traverse_by(type, value)
      end
    end # /ClassMethods

  end
end