Class: LeadsController

Inherits:
ApplicationController show all
Includes:
DataControllerConfiguration::ProjectDataControllerConfiguration
Defined in:
app/controllers/leads_controller.rb

Constant Summary

Constants included from ProjectsHelper

ProjectsHelper::CLASSIFIER, ProjectsHelper::CLASSIFIER_ANNOTATION

Instance Method Summary collapse

Methods included from DataControllerConfiguration::ProjectDataControllerConfiguration

#annotator_params

Methods inherited from ApplicationController

#set_time_zone

Methods included from RedirectHelper

#destroy_redirect

Methods included from RequestType

#json_request?

Methods included from LogRecent

#log_user_recent_route

Methods included from Cookies

#digest_cookie, #digested_cookie_exists?

Methods included from Whitelist

#whitelist_constantize

Methods included from ProjectsHelper

#cumulative_gb_per_year, #cumulative_projects_created_per_year, #document_cumulative_gb_per_year, #document_gb_per_year, #gb_per_year, #image_cumulative_gb_per_year, #image_gb_per_year, #invalid_object, #project_classification, #project_initials, #project_link, #project_login_link, #project_matches, #project_tag, #projects_list, #projects_search_form, #sound_cumulative_gb_per_year, #sound_gb_per_year, #taxonworks_classification, #week_in_review_graphs

Methods included from Api::Intercept

#intercept_api

Methods included from TokenAuthentication

#intercept_project, #intercept_user, #intercept_user_or_project, #project_token_authenticate, #token_authenticate

Instance Method Details

#add_childrenObject

POST /leads/1/add_children.json



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'app/controllers/leads_controller.rb', line 157

def add_children
  num_to_add = params[:num_to_add]
  if num_to_add < 1 || num_to_add > 2
    @lead.errors.add(:add_children,
      "request must be for 1 or 2, was '#{num_to_add}'"
    )
    render json: @lead.errors, status: :unprocessable_content
    return
  end

  # currently infers num_to_add
  @lead.add_children(sessions_current_project_id, sessions_current_user_id)
  expand_lead
  render action: :show, status: :created, location: @lead
end

#api_eliminated_otusObject



393
394
395
396
397
398
399
400
# File 'app/controllers/leads_controller.rb', line 393

def api_eliminated_otus
  lead = Lead.find(params[:id])

  @otus = lead.eliminated_otus

  render '/leads/api/v1/otus'

end

#api_indexObject



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'app/controllers/leads_controller.rb', line 52

def api_index
  # Default excludes virtual (simple) keys so existing API consumers keep
  # the shape they expect. Callers that want simple keys pass is_virtual=true;
  # is_virtual=all returns both.
  is_virtual =
    if params[:is_virtual].to_s == 'all'
      :all
    elsif params[:is_virtual].present?
      ActiveRecord::Type::Boolean.new.cast(params[:is_virtual])
    else
      false
    end

  @leads = Lead
    .roots_with_data(sessions_current_project_id, true, is_virtual:)
    .where(is_public: true)

  render '/leads/api/v1/index'
end

#api_keyObject



427
428
429
430
# File 'app/controllers/leads_controller.rb', line 427

def api_key
  @lead = Lead.where(project_id: sessions_current_project_id, is_public: true).find(params.require(:id))
  render '/leads/api/v1/key'
end

#api_remaining_otusObject



384
385
386
387
388
389
390
391
# File 'app/controllers/leads_controller.rb', line 384

def api_remaining_otus
  lead = Lead.find(params[:id])

  @otus = lead.remaining_otus

  render '/leads/api/v1/otus'

end

#autocompleteObject



402
403
404
405
406
407
# File 'app/controllers/leads_controller.rb', line 402

def autocomplete
  @leads = ::Queries::Lead::Autocomplete.new(
    params.require(:term),
    project_id: sessions_current_project_id,
  ).autocomplete
end

#batch_create_lead_itemsObject

Creates a new key populated with otus from params.



433
434
435
436
437
438
439
440
441
442
443
444
# File 'app/controllers/leads_controller.rb', line 433

def batch_create_lead_items
  @lead = Lead.create!(
    params.require(:lead).permit(:text, :observation_matrix_id)
  )
  @lead.batch_populate_lead_items(params[:otu_query],
    sessions_current_project_id, sessions_current_user_id
  )

  @lead.add_children(sessions_current_project_id, sessions_current_user_id)

  render json: @lead
end

#cite_key_bootstrapObject

GET /leads/cite_key_bootstrap.json Accepts either an explicit otu_ids[] list or a nested otu_query (a Queries::Otu::Filter param hash). Returns the MRCA OTU (parent taxon) for the resulting OTU set and the embedded OTU details so the cite_key task can prefill Step 1.



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'app/controllers/leads_controller.rb', line 77

