Class: OperationType

Inherits:
ActiveRecord::Base
  • Object
show all
Includes:
CodeHelper, DataAssociator, FieldTyper, HasTiming, OperationTypeExport, OperationTypePlanner, OperationTypeRandom, OperationTypeWorkflow
Defined in:
app/models/operation_type.rb

Overview

Defines a type of lab procedure, with the input-types, output-types, and the instructions for converting inputs into outputs. Executable unit Operations can be instantiated from an OperationType, and specific inputs and outputs are then given.

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DataAssociator

#append_notes, #associate, #associations, #data_associations, #get, #get_association, #lazy_associate, #modify, #notes, #notes=, #upload

Class Method Details

.numbers(user = nil) ⇒ Object



343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
# File 'app/models/operation_type.rb', line 343

def self.numbers(user = nil)
  q = if user.nil?
        "
          SELECT   status, operation_type_id, COUNT(status)
          FROM     operations
          GROUP BY operation_type_id, status
        "
      else
        "
          SELECT   status, operation_type_id, COUNT(status)
          FROM     operations
          WHERE    user_id = #{user.id}
          GROUP BY operation_type_id, status
        "
      end

  r = ActiveRecord::Base.connection.execute(q).entries

  result = {}
  r.each do |status, ot_id, count|
    result[ot_id] ||= { planning: 0, waiting: 0, pending: 0, delayed: 0, deferred: 0, primed: 0, scheduled: 0, running: 0, error: 0, done: 0 }
    result[ot_id][status] = count
    result[ot_id][:waiting] = count if status == 'primed'
  end

  result
end

Instance Method Details

#add_cost_model(content:, user:) ⇒ Object



130
131
132
133
134
135
136
# File 'app/models/operation_type.rb', line 130

def add_cost_model(content:, user:)
  if cost_model
    cost_model.commit(content, user)
  else
    new_code('cost_model', content, user)
  end
end

#add_input(name, sample_name, container_name, opts = {}) ⇒ Object



49
50
51
# File 'app/models/operation_type.rb', line 49

def add_input(name, sample_name, container_name, opts = {})
  add_field(name, sample_name, container_name, 'input', opts)
end

#add_io(name, sample_name, container_name, role, opts) ⇒ Object



45
46
47
# File 'app/models/operation_type.rb', line 45

def add_io(name, sample_name, container_name, role, opts)
  add_field(name, sample_name, container_name, role, opts)
end

#add_new_allowable_field_type(ft, new_type) ⇒ Object

Update Methods for Field Types from Front End Start Here



182
183
184
185
186
187
188
189
190
# File 'app/models/operation_type.rb', line 182

def add_new_allowable_field_type(ft, new_type)
  st = (SampleType.find_by(name: new_type[:sample_type][:name]) if new_type[:sample_type])
  ot = (ObjectType.find_by(name: new_type[:object_type][:name]) if new_type[:object_type] && new_type[:object_type][:name] != '')

  ft.allowable_field_types.create(
    sample_type_id: st ? st.id : nil,
    object_type_id: ot ? ot.id : nil
  )
end

#add_new_field_type(new_type) ⇒ Object



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'app/models/operation_type.rb', line 210

def add_new_field_type(new_type)
  sample_type_names = []
  container_names = []
  if new_type[:allowable_field_types]

    sample_type_names = new_type[:allowable_field_types].collect do |aft|
      aft[:sample_type] ? aft[:sample_type][:name] : nil
    end

    container_names = new_type[:allowable_field_types]
                      .select { |aft| aft[:object_type] && aft[:object_type][:name] && aft[:object_type][:name] != '' }
                      .collect do |aft|
      raise "Object type '#{aft[:object_type][:name]}' not defined by browser for #{ft[:name]}." unless ObjectType.find_by(name: aft[:object_type][:name])

      aft[:object_type][:name]
    end
  else
    sample_type_names = new_type[:sample_types] if new_type[:sample_types]
    container_names = new_type[:object_types] if new_type[:object_types]
  end

  add_io(
    new_type[:name],
    sample_type_names,
    container_names,
    new_type[:role],
    array: new_type[:array],
    part: new_type[:part],
    routing: new_type[:routing],
    ftype: new_type[:ftype],
    choices: new_type[:choices]
  )

  field_types.where(name: new_type[:name], role: new_type[:role])[0]
end

#add_output(name, sample_name, container_name, opts = {}) ⇒ Object



53
54
55
# File 'app/models/operation_type.rb', line 53

