Class: Card::Director

Inherits:
Object
  • Object
show all
Extended by:
ActDirection, EventDelay
Includes:
Phases, Run, Stages, Store
Defined in:
lib/card/director.rb,
lib/card/director/run.rb,
lib/card/director/store.rb,
lib/card/director/phases.rb,
lib/card/director/stages.rb,
lib/card/director/event_delay.rb,
lib/card/director/card_methods.rb,
lib/card/director/act_direction.rb,
lib/card/director/subdirector_array.rb

Overview

Directs the symphony of a card act.

Each act is divided into actions: one action for each card. There are three action types: create, update, and delete.

Each action is divided into three phases: validation, storage, and integration.

Each phase is divided into three stages, as follows:

#### Validation Stages

  • __VI__: initialize

  • __VP__: prepare_to_validate

  • __VV__: validate

#### Storage Stages

  • __SP__: prepare_to_store

  • __SS__: store

  • __SF__: finalize

#### Integration Stages

  • __II__: integrate

  • __IA__: after_integrate

  • __ID__: integrate_with_delay

And each stage can have many events, each of which is defined using the Event API.

The table below gives you an overview events can/should do in each stage:

| Phase: | validation | storage | integration | Stage: | VI - VP - VV | SP - SS - SF | II - IA - ID |—————————| :—: | :—: |:—: | tasks | attach subcard | yes! yes! yes | yes yes yes | yes yes no | detach subcard | yes! yes! yes | yes no no! | no! | validate | yes yes yes! | no | no | insecure change [^1] | yes yes! no | no! | no! | secure change [^2] | yes | yes! no! no! | no! | abort | yes! | yes | yes | add errors | yes! | no! | no! | subsave | yes | yes | yes! | has id (new card) | no | no no? yes | yes | within web request | yes | yes | yes yes no | within transaction [^3] | yes | yes | no | values | dirty attributes | yes | yes | yes | params | yes | yes | yes | success | yes | yes | yes | session | yes | yes | yes yes no

#### Understanding the Table

- **yes!**  the recommended stage to do that
- **yes**   ok to do it here
- **no**    not recommended; risky but not guaranteed to fail
- **no!**   never do it here. it won't work or will break things

If there is only a single entry in a phase column it counts for all stages of that phase

[^1]: ‘insecure’ means a change that might make the card invalid to save [^2]: ‘secure’ means you’re sure that the change won’t invalidate the card [^3]: If an exception is raised in the validation or storage phase

everything will rollback. If an integration event fails, db changes
of the other two phases will remain persistent, and other integration
events will continue to run.

## Director, Directors, and Subdirectors

Only one act can be performed at a time in any given Card process. Information about that act is managed by _Director class methods_. Every act is associated with a single “main” card.

The act, however, may involve many cards/actions. Each action has its own _Director instance_ that leads the card through all its stages. When a card action (A1) initiates a new action on a different card (A2), a new Director object is initialized. The new A2 subdirector’s @parent is the director of the A1 card. Conversely, the A1 card stores a SubdirectorArray in @subdirectors to give it access to A2’s Director and any little Director babies to which it gave birth.

