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