Module: Card::Machine

Overview

What are Machines?

Machine and MachineInput together implement a kind of observer pattern. Machine processes a collection of input cards to generate an output card (a Set::Type::File card by default). If one of the input cards is changed the output card will be updated.

The classic example: A style card observes a collection of css and sccs card to generate a file card with a css file that contains the assembled compressed css.

Using Machines

Include the Machine module in the card set that is supposed to produce the output card. If the output card should be automatically updated when a input card is changed the input card has to be in a set that includes the MachineInput module.

The default machine:

  • uses its item cards as input cards or the card itself if there are no item cards;
  • can be changed by passing a block to collect_input_cards
  • takes the raw view of the input cards to generate the output;
  • can be changed by passing a block to machine_input (in the input card set)
  • stores the output as a .txt file in the '+machine output' card;
  • can be changed by passing a filetype and/or a block to store_machine_output

How does it work?

Machine cards have a '+machine input' and a '+machine output' card. The '+machine input' card is a pointer to all input cards. Including the MachineInput module creates an 'on: save' event that runs the machines of all cards that are linked to that card via the +machine input pointer.

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.define_machine_events(host_class) ⇒ Object



75
76
77
78
79
80
81
# File 'mod/machines/lib/card/machine.rb', line 75

def define_machine_events host_class
  event_suffix = host_class.name.tr ":", "_"
  event_name = "reset_machine_output_#{event_suffix}".to_sym
  host_class.event event_name, after: :expire_related, on: :save do
    reset_machine_output
  end
end

.define_machine_views(host_class) ⇒ Object



83
84
85
86
87
88
89
# File 'mod/machines/lib/card/machine.rb', line 83

def define_machine_views host_class
  host_class.format do
    view :machine_output_url do |_args|
      machine_output_url
    end
  end
end

.included(host_class) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'mod/machines/lib/card/machine.rb', line 60

def included host_class
  host_class.extend(ClassMethods)
  host_class.output_config = { filetype: "txt" }

  # for compatibility with old migrations
  return unless  Codename[:machine_output]

  host_class.card_accessor :machine_output, type: :file
  host_class.card_accessor :machine_input, type: :pointer

  set_default_machine_behaviour host_class
  define_machine_views host_class
  define_machine_events host_class
end

.set_default_input_collection_method(host_class) ⇒ Object



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'mod/machines/lib/card/machine.rb', line 118

def set_default_input_collection_method host_class
  host_class.collect_input_cards do
    # traverse through all levels of pointers and
    # collect all item cards as input
    items = [self]
    new_input = []
    already_extended = {} # avoid loops
    loop_limit = 5
    until items.empty?
      item = items.shift
      next if item.trash || already_extended[item.id].to_i > loop_limit
      if item.item_cards == [item] # no pointer card
        new_input << item
      else
        # item_cards instantiates non-existing cards
        # we don't want those
        items.insert(0, item.item_cards.reject(&:unknown?))
        items.flatten!

        new_input << item if item != self && item.known?
        already_extended[item] = already_extended[item].to_i + 1
      end
    end
    new_input
  end
end

.set_default_input_preparation_method(host_class) ⇒ Object



98
99
100
# File 'mod/machines/lib/card/machine.rb', line 98

def set_default_input_preparation_method host_class
  host_class.prepare_machine_input {}
end

.set_default_machine_behaviour(host_class) ⇒ Object



91
92
93
94
95
96
# File 'mod/machines/lib/card/machine.rb', line 91

def set_default_machine_behaviour host_class
  set_default_input_collection_method host_class
  set_default_input_preparation_method host_class
  set_default_output_storage_method host_class
  host_class.machine_engine { |input| input }
end

.set_default_output_storage_method(host_class) ⇒ Object



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'mod/machines/lib/card/machine.rb', line 102

def set_default_output_storage_method host_class
  host_class.store_machine_output do |output|
    filetype = host_class.output_config[:filetype]
    file = Tempfile.new [id.to_s, ".#{filetype}"]
    file.write output
    file.rewind
    Card::Auth.as_bot do
      p = machine_output_card
      p.file = file
      p.save!
    end
    file.close
    file.unlink
  end
end

Instance Method Details

#cache_output_part(input_card, output) ⇒ Object



