quorum 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/.gitignore +3 -5
  2. data/.travis.yml +0 -2
  3. data/Gemfile.lock +2 -2
  4. data/HISTORY.md +12 -0
  5. data/Rakefile +13 -48
  6. data/app/models/quorum/blastn_job.rb +15 -22
  7. data/app/models/quorum/blastp_job.rb +15 -22
  8. data/app/models/quorum/blastx_job.rb +15 -22
  9. data/app/models/quorum/tblastn_job.rb +15 -22
  10. data/app/views/quorum/jobs/form/_blastn_form.html.erb +4 -4
  11. data/app/views/quorum/jobs/form/_blastp_form.html.erb +4 -4
  12. data/app/views/quorum/jobs/form/_blastx_form.html.erb +4 -4
  13. data/app/views/quorum/jobs/form/_tblastn_form.html.erb +4 -4
  14. data/app/views/quorum/jobs/templates/_blast_detailed_report_template.html.erb +6 -17
  15. data/db/migrate/20120918205556_rename_max_score_column.rb +15 -0
  16. data/lib/generators/templates/blast.rb +37 -31
  17. data/lib/quorum/version.rb +1 -1
  18. data/spec/config/quorum_settings.yml +55 -0
  19. data/spec/javascripts/fixtures/quorum_search_form.html +8 -8
  20. data/spec/javascripts/jobs_spec.js +8 -8
  21. data/spec/lib/tasks/travis.rake +120 -0
  22. data/spec/models/blastn_job_spec.rb +41 -22
  23. data/spec/models/blastp_job_spec.rb +41 -22
  24. data/spec/models/blastx_job_spec.rb +41 -22
  25. data/spec/models/job_spec.rb +1 -1
  26. data/spec/models/tblastn_job_spec.rb +41 -22
  27. data/spec/requests/jobs_spec.rb +8 -8
  28. data/spec/templates/blast_db_spec.rb +1 -1
  29. data/spec/templates/blast_spec.rb +1 -1
  30. metadata +7 -32
  31. data/spec/dummy/config/initializers/quorum_initializer.rb +0 -36
  32. data/spec/dummy/config/locales/quorum.en.yml +0 -8
  33. data/spec/dummy/config/quorum_settings.yml +0 -141
  34. data/spec/dummy/db/schema.rb +0 -213
  35. data/spec/dummy/quorum/bin/fetch +0 -115
  36. data/spec/dummy/quorum/bin/search +0 -141
  37. data/spec/dummy/quorum/blastdb/test/contigs.fa +0 -15
  38. data/spec/dummy/quorum/blastdb/test/peptides.fa +0 -4
  39. data/spec/dummy/quorum/blastdb/test.nhd +0 -2
  40. data/spec/dummy/quorum/blastdb/test.nhi +0 -0
  41. data/spec/dummy/quorum/blastdb/test.nhr +0 -0
  42. data/spec/dummy/quorum/blastdb/test.nin +0 -0
  43. data/spec/dummy/quorum/blastdb/test.nog +0 -0
  44. data/spec/dummy/quorum/blastdb/test.nsd +0 -2
  45. data/spec/dummy/quorum/blastdb/test.nsi +0 -0
  46. data/spec/dummy/quorum/blastdb/test.nsq +0 -0
  47. data/spec/dummy/quorum/blastdb/test.phd +0 -2
  48. data/spec/dummy/quorum/blastdb/test.phi +0 -0
  49. data/spec/dummy/quorum/blastdb/test.phr +0 -0
  50. data/spec/dummy/quorum/blastdb/test.pin +0 -0
  51. data/spec/dummy/quorum/blastdb/test.pog +0 -0
  52. data/spec/dummy/quorum/blastdb/test.psd +0 -2
  53. data/spec/dummy/quorum/blastdb/test.psi +0 -0
  54. data/spec/dummy/quorum/blastdb/test.psq +0 -0
  55. data/spec/dummy/quorum/lib/fetch_tools/blast_db.rb +0 -106
  56. data/spec/dummy/quorum/lib/logger.rb +0 -43
  57. data/spec/dummy/quorum/lib/search_tools/blast.rb +0 -452
  58. data/spec/dummy/quorum/lib/trollop.rb +0 -781
