Class: LeadItem

Inherits:
ApplicationRecord show all
Includes:
Housekeeping, Shared::IsData
Defined in:
app/models/lead_item.rb

Overview

A LeadItem relates an otu to a lead for the purpose of tracking which otus from an initial set of otus for a key are still under consideration at a particular stage of the key.

Instance Attribute Summary collapse

Class Method Summary collapse

Methods included from Shared::IsData

#errors_excepting, #full_error_messages_excepting, #identical, #is_community?, #is_destroyable?, #is_editable?, #is_in_use?, #is_in_users_projects?, #metamorphosize, #similar

Methods included from Housekeeping

#has_polymorphic_relationship?

Methods inherited from ApplicationRecord

transaction_with_retry

Instance Attribute Details

#lead_idinteger

id of the lead to which otu_id is attached

Returns:

  • (integer)


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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'app/models/lead_item.rb', line 17

class LeadItem < ApplicationRecord
  include Housekeeping
  include Shared::IsData

  acts_as_list scope: [:lead_id, :project_id]

  belongs_to :otu, inverse_of: :lead_items
  belongs_to :lead, inverse_of: :lead_items

  has_one :taxon_name, through: :otu

  validates :otu, :lead, presence: true

  def self.batch_add_otus_for_lead(lead_id, otu_ids, project_id, user_id)
    attributes = otu_ids.map { |otu_id|
      {
        otu_id:,
        lead_id:,
        project_id:,
        created_by_id: user_id,
        updated_by_id: user_id
      }
    }

    LeadItem.insert_all(attributes)
  end

  def self.move_items(items_scope, lead)
    items_scope.update_all(lead_id: lead.id)
  end

  # Transfers any items on leaf-node descendants of source to a new lead.
  # @param target [Lead or nil] A lead to add the descendant lead_items to; if
  #   nil a new lead is created.
  # @return [Lead or nil] If items exist, the lead now holding those items.
  def self.consolidate_descendant_items(lead, target = nil)
    # Both lead and lead_item have `position`, which .leaves orders by, so need
    # to remove that.
    items =
      lead.leaves.unscope(:order).joins(:lead_items).pluck('lead_items.id')

    return nil if items.empty?

    items_lead = target || Lead.create!(text:
      'PLACEHOLDER LEAD TO HOLD OTU OPTIONS FROM A DELETED SUBTREE'
    )

    self.move_items(LeadItem.where(id: items), items_lead)

    items_lead
  end

  def self.add_otu_index_for_lead(lead, otu_id, exclusive)
    begin
      Lead.transaction do
        if exclusive
          LeadItem.where(lead_id: lead.sibling_ids, otu_id:).destroy_all
        end

        LeadItem.create!(lead_id: lead.id, otu_id:)
      end
    rescue ActiveRecord::RecordNotDestroyed => e
      lead.errors.add(:base, "Destroy sibling LeadItems failed! '#{e}'")
      return false
    rescue ActiveRecord::RecordInvalid => e
      lead.errors.add(:base, "New LeadItem creation failed! '#{e}'")
      return false
    end

    true
  end

  def self.remove_otu_index_for_lead(lead, otu_id)
    count = LeadItem
      .where(otu_id:, lead_id: lead.self_and_siblings.map(&:id)).count

    if count == 1
      lead.errors.add(:base, "Can't destroy last lead item for otu #{otu_id}!")
      return false
    end

    begin
      LeadItem.where(lead_id: lead.id, otu_id:).destroy_all
    rescue ActiveRecord::RecordNotDestroyed => e
      lead.errors.add(:base, "Destroy LeadItem for lead #{lead.id}, otu #{otu_id} failed! '#{e}'")
      return false
    end

    true
  end

  def self.add_items_to_child_lead(parent_lead, otu_ids)
    child_to_add_to = nil
    parent_lead.children.to_a.reverse.each do |c|
      if c.children.exists?
        next
      else
        # We add to the rightmost available child.
        child_to_add_to ||= c
      end
    end

   if child_to_add_to == nil
      parent_lead.errors.add(:base, 'No available child to add otu to!')
      return false
    end

    existing = child_to_add_to.lead_items.to_a.map(&:otu_id)
    otu_ids = otu_ids - existing

    a = otu_ids.map { |id| { lead_id: child_to_add_to.id, otu_id: id } }
    # TODO: return failures and already existing?
    LeadItem.create(a)
    true
  end

end

#otu_idinteger

id of an otu which should be associated with lead_id

Returns:

  • (integer)


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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'app/models/lead_item.rb', line 17

class LeadItem < ApplicationRecord
  include Housekeeping
  include Shared::IsData

  acts_as_list scope: [:lead_id, :project_id]

  belongs_to :otu, inverse_of: :lead_items
  belongs_to :lead, inverse_of: :lead_items

  has_one :taxon_name, through: :otu

  validates :otu, :lead, presence: true

  def self.batch_add_otus_for_lead(lead_id, otu_ids, project_id, user_id)
    attributes = otu_ids.map { |otu_id|
      {
        otu_id:,
        lead_id:,
        project_id:,
        created_by_id: user_id,
        updated_by_id: user_id
      }
    }

    LeadItem.insert_all(attributes)
  end

  def self.move_items(items_scope, lead)
    items_scope.update_all(lead_id: lead.id)
  end

  # Transfers any items on leaf-node descendants of source to a new lead.
  # @param target [Lead or nil] A lead to add the descendant lead_items to; if
  #   nil a new lead is created.
  # @return [Lead or nil] If items exist, the lead now holding those items.
  def self.consolidate_descendant_items(lead, target = nil)
    # Both lead and lead_item have `position`, which .leaves orders by, so need
    # to remove that.
    items =
      lead.leaves.unscope(:order).joins(:lead_items).pluck('lead_items.id')

    return nil if items.empty?

    items_lead = target || Lead.create!(text:
      'PLACEHOLDER LEAD TO HOLD OTU OPTIONS FROM A DELETED SUBTREE'
    )

    self.move_items(LeadItem.where(id: items), items_lead)

    items_lead
  end

  def self.add_otu_index_for_lead(lead, otu_id, exclusive)
    begin
      Lead.transaction do
        if exclusive
          LeadItem.where(lead_id: lead.sibling_ids, otu_id:).destroy_all
        end

        LeadItem.create!(lead_id: lead.id, otu_id:)
      end
    rescue ActiveRecord::RecordNotDestroyed => e
      lead.errors.add(:base, "Destroy sibling LeadItems failed! '#{e}'")
      return false
    rescue ActiveRecord::RecordInvalid => e
      lead.errors.add(:base, "New LeadItem creation failed! '#{e}'")
      return false
    end

    true
  end

  def self.remove_otu_index_for_lead(lead, otu_id)
    count = LeadItem
      .where(otu_id:, lead_id: lead.self_and_siblings.map(&:id)).count

    if count == 1
      lead.errors.add(:base, "Can't destroy last lead item for otu #{otu_id}!")
      return false
    end

    begin
      LeadItem.where(lead_id: lead.id, otu_id:).destroy_all
    rescue ActiveRecord::RecordNotDestroyed => e
      lead.errors.add(:base, "Destroy LeadItem for lead #{lead.id}, otu #{otu_id} failed! '#{e}'")
      return false
    end

    true
  end

  def self.add_items_to_child_lead(parent_lead, otu_ids)
    child_to_add_to = nil
    parent_lead.children.to_a.reverse.each do |c|
      if c.children.exists?
        next
      else
        # We add to the rightmost available child.
        child_to_add_to ||= c
      end
    end

   if child_to_add_to == nil
      parent_lead.errors.add(:base, 'No available child to add otu to!')
      return false
    end

    existing = child_to_add_to.lead_items.to_a.map(&:otu_id)
    otu_ids = otu_ids - existing

    a = otu_ids.map { |id| { lead_id: child_to_add_to.id, otu_id: id } }
    # TODO: return failures and already existing?
    LeadItem.create(a)
    true
  end

end

#project_idInteger

the project ID

Returns:

  • (Integer)


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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'app/models/lead_item.rb', line 17

class LeadItem < ApplicationRecord
  include Housekeeping
  include Shared::IsData

  acts_as_list scope: [:lead_id, :project_id]

  belongs_to :otu, inverse_of: :lead_items
  belongs_to :lead, inverse_of: :lead_items

  has_one :taxon_name, through: :otu

  validates :otu, :lead, presence: true

  def self.batch_add_otus_for_lead(lead_id, otu_ids, project_id, user_id)
    attributes = otu_ids.map { |otu_id|
      {
        otu_id:,
        lead_id:,
        project_id:,
        created_by_id: user_id,
        updated_by_id: user_id
      }
    }

    LeadItem.insert_all(attributes)
  end

  def self.move_items(items_scope, lead)
    items_scope.update_all(lead_id: lead.id)
  end

  # Transfers any items on leaf-node descendants of source to a new lead.
  # @param target [Lead or nil] A lead to add the descendant lead_items to; if
  #   nil a new lead is created.
  # @return [Lead or nil] If items exist, the lead now holding those items.
  def self.consolidate_descendant_items(lead, target = nil)
    # Both lead and lead_item have `position`, which .leaves orders by, so need
    # to remove that.
    items =
      lead.leaves.unscope(:order).joins(:lead_items).pluck('lead_items.id')

    return nil if items.empty?

    items_lead = target || Lead.create!(text:
      'PLACEHOLDER LEAD TO HOLD OTU OPTIONS FROM A DELETED SUBTREE'
    )

    self.move_items(LeadItem.where(id: items), items_lead)

    items_lead
  end

  def self.add_otu_index_for_lead(lead, otu_id, exclusive)
    begin
      Lead.transaction do
        if exclusive
          LeadItem.where(lead_id: lead.sibling_ids, otu_id:).destroy_all
        end

        LeadItem.create!(lead_id: lead.id, otu_id:)
      end
    rescue ActiveRecord::RecordNotDestroyed => e
      lead.errors.add(:base, "Destroy sibling LeadItems failed! '#{e}'")
      return false
    rescue ActiveRecord::RecordInvalid => e
      lead.errors.add(:base, "New LeadItem creation failed! '#{e}'")
      return false
    end

    true
  end

  def self.remove_otu_index_for_lead(lead, otu_id)
    count = LeadItem
      .where(otu_id:, lead_id: lead.self_and_siblings.map(&:id)).count

    if count == 1
      lead.errors.add(:base, "Can't destroy last lead item for otu #{otu_id}!")
      return false
    end

    begin
      LeadItem.where(lead_id: lead.id, otu_id:).destroy_all
    rescue ActiveRecord::RecordNotDestroyed => e
      lead.errors.add(:base, "Destroy LeadItem for lead #{lead.id}, otu #{otu_id} failed! '#{e}'")
      return false
    end

    true
  end

  def self.add_items_to_child_lead(parent_lead, otu_ids)
    child_to_add_to = nil
    parent_lead.children.to_a.reverse.each do |c|
      if c.children.exists?
        next
      else
        # We add to the rightmost available child.
        child_to_add_to ||= c
      end
    end

   if child_to_add_to == nil
      parent_lead.errors.add(:base, 'No available child to add otu to!')
      return false
    end

    existing = child_to_add_to.lead_items.to_a.map(&:otu_id)
    otu_ids = otu_ids - existing

    a = otu_ids.map { |id| { lead_id: child_to_add_to.id, otu_id: id } }
    # TODO: return failures and already existing?
    LeadItem.create(a)
    true
  end

end