def add_output(name, sample_name, container_name, opts = {})
  add_field(name, sample_name, container_name, 'output', opts)
end

#add_parameter(name:, type:, choices:) ⇒ Object



57
58
59
# File 'app/models/operation_type.rb', line 57

def add_parameter(name:, type:, choices:)
  add_field(name, nil, nil, 'input', ftype: type, choices: choices)
end

#add_precondition(content:, user:) ⇒ Object



146
147
148
149
150
151
152
# File 'app/models/operation_type.rb', line 146

def add_precondition(content:, user:)
  if precondition
    precondition.commit(content, user)
  else
    new_code('precondition', content, user)
  end
end

#add_protocol(content:, user:) ⇒ Object



108
109
110
111
112
113
114
# File 'app/models/operation_type.rb', line 108

def add_protocol(content:, user:)
  if protocol
    protocol.commit(content, user)
  else
    new_code('protocol', content, user)
  end
end

#add_test(content:, user:) ⇒ Object



166
167
168
169
170
171
172
# File 'app/models/operation_type.rb', line 166

def add_test(content:, user:)
  if test
    test.commit(content, user)
  else
    new_code('test', content, user)
  end
end

#categoryString

Gets category of OperationType.

Returns:

  • (String)

    the category of the OperationType, as in "Cloning"



31
# File 'app/models/operation_type.rb', line 31

attr_accessible :category

#cost_modelObject



126
127
128
# File 'app/models/operation_type.rb', line 126

def cost_model
  code('cost_model')
end

#cost_model?Boolean

Returns:

  • (Boolean)


138
139
140
# File 'app/models/operation_type.rb', line 138

def cost_model?
  !cost_model.nil? && cost_model.defined_methods.include?('cost')
end

#create_operation(status:, user_id:, x: -1,, y: -1,, parent_id: -1)) ⇒ Object



41
42
43
# File 'app/models/operation_type.rb', line 41

def create_operation(status:, user_id:, x: -1, y: -1, parent_id: -1)
  operations.create(status: status, user_id: user_id, x: x, y: y, parent_id: parent_id)
end

#documentationObject



158
159
160
# File 'app/models/operation_type.rb', line 158

def documentation
  code('documentation')
end

#doneObject



100
101
102
# File 'app/models/operation_type.rb', line 100

def done
  operations.where(status: 'done')
end

#error_out_obsolete_operationsObject



310
311
312
313
314
315
316
317
318
319
# File 'app/models/operation_type.rb', line 310

def error_out_obsolete_operations
  Operation.where(operation_type_id: id, status: %w[pending scheduled]).each do |op|
    op.field_values.each do |fv|
      if !fv.field_type || !fv.allowable_field_type
        puts("ERRORING OUT OP #{op.id}")
        op.error(:obsolete, 'The operation type definition for this operation has changed too much since it was created.')
      end
    end
  end
end

#input(name) ⇒ Object

Returns the input of this OperationType with the given name.

Parameters:

  • name (String)

    the name of the input



72
73
74
# File 'app/models/operation_type.rb', line 72

def input(name)
  inputs.select { |field_type| field_type[:name] == name }.first
end

#inputsArray<FieldType>

The input types of this OperationType.

Returns:

  • (Array<FieldType>)

    meta definitions of the inputs that would be required to instantiate an operation of this type.



65
66
67
# File 'app/models/operation_type.rb', line 65

def inputs
  field_types.select { |ft| ft.role == 'input' }
end

#nameString

Gets name of OperationType.

Returns:

  • (String)

    the name of the OperationType, as in "Rehydrate Primer"



26
# File 'app/models/operation_type.rb', line 26

attr_accessible :name

#output(name) ⇒ FieldType

Returns the output of this OperationType with the given name.

Parameters:

  • name (String)

    the name of the input

Returns:



88
89
90
# File 'app/models/operation_type.rb', line 88

def output(name)
  outputs.select { |field_type| field_type[:name] == name }.first
end

#outputsArray<FieldType>

The output types of this OperationType.

Returns:

  • (Array<FieldType>)

    meta definitions of the outputs that could be produced by a successful operation of this type



80
81
82
# File 'app/models/operation_type.rb', line 80

def outputs
  field_types.select { |ft| ft.role == 'output' }
end

#pendingObject



96
97
98
# File 'app/models/operation_type.rb', line 96

def pending
  operations.where(status: 'pending')
end

#preconditionObject



142
143
144
# File 'app/models/operation_type.rb', line 142