def cite_key_bootstrap
  if params[:otu_ids].present?
    otu_ids = Array(params[:otu_ids]).flatten.compact.map(&:to_i).uniq
    scope = ::Otu.where(project_id: sessions_current_project_id, id: otu_ids)
  elsif params[:otu_query].present?
    scope = ::Queries::Otu::Filter.new(params[:otu_query]).all
      .where(project_id: sessions_current_project_id)
  else
    scope = ::Otu.none
  end

  cap = 2000
  total = scope.count
  otus = scope.includes(:taxon_name).limit(cap).to_a
  taxon_name_ids = otus.map(&:taxon_name_id).compact.uniq

  mrca_taxon = ::TaxonName.mrca(taxon_name_ids)
  parent_otu = mrca_taxon &&
    ::Otu.where(project_id: sessions_current_project_id, taxon_name_id: mrca_taxon.id).first

  render json: {
    parent_otu: parent_otu && otu_payload(parent_otu),
    otus: otus.map { |o| otu_payload(o) },
    total: total,
    truncated: total > cap
  }
end

#createObject

POST /leads POST /leads.json



143
144
145
146
147
148
149
150
151
152
153
154
# File 'app/controllers/leads_controller.rb', line 143

def create
  @lead = Lead.new(lead_params)
  respond_to do |format|
    if @lead.save
      new_couplet unless @lead.is_virtual
      expand_lead
      format.json { render action: :show, status: :created, location: @lead }
    else
      format.json { render json: @lead.errors, status: :unprocessable_content}
    end
  end
end

#delete_childrenObject

For deleting all children at a particular level of the key where at most one of the children has its own children; the grandchildren, if any, are reparented.



258
259
260
261
262
263
264
265
266
267
268
269
# File 'app/controllers/leads_controller.rb', line 258

def delete_children
  respond_to do |format|
    if @lead.destroy_children
      format.json { head :no_content }
    else
      @lead.errors.add(:delete, 'failed!')
      format.json {
        render json: @lead.errors, status: :unprocessable_content
      }
    end
  end
end

#depictionsObject

POST /leads/:id/depictions.json



322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'app/controllers/leads_controller.rb', line 322

def depictions
  image_ids = params.permit(image_ids: [])[:image_ids] || []
  created = 0

  render json: { success: true, created: } if image_ids.empty?

  Depiction.transaction do
    image_ids.each do |image_id|
      depiction = Depiction.find_or_initialize_by(
        depiction_object: @lead,
        image_id: image_id
      )
      next unless depiction.new_record?

      unless depiction.save
        render json: depiction.errors, status: :unprocessable_content
        raise ActiveRecord::Rollback
      end
      created += 1
    end
  end

  render json: { success: true, created: created }
end

#destroyObject

DELETE /leads/1 DELETE /leads/1.json



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'app/controllers/leads_controller.rb', line 193

def destroy
  if @lead.parent_id
    respond_to do |format|
      flash[:error] = 'Delete aborted - you can only delete on root nodes.'
      format.html { redirect_back(fallback_location: (request.referer || root_path)) }
      format.json { head :no_content, status: :unprocessable_content }
    end
    return
  end

  begin
    @lead.transaction_nuke
    respond_to do |format|
      flash[:notice] = 'Key was successfully destroyed.'
      format.html { destroy_redirect @lead }
      format.json { head :no_content }
    end
  rescue ActiveRecord::RecordInvalid
    respond_to do |format|
      flash[:error] = 'Delete failed!'
      format.html { redirect_back(fallback_location: (request.referer || root_path)) }
      format.json { render json: @lead.errors, status: :unprocessable_content }
    end
  end
end

#destroy_childrenObject

For destroying all children at a particular level of the key where none of the children have their own children.



241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'app/controllers/leads_controller.rb', line 241

def destroy_children
  if @lead.parent_id.present?
    if @lead.destroy_children
      head :no_content
    else
      @lead.errors.add(:delete, 'failed!')
      render json: @lead.errors, status: :unprocessable_content
    end
  else
    @lead.errors.add(:destroy, "failed - can't delete the only couplet.")
    render json: @lead.errors, status: :unprocessable_content
  end
end

#destroy_simple_leadObject

DELETE /leads/1/destroy_simple_lead.json Simple (virtual) keys are flat by construction and are managed by the cite_key task. This action lets that task destroy either a species-child leaf or the entire simple key from the same endpoint. It refuses anything that isn't virtual so it can't be repurposed to remove a lead from an ordinary dichotomous couplet (which #destroy still guards against).



277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'app/controllers/leads_controller.rb', line 277

def destroy_simple_lead
  unless @lead.is_virtual
    render json: { errors: { base: ['Only virtual (simple key) leads can be destroyed here.'] } },
      status: :unprocessable_content
    return
  end

  begin
    @lead.destroy!
    head :no_content
  rescue ActiveRecord::RecordNotDestroyed, ActiveRecord::RecordInvalid
    render json: @lead.errors, status: :unprocessable_content
  end
end

#destroy_subtreeObject

A separate action from destroy to be called in different contexts: this one can be called on any lead, not just root. POST /leads/1/destroy_subtree.json



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'app/controllers/leads_controller.rb', line 222

def destroy_subtree
  parent = @lead.parent
  begin
    Lead.destroy_lead_and_descendants(
      @lead, sessions_current_project_id, sessions_current_user_id
    )
  rescue TaxonWorks::Error => e
    parent.errors.add(:error, e)
    render json: parent.errors, status: :unprocessable_content
    return
  end

  @lead = parent.reload
  expand_lead
  render :show, status: :ok, location: @lead
end

#downloadObject

GET /leads/download



419
420
421
422
423
424
425
# File 'app/controllers/leads_controller.rb', line 419

def download
  send_data Export::CSV.generate_csv(
      Lead.where(project_id: sessions_current_project_id)
    ),
    type: 'text',
    filename: "leads_#{DateTime.now}.tsv"
end

#duplicateObject

POST /leads/1/duplicate.html



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'app/controllers/leads_controller.rb', line 293

def duplicate
  respond_to do |format|
    format.html {
      if !@lead.parent_id
        if @lead.dupe
          flash[:notice] = 'Key cloned.'
        else
          flash[:error] = @lead.errors.full_messages.join('; ')
        end
      else
        flash[:error] = 'Clone aborted - you can only clone on a root node.'
      end

      redirect_to action: :list
    }
  end
end

#editObject

GET /leads/1/edit



133
134
135
136
137
138
139
# File 'app/controllers/leads_controller.rb', line 133

def edit
  if @lead.is_virtual
    redirect_to cite_key_task_path(lead_id: @lead.id)
  else
    redirect_to new_lead_task_path(lead_id: @lead.id)
  end
end

#eliminated_otusObject



375
376
377
378
379
380
381
382
# File 'app/controllers/leads_controller.rb', line 375

def eliminated_otus
  lead = Lead.find(params[:id])

  @otus = lead.eliminated_otus

  render '/leads/otus'

end

#expand_leadObject (private)



497
498
499
500
# File 'app/controllers/leads_controller.rb', line 497

def expand_lead
  @children = @lead.children
  @lead_item_otus = @lead.apportioned_lead_item_otus
end

#indexObject

GET /leads GET /leads.json



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'app/controllers/leads_controller.rb', line 13

def index
  respond_to do |format|
    format.html {
      one_week_ago = Time.now.utc.to_date - 7
      @recent_objects = Lead
        .roots_with_data(sessions_current_project_id, false, is_virtual: :all)
        .where('key_updated_at > ?', one_week_ago)
        .reorder(key_updated_at: :desc)
        .limit(10)

      render '/shared/data/all/index'
    }
    format.json {
      is_virtual =
        if params[:is_virtual].to_s == 'all'
          :all
        elsif params[:is_virtual].present?
          ActiveRecord::Type::Boolean.new.cast(params[:is_virtual])
        else
          nil
        end

      @leads = Lead.roots_with_data(
        sessions_current_project_id,
        !!params[:load_root_otus],
        is_virtual:
      )

      @leads = @leads.where(id: params[:id]) if params[:id].present?

      if params[:recent].present? && ActiveRecord::Type::Boolean.new.cast(params[:recent])
        @leads = @leads.reorder('key_updated_at DESC NULLS LAST')
      end

      @leads = @leads.page(params[:page]).per(params[:per])
    }
  end
end

#insert_coupletObject

POST /leads/1/insert_couplet.json



174
175
176
177
# File 'app/controllers/leads_controller.rb', line 174

def insert_couplet
  @lead.insert_couplet
  head :no_content
end

#insert_keyObject

POST /leads/1/insert_key.json?key_to_insert=:id



312
313
314
315
316
317
318
319
# File 'app/controllers/leads_controller.rb', line 312

def insert_key
  if !@lead.insert_key(params[:key_to_insert])
    render json: @lead.errors, status: :unprocessable_content
    return
  end

  head :no_content
end

#lead_paramsObject (private)



485
486
487
488
489
490
491
# File 'app/controllers/leads_controller.rb', line 485

def lead_params()
  params.require(:lead).permit(
    :parent_id,
    :otu_id, :text, :origin_label, :description, :redirect_id,
    :link_out, :link_out_text, :is_public, :is_virtual, :position
  )
end

#listObject



105
106
107
108
109
# File 'app/controllers/leads_controller.rb', line 105

def list
  @leads = Lead
    .roots_with_data(sessions_current_project_id, false, is_virtual: :all)
    .page(params[:page])
end

#newObject

GET /leads/new



128
129
130
# File 'app/controllers/leads_controller.rb', line 128

def new
  redirect_to new_lead_task_path
end

#new_coupletObject (private)



502
503
504
505
506
# File 'app/controllers/leads_controller.rb', line 502

def new_couplet
  2.times do
    @lead.children.create!
  end
end

#otu_payload(otu) ⇒ Hash (private)

Returns the OTU + taxon_name shape the cite_key task expects.

Returns:

  • (Hash)

    the OTU + taxon_name shape the cite_key task expects



465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
# File 'app/controllers/leads_controller.rb', line 465

def otu_payload(otu)
  tn = otu.taxon_name
  {
    id: otu.id,
    object_tag: helpers.object_tag(otu),
    taxon_name: tn && {
      id: tn.id,
      cached_html: tn.cached_html,
      cached_author_year: tn.cached_author_year,
      cached_nomenclature_date: tn.cached_nomenclature_date,
      cached_is_valid: tn.cached_is_valid,
      cached_misspelling: tn.cached_misspelling
    }
  }
end

#otusObject

GET /leads/1/otus.json



362
363
364
# File 'app/controllers/leads_controller.rb', line 362

def otus
  @otus = Otu.associated_with_key(@lead)
end

#redirect_option_textsObject

GET /leads/1/redirect_option_texts.json



112
113
114
# File 'app/controllers/leads_controller.rb', line 112

def redirect_option_texts
  @texts = @lead.redirect_options(sessions_current_project_id)
end

#remaining_otusObject



366
367
368
369
370
371
372
373
# File 'app/controllers/leads_controller.rb', line 366

def remaining_otus
  lead = Lead.find(params[:id])

  @otus = lead.remaining_otus

  render '/leads/otus'

end

#reorder_childrenObject

PATCH /leads/1/reorder_children.json



348
349
350
351
352
353
354
355
356
357
358
359
# File 'app/controllers/leads_controller.rb', line 348

def reorder_children
  begin
    @lead.reorder_children(reorder_params[:reorder_list])
  rescue TaxonWorks::Error => e
    @lead.errors.add(:reorder_failed, e.to_s)
    render json: @lead.errors, status: :unprocessable_content
    return
  end

  @leads = @lead.reload.children
  @lead_item_otus = @lead.apportioned_lead_item_otus
end

#reorder_paramsObject (private)



493
494
495
# File 'app/controllers/leads_controller.rb', line 493

def reorder_params
  params.permit(reorder_list: [])
end

#reset_lead_itemsObject



446
447
448
449
450
# File 'app/controllers/leads_controller.rb', line 446

def reset_lead_items
  @lead.reset_lead_items

  head :no_content
end

#searchObject



409
410
411
412
413
414
415
416
# File 'app/controllers/leads_controller.rb', line 409

def search
  if params[:id].blank?
    redirect_to(lead_path,
                alert: 'You must select an item from the list with a click or tab press before clicking show.')
  else
    redirect_to lead_path(params[:id])
  end
end

#set_leadObject (private)



481
482
483
# File 'app/controllers/leads_controller.rb', line 481

def set_lead
  @lead = Lead.find(params[:id])
end

#set_observation_matrixObject



452
453
454
455
456
457
458
459
460
# File 'app/controllers/leads_controller.rb', line 452

def set_observation_matrix
  if params[:observation_matrix_id].nil?
    @lead.errors.add(:observation_matrix_id, 'is required.')
    render json: @lead.errors, status: :unprocessable_content
  end

  @lead.update!(observation_matrix_id: params[:observation_matrix_id])
  head :no_content
end

#showObject

GET /leads/1 GET /leads/1.json



118
119
120
121
122
123
124
125
# File 'app/controllers/leads_controller.rb', line 118

def show
  if @lead.children.present?
    expand_lead
  else
    @children = nil
    @lead_item_otus = @lead.apportioned_lead_item_otus
  end
end

#updateObject

PATCH/PUT /leads/1.json



180
181
182
183
184
185
186
187
188
189
# File 'app/controllers/leads_controller.rb', line 180

def update
  begin
    @lead.update!(lead_params)
    @lead_item_otus = @lead.parent_id ?
      @lead.parent.apportioned_lead_item_otus :
      { parent: [], children: [] }
  rescue ActiveRecord::RecordInvalid
    render json: @lead.errors, status: :unprocessable_content
  end
end