Class: Card::Director

Inherits:
Object show all
Extended by:
ClassMethods
Includes:
Phases, Run, Stages, Store
Defined in:
card/lib/card/director.rb,
card/lib/card/director/all.rb,
card/lib/card/director/run.rb,
card/lib/card/director/store.rb,
card/lib/card/director/phases.rb,
card/lib/card/director/stages.rb,
card/lib/card/director/card_class.rb,
card/lib/card/director/event_delay.rb,
card/lib/card/director/class_methods.rb,
card/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

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
  subcard "foo"
end

And then you run Card[:bar].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.


  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. 

Defined Under Namespace

Modules: All, CardClass, ClassMethods, EventDelay, Phases, Run, Stages, Store Classes: SubdirectorArray

Constant Summary

Constants included from Stages

Stages::INDECES, Stages::SYMBOLS

Instance Attribute Summary collapse

Attributes included from ClassMethods

#act_card

Instance Method Summary collapse

Methods included from ClassMethods

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

Methods included from EventDelay

#contextualize_delayed_event, #delaying?, #run_job_with_act, #with_delay_act, #with_env_and_auth

Methods included from Store

#after_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.



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

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
  @current_stage_index = 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.



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

def act
  @act
end

#cardObject

Returns the value of attribute card.



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

def card
  @card
end

#current_stage_indexObject

Returns the value of attribute current_stage_index.



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

def current_stage_index
  @current_stage_index
end

#headObject

Returns the value of attribute head.



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

def head
  @head
end

#parentObject

Returns the value of attribute parent.



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

def parent
  @parent
end

#runningObject (readonly) Also known as: running?

Returns the value of attribute running.



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

def running
  @running
end

#subdirectorsObject

Returns the value of attribute subdirectors.



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

def subdirectors
  @subdirectors
end

Instance Method Details

#abortObject



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

def abort
  @abort = true
end

#appoint(card) ⇒ Object



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

def appoint card
  reset_stage
  update_card card
  @head = true
end

#deleteObject



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

def delete
  @parent&.subdirectors&.delete self
  @card.director = nil
  @subdirectors.clear
  @current_stage_index = nil
  @action = nil
  @running = false
end

#head?Boolean

Returns:

  • (Boolean)


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

def head?
  @head || main?
end

#main?Boolean

Returns:

  • (Boolean)


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

def main?
  parent.nil?
end

#main_directorObject



202
203
204
205
206
# File 'card/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 'card/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



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

def register
  Director.add self
end

#replace_card(card) ⇒ Object



217
218
219
220
221
222
223
# File 'card/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 @current_stage_index if @current_stage_index
end

#to_s(level = 1) ⇒ Object



208
209
210
211
212
213
214
215
# File 'card/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



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

def unregister
  Director.delete self
end

#update_card(card) ⇒ Object



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

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