labimotion 2.0.0.rc6 → 2.0.0.rc8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b55a7bdf8c25fd127bd7a7bab35676d74e3f3523cb55e03516cc28d3e2df738c
4
- data.tar.gz: a28bad0092a10adc3befb26c3803d5f969d40bf9e387a7015890c2f81cd3089e
3
+ metadata.gz: e53e20059dd2a04452a513beb5024c762f8596d5694874c67b58d64baaf0ce47
4
+ data.tar.gz: d6b10330fe1dbce975b54a799e5a25d4cb13202fcf7b0a909ae9204fa1442146
5
5
  SHA512:
6
- metadata.gz: 94b3f84c3b2588b08f19c66bafdd17fb9eef3674040bd3dc0683132a4b5709c11f643e81afda2109741da24e4a38c6b407c4eff279c00c9292ef0942611c4ec8
7
- data.tar.gz: 19082b803643920e698b9f14c1ce89b68aac5121a5adeab473d86c16c7129ba1d9e024e1bcec83b7c802ed79409183e1129967881295b66c363912652d563191
6
+ metadata.gz: 118380f7c4b0483f3ceb7b80d5aa324db7d3fcde3e0b7a5ee66a92a02528fd8e8b352072c79fce563c9955eedc7efcf199e1f1645d391736c54715993f8d4c1d
7
+ data.tar.gz: c23b0428e3854382643548c08abdd0b875efbcdc052b7b6dd7a612996781056679dc23ce09d46b99e844c1e69016bd17dc54d9dd6d99bbfe1d22a6ce9bd06c44
@@ -31,7 +31,7 @@ module Labimotion
31
31
  source_id: params[:source_id],
32
32
  layer_id: params[:layer_id],
33
33
  field_id: params[:name],
34
- identifier: params[:source_id],
34
+ identifier: SecureRandom.uuid,
35
35
  created_by: current_user.id,
36
36
  properties: declared(params, include_missing: false),
37
37
  }
@@ -13,6 +13,10 @@ module Labimotion
13
13
  ENCODING = 'UTF-8'
14
14
  end
15
15
 
16
+ module CHMO
17
+ CV = 'Cyclic Voltammetry'
18
+ end
19
+
16
20
  module Mapper
17
21
  NMR_CONFIG = ::File.join(__dir__, 'libs', 'data', 'mapper', 'Source.json').freeze
18
22
  WIKI_CONFIG = ::File.join(__dir__, 'libs', 'data', 'mapper', 'Chemwiki.json').freeze
@@ -11,9 +11,10 @@ module Labimotion
11
11
 
12
12
  case voc['source']
13
13
  when Labimotion::Prop::ELEMENT
14
- if voc['identifier'] == 'element.name'
14
+ voc['source_name'] = ElementKlass.find_by(identifier: voc['source_id'])&.label
15
+ # if voc['identifier'] == 'element.name'
15
16
  # voc['source_name'] = ElementKlass.find_by(identifier: voc['source_id'])&.name
16
- end
17
+ # end
17
18
  when Labimotion::Prop::SEGMENT
18
19
  voc['source_name'] = SegmentKlass.find_by(identifier: voc['source_id'])&.label
19
20
  when Labimotion::Prop::DATASET
@@ -127,11 +127,12 @@ module Labimotion
127
127
  attributes['properties']['uuid'] = uuid
128
128
  attributes['uuid'] = uuid
129
129
  attributes['klass_uuid'] = properties['klass_uuid']
130
- element.update(attributes)
130
+ element.update_columns(attributes)
131
131
  end
132
132
  element.save_segments(segments: params[:segments], current_user_id: current_user.id)
133
133
  element.reload
134
134
  element.properties = update_vocabularies(element.properties, current_user, element)
135
+ ## element.user_for_revision = current_user
135
136
  element.save!
136
137
  element