Subdirectors follow one of two distinct patterns:

  1. Subcards. When a card is altered using the subcards API, the director follows a “breadth-first” pattern. For each stage a card runs its stage events and then triggers its subcards to run that stage before proceeding to the next stage. If a subcard is added in a stage then by the end of that stage the director will catch it up to the current stage.

  2. Subsaves. When a card is altered by a direct save (‘Card.create(!)`, `card.update(!)`, `card.delete(!)`, `card.save(!)`…), then the validation and storage phases are executed immediately (depth-first), returning the saved card. The integration phase, however, is executed following the same pattern as with subcards.

Let’s consider a subcard example. Suppose you define the following event on self/bar.rb

event :met_a_foo_at_the_bar, :prepare_to_store, on: :update do
  add_subcard "foo"
end

And then you run ‘Card.update!({})`.

When bar reaches the event in its ‘prepare_to_store` stage, the “foo” subcard will be added. After that stage ends, the stages `initialize`, `prepare_to_validate`, `validate`, and `prepare_to_store` are executed for foo so that it is now caught up with Bar at the `prepare_to_store` stage.

If you have subcards within subcards, stages are executed preorder depth-first.

Eg, assuming:

  • A has subcards AA and AB

  • AA has subcard AAA

  • AB has subcard ABA

…then the order of execution is:

  1. A

  2. AA

  3. AAA

  4. AB

  5. ABA

A special case can happen in the store stage when a supercard needs a subcard’s id (for example as left_id or as type_id) and the subcard doesn’t have an id yet (because it gets created in the same act). In this case the subcard’s store stage is executed BEFORE the supercard’s store stage.


Defined Under Namespace

Modules: ActDirection, CardMethods, EventDelay, Phases, Run, Stages, Store Classes: SubdirectorArray

Constant Summary

Constants included from Stages

Stages::STAGES, Stages::STAGE_INDEX

Instance Attribute Summary collapse

Attributes included from ActDirection

#act_card

Instance Method Summary collapse

Methods included from EventDelay

contextualize_delayed_event, delaying?, run_job_with_act, with_delay_act, with_env_and_auth

Methods included from ActDirection

act_director, add, card_changed, clear, deep_delete, directors, expire, expirees, fetch, include?, new_director, run_act, running_act?

Methods included from Store

#after_store

Methods included from Run

#catch_up_to_stage, #delay!, #restart, #run_delayed_event

Methods included from Phases

#integration_phase, #integration_phase_callback?, #prepare_for_phases, #storage_phase, #validation_phase, #validation_phase_callback?

Methods included from Stages

#finished_stage?, #reset_stage, #stage_index, #stage_ok?, #stage_symbol

Constructor Details

#initialize(card, parent) ⇒ Director

Returns a new instance of Director.



148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/card/director.rb', line 148

def initialize card, parent
  @card = card
  @card.director = self
  # for read actions there is no validation phase
  # so we have to set the action here
  @stage = nil
  @running = false
  @prepared = false
  @parent = parent
  @subdirectors = SubdirectorArray.initialize_with_subcards(self)
  register
end

Instance Attribute Details

#actObject

Returns the value of attribute act.



144
145
146
# File 'lib/card/director.rb', line 144

def act
  @act
end

#cardObject

Returns the value of attribute card.



144
145
146
# File 'lib/card/director.rb', line 144

def card
  @card
end

#headObject

Returns the value of attribute head.



144
145
146
# File 'lib/card/director.rb', line 144

def head
  @head
end

#parentObject

Returns the value of attribute parent.



144
145
146
# File 'lib/card/director.rb', line 144

def parent
  @parent
end

#runningObject (readonly) Also known as: running?

Returns the value of attribute running.



145
146
147
# File 'lib/card/director.rb', line 145

def running
  @running
end

#stageObject

Returns the value of attribute stage.



144
145
146
# File 'lib/card/director.rb', line 144

def stage
  @stage
end

#subdirectorsObject

Returns the value of attribute subdirectors.



144
145
146
# File 'lib/card/director.rb', line 144

def subdirectors
  @subdirectors
end

Instance Method Details

#abortObject



191
192
193
# File 'lib/card/director.rb', line 191

def abort
  @abort = true
end

#appoint(card) ⇒ Object



185
186
187
188
189
# File 'lib/card/director.rb', line 185

def appoint card
  reset_stage
  update_card card
  @head = true
end

#deleteObject



177
178
179
180
181
182
183
# File 'lib/card/director.rb', line 177

def delete
  @parent&.subdirectors&.delete self
  @card.director = nil
  @subdirectors.clear
  @stage = nil
  @action = nil
end

#head?Boolean

Returns:

  • (Boolean)


165
166
167
# File 'lib/card/director.rb', line 165

def head?
  @head || main?
end

#main?Boolean

Returns:

  • (Boolean)


161
162
163
# File 'lib/card/director.rb', line 161

def main?
  parent.nil?
end

#main_directorObject



202
203
204
205
206
# File 'lib/card/director.rb', line 202

def main_director
  return self if main?

  Director.act_director || (@parent&.main_director)
end

#need_actObject

Raises:



195
196
197
198
199
200
# File 'lib/card/director.rb', line 195

def need_act
  act_director = main_director
  raise Card::Error, "act requested without a main director" unless act_director

  @act = act_director.act ||= Director.need_act
end

#registerObject



169
170
171
# File 'lib/card/director.rb', line 169

def register
  Director.add self
end

#replace_card(card) ⇒ Object



217
218
219
220
221
222
223
# File 'lib/card/director.rb', line 217

def replace_card card
  card.action = @card.action
  card.director = self
  @card = card
  reset_stage
  catch_up_to_stage @stage if @stage
end

#to_s(level = 1) ⇒ Object



208
209
210
211
212
213
214
215
# File 'lib/card/director.rb', line 208

def to_s level=1
  str = @card.name.to_s.clone
  if @subdirectors.present?
    subs = subdirectors.map { |d| "  " * level + d.to_s(level + 1) }.join "\n"
    str << "\n#{subs}"
  end
  str
end

#unregisterObject



173
174
175
# File 'lib/card/director.rb', line 173

def unregister
  Director.delete self
end

#update_card(card) ⇒ Object



225
226
227
228
229
# File 'lib/card/director.rb', line 225

def update_card card
  old_card = @card
  @card = card
  Director.card_changed old_card
end