@@ -1,452 +0,0 @@
1
- $LOAD_PATH.unshift(File.expand_path("../../", __FILE__))
2
-
3
- require 'bio-blastxmlparser'
4
- require 'logger'
5
-
6
- module Quorum
7
- module SearchTools
8
- #
9
- # Blast Search Tool
10
- #
11
- class Blast
12
-
13
- class QuorumJob < ActiveRecord::Base
14
- self.table_name = "quorum_jobs"
15
-
16
- has_one :quorum_blastn_job,
17
- :foreign_key => "job_id"
18
- has_many :quorum_blastn_job_reports,
19
- :foreign_key => "blastn_job_id"
20
-
21
- has_one :quorum_blastx_job,
22
- :foreign_key => "job_id"
23
- has_many :quorum_blastx_job_reports,
24
- :foreign_key => "blastx_job_id"
25
-
26
- has_one :quorum_tblastn_job,
27
- :foreign_key => "job_id"
28
- has_many :quorum_tblastn_job_reports,
29
- :foreign_key => "tblastn_job_id"
30
-
31
- has_one :quorum_blastp_job,
32
- :foreign_key => "job_id"
33
- has_many :quorum_blastp_job_reports,
34
- :foreign_key => "blastp_job_id"
35
- end
36
-
37
- class QuorumBlastnJob < ActiveRecord::Base
38
- self.table_name = "quorum_blastn_jobs"
39
- belongs_to :quorum_job
40
- has_many :quorum_blastn_job_reports
41
- end
42
-
43
- class QuorumBlastnJobReport < ActiveRecord::Base
44
- self.table_name = "quorum_blastn_job_reports"
45
- belongs_to :quorum_blastn_job
46
- end
47
-
48
- class QuorumBlastxJob < ActiveRecord::Base
49
- self.table_name = "quorum_blastx_jobs"
50
- belongs_to :quorum_job
51
- has_many :quorum_blastx_job_reports
52
- end
53
-
54
- class QuorumBlastxJobReport < ActiveRecord::Base
55
- self.table_name = "quorum_blastx_job_reports"
56
- belongs_to :quorum_blastx_job
57
- end
58
-
59
- class QuorumTblastnJob < ActiveRecord::Base
60
- self.table_name = "quorum_tblastn_jobs"
61
- belongs_to :quorum_job
62
- has_many :quorum_tblastn_job_reports
63
- end
64
-
65
- class QuorumTblastnJobReport < ActiveRecord::Base
66
- self.table_name = "quorum_tblastn_job_reports"
67
- belongs_to :quorum_tblastn_job
68
- end
69
-
70
- class QuorumBlastpJob < ActiveRecord::Base
71
- self.table_name = "quorum_blastp_jobs"
72
- belongs_to :quorum_job
73
- has_many :quorum_blastp_job_reports
74
- end
75
-
76
- class QuorumBlastpJobReport < ActiveRecord::Base
77
- self.table_name = "quorum_blastp_job_reports"
78
- belongs_to :quorum_blastp_job
79
- end
80
-
81
- private
82
-
83
- def initialize(args)
84
- @algorithm = args[:search_tool]
85
- @id = args[:id]
86
- @log_directory = args[:log_directory]
87
- @tmp = args[:tmp_directory]
88
- @search_database = args[:search_database]
89
- @threads = args[:threads]
90
-
91
- @logger = Quorum::Logger.new(@log_directory)
92
-
93
- begin
94
- @job = QuorumJob.find(@id)
95
- rescue Exception => e
96
- @logger.log("ActiveRecord", e.message, 1)
97
- end
98
-
99
- @na_sequence = @job.na_sequence
100
- @aa_sequence = @job.aa_sequence
101
-
102
- ## Create for method invocation ##
103
- @job_association = "quorum_#{@algorithm}_job".to_sym
104
- @job_report_association = "quorum_#{@algorithm}_job_reports".to_sym
105
-
106
- @filter = @job.method(@job_association).call.filter
107
- @expectation = @job.method(@job_association).call.expectation
108
- @max_score = @job.method(@job_association).call.max_score
109
- @min_score = @job.method(@job_association).call.min_bit_score
110
- @gapped_alignments = @job.method(@job_association).call.gapped_alignments
111
- @gap_opening_penalty = @job.method(@job_association).call.gap_opening_penalty
112
- @gap_extension_penalty = @job.method(@job_association).call.gap_extension_penalty
113
-
114
- @db = @job.method(@job_association).call.blast_dbs.split(';')
115
- @db.map! { |d| File.join(@search_database, d) }
116
- @db = @db.join(' ')
117
-
118
- @hash = create_hash(@job.sequence)
119
- @tmp_files = File.join(@tmp, @hash) << "*"
120
- end
121
-
122
- #
123
- # Create a unique hash plus timestamp.
124
- #
125
- def create_hash(sequence)
126
- Digest::MD5.hexdigest(sequence).to_s + "-" + Time.now.to_f.to_s
127
- end
128
-
129
- #
130
- # Blastn command
131
- #
132
- def generate_blastn_cmd
133
- blastn = "blastn " <<
134
- "-db \"#{@db}\" " <<
135
- "-query #{@na_fasta} " <<
136
- "-outfmt 5 " <<
137
- "-num_threads #{@threads} " <<
138
- "-evalue #{@expectation} " <<
139
- "-max_target_seqs #{@max_score} " <<
140
- "-out #{@out} "
141
- if @gapped_alignments
142
- blastn << "-gapopen #{@gap_opening_penalty} "
143
- blastn << "-gapextend #{@gap_extension_penalty} "
144
- else
145
- blastn << "-ungapped "
146
- end
147
- blastn << "-dust yes " if @filter
148
- blastn
149
- end
150
-
151
- #
152
- # Blastx command
153
- #
154
- def generate_blastx_cmd
155
- blastx = "blastx " <<
156
- "-db \"#{@db}\" " <<
157
- "-query #{@na_fasta} " <<
158
- "-outfmt 5 " <<
159
- "-num_threads #{@threads} " <<
160
- "-evalue #{@expectation} " <<
161
- "-max_target_seqs #{@max_score} " <<
162
- "-out #{@out} "
163
- if @gapped_alignments
164
- blastx << "-gapopen #{@gap_opening_penalty} "
165
- blastx << "-gapextend #{@gap_extension_penalty} "
166
- else
167
- blastx << "-ungapped "
168
- end
169
- blastx << "-seg yes " if @filter
170
- blastx
171
- end
172
-
173
- #
174
- # Tblastn command
175
- #
176
- def generate_tblastn_cmd
177
- tblastn = "tblastn " <<
178
- "-db \"#{@db}\" " <<
179
- "-query #{@aa_fasta} " <<
180
- "-outfmt 5 " <<
181
- "-num_threads #{@threads} " <<
182
- "-evalue #{@expectation} " <<
183
- "-max_target_seqs #{@max_score} " <<
184
- "-out #{@out} "
185
- if @gapped_alignments
186
- tblastn << "-gapopen #{@gap_opening_penalty} "
187
- tblastn << "-gapextend #{@gap_extension_penalty} "
188
- tblastn << "-comp_based_stats D "
189
- else
190
- tblastn << "-ungapped "
191
- tblastn << "-comp_based_stats F "
192
- end
193
- tblastn << "-seg yes " if @filter
194
- tblastn
195
- end
196
-
197
- #
198
- # Blastp command
199
- #
200
- def generate_blastp_cmd
201
- blastp = "blastp " <<
202
- "-db \"#{@db}\" " <<
203
- "-query #{@aa_fasta} " <<
204
- "-outfmt 5 " <<
205
- "-num_threads #{@threads} " <<
206
- "-evalue #{@expectation} " <<
207
- "-max_target_seqs #{@max_score} " <<
208
- "-out #{@out} "
209
- if @gapped_alignments
210
- blastp << "-gapopen #{@gap_opening_penalty} "
211
- blastp << "-gapextend #{@gap_extension_penalty} "
212
- blastp << "-comp_based_stats D "
213
- else
214
- blastp << "-ungapped "
215
- blastp << "-comp_based_stats F "
216
- end
217
- blastp << "-seg yes " if @filter
218
- blastp
219
- end
220
-
221
- #
222
- # Generate Blast Command
223
- #
224
- def generate_blast_cmd
225
- @cmd = ""
226
-
227
- @na_fasta = File.join(@tmp, @hash + ".na.fa")
228
- @aa_fasta = File.join(@tmp, @hash + ".aa.fa")
229
- File.open(@na_fasta, "w") { |f| f << @na_sequence }
230
- File.open(@aa_fasta, "w") { |f| f << @aa_sequence }
231
-
232
- @out = File.join(@tmp, @hash + ".out.xml")
233
- File.new(@out, "w")
234
-
235
- case @algorithm
236
- when "blastn"
237
- @cmd << generate_blastn_cmd
238
- when "blastx"
239
- @cmd << generate_blastx_cmd
240
- when "tblastn"
241
- @cmd << generate_tblastn_cmd
242
- when "blastp"
243
- @cmd << generate_blastp_cmd
244
- end
245
- end
246
-
247
- #
248
- # Format Blast report hit_display_id and hit_def.
249
- #
250
- # For added flexibility, Quorum doesn't parse seqids or deflines.
251
- # Instead, format_hit splits the hit_def on whitespace and
252
- # reports hit_display_id as the first element and hit_def as the second.
253
- #
254
- def format_hit(str)
255
- hit_display_id = ""
256
- hit_def = ""
257
-
258
- hit = str.split(" ", 2)
259
- if hit.length < 2
260
- hit_display_id = hit.first
261
- hit_def = "None"
262
- else
263
- hit_display_id = hit.first
264
- hit_def = hit.last
265
- end
266
-
267
- return hit_display_id, hit_def
268
- end
269
-
270
- #
271
- # Save Blast Job Report
272
- #
273
- # Hsps are only reported if a query hit against the Blast db.
274
- # Only save the @data if bit_score exists and it's > the user
275
- # defined minimum score.
276
- #
277
- # Set the attribute results to true for downstream processes.
278
- #
279
- def save_hsp_results
280
- if @data[:bit_score] && (@data[:bit_score].to_i > @min_score.to_i)
281
- @data[:results] = true
282
- @data["#{@algorithm}_job_id".to_sym] = @job.method(@job_association).call.job_id
283
-
284
- job_report = @job.method(@job_report_association).call.build(@data)
285
-
286
- unless job_report.save!
287
- @logger.log(
288
- "ActiveRecord",
289
- "Unable to save #{@algorithm} results to database.",
290
- 1,
291
- @tmp_files
292
- )
293
- end
294
- end
295
- end
296
-
297
- #
298
- # Save empty Blast Job Report
299
- #
300
- # Set the attribute results to false for downstream processes.
301
- #
302
- def save_empty_results
303
- job_report = @job.method(@job_report_association).call.build(
304
- "#{@algorithm}_job_id" => @job.method(@job_association).call.job_id,
305
- "results" => false
306
- )
307
- unless job_report.save!
308
- @logger.log(
309
- "ActiveRecord",
310
- "Unable to save #{@algorithm} results to database.",
311
- 1,
312
- @tmp_files
313
- )
314
- end
315
- @logger.log(
316
- "NCBI Blast",
317
- "#{@algorithm} report empty.",
318
- 0,
319
- @tmp_files
320
- )
321
- end
322
-
323
- #
324
- # Parse and save Blast results.
325
- #
326
- # Parse the Blast XML output and save results.
327
- #
328
- def parse_and_save_results
329
- @results = false # Did the xml contain results?
330
-
331
- if File.size(@out) > 0
332
- report = Bio::Blast::XmlIterator.new(@out)
333
- report.to_enum.each do |iteration|
334
-
335
- @data = {}
336
-
337
- @data[:query] = iteration.query_def.split(" ").first
338
- @data[:query_len] = iteration.query_len
339
-
340
- iteration.each do |hit|
341
- @data[:hit_display_id], @data[:hit_def] = format_hit(hit.hit_def)
342
-
343
- @data[:hit_id] = hit.hit_id
344
- @data[:hit_accession] = hit.accession
345
- @data[:hit_len] = hit.len
346
-
347
- hit.each do |hsp|
348
- @data[:hsp_num] = hsp.hsp_num
349
- @data[:bit_score] = hsp.bit_score
350
- @data[:score] = hsp.score
351
- @data[:evalue] = hsp.evalue
352
- @data[:query_from] = hsp.query_from
353
- @data[:query_to] = hsp.query_to
354
- @data[:hit_from] = hsp.hit_from
355
- @data[:hit_to] = hsp.hit_to
356
- @data[:query_frame] = hsp.query_frame
357
- @data[:hit_frame] = hsp.hit_frame
358
- @data[:identity] = hsp.identity
359
- @data[:positive] = hsp.positive
360
- @data[:gaps] = hsp.gaps
361
- @data[:align_len] = hsp.align_len
362
- @data[:qseq] = hsp.qseq
363
- @data[:hseq] = hsp.hseq
364
- @data[:midline] = hsp.midline
365
-
366
- if @data[:bit_score] &&
367
- (@data[:bit_score].to_i > @min_score.to_i)
368
- @results = true
369
- save_hsp_results
370
- end
371
-
372
- end
373
- end
374
- end
375
- end
376
-
377
- save_empty_results unless @results
378
- end
379
-
380
- #
381
- # Group record ids of hsps belonging to the same query and hit_accession.
382
- #
383
- def add_hps_groups_to_reports
384
- groups = {}
385
-
386
- query_hit_acc = @job.method(
387
- @job_report_association
388
- ).call.select("DISTINCT query, hit_accession")
389
-
390
- # Populate the hash of hashes with correct query and hit_accession.
391
- query_hit_acc.each do |q|
392
- groups[q.query] = {}
393
- end
394
- query_hit_acc.each do |q|
395
- groups[q.query][q.hit_accession] = []
396
- end
397
-
398
- # Group record ids.
399
- reports = @job.method(@job_report_association).call.order("id ASC")
400
- reports.each do |r|
401
- if groups[r.query][r.hit_accession]
402
- groups[r.query][r.hit_accession] << r.id
403
- end
404
- end
405
-
406
- # Save hsp_group as a comma delimited string.
407
- reports.each do |r|
408
- if groups[r.query][r.hit_accession]
409
- if groups[r.query][r.hit_accession].length > 1
410
- r.hsp_group = groups[r.query][r.hit_accession].join(",")
411
- unless r.save!
412
- @logger.log(
413
- "ActiveRecord",
414
- "Unable to save #{@algorithm} results to database.",
415
- 1,
416
- @tmp_files
417
- )
418
- end
419
- end
420
- end
421
- end
422
- end
423
-
424
- #
425
- # Remove tmp files.
426
- #
427
- def remove_tmp_files
428
- `rm #{@tmp_files}` if @tmp_files
429
- end
430
-
431
- public
432
-
433
- #
434
- # Execute Blast on a given dataset.
435
- #
436
- def execute_blast
437
- generate_blast_cmd
438
- @logger.log("NCBI Blast", @cmd)
439
- system(@cmd)
440
-
441
- # Wrap these methods in a transaction to prevent premature return.
442
- @job.method(@job_report_association).call.transaction do
443
- parse_and_save_results
444
- add_hps_groups_to_reports
445
- end
446
-
447
- remove_tmp_files
448
- end
449
-
450
- end
451
- end
452
- end