137
138
  rescue StandardError => e
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "CHMO:0000025": {
3
- "index": ["A", "B", "C", "Q1", "Q2", "Q3", "G", "H", "Q4", "Q5", "K", "L", "Q6", "N", "O", "P", "Q7", "R", "S", "T", "U", "V", "W", "X", "Q8", "Q9", "Q10"],
3
+ "index": ["A", "B", "C", "D", "Q2", "Q3", "G", "H", "Q4", "Q5", "K", "L", "Q6", "N", "O", "P", "Q7", "R", "S", "T", "U", "V", "W", "X", "Q8", "Q9", "Q10"],
4
4
  "mapper": {
5
5
  "A": {
6
6
  "sources": ["inchikey"]
@@ -11,6 +11,10 @@
11
11
  "C": {
12
12
  "sources": ["concentration"]
13
13
  },
14
+ "D": {
15
+ "sources": ["redox"],
16
+ "separator": ","
17
+ },
14
18
  "G": {
15
19
  "sources": ["solvent_volume"]
16
20
  },
@@ -54,10 +58,6 @@
54
58
  "X": {
55
59
  "sources": ["re"]
56
60
  },
57
- "Q1": {
58
- "sources": ["redox"],
59
- "separator": ","
60
- },
61
61
  "Q2": {
62
62
  "sources": ["solvent"]
63
63
  },
@@ -198,8 +198,8 @@
198
198
  },
199
199
  "redox": {
200
200
  "title": "Redox-Potential",
201
- "param": "n/a",
202
- "type": "string"
201
+ "param": 6,
202
+ "type": "spc"
203
203
  },
204
204
  "elect_molfile": {
205
205
  "title": "Electrolyte_inchikey",
@@ -55,7 +55,7 @@
55
55
  "id": 4,
56
56
  "identifier": "user-name",
57
57
  "name": "creator",
58
- "label": "creator",
58
+ "label": "Creator",
59
59
  "field_type": "text",
60
60
  "opid": 9,
61
61
  "term_id": "NCIT_C42628",
@@ -23,6 +23,7 @@ module Labimotion
23
23
  @analysis = @dataset&.element&.parent
24
24
  @element = @analysis&.root&.containable if @analysis.present?
25
25
  @element_type = @element.class.name if @element.present?
26
+ @spectra_values = []
26
27
  end
27
28
 
28
29
  def read
@@ -37,44 +38,12 @@ module Labimotion
37
38
  Labimotion.log_exception(e)
38
39
  end
39
40
 
40
- def spectra
41
- name_mapping = []
42
- ## gds = Labimotion::Dataset.find_by(element_id: id, element_type: 'Container')
43
- cds = Container.find(@id)
44
- cds_csv = cds.attachments.where(aasm_state: 'csv').order(:filename)
45
- csv_length = cds_csv.length
46
- return if csv_length.zero?
47
-
48
- cds_csv.each_with_index do |att, idx|
49
- sheet_name = "Sheet#{idx+1}"
50
- sheet = @xfile.workbook.add_worksheet(name: sheet_name)
51
- name_mapping.push([sheet_name, att.filename])
52
- File.open(att.attachment_url) do |fi|
53
- fi.each_line do |line|
54
- sheet.add_row(line.split(','))
55
- end
56
- end
57
- end
58
- return unless name_mapping.length > 1
59
-
60
- first_sheet = @xfile.workbook.worksheets&.first
61
- header_style = first_sheet&.styles.add_style(sz: 12, fg_color: 'FFFFFF', bg_color: '00008B', border: { style: :thick, color: 'FF777777', edges: [:bottom] })
62
- first_sheet&.add_row(['Sheet name', 'File name'], style: header_style)
63
- name_mapping&.each do |mapping|
64
- next if mapping.length < 2
65
-
66
- @xfile.workbook.worksheets&.first&.add_row([mapping[0].to_s, mapping[1].to_s])
67
- end
68
- rescue StandardError => e
69
- Labimotion.log_exception(e)
70
- end
71
-
72
41
  def export
73
42
  return if @dataset.nil? || @analysis.nil? || @element.nil?
74
43
 
75
44
  description
76
45
  dataset_info
77
- # element_info
46
+ spectra_info
78
47
  chemwiki_info
79
48
  rescue StandardError => e
80
49
  Labimotion.log_exception(e)
@@ -83,6 +52,7 @@ module Labimotion
83
52
  private
84
53
 
85
54
  def chemwiki_info
55
+ @spectra_values
86
56
  config = Labimotion::MapperUtils.load_config(File.read(Constants::Mapper::WIKI_CONFIG))
87
57
  return if config.nil? || config[@ols_term_id].nil?
88
58
 
@@ -97,31 +67,45 @@ module Labimotion
97
67
 
98
68
  # sheet.column_info.each { |col| col.width = map_width }
99
69
 
100
- array_header = []
101
- array_data = []
102
- map_index.each do |key|
103
- mapper = map_mapper.dig(key)
104
- next if mapper.nil? || mapper['sources'].nil? || !mapper.is_a?(Hash)
105
-
106
- col_header = ''
107
- col_value = ''
108
- mapper['sources'].each_with_index do |source_key, idx|
109
- source = map_source&.dig(source_key)
110
- next if source.nil? || source['title'].nil?
111
-
112
- col_header = source['title']
70
+ if @spectra_values.length == 0
71
+ @spectra_values.push([])
72
+ end
113
73
 
114
- if col_value.present?
115
- col_value += (mapper['separator'] || '') + source_data(source)
116
- else
117
- col_value = source_data(source)
74
+ @spectra_values.each_with_index do |spdata, idx|
75
+ array_header = []
76
+ array_data = []
77
+ map_index.each do |key|
78
+ mapper = map_mapper.dig(key)
79
+ next if mapper.nil? || mapper['sources'].nil? || !mapper.is_a?(Hash)
80
+
81
+ col_header = ''
82
+ col_value = ''
83
+ mapper['sources'].each_with_index do |source_key, idx|
84
+ source = map_source&.dig(source_key)
85
+ next if source.nil? || source['title'].nil?
86
+
87
+ col_header = source['title']
88
+
89
+ if col_value.present?
90
+ col_value += (mapper['separator'] || '') + source_data(source, spdata)
91
+ else
92
+ col_value = source_data(source, spdata)
93
+ end
118
94
  end
95
+ array_header.push(col_header || '')
96
+ array_data.push(col_value || '')
119
97
  end
120
- array_header.push(col_header || '')
121
- array_data.push(col_value || '')
98
+ if idx == 0
99
+ sheet.add_row(array_header)
100
+ end
101
+ sheet.add_row(array_data)
102
+
103
+ ## for Redox potential
104
+ last_row = sheet.rows.last
105
+ last_row.cells[3].type = :string
122
106
  end
123
- sheet.add_row(array_header)
124
- sheet.add_row(array_data)
107
+
108
+
125
109
  rescue StandardError => e
126
110
  Labimotion.log_exception(e)
127
111
  end
@@ -132,26 +116,28 @@ module Labimotion
132
116
 
133
117
  case field['type']
134
118
  when 'select'
119
+ return '' if field['value'].empty?
135
120
  Labimotion::Utils.find_options_val(field, properties) || field['value']
136
121
  else
137
122
  field['value']
138
123
  end
139
124
  end
140
125
 
141
- def source_data(source)
126
+ def source_data(source, spdata)
142
127
  param = source['param']
143
128
  return '' if param.nil? || source['type'].nil?
144
129
 
145
130
  val = case source['type']
146
131
  when 'string'
147
132
  param
133
+ when 'spc'
134
+ spdata.length.positive? && param.is_a?(Integer) ? spdata.pluck(param).join(';') : ''
148
135
  when 'sample'
149
136
  @element.send(param) if @element.respond_to?(param)
150
137
  when 'molecule'
151
138
  @element.molecule.send(param) if @element_type == 'Sample' && @element&.molecule&.respond_to?(param)
152
139
  when 'dataset'
153
140
  field = Labimotion::Utils.find_field(@dataset.properties, param)
154
- # (field && field['value']) || ''
155
141
  conv_value(field, @dataset.properties)
156
142
  end
157
143
  val || ''
@@ -170,8 +156,6 @@ module Labimotion
170
156
  end
171
157
 
172
158
  def ols_name
173
- #name = @dataset.dataset_klass.label
174
-
175
159
  match = @label.match(/\((.*?)\)/)
176
160
  name = match && match.length > 1 ? match[1] : @label
177
161
 
@@ -212,54 +196,207 @@ module Labimotion
212
196
  name = ols_name
213
197
  return if name.nil?
214
198
 
199
+ sheet = create_dataset_sheet(name)
200
+ process_layers(sheet)
201
+ rescue StandardError => e
202
+ Labimotion.log_exception(e)
203
+ end
204
+
205
+ def create_dataset_sheet(name)
215
206
  sheet = @xfile.workbook.add_worksheet(name: name)
216
207
  sheet.add_row([@dataset.dataset_klass.label])
217
- header_style = sheet.styles.add_style(sz: 12, fg_color: 'FFFFFF', bg_color: '00008B', border: { style: :thick, color: 'FF777777', edges: [:bottom] })
218
- layer_style = sheet.styles.add_style(b: true, bg_color: 'CEECF5')
219
- sheet.add_row(header, style: header_style)
208
+ sheet.add_row(header, style: create_header_style(sheet))
209
+ sheet
210
+ end
220
211
 
212
+ def process_layers(sheet)
221
213
  layers = @dataset.properties[Labimotion::Prop::LAYERS] || {}
222
214
  options = @dataset.properties[Labimotion::Prop::SEL_OPTIONS]
223
- layer_keys = layers.keys.sort_by { |key| layers[key]['position'] }
224
- layer_keys.each do |key|
215
+
216
+ sort_layers(layers).each do |key|
225
217
  layer = layers[key]
226
- sheet.add_row([layer['label'], ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], style: layer_style)
227
- sorted_fields = layer[Labimotion::Prop::FIELDS].sort_by { |obj| obj['position'] }
228
- sorted_fields.each do |field|
229
- next if field['type'] == 'dummy'
230
-
231
- type = field['type']
232
- from_device = field['device'].present? ? 'Device' : ''
233
- from_device = field['system'].present? ? 'Chemotion' : from_device
234
- type = "#{field['type']}-#{field['option_layers']}" if field['type'] == Labimotion::FieldType::SELECT || field['type'] == Labimotion::FieldType::SYSTEM_DEFINED
235
-
236
- show_value = field['value'] =~ /\A\d+,\d+\z/ ? field['value']&.gsub(',', '.') : field['value']
237
- ols_short_form = (field['ontology'] && field['ontology']['short_form']) || ''
238
- ols_label = (field['ontology'] && field['ontology']['label']) || ''
239
- ols_iri = (field['ontology'] && field['ontology']['iri']) || ''
240
- sheet.add_row([' ', field['label'], nil, field['value_system'], field['field'], type, from_device, field['dkey'], nil, ols_short_form, ols_label, ols_iri].freeze)
241
-
242
- case field['type']
243
- when Labimotion::FieldType::SELECT
244
- sheet.rows.last.cells[2].type = :string
245
- sheet.rows.last.cells[8].type = :string
246
- show_value = opt_value(field, options) if show_value.present?
247
- when Labimotion::FieldType::SYSTEM_DEFINED, Labimotion::FieldType::INTEGER
248
- sheet.rows.last.cells[2].type = :float
249
- sheet.rows.last.cells[8].type = :float
250
- else
251
- sheet.rows.last.cells[2].type = :string
252
- sheet.rows.last.cells[8].type = :string
253
- end
254
- sheet.rows.last.cells[2].value = show_value
255
- sheet.rows.last.cells[8].value = field['system'] || field['device']
256
- end
257
- # sheet.column_widths nil, nil, nil, nil, 0, 0, 0, 0, 0
218
+ add_layer_row(sheet, layer)
219
+ process_fields(sheet, layer, options)
220
+ end
221
+ end
222
+
223
+ def sort_layers(layers)
224
+ layers.keys.sort_by { |key| layers[key]['position'] }
225
+ end
226
+
227
+ def add_layer_row(sheet, layer)
228
+ layer_style = sheet.styles.add_style(b: true, bg_color: 'CEECF5')
229
+ sheet.add_row([layer['label']] + [' '] * 8, style: layer_style)
230
+ end
231
+
232
+ def process_fields(sheet, layer, options)
233
+ sorted_fields = sort_fields(layer)
234
+
235
+ sorted_fields.each do |field|
236
+ next if field['type'] == 'dummy'
237
+
238
+ add_field_row(sheet, field)
239
+ set_field_types(sheet, field, options)
258
240
  end
241
+ end
242
+
243
+ def sort_fields(layer)
244
+ layer[Labimotion::Prop::FIELDS].sort_by { |obj| obj['position'] }
245
+ end
246
+
247
+ def add_field_row(sheet, field)
248
+ type = determine_field_type(field)
249
+ from_device = determine_source(field)
250
+ show_value = format_value(field['value'])
251
+ ontology_data = extract_ontology_data(field)
252
+
253
+ row_data = [
254
+ ' ',
255
+ field['label'],
256
+ nil,
257
+ field['value_system'],
258
+ field['field'],
259
+ type,
260
+ from_device,
261
+ field['dkey'],
262
+ nil
263
+ ] + ontology_data
264
+
265
+ sheet.add_row(row_data.freeze)
266
+ end
267
+
268
+ def determine_field_type(field)
269
+ return "#{field['type']}-#{field['option_layers']}" if [
270
+ Labimotion::FieldType::SELECT,
271
+ Labimotion::FieldType::SYSTEM_DEFINED
272
+ ].include?(field['type'])
273
+
274
+ field['type']
275
+ end
276
+
277
+ def determine_source(field)
278
+ return 'Chemotion' if field['system'].present?
279
+ return 'Device' if field['device'].present?
280
+ ''
281
+ end
282
+
283
+ def format_value(value)
284
+ return value&.gsub(',', '.') if value =~ /\A\d+,\d+\z/
285
+ value
286
+ end
287
+
288
+ def extract_ontology_data(field)
289
+ ontology = field['ontology'] || {}
290
+ [
291
+ ontology['short_form'] || '',
292
+ ontology['label'] || '',
293
+ ontology['iri'] || ''
294
+ ]
295
+ end
296
+
297
+ def set_field_types(sheet, field, options)
298
+ last_row = sheet.rows.last
299
+
300
+ case field['type']
301
+ when Labimotion::FieldType::SELECT
302
+ set_select_field_type(last_row, field, options)
303
+ when Labimotion::FieldType::SYSTEM_DEFINED, Labimotion::FieldType::INTEGER
304
+ set_numeric_field_type(last_row)
305
+ else
306
+ set_string_field_type(last_row)
307
+ end
308
+
309
+ set_field_values(last_row, field)
310
+ end
311
+
312
+ def set_select_field_type(row, field, options)
313
+ row.cells[2].type = :string
314
+ row.cells[8].type = :string
315
+ row.cells[2].value = opt_value(field, options) if field['value'].present?
316
+ end
317
+
318
+ def set_numeric_field_type(row)
319
+ row.cells[2].type = :float
320
+ row.cells[8].type = :float
321
+ end
322
+
323
+ def set_string_field_type(row)
324
+ row.cells[2].type = :string
325
+ row.cells[8].type = :string
326
+ end
327
+
328
+ def set_field_values(row, field)
329
+ row.cells[8].value = field['system'] || field['device']
330
+ end
331
+
332
+ def spectra_info
333
+ name_mapping = process_csv_files
334
+ return if name_mapping.nil? || name_mapping.length <= 1
335
+
336
+ create_mapping_sheet(name_mapping)
259
337
  rescue StandardError => e
260
338
  Labimotion.log_exception(e)
261
339
  end
262
340
 
341
+ def process_csv_files
342
+ cds_csv = Container.find(@id).attachments.where(aasm_state: 'csv').order(:filename)
343
+ return if cds_csv.empty?
344
+
345
+ cds_csv.each_with_index.map do |att, index|
346
+ process_single_csv(att, index)
347
+ end.compact
348
+ end
349
+
350
+ def process_single_csv(attachment, index)
351
+ sheet_name = "Sheet#{index + 1}"
352
+ sheet = @xfile.workbook.add_worksheet(name: sheet_name)
353
+
354
+ File.open(attachment.attachment_url) do |file|
355
+ first_line = file.readline.chomp
356
+ next unless cv_spc?(first_line)
357
+
358
+ process_csv_content(first_line, file, sheet)
359
+ end
360
+ [sheet_name, attachment.filename]
361
+ end
362
+
363
+ def process_csv_content(first_line, file, sheet)
364
+ lines = []
365
+ file.each_line.with_index do |line, index|
366
+ line = first_line if index.zero?
367
+ row_data = line.split(',')
368
+ sheet.add_row(row_data)
369
+ lines << row_data if cv_data?(row_data, index)
370
+ end
371
+ @spectra_values << lines if lines.any?
372
+ end
373
+
374
+ def create_mapping_sheet(name_mapping)
375
+ first_sheet = @xfile.workbook.worksheets&.first
376
+ return unless first_sheet
377
+
378
+ header_style = create_header_style(first_sheet)
379
+ first_sheet.add_row(['Sheet name', 'File name'], style: header_style)
380
+
381
+ name_mapping.each do |sheet_name, filename|
382
+ first_sheet.add_row([sheet_name.to_s, filename.to_s])
383
+ end
384
+ end
385
+
386
+
387
+ def create_header_style(sheet)
388
+ sheet.styles.add_style(
389
+ sz: 12,
390
+ fg_color: 'FFFFFF',
391
+ bg_color: '00008B',
392
+ border: {
393
+ style: :thick,
394
+ color: 'FF777777',
395
+ edges: [:bottom]
396
+ }
397
+ )
398
+ end
399
+
263
400
  def opt_value(field, options)
264
401
  return nil if field.nil? || options.nil? || field['value']&.empty? || field['option_layers']&.empty?
265
402
  return nil unless opts = options.fetch(field['option_layers'], nil)&.fetch('options', nil)
@@ -271,6 +408,15 @@ module Labimotion
271
408
  field['value']
272
409
  end
273
410
 
411
+ def cv_spc?(first_line)
412
+ data = first_line.split(',')
413
+ data && data.length > 2 && data[2] == Constants::CHMO::CV
414
+ end
415
+
416
+ def cv_data?(data, idx)
417
+ data.length > 7 && idx > 7
418
+ end
419
+
274
420
  def header
275
421
  ['Layer Label', 'Field Label', 'Value', 'Unit', 'Name', 'Type', 'Source?', 'Source identifier', 'Source data', 'Ontology', 'Ontology Label', 'iri'].freeze
276
422
  end
@@ -43,6 +43,7 @@ module Labimotion
43
43
  released_by: released_by,
44
44
  uuid: uuid,
45
45
  version: version,
46
+ created_by: updated_by,
46
47
  properties_release: properties_release,
47
48
  released_at: released_at
48
49
  }
@@ -8,6 +8,8 @@ module Labimotion
8
8
  after_create :create_vault
9
9
  after_update :save_to_vault
10
10
  before_destroy :delete_attachments
11
+
12
+ ## attr_accessor :user_for_revision
11
13
  end
12
14
 
13
15
  def create_vault
@@ -19,6 +21,7 @@ module Labimotion
19
21
  uuid: uuid,
20
22
  klass_uuid: klass_uuid,
21
23
  properties: properties,
24
+ ## created_by: user_for_revision&.id,
22
25
  properties_release: properties_release
23
26
  }
24
27
  attributes["#{Labimotion::Utils.element_name_dc(self.class.name)}_id"] = id
@@ -97,9 +97,8 @@ module Labimotion
97
97
  options = option_layers && option_layers['options']
98
98
  return if options.nil?
99
99
 
100
- sel_option = options.find { |o| o['key'] = field['value'] }
100
+ sel_option = options.find { |o| o['key'] == field['value'] }
101
101
  sel_option && sel_option['label']
102
-
103
102
  end
104
103
 
105
104
  def self.pkg(pkg)
@@ -2,5 +2,5 @@
2
2
 
3
3
  ## Labimotion Version
4
4
  module Labimotion
5
- VERSION = '2.0.0.rc6'
5
+ VERSION = '2.0.0.rc8'
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: labimotion
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.rc6
4
+ version: 2.0.0.rc8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chia-Lin Lin
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2025-02-21 00:00:00.000000000 Z
12
+ date: 2025-02-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails