Class: Card::Director

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

def act
  @act
end

#cardObject

Returns the value of attribute card.



143
144
145
# File '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 '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 'lib/card/director.rb', line 143

def head
  @head
end

#parentObject

Returns the value of attribute parent.



143
144
145
# File '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 'lib/card/director.rb', line 144

def running
  @running
end

#subdirectorsObject

Returns the value of attribute subdirectors.



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

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



176
177
178
179
180
181
182
183
# File '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 'lib/card/director.rb', line 164

def head?
  @head || main?
end

#main?Boolean

Returns:

  • (Boolean)


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

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



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

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 @current_stage_index if @current_stage_index
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



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

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