nkeyes/philotic

View on GitHub
lib/philotic/message.rb

Summary

Maintainability
A
2 hrs
Test Coverage
require 'philotic/constants'
require 'philotic/singleton'
require 'active_support/all'

module Philotic
  class Message
    NotInstantiableError = Class.new(RuntimeError)

    attr_accessor :connection, :publish_error, :delivery_info
    attr_writer :published

    class << self
      def attr_routable_accessors
        @attr_routable_accessors ||= Set.new
      end

      def attr_payload_accessors
        @attr_payload_accessors ||= Set.new
      end

      def attr_routable(*names)
        attr_routable_accessors.merge(names)
        attr_accessor(*names)
      end

      def attr_payload(*names)
        attr_payload_accessors.merge(names)
        attr_accessor(*names)
      end
    end

    def initialize(routables={}, payloads={}, connection=nil)
      raise NotInstantiableError if self.class == Philotic::Message
      self.timestamp               = Time.now.to_i
      self.philotic_firehose       = true
      self.connection              = connection
      self.philotic_serializations = self.connection.config.serializations

      # dynamically insert any passed in routables into both attr_routable
      # and attr_payload
      # result:  ability to arbitrarily send a easily routable hash
      # over into the bus
      _set_routables_or_payloads(:routable, routables)
      _set_routables_or_payloads(:payload, payloads)

      @published = false
    end

    def self.inherited(sub_class)
      sub_class.attr_routable(*Philotic::PHILOTIC_HEADERS)
      sub_class.attr_routable(*self.attr_routable_accessors.dup)
      sub_class.attr_payload(*self.attr_payload_accessors.dup)
    end

    self.inherited(self)

    Philotic::MESSAGE_OPTIONS.each do |message_option|
      attr_reader message_option
      define_method :"#{message_option}=" do |val|
        instance_variable_set(:"@#{message_option}", val)
        self.metadata[message_option] = val
      end
    end

    def connection
      @connection ||= Philotic.connection
    end

    def published?
      !!@published
    end

    def publish
      connection.publish self
    end

    def self.publish(*args)
      message_class = self == Philotic::Message ? Class.new(self) : self
      message_class.new(*args).publish
    end

    def delivery_tag
      delivery_info && delivery_info.delivery_tag
    end

    def payload
      _payload_or_headers(:payload)
    end

    def headers
      _payload_or_headers(:routable)
    end

    def attributes
      payload.merge headers
    end

    def metadata
      @metadata ||= {}
    end

    def metadata=(options)
      @metadata ||= {}
      @metadata.merge! options
    end

    private

    def _is_anonymous_message?
      self.is_a?(Philotic::Message) && self.class.name.nil?
    end

    def _payload_or_headers(payload_or_headers)
      attribute_hash = {}
      self.class.send("attr_#{payload_or_headers}_accessors").each do |attr|
        attr                 = attr.to_sym
        attribute_hash[attr] = send(attr)
      end
      attribute_hash
    end

    def _set_routables_or_payloads(type, attrs)
      attrs.each do |key, value|
        key = _normalize_attr_name(key)
        if self.respond_to?(:"#{key}=")
          send(:"#{key}=", value)
        elsif _is_anonymous_message?
          _set_message_attribute(type, key, value)
        end
      end
    end

    def _set_message_attribute(type, key, value)
      self.class.send("attr_#{type}_accessors").merge([key])
      _set_message_attribute_accessor(key, value)
    end

    def _set_message_attribute_accessor(attr, value)
      attr = _normalize_attr_name(attr)
      _set_message_attribute_getter(attr)
      _set_message_attribute_setter(attr)
      self.send(:"#{attr}=", value)
    end

    def _set_message_attribute_getter(attr)
      attr = _normalize_attr_name(attr)
      self.define_singleton_method :"#{attr}" do
        instance_variable_get(:"@#{attr}")
      end
    end

    def _set_message_attribute_setter(attr)
      attr = _normalize_attr_name(attr)
      self.define_singleton_method :"#{attr}=" do |v|
        instance_variable_set(:"@#{attr}", v)
      end
    end

    def _normalize_attr_name(attr)
      %i[_underscore_attr_name].reduce(attr) do |attr, normalizer|
        send(normalizer, attr)
      end.to_sym
    end
    def _underscore_attr_name(attr)
      attr.to_s.tr('-', '_')

    end
  end
end