176
177
178
179
180
181
# File 'mod/machines/lib/card/machine.rb', line 176

def cache_output_part input_card, output
  Auth.as_bot do
    cache_card = fetch_cache_card(input_card, true)
    cache_card.update_attributes! content: output
  end
end

#ensure_machine_outputObject



255
256
257
258
259
# File 'mod/machines/lib/card/machine.rb', line 255

def ensure_machine_output
  output = fetch trait: :machine_output
  return if output && output.selected_content_action_id
  update_machine_output
end

#fetch_cache_card(input_card, new = nil) ⇒ Object



171
172
173
174
# File 'mod/machines/lib/card/machine.rb', line 171

def fetch_cache_card input_card, new=nil
  new &&= { type_id: PlainTextID }
  Card.fetch input_card.name, name, :machine_cache, new: new
end

#input_item_cardsObject



240
241
242
# File 'mod/machines/lib/card/machine.rb', line 240

def input_item_cards
  machine_input_card.item_cards
end

#lockObject



203
204
205
206
207
208
209
210
211
212
# File 'mod/machines/lib/card/machine.rb', line 203

def lock
  if ok?(:read) && !(was_already_locked = locked?)
    Auth.as_bot do
      lock!
      yield
    end
  end
ensure
  unlock! unless was_already_locked
end

#lock!Object



222
223
224
# File 'mod/machines/lib/card/machine.rb', line 222

def lock!
  Card.cache.write lock_cache_key, true
end

#lock_cache_keyObject



214
215
216
# File 'mod/machines/lib/card/machine.rb', line 214

def lock_cache_key
  "UPDATE-LOCK:#{key}"
end

#locked?Boolean

Returns:

  • (Boolean)


218
219
220
# File 'mod/machines/lib/card/machine.rb', line 218

def locked?
  Card.cache.read lock_cache_key
end

#machine_output_pathObject



250
251
252
253
# File 'mod/machines/lib/card/machine.rb', line 250

def machine_output_path
  ensure_machine_output
  machine_output_card.file.path
end

#machine_output_urlObject



244
245
246
247
248
# File 'mod/machines/lib/card/machine.rb', line 244

def machine_output_url
  ensure_machine_output
  machine_output_card.file.url # (:default, timestamp: false)
  # to get rid of additional number in url
end

#regenerate_machine_outputObject



197
198
199
200
201
# File 'mod/machines/lib/card/machine.rb', line 197

def regenerate_machine_output
  lock do
    run_machine
  end
end

#reset_machine_outputObject



183
184
185
186
187
188
# File 'mod/machines/lib/card/machine.rb', line 183

def reset_machine_output
  Auth.as_bot do
    (moc = machine_output_card) && moc.real? && moc.delete!
    update_input_card
  end
end

#run_engine(input_card) ⇒ Object



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'mod/machines/lib/card/machine.rb', line 155

def run_engine input_card
  return if input_card.is_a? Card::Set::Type::Pointer
  if (cached = fetch_cache_card(input_card))
    return cached.content
  end

  input = if input_card.respond_to? :machine_input
            input_card.machine_input
          else
            input_card.format._render_raw
          end
  output = engine(input)
  cache_output_part input_card, output
  output
end

#run_machine(joint = "\n") ⇒ Object



146
147
148
149
150
151
152
153
# File 'mod/machines/lib/card/machine.rb', line 146

def run_machine joint="\n"
  before_engine
  output =
    input_item_cards.map do |input_card|
      run_engine input_card
    end.select(&:present?).join(joint)
  after_engine output
end

#unlock!Object



226
227
228
# File 'mod/machines/lib/card/machine.rb', line 226

def unlock!
  Card.cache.write lock_cache_key, false
end

#update_input_cardObject



230
231
232
233
234
235
236
237
238
# File 'mod/machines/lib/card/machine.rb', line 230

def update_input_card
  if DirectorRegister.running_act?
    input_card = attach_subcard! machine_input_card
    input_card.content = ""
    engine_input.each { |input| input_card << input }
  else
    machine_input_card.items = engine_input
  end
end

#update_machine_outputObject



190
191
192
193
194
195
# File 'mod/machines/lib/card/machine.rb', line 190

def update_machine_output
  lock do
    update_input_card
    run_machine
  end
end