def precondition
  code('precondition')
end

#precondition?Boolean

Returns:

  • (Boolean)


154
155
156
# File 'app/models/operation_type.rb', line 154

def precondition?
  !precondition.nil? && precondition.defined_methods.include?('precondition')
end

#protocolObject



104
105
106
# File 'app/models/operation_type.rb', line 104

def protocol
  code('protocol')
end

#protocol?Boolean

Returns:

  • (Boolean)


116
117
118
# File 'app/models/operation_type.rb', line 116

def protocol?
  !protocol.nil? && protocol.defined_classes.include?('Protocol')
end

#statsObject



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'app/models/operation_type.rb', line 321

def stats
  r = { 'done' => 0, 'error' => 0 }

  operations.each do |op|
    r[op.status] ||= 0
    r[op.status] = r[op.status] + 1
  end

  r['success'] = if r['done'] + r['error'] != 0
                   r['done'].to_f / (r['done'] + r['error'])
                 else
                   0.0
                 end

  unless operations.empty?
    r['first_run'] = operations[0].updated_at
    r['last_run'] = operations.last.updated_at
  end

  r
end

#testObject



162
163
164
# File 'app/models/operation_type.rb', line 162

def test
  code('test')
end

#test?Boolean

Returns:

  • (Boolean)


174
175
176
# File 'app/models/operation_type.rb', line 174

def test?
  !test.nil? && test.defined_classes.include?('ProtocolTest')
end

#update_allowable_field_type(old_aft, new_aft) ⇒ Object



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'app/models/operation_type.rb', line 192

def update_allowable_field_type(old_aft, new_aft)
  if new_aft[:sample_type]
    st = SampleType.find_by(name: new_aft[:sample_type][:name])
    old_aft.sample_type_id = st.id if st
  else
    old_aft.sample_type_id = nil
  end

  if new_aft[:object_type] && new_aft[:object_type][:name] != ''
    ot = ObjectType.find_by(name: new_aft[:object_type][:name])
    old_aft.object_type_id = ot.id if ot
  else
    old_aft.sample_type_id = nil
  end

  old_aft.save
end

#update_field_type(old_type:, new_type:) ⇒ Object



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'app/models/operation_type.rb', line 246

def update_field_type(old_type:, new_type:)
  keepers = []
  old_type.name = new_type[:name]

  if old_type.sample?
    old_type.routing = new_type[:routing]
    old_type.array = new_type[:array]
    old_type.part = new_type[:part]
    old_type.preferred_operation_type_id = new_type[:preferred_operation_type_id]
    old_type.preferred_field_type_id = new_type[:preferred_field_type_id]

    puts "PREF(#{old_type.name}): #{new_type[:preferred_field_type_id]}"

    if new_type[:allowable_field_types]
      new_type[:allowable_field_types].each do |newaft|
        matching_types = old_type.allowable_field_types.select { |aft| aft.id == newaft[:id] }
        if matching_types.length == 1
          oldaft = matching_types[0]
          keepers << oldaft
          update_allowable_field_type oldaft, newaft
        elsif matching_types.empty?
          keepers << add_new_allowable_field_type(old_type, newaft)
        else
          raise 'More than one allowable field type matched.'
        end
      end
    end

  else
    old_type.ftype = new_type[:ftype]
    old_type.choices = new_type[:choices]
  end

  old_type.save

  old_type.allowable_field_types.reject { |aft| keepers.include? aft }.each(&:destroy) unless keepers.empty?
end

#update_field_types(fts) ⇒ Object



284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'app/models/operation_type.rb', line 284

def update_field_types(fts)
  keepers = []

  if fts
    fts.each do |new_ft|
      matching_fts = field_types.select { |field_type| field_type.id == new_ft[:id] && field_type.role == new_ft[:role] }
      if matching_fts.length == 1
        old_ft = matching_fts[0]
        keepers << old_ft
        update_field_type(old_type: old_ft, new_type: new_ft)
      elsif matching_fts.empty?
        keepers << add_new_field_type(new_ft)
      else
        raise "Multiple inputs (or outputs) named #{new_ft[:name]}"
      end
    end
  end

  field_types.reject { |ft| keepers.include? ft }.each do |ft|
    puts "DELETING FT #{ft.name}/#{ft.role}"
    ft.destroy
  end

  error_out_obsolete_operations
end

#waitingObject



92
93
94
# File 'app/models/operation_type.rb', line 92

def waiting
  operations.where(status: 'waiting')
end