Sandthorn/sandthorn

View on GitHub
lib/sandthorn/aggregate_root_base.rb

Summary

Maintainability
A
25 mins
Test Coverage
module Sandthorn
  module AggregateRoot
    module Base

      attr_reader :aggregate_id
      attr_reader :aggregate_events
      attr_reader :aggregate_current_event_version
      attr_reader :aggregate_originating_version
      attr_reader :aggregate_trace_information

      alias :id :aggregate_id
      alias :aggregate_version :aggregate_current_event_version


      def aggregate_base_initialize
        @aggregate_current_event_version = 0
        @aggregate_originating_version = 0
        @aggregate_events = []
      end

      def save
        if aggregate_events.any?
          Sandthorn.save_events(
            aggregate_events,
            aggregate_id,
            self.class
          )
          @aggregate_events = []
          @aggregate_originating_version = @aggregate_current_event_version
        end

        Sandthorn.save_snapshot self if self.class.snapshot

        self
      end

      def ==(other)
        other.respond_to?(:aggregate_id) && aggregate_id == other.aggregate_id
      end

      def unsaved_events?
        aggregate_events.any?
      end

      def aggregate_trace args
        @aggregate_trace_information = args
        yield self if block_given?
        @aggregate_trace_information = nil
      end

      def commit
        event_name = caller_locations(1,1)[0].label.gsub(/block ?(.*) in /, "")
        commit_with_event_name(event_name)
      end

      def default_attributes
        #NOOP
      end

      alias :record_event :commit

      module ClassMethods

        @@aggregate_trace_information = nil
        def aggregate_trace args
          @@aggregate_trace_information = args
          yield self
          @@aggregate_trace_information = nil
        end

        def event_store(event_store = nil)
          if event_store
            @event_store = event_store
          else
            @event_store
          end
        end

        def snapshot(value = nil)
          if value
            @snapshot = value
          else
            @snapshot
          end
        end

        def all
          Sandthorn.all(self).map { |events|
            aggregate_build events, nil
          }
        end

        def find id
          return aggregate_find id unless id.respond_to?(:each)
          return id.map { |e| aggregate_find e }
        end

        def aggregate_find aggregate_id
          begin
            aggregate_from_snapshot = Sandthorn.find_snapshot(aggregate_id) if self.snapshot
            current_aggregate_version = aggregate_from_snapshot.nil? ? 0 : aggregate_from_snapshot.aggregate_current_event_version
            events = Sandthorn.find(aggregate_id, self, current_aggregate_version)
            if aggregate_from_snapshot.nil? && events.empty?
              raise Errors::AggregateNotFound
            end

            return aggregate_build events, aggregate_from_snapshot
          rescue Exception
            raise Errors::AggregateNotFound
          end
            
        end

        def new *args, &block
          aggregate = create_new_empty_aggregate()
          aggregate.aggregate_base_initialize
          aggregate.aggregate_initialize

          aggregate.default_attributes
          aggregate.send :initialize, *args, &block
          aggregate.send :set_aggregate_id, Sandthorn.generate_aggregate_id

          aggregate.aggregate_trace @@aggregate_trace_information do |aggr|
            aggr.send :commit
            return aggr
          end

        end

        def aggregate_build events, aggregate_from_snapshot = nil
          aggregate = aggregate_from_snapshot || create_new_empty_aggregate

          if events.any?
            current_aggregate_version = events.last[:aggregate_version]
            aggregate.send :set_orginating_aggregate_version!, current_aggregate_version
            aggregate.send :set_current_aggregate_version!, current_aggregate_version
            aggregate.send :set_aggregate_id, events.first.fetch(:aggregate_id)
          end
          attributes = build_instance_vars_from_events events
          aggregate.send :clear_aggregate_events

          aggregate.default_attributes
          aggregate.send :aggregate_initialize

          aggregate.send :set_instance_variables!, attributes
          aggregate
        end

        def stateless_events(*event_names)
          event_names.each do |name|
            define_singleton_method name do |aggregate_id, *args|
              event = build_stateless_event(aggregate_id, name.to_s, args)
              Sandthorn.save_events([event], aggregate_id, self)
              return aggregate_id
            end
          end
        end

        def constructor_events(*event_names)
          event_names.each do |name|
            define_singleton_method name do |*args, &block|

              create_new_empty_aggregate.tap  do |aggregate|
                aggregate.aggregate_base_initialize
                aggregate.aggregate_initialize
                aggregate.send :set_aggregate_id, Sandthorn.generate_aggregate_id
                aggregate.instance_eval(&block) if block
                aggregate.send :commit_with_event_name, name.to_s
                return aggregate
              end

            end
            self.singleton_class.class_eval { private name.to_s }
          end
        end

        def events(*event_names)
          event_names.each do |name|
            define_method(name) do |*args, &block|
              block.call() if block
              commit_with_event_name(name.to_s)
            end
            private name.to_s
          end
        end

        private

        def build_stateless_event aggregate_id, name, args = []

          deltas = {}
          args.first.each do |key, value|
            deltas[key.to_sym] = { old_value: nil, new_value: value }
          end unless args.empty?

          return {
            aggregate_version: nil,
            aggregate_id: aggregate_id,
            event_name: name,
            event_data: deltas,
            event_metadata: nil
          }

        end

        def build_instance_vars_from_events events
          events.each_with_object({}) do |event, instance_vars|
            attribute_deltas = event[:event_data]
            unless attribute_deltas.nil?
              deltas = {}
              attribute_deltas.each do |key, value|
                deltas[key] = value[:new_value]
              end
              instance_vars.merge! deltas
            end
          end
        end

        def create_new_empty_aggregate
          allocate
        end
      end

      private

      def set_instance_variables! attributes
        attributes.each_pair do |k,v|
          self.instance_variable_set "@#{k}", v
        end
      end

      def extract_relevant_aggregate_instance_variables
        instance_variables.select do |variable|
           !variable.to_s.start_with?("@aggregate_")
        end
      end

      def set_orginating_aggregate_version! aggregate_version
        @aggregate_originating_version = aggregate_version
      end

      def increase_current_aggregate_version!
        @aggregate_current_event_version += 1
      end

      def set_current_aggregate_version! aggregate_version
        @aggregate_current_event_version = aggregate_version
      end

      def clear_aggregate_events
        @aggregate_events = []
      end

      def aggregate_clear_current_event_version!
        @aggregate_current_event_version = 0
      end

      def set_aggregate_id aggregate_id
        @aggregate_id = aggregate_id
      end

      def commit_with_event_name(event_name)
        increase_current_aggregate_version!

        @aggregate_events << ({
          aggregate_version: @aggregate_current_event_version,
          aggregate_id: @aggregate_id,
          event_name: event_name,
          event_data: get_delta(),
          event_metadata: @aggregate_trace_information
        })

        self
      end

    end
  end
end