lnagel/event-sourced-accounting

View on GitHub
lib/esa/filters/accountable_filter.rb

Summary

Maintainability
A
3 hrs
Test Coverage
module ESA
  module Filters
    module AccountableFilter
      def self.make_union_query(definitions = {})
        fragments = definitions.map do |type,accountable|
          make_fragments(type, accountable)
        end.flatten

        if fragments.count > 0
          fragments.join(' UNION ')
        else
          "SELECT -1 AS id, 'Nothing' AS type"
        end
      end

      def self.make_fragments(type, accountable)
        if accountable.is_a? ActiveRecord::Relation
          [accountable.select("`#{accountable.table_name}`.`#{accountable.primary_key}` AS id, '#{type}' AS type").to_sql.squish]
        elsif accountable.is_a? ActiveRecord::Base
          ["SELECT #{accountable.id} AS id, '#{type}' AS type"]
        elsif accountable.is_a? Integer
          ["SELECT #{accountable} AS id, '#{type}' AS type"]
        elsif accountable.respond_to? :each
          accountable.map{|a| make_fragments(type, a)}.flatten
        else
          []
        end
      end

      module TransactionAccountable
        extend ActiveSupport::Concern

        included do
          scope :with_accountable, lambda { |accountable,type|
            joins(:transaction).where(esa_transactions: {accountable_id: accountable, accountable_type: type})
          }
          scope :excl_accountable, lambda { |accountable,type|
            joins(:transaction).where(ESA::Transaction.arel_table[:accountable_id].not_eq(accountable), ESA::Transaction.arel_table[:accountable_type].not_eq(type))
          }

          scope :with_accountable_def, lambda { |definitions| joins(:transaction).joins("INNER JOIN (#{ESA::Filters::AccountableFilter.make_union_query(definitions)}) `accountables-#{(hash ^ definitions.hash).to_s(36)}` ON `esa_transactions`.`accountable_id` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`id` AND `esa_transactions`.`accountable_type` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`type`") }
          scope :excl_accountable_def, lambda { |definitions| joins(:transaction).joins("LEFT OUTER JOIN (#{ESA::Filters::AccountableFilter.make_union_query(definitions)}) `accountables-#{(hash ^ definitions.hash).to_s(36)}` ON `esa_transactions`.`accountable_id` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`id` AND `esa_transactions`.`accountable_type` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`type`").where("`accountables-#{(hash ^ definitions.hash).to_s(36)}`.`id` IS NULL") }
        end
      end

      module ObjectAccountable
        extend ActiveSupport::Concern

        included do
          scope :with_accountable, lambda { |accountable,type|
            where(accountable_id: accountable, accountable_type: type)
          }
          scope :excl_accountable, lambda { |accountable,type|
            where(arel_table[:accountable_id].not_eq(accountable), arel_table[:accountable_type].not_eq(type))
          }

          scope :with_accountable_def, lambda { |definitions| joins("INNER JOIN (#{ESA::Filters::AccountableFilter.make_union_query(definitions)}) `accountables-#{(hash ^ definitions.hash).to_s(36)}` ON `#{table_name}`.`accountable_id` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`id` AND `#{table_name}`.`accountable_type` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`type`") }
          scope :excl_accountable_def, lambda { |definitions| joins("LEFT OUTER JOIN (#{ESA::Filters::AccountableFilter.make_union_query(definitions)}) `accountables-#{(hash ^ definitions.hash).to_s(36)}` ON `#{table_name}`.`accountable_id` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`id` AND `#{table_name}`.`accountable_type` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`type`").where("`accountables-#{(hash ^ definitions.hash).to_s(36)}`.`id` IS NULL") }
        end
      end
    end
  end
end

ESA::Amount.send :include, ESA::Filters::AccountableFilter::TransactionAccountable
ESA::Event.send :include, ESA::Filters::AccountableFilter::ObjectAccountable
ESA::Flag.send :include, ESA::Filters::AccountableFilter::ObjectAccountable
ESA::Transaction.send :include, ESA::Filters::AccountableFilter::ObjectAccountable