Class Method Details

.add_items_to_child_lead(parent_lead, otu_ids) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'app/models/lead_item.rb', line 108

def self.add_items_to_child_lead(parent_lead, otu_ids)
  child_to_add_to = nil
  parent_lead.children.to_a.reverse.each do |c|
    if c.children.exists?
      next
    else
      # We add to the rightmost available child.
      child_to_add_to ||= c
    end
  end

 if child_to_add_to == nil
    parent_lead.errors.add(:base, 'No available child to add otu to!')
    return false
  end

  existing = child_to_add_to.lead_items.to_a.map(&:otu_id)
  otu_ids = otu_ids - existing

  a = otu_ids.map { |id| { lead_id: child_to_add_to.id, otu_id: id } }
  # TODO: return failures and already existing?
  LeadItem.create(a)
  true
end

.add_otu_index_for_lead(lead, otu_id, exclusive) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'app/models/lead_item.rb', line 69

def self.add_otu_index_for_lead(lead, otu_id, exclusive)
  begin
    Lead.transaction do
      if exclusive
        LeadItem.where(lead_id: lead.sibling_ids, otu_id:).destroy_all
      end

      LeadItem.create!(lead_id: lead.id, otu_id:)
    end
  rescue ActiveRecord::RecordNotDestroyed => e
    lead.errors.add(:base, "Destroy sibling LeadItems failed! '#{e}'")
    return false
  rescue ActiveRecord::RecordInvalid => e
    lead.errors.add(:base, "New LeadItem creation failed! '#{e}'")
    return false
  end

  true
end

.batch_add_otus_for_lead(lead_id, otu_ids, project_id, user_id) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'app/models/lead_item.rb', line 30

def self.batch_add_otus_for_lead(lead_id, otu_ids, project_id, user_id)
  attributes = otu_ids.map { |otu_id|
    {
      otu_id:,
      lead_id:,
      project_id:,
      created_by_id: user_id,
      updated_by_id: user_id
    }
  }

  LeadItem.insert_all(attributes)
end

.consolidate_descendant_items(lead, target = nil) ⇒ Lead or nil

Transfers any items on leaf-node descendants of source to a new lead.

Parameters:

  • target (Lead or nil) (defaults to: nil)

    A lead to add the descendant lead_items to; if nil a new lead is created.

Returns:

  • (Lead or nil)

    If items exist, the lead now holding those items.



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'app/models/lead_item.rb', line 52

def self.consolidate_descendant_items(lead, target = nil)
  # Both lead and lead_item have `position`, which .leaves orders by, so need
  # to remove that.
  items =
    lead.leaves.unscope(:order).joins(:lead_items).pluck('lead_items.id')

  return nil if items.empty?

  items_lead = target || Lead.create!(text:
    'PLACEHOLDER LEAD TO HOLD OTU OPTIONS FROM A DELETED SUBTREE'
  )

  self.move_items(LeadItem.where(id: items), items_lead)

  items_lead
end

.move_items(items_scope, lead) ⇒ Object



44
45
46
# File 'app/models/lead_item.rb', line 44

def self.move_items(items_scope, lead)
  items_scope.update_all(lead_id: lead.id)
end

.remove_otu_index_for_lead(lead, otu_id) ⇒ Object



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'app/models/lead_item.rb', line 89

def self.remove_otu_index_for_lead(lead, otu_id)
  count = LeadItem
    .where(otu_id:, lead_id: lead.self_and_siblings.map(&:id)).count

  if count == 1
    lead.errors.add(:base, "Can't destroy last lead item for otu #{otu_id}!")
    return false
  end

  begin
    LeadItem.where(lead_id: lead.id, otu_id:).destroy_all
  rescue ActiveRecord::RecordNotDestroyed => e
    lead.errors.add(:base, "Destroy LeadItem for lead #{lead.id}, otu #{otu_id} failed! '#{e}'")
    return false
  end

  true
end