decko-commons/decko

View on GitHub
card/lib/card/set/event.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
class Card
  module Set
    # Supports the definition of events via the {Api Events API}
    class Event
      # Events are the building blocks of the three transformative card actions: _create_,
      # _update_, and _delete_.
      #
      # (The fourth kind of action, _read_, does not transform cards, and is associated
      # with {Card::Format views}, not events).
      #
      # As described in detail in {Card::Director}, each act can have many actions, each
      # action has three phases, each phase has three stages, and each stage has many
      # events.
      #
      # Events are defined in set modules in {Cardio::Mod **mods**}. Learn more about
      # {Cardio::Mod set modules}.
      #
      # A simple event definition looks something like this:
      #
      #     event :append_you_know, :prepare_to_validate, on: :create do
      #       self.content = content + ", you know?"
      #     end
      #
      # Note:
      #
      # - `:append_you_know` is a unique event name.
      # - `:prepare_to_validate` is a {Card::Director stage}.
      # - `on: :create` specifies the action to which the event applies
      # - `self`, within the event card, is a card object.
      #
      # Any card within the {Card::Set set} on which this event is defined will
      # run this event during the `prepare_to_validate` stage when it is created.
      #
      # Events should not be defined within format blocks.
      module Api
        OPTIONS = {
          on: %i[create update delete save read],
          changed: Card::Dirty.dirty_options,
          changing: Card::Dirty.dirty_options,
          skip: [:allowed],
          trigger: [:required],
          when: nil
        }.freeze

        # Define an event for a set of cards.
        #
        # @param event [Symbol] unique event name
        # @param stage_or_opts [Symbol, Hash] if a Symbol, defines event's
        #   {Card::Director stage}. If a Hash, it's treated as the opts param.
        # @param opts [Hash] event options
        # @option opts [Symbol, Array] :on one or more actions in which the event
        #   should be executed. :save is shorthand for [:create, :update]. If no value
        #   is specified, event will fire on create, update, and delete.
        # @option opts [Symbol, Array] :changed fire event only if field has changed.
        #   valid values: name, content, db_content, type, type_id, left_id, right_id,
        #   codename, trash.
        # @option opts [Symbol, Array] :changing alias for :changed
        # @option opts [Symbol] :skip allow actions to skip this event.
        #   (eg. `skip: :allowed`)
        # @option opts [Symbol] :trigger allow actions to trigger this event
        #   explicitly. If `trigger: :required`, then event will not run unless explicitly
        #   triggered.
        # @option opts [Symbol, Proc] :when method (Symbol) or code (Proc) to execute
        #   to determine whether to fire event. Proc is given card as argument.
        # @option opts [Symbol] :before fire this event before the event specified.
        # @option opts [Symbol] :after fire this event after the event specified.
        # @option opts [Symbol] :around fire this event before the event specified. This
        #   event will receive a block and will need to call it for the specified
        #   event to fire.
        # @option opts [Symbol] :stage alternate representation for specifying stage
        # @option opts [True/False] :after_subcards run event after running subcard events
        def event event, stage_or_opts={}, opts={}, &final
          Event.new(event, self).register stage_or_opts, opts, &final
        end
      end

      CONDITIONS = ::Set.new(Api::OPTIONS.keys).freeze

      include DelayedEvent
      include Options
      include Callbacks

      attr_reader :set_module, :opts

      def initialize event, set_module
        @event = event
        @set_module = set_module
      end

      def register stage_or_opts, opts, &final
        @opts = event_opts stage_or_opts, opts
        @event_block = final
        validate_conditions
        define
      end

      def define
        Card.define_callbacks @event
        define_event
        set_event_callbacks
      end

      # @return the name of the event
      def name
        @event
      end

      def block
        @event_block
      end

      # the name for the method that only executes the code
      # defined in the event
      def simple_method_name
        "#{@event}_without_callbacks"
      end

      # the name for the method that adds the event to
      # the delayed job queue
      def delaying_method_name
        "#{@event}_with_delay"
      end

      private

      # EVENT DEFINITION

      def define_event
        define_simple_method
        define_event_method
      end

      def define_simple_method
        @set_module.class_exec(self) do |event|
          define_method event.simple_method_name, &event.block
        end
      end

      def define_event_method
        send "define_#{event_type}_event_method"
      end

      def event_type
        with_delay?(@opts) ? :delayed : :standard
      end

      def define_standard_event_method method_name=simple_method_name
        is_integration = @stage.to_s.match?(/integrate/)
        @set_module.class_exec(@event) do |event_name|
          define_method event_name do
            rescuing_if_integration is_integration do
              log_event_call event_name
              run_callbacks event_name do
                send method_name
              end
            end
          end
        end
      end
    end
  end

  def rescuing_if_integration is_integration, &block
    is_integration ? rescuing_integration(&block) : yield
  end

  # one failed integration event should not harm others.
  def rescuing_integration
    yield
  rescue StandardError => e
    # puts "integration error: #{e.message}".red
    Card::Error.report e, self
  ensure
    true
  end

  def log_event_call event
    Rails.logger.debug "#{name}: #{event}"
    # puts "#{name}: #{event}"
    # puts "#{Card::Director.to_s}".green
  end
end