Class: AssetLink

Inherits:
ApplicationRecord show all
Includes:
Api::AssetLinkIo::Extensions, Uuid::Uuidable
Defined in:
app/models/asset_link.rb

Overview

AssetLink is powered by acts-as-dag Briefly, acts-as-dag attempts to implement a directed-acyclic-graph in a relational database. In order to optimize for retrieval it inserts an AssetLink record for EACH ancestor-descendant link. As a result, it is possible to retrieve ALL ancestors for a given plate in a single query. On the flip side, this makes insert operations more expensive as the graph grows.

As a result, try and avoid adding wells in to asset links, and link between Labware only.

The children,parents,ancestors,descendants methods are all Rails associations, and so can have further scopes applied to them

Examples:

Example methods

plate.children # => [<Plate: child of plate>,<Plate: child of plate>]
plate.parents # => [<Plate: parent of plate>]
plate.descendants # => [<Plate: child of plate>,<Plate: child of plate>,<Plate: grandchild of plate>]
plate.ancestors # => [<Plate: parent of plate>,<Plate: grandparent of plate>]

Retrieve all ancestors of a particular purpose

plate.ancestors.where(purpose_id: 4)

See Also:

Defined Under Namespace

Modules: Associations Classes: Job

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Uuid::Uuidable

included, #unsaved_uuid!, #uuid

Methods included from Api::AssetLinkIo::Extensions

included

Methods inherited from ApplicationRecord

alias_association, convert_labware_to_receptacle_for, find_by_id_or_name, find_by_id_or_name!

Methods included from Squishify

extended

Class Method Details

.create_edge(ancestor, descendant) ⇒ Boolean

Creates an edge between the ancestor and descendant nodes using save.

This method first attempts to find an existing link between the ancestor and descendant. If no link is found, it builds a new edge and saves it. If a link is found, it makes the link an edge and saves it.

This method is overridden to handle race conditions in finding an existing link and has_duplicates validation. It also assumes that there is a unique-together index on ancestor_id and descendant_id columns.

Parameters:

  • ancestor (Dag::Standard::EndPoint)

    The ancestor node.

  • descendant (Dag::Standard::EndPoint)

    The descendant node.

Returns:

  • (Boolean)

    Returns true if the edge is successfully created or already exists, false otherwise.

Raises:

  • (ActiveRecord::RecordNotUnique)

    Re-raises any exception if it is not a constraint violation that involves ancestor_id and descendant_id columns.



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'app/models/asset_link.rb', line 97

def self.create_edge(ancestor, descendant)
  # Two processes try to find an existing link.
  link = find_link(ancestor, descendant)
  # Either or both may find no link and try to create a new edge.
  if link.nil?
    edge = build_edge(ancestor, descendant)
    result = save_edge_or_handle_error(edge)
    return result unless result.nil? # Bubble up.
    # Losing process finds the edge created by the winning process.
    link = find_link(ancestor, descendant)
  end

  return if link.nil?

  link.make_direct
  link.changed? ? link.save : true
end

.save_edge_or_handle_error(edge) ⇒ Boolean

Saves the edge between the ancestor and descendant nodes or handles errors.

Parameters:

  • edge (AssetLink)

    The edge object containing the errors.

Returns:

  • (Boolean)

    Returns true if the edge is successfully saved, nil if the error is unique validation or constraint violation, false if the error is another validation error.

Raises:

  • (ActiveRecord::RecordNotUnique)

    Re-raises an exception if the exception caught is not a unique constraint violation.



123
124
125
126
127
128
129
130
131
132
133
134
# File 'app/models/asset_link.rb', line 123

def self.save_edge_or_handle_error(edge)
  # Winning process successfully saves the edge (direct link).
  return true if edge.save
  # has_duplicate validation may see it for the losing process before
  # hitting the DB.
  return false unless unique_validation_error?(edge) # Bubble up.
  edge.errors.clear # Clear all errors and use the existing link.
rescue ActiveRecord::RecordNotUnique => e
  # Unique constraint violation is triggered for the losing process after
  # hitting the DB.
  raise unless unique_violation_error?(edge, e) # Bubble up.
end

.unique_validation_error?(edge) ⇒ Boolean

Checks if the validation error includes a specific message indicating a unique link already exists.

Parameters:

  • edge (AssetLink)

    The edge object containing the errors.

Returns:

  • (Boolean)

    Returns true if the errors include the message “Link already exists between these points”, false otherwise.



142
143
144
# File 'app/models/asset_link.rb', line 142

def self.unique_validation_error?(edge)
  edge.errors[:base].include?('Link already exists between these points')
end

.unique_violation_error?(edge, exception) ⇒ Boolean

Checks if the unique constraint violation involves the specified columns.

Parameters:

  • edge (AssetLink)

    The edge object containing the column names.

  • exception (ActiveRecord::RecordNotUnique)

    The exception raised due to the unique constraint violation.

Returns:

  • (Boolean)

    Returns true if the exception message includes both the ancestor and descendant column names, false otherwise.



153
154
155
# File 'app/models/asset_link.rb', line 153

def self.unique_violation_error?(edge, exception)
  [edge.ancestor_id_column_name, edge.descendant_id_column_name].all? { |col| exception.message.include?(col) }
end

Instance Method Details

#destroy!Object



34
35
# File 'app/models/asset_link.rb', line 34

def destroy!
end