labimotion 0.2.1 → 1.0.0
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 +4 -4
- data/lib/labimotion/apis/generic_element_api.rb +2 -2
- data/lib/labimotion/helpers/dataset_helpers.rb +3 -2
- data/lib/labimotion/helpers/element_helpers.rb +40 -15
- data/lib/labimotion/helpers/generic_helpers.rb +10 -9
- data/lib/labimotion/helpers/segment_helpers.rb +5 -4
- data/lib/labimotion/libs/converter.rb +8 -2
- data/lib/labimotion/libs/export_dataset.rb +13 -3
- data/lib/labimotion/libs/nmr_mapper.rb +14 -2
- data/lib/labimotion/libs/nmr_mapper_repo.rb +20 -5
- data/lib/labimotion/models/concerns/datasetable.rb +1 -0
- data/lib/labimotion/models/concerns/generic_klass_revisions.rb +13 -4
- data/lib/labimotion/models/concerns/segmentable.rb +1 -0
- data/lib/labimotion/utils/utils.rb +0 -1
- data/lib/labimotion/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 387a4b4a47a140008970b10f005472e8ba882c80ddaab99863ced5acdb0dfa29
|
4
|
+
data.tar.gz: 90f5839a92d045e45861a2354354bf5ef1542799a484cda94fa9c1263c206d5e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cca4c43074754aefbe3f2b3ca09b55af69d25ebf2a4b7f456990bc692ee7ab19905020aed4505d6360b720d94e497e91ab5910b8c50d4b5b914a01d23e840796
|
7
|
+
data.tar.gz: 5280180e034e2bd2a38000b62e8001ebee247cae2dd29e30f5b66eb3292d11e0e9f0566986c5eb918e6f6dcfc9d95024553204d834da44e7a1aee2b13500d454
|
@@ -225,7 +225,7 @@ module Labimotion
|
|
225
225
|
@klz = fetch_klass(params[:klass], params[:id])
|
226
226
|
end
|
227
227
|
post do
|
228
|
-
update_template(params)
|
228
|
+
update_template(params, current_user)
|
229
229
|
end
|
230
230
|
end
|
231
231
|
|
@@ -237,10 +237,10 @@ module Labimotion
|
|
237
237
|
optional :from_date, type: Integer, desc: 'created_date from in ms'
|
238
238
|
optional :to_date, type: Integer, desc: 'created_date to in ms'
|
239
239
|
optional :filter_created_at, type: Boolean, desc: 'filter by created at or updated at'
|
240
|
+
optional :sort_column, type: String, desc: 'sort by updated_at or selected layers property'
|
240
241
|
end
|
241
242
|
paginate per_page: 7, offset: 0, max_per_page: 100
|
242
243
|
get do
|
243
|
-
|
244
244
|
scope = list_serialized_elements(params, current_user)
|
245
245
|
|
246
246
|
reset_pagination_page(scope)
|
@@ -16,7 +16,8 @@ module Labimotion
|
|
16
16
|
def create_repo_klass(params, current_user)
|
17
17
|
response = Labimotion::TemplateHub.fetch_identifier('DatasetKlass', params[:identifier])
|
18
18
|
attributes = response.slice('ols_term_id', 'label', 'desc', 'uuid', 'identifier', 'properties_release', 'version') # .except(:id, :is_active, :place, :created_by, :created_at, :updated_at)
|
19
|
-
attributes['
|
19
|
+
attributes['properties_release']['identifier'] = attributes['identifier']
|
20
|
+
attributes['properties_template'] = attributes['properties_release']
|
20
21
|
attributes['place'] = ((Labimotion::DatasetKlass.all.length * 10) || 0) + 10
|
21
22
|
attributes['is_active'] = false
|
22
23
|
attributes['updated_by'] = current_user.id
|
@@ -29,7 +30,7 @@ module Labimotion
|
|
29
30
|
attributes['created_by'] = current_user.id
|
30
31
|
ds = Labimotion::DatasetKlass.create!(attributes)
|
31
32
|
end
|
32
|
-
ds.create_klasses_revision(current_user
|
33
|
+
ds.create_klasses_revision(current_user)
|
33
34
|
rescue StandardError => e
|
34
35
|
Labimotion.log_exception(e, current_user)
|
35
36
|
# { error: e.message }
|
@@ -20,12 +20,10 @@ module Labimotion
|
|
20
20
|
uuid = SecureRandom.uuid
|
21
21
|
template = { uuid: uuid, layers: {}, select_options: {} }
|
22
22
|
attributes = declared(params, include_missing: false)
|
23
|
-
if attributes[:properties_template].
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
attributes[:properties_template]['klass'] = 'ElementKlass'
|
28
|
-
end
|
23
|
+
attributes[:properties_template] = template if attributes[:properties_template].nil?
|
24
|
+
attributes[:properties_template]['uuid'] = uuid
|
25
|
+
attributes[:properties_template]['pkg'] = Labimotion::Utils.pkg(attributes[:properties_template]['pkg'])
|
26
|
+
attributes[:properties_template]['klass'] = 'ElementKlass'
|
29
27
|
attributes[:is_active] = false
|
30
28
|
attributes[:uuid] = uuid
|
31
29
|
attributes[:released_at] = DateTime.now
|
@@ -34,7 +32,7 @@ module Labimotion
|
|
34
32
|
|
35
33
|
new_klass = Labimotion::ElementKlass.create!(attributes)
|
36
34
|
new_klass.reload
|
37
|
-
new_klass.create_klasses_revision(current_user
|
35
|
+
new_klass.create_klasses_revision(current_user)
|
38
36
|
klass_names_file = Rails.root.join('app/packs/klasses.json')
|
39
37
|
klasses = Labimotion::ElementKlass.where(is_active: true)&.pluck(:name) || []
|
40
38
|
File.write(klass_names_file, klasses)
|
@@ -71,6 +69,7 @@ module Labimotion
|
|
71
69
|
params[:properties]['klass_uuid'] = klass[:uuid]
|
72
70
|
params[:properties]['pkg'] = Labimotion::Utils.pkg(params[:properties]['pkg'])
|
73
71
|
params[:properties]['klass'] = 'Element'
|
72
|
+
params[:properties]['identifier'] = klass[:identifier]
|
74
73
|
properties = params[:properties]
|
75
74
|
properties.delete('flow') unless properties['flow'].nil?
|
76
75
|
properties.delete('flowObject') unless properties['flowObject'].nil?
|
@@ -247,7 +246,7 @@ module Labimotion
|
|
247
246
|
.where(
|
248
247
|
element_klasses: { name: params[:el_type] },
|
249
248
|
collections_elements: { collection_id: collection_id },
|
250
|
-
).includes(:tag, collections: :sync_collections_users)
|
249
|
+
).includes(:tag, collections: :sync_collections_users)
|
251
250
|
else
|
252
251
|
Labimotion::Element.none
|
253
252
|
end
|
@@ -256,10 +255,35 @@ module Labimotion
|
|
256
255
|
from = params[:from_date]
|
257
256
|
to = params[:to_date]
|
258
257
|
by_created_at = params[:filter_created_at] || false
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
258
|
+
if params[:sort_column]&.include?('.')
|
259
|
+
layer, field = params[:sort_column].split('.')
|
260
|
+
|
261
|
+
element_klass = Labimotion::ElementKlass.find_by(name: params[:el_type])
|
262
|
+
allowed_fields = element_klass.properties_release.dig('layers', layer, 'fields')&.pluck('field') || []
|
263
|
+
|
264
|
+
if field.in?(allowed_fields)
|
265
|
+
query = ActiveRecord::Base.sanitize_sql(
|
266
|
+
[
|
267
|
+
"LEFT JOIN LATERAL(
|
268
|
+
SELECT field->'value' AS value
|
269
|
+
FROM jsonb_array_elements(properties->'layers'->:layer->'fields') a(field)
|
270
|
+
WHERE field->>'field' = :field
|
271
|
+
) a ON true",
|
272
|
+
{ layer: layer, field: field },
|
273
|
+
],
|
274
|
+
)
|
275
|
+
scope = scope.joins(query).order('value ASC NULLS FIRST')
|
276
|
+
else
|
277
|
+
scope = scope.order(updated_at: :desc)
|
278
|
+
end
|
279
|
+
else
|
280
|
+
scope = scope.order(updated_at: :desc)
|
281
|
+
end
|
282
|
+
|
283
|
+
scope = scope.elements_created_time_from(Time.at(from)) if from && by_created_at
|
284
|
+
scope = scope.elements_created_time_to(Time.at(to) + 1.day) if to && by_created_at
|
285
|
+
scope = scope.elements_updated_time_from(Time.at(from)) if from && !by_created_at
|
286
|
+
scope = scope.elements_updated_time_to(Time.at(to) + 1.day) if to && !by_created_at
|
263
287
|
scope
|
264
288
|
rescue StandardError => e
|
265
289
|
Labimotion.log_exception(e, current_user)
|
@@ -268,8 +292,9 @@ module Labimotion
|
|
268
292
|
|
269
293
|
def create_repo_klass(params, current_user)
|
270
294
|
response = Labimotion::TemplateHub.fetch_identifier('ElementKlass', params[:identifier])
|
271
|
-
attributes = response.slice('name', 'label', 'desc', 'icon_name', 'klass_prefix', 'is_generic', 'identifier', 'properties_release', 'version')
|
272
|
-
attributes['
|
295
|
+
attributes = response.slice('name', 'label', 'desc', 'icon_name', 'klass_prefix', 'is_generic', 'identifier', 'properties_release', 'version')
|
296
|
+
attributes['properties_release']['identifier'] = attributes['identifier']
|
297
|
+
attributes['properties_template'] = attributes['properties_release']
|
273
298
|
attributes['place'] = ((Labimotion::DatasetKlass.all.length * 10) || 0) + 10
|
274
299
|
attributes['is_active'] = false
|
275
300
|
attributes['updated_by'] = current_user.id
|
@@ -283,7 +308,7 @@ module Labimotion
|
|
283
308
|
attributes['created_by'] = current_user.id
|
284
309
|
element_klass = Labimotion::ElementKlass.create!(attributes)
|
285
310
|
end
|
286
|
-
element_klass.create_klasses_revision(current_user
|
311
|
+
element_klass.create_klasses_revision(current_user)
|
287
312
|
rescue StandardError => e
|
288
313
|
Labimotion.log_exception(e, current_user)
|
289
314
|
# { error: e.message }
|
@@ -41,22 +41,22 @@ module Labimotion
|
|
41
41
|
raise e
|
42
42
|
end
|
43
43
|
|
44
|
-
def update_template(params)
|
44
|
+
def update_template(params, current_user)
|
45
45
|
klz = fetch_klass(params[:klass], params[:id])
|
46
46
|
uuid = SecureRandom.uuid
|
47
47
|
properties = params[:properties_template]
|
48
48
|
properties['uuid'] = uuid
|
49
|
-
|
50
49
|
klz.version = Labimotion::Utils.next_version(params[:release], klz.version)
|
51
50
|
properties['version'] = klz.version
|
52
|
-
properties['pkg'] = Labimotion::Utils.pkg(params['pkg'] || klz.properties_template['pkg'])
|
51
|
+
properties['pkg'] = Labimotion::Utils.pkg(params['pkg'] || (klz.properties_template && klz.properties_template['pkg']))
|
53
52
|
properties['klass'] = klz.class.name.split('::').last
|
53
|
+
properties['identifier'] = klz.identifier
|
54
54
|
properties.delete('eln') if properties['eln'].present?
|
55
55
|
klz.updated_by = current_user.id
|
56
56
|
klz.properties_template = properties
|
57
57
|
klz.save!
|
58
58
|
klz.reload
|
59
|
-
klz.create_klasses_revision(current_user
|
59
|
+
klz.create_klasses_revision(current_user) if params[:release] != 'draft'
|
60
60
|
klz
|
61
61
|
rescue StandardError => e
|
62
62
|
Labimotion.log_exception(e, current_user)
|
@@ -188,9 +188,9 @@ module Labimotion
|
|
188
188
|
raise e
|
189
189
|
end
|
190
190
|
|
191
|
-
def create_attachments(files, del_files, type, id, user_id)
|
191
|
+
def create_attachments(files, del_files, type, id, identifier, user_id)
|
192
192
|
attach_ary = []
|
193
|
-
(files || []).
|
193
|
+
(files || []).each_with_index do |file, index|
|
194
194
|
next unless (tempfile = file[:tempfile])
|
195
195
|
|
196
196
|
a = Attachment.new(
|
@@ -200,13 +200,14 @@ module Labimotion
|
|
200
200
|
file_path: file[:tempfile],
|
201
201
|
created_by: user_id,
|
202
202
|
created_for: user_id,
|
203
|
+
identifier: identifier[index],
|
203
204
|
content_type: file[:type],
|
204
205
|
attachable_type: type,
|
205
206
|
attachable_id: id,
|
206
207
|
)
|
207
|
-
|
208
|
-
|
209
|
-
attach_ary.push(
|
208
|
+
ActiveRecord::Base.transaction do
|
209
|
+
att.save!
|
210
|
+
attach_ary.push(att.id)
|
210
211
|
ensure
|
211
212
|
tempfile.close
|
212
213
|
tempfile.unlink
|
@@ -38,7 +38,7 @@ module Labimotion
|
|
38
38
|
attributes[:properties_release] = attributes[:properties_template]
|
39
39
|
klass = Labimotion::SegmentKlass.create!(attributes)
|
40
40
|
klass.reload
|
41
|
-
klass.create_klasses_revision(current_user
|
41
|
+
klass.create_klasses_revision(current_user)
|
42
42
|
klass
|
43
43
|
rescue StandardError => e
|
44
44
|
Labimotion.log_exception(e, current_user)
|
@@ -65,8 +65,9 @@ module Labimotion
|
|
65
65
|
|
66
66
|
def create_repo_klass(params, current_user)
|
67
67
|
response = Labimotion::TemplateHub.fetch_identifier('SegmentKlass', params[:identifier])
|
68
|
-
attributes = response.slice('label', 'desc', 'uuid', 'identifier', 'released_at', 'properties_release', 'version')
|
69
|
-
attributes['
|
68
|
+
attributes = response.slice('label', 'desc', 'uuid', 'identifier', 'released_at', 'properties_release', 'version')
|
69
|
+
attributes['properties_release']['identifier'] = attributes['identifier']
|
70
|
+
attributes['properties_template'] = attributes['properties_release']
|
70
71
|
attributes['place'] = ((Labimotion::SegmentKlass.all.length * 10) || 0) + 10
|
71
72
|
attributes['is_active'] = false
|
72
73
|
attributes['updated_by'] = current_user.id
|
@@ -89,7 +90,7 @@ module Labimotion
|
|
89
90
|
attributes['created_by'] = current_user.id
|
90
91
|
segment_klass = Labimotion::SegmentKlass.create!(attributes)
|
91
92
|
end
|
92
|
-
segment_klass.create_klasses_revision(current_user
|
93
|
+
segment_klass.create_klasses_revision(current_user)
|
93
94
|
rescue StandardError => e
|
94
95
|
Labimotion.log_exception(e, current_user)
|
95
96
|
raise e
|
@@ -4,6 +4,7 @@ require 'net/http'
|
|
4
4
|
require 'uri'
|
5
5
|
require 'json'
|
6
6
|
require 'date'
|
7
|
+
require 'labimotion/version'
|
7
8
|
require 'labimotion/utils/utils'
|
8
9
|
|
9
10
|
# rubocop: disable Metrics/AbcSize
|
@@ -116,7 +117,7 @@ module Labimotion
|
|
116
117
|
)
|
117
118
|
# att.attachment_attacher.attach(tmp_file)
|
118
119
|
if att.valid? && Labimotion::IS_RAILS5 == false
|
119
|
-
att.attachment_attacher.create_derivatives
|
120
|
+
## att.attachment_attacher.create_derivatives
|
120
121
|
att.save!
|
121
122
|
end
|
122
123
|
if att.valid? && Labimotion::IS_RAILS5 == true
|
@@ -148,7 +149,12 @@ module Labimotion
|
|
148
149
|
response = nil
|
149
150
|
begin
|
150
151
|
ofile = Rails.root.join(data[:f], data[:a].filename)
|
151
|
-
|
152
|
+
if Labimotion::IS_RAILS5 == true
|
153
|
+
FileUtils.cp(data[:a].store.path, ofile)
|
154
|
+
else
|
155
|
+
FileUtils.cp(data[:a].attachment_url, ofile)
|
156
|
+
end
|
157
|
+
|
152
158
|
File.open(ofile, 'r') do |f|
|
153
159
|
body = { file: f }
|
154
160
|
response = HTTParty.post(
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'export_table'
|
3
|
+
require 'labimotion/version'
|
3
4
|
|
4
5
|
module Labimotion
|
5
6
|
## ExportDataset
|
@@ -101,9 +102,18 @@ module Labimotion
|
|
101
102
|
cds.attachments.where(aasm_state: 'csv').each do |att|
|
102
103
|
name = File.basename(att.filename, '.csv')
|
103
104
|
sheet = @xfile.workbook.add_worksheet(name: name)
|
104
|
-
|
105
|
-
|
106
|
-
|
105
|
+
|
106
|
+
if Labimotion::IS_RAILS5 == true
|
107
|
+
File.open(att.store.path) do |fi|
|
108
|
+
fi.each_line do |line|
|
109
|
+
sheet.add_row(line.split(','))
|
110
|
+
end
|
111
|
+
end
|
112
|
+
else
|
113
|
+
File.open(att.attachment_url) do |fi|
|
114
|
+
fi.each_line do |line|
|
115
|
+
sheet.add_row(line.split(','))
|
116
|
+
end
|
107
117
|
end
|
108
118
|
end
|
109
119
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'labimotion/version'
|
3
4
|
require 'labimotion/utils/utils'
|
4
5
|
|
5
6
|
module Labimotion
|
@@ -7,8 +8,8 @@ module Labimotion
|
|
7
8
|
class NmrMapper
|
8
9
|
def self.is_brucker_binary(id)
|
9
10
|
att = Attachment.find(id)
|
10
|
-
if
|
11
|
-
Zip::File.open(att.
|
11
|
+
if Labimotion::IS_RAILS5 == true
|
12
|
+
Zip::File.open(att.store.path) do |zip_file|
|
12
13
|
zip_file.each do |entry|
|
13
14
|
if entry.name.include?('/pdata/') && entry.name.include?('parm.txt')
|
14
15
|
metadata = entry.get_input_stream.read.force_encoding('UTF-8')
|
@@ -16,6 +17,17 @@ module Labimotion
|
|
16
17
|
end
|
17
18
|
end
|
18
19
|
end
|
20
|
+
else
|
21
|
+
if att&.attachment_attacher&.file&.url
|
22
|
+
Zip::File.open(att.attachment_attacher.file.url) do |zip_file|
|
23
|
+
zip_file.each do |entry|
|
24
|
+
if entry.name.include?('/pdata/') && entry.name.include?('parm.txt')
|
25
|
+
metadata = entry.get_input_stream.read.force_encoding('UTF-8')
|
26
|
+
return metadata
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
19
31
|
end
|
20
32
|
nil
|
21
33
|
end
|
@@ -1,18 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require 'labimotion/version'
|
2
3
|
require 'labimotion/utils/utils'
|
3
4
|
|
4
5
|
module Labimotion
|
5
6
|
class NmrMapperRepo
|
6
7
|
def self.is_brucker_binary(id)
|
7
8
|
att = Attachment.find(id)
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
if Labimotion::IS_RAILS5 == true
|
10
|
+
Zip::File.open(att.store.path) do |zip_file|
|
11
|
+
zip_file.each do |entry|
|
12
|
+
if entry.name.include?('/pdata/') && entry.name.include?('parm.txt')
|
13
|
+
metadata = entry.get_input_stream.read.force_encoding('UTF-8')
|
14
|
+
return metadata
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
else
|
19
|
+
if att&.attachment_attacher&.file&.url
|
20
|
+
Zip::File.open(att.attachment_attacher.file.url) do |zip_file|
|
21
|
+
zip_file.each do |entry|
|
22
|
+
if entry.name.include?('/pdata/') && entry.name.include?('parm.txt')
|
23
|
+
metadata = entry.get_input_stream.read.force_encoding('UTF-8')
|
24
|
+
return metadata
|
25
|
+
end
|
26
|
+
end
|
13
27
|
end
|
14
28
|
end
|
15
29
|
end
|
30
|
+
|
16
31
|
nil
|
17
32
|
end
|
18
33
|
|
@@ -1,13 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Labimotion
|
4
|
-
|
4
|
+
## Generic Klass Revisions Helpers
|
5
|
+
module GenericKlassRevisions
|
5
6
|
extend ActiveSupport::Concern
|
6
7
|
included do
|
7
8
|
# has_many :element_klasses_revisions, dependent: :destroy
|
8
9
|
end
|
9
10
|
|
10
|
-
def create_klasses_revision(
|
11
|
+
def create_klasses_revision(current_user)
|
11
12
|
properties_release = properties_template
|
12
13
|
migrate_workflow if properties_release['flow'].present?
|
13
14
|
|
@@ -21,11 +22,19 @@ module Labimotion
|
|
21
22
|
end
|
22
23
|
properties_release['flowObject']['nodes'] = elements
|
23
24
|
end
|
25
|
+
klass_attributes = {
|
26
|
+
uuid: properties_template['uuid'],
|
27
|
+
properties_template: properties_release,
|
28
|
+
properties_release: properties_release,
|
29
|
+
released_at: DateTime.now,
|
30
|
+
updated_by: current_user&.id,
|
31
|
+
released_by: current_user&.id,
|
32
|
+
}
|
24
33
|
|
25
|
-
self.update!(
|
34
|
+
self.update!(klass_attributes)
|
26
35
|
reload
|
27
36
|
attributes = {
|
28
|
-
released_by:
|
37
|
+
released_by: released_by,
|
29
38
|
uuid: uuid,
|
30
39
|
version: version,
|
31
40
|
properties_release: properties_release,
|
@@ -48,6 +48,7 @@ module Labimotion
|
|
48
48
|
uuid = SecureRandom.uuid
|
49
49
|
props = seg['properties']
|
50
50
|
props['pkg'] = Labimotion::Utils.pkg(props['pkg'])
|
51
|
+
props['identifier'] = identifier
|
51
52
|
props['uuid'] = uuid
|
52
53
|
props['klass'] = 'Segment'
|
53
54
|
segment = Labimotion::Segment.find_by(element_type: Labimotion::Utils.element_name(self.class.name), element_id: self.id, segment_klass_id: seg['segment_klass_id'])
|
data/lib/labimotion/version.rb
CHANGED
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: 0.
|
4
|
+
version: 1.0.0
|
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: 2023-08-
|
12
|
+
date: 2023-08-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|