Class: Labimotion::LinkedElement

Inherits:
Object
  • Object
show all
Defined in:
lib/labimotion/libs/linked_element.rb

Overview

Resolves the "inline attributes" of an element linked from a generic drag_element field.

A drag_element link target is identified by the stored value's el_klass:

* a generic element klass name  -> the target is a Labimotion::Element and
its selectable attributes are its own property fields (layer + field);
* a permit-target key (reaction, sample, molecule, wellplate, screen,
research_plan, device_description, cell_line) -> the target is a standard
ELN model and its selectable attributes are a curated set of database columns.

Used both to list the attributes a user can pick (#available) and to refresh the current values of a previously stored selection (#resolve).

Constant Summary collapse

SOURCE_PROPERTY =
'property'
SOURCE_COLUMN =
'column'
IS_DEFAULT =

Marks an attribute injected from the user's stored default rather than picked on this element. Stripped again before properties are persisted, so a default never silently becomes an explicit per-instance selection.

'is_default'
MODELS =

el_klass (permit-target key) => model class name

{
  'reaction' => '::Reaction',
  'sample' => '::Sample',
  'molecule' => '::Molecule',
  'wellplate' => '::Wellplate',
  'screen' => '::Screen',
  'research_plan' => '::ResearchPlan',
  'device_description' => '::DeviceDescription',
  'cell_line' => '::CelllineSample'
}.freeze
PERMIT_TARGET_LABELS =

Display names for the above, for screens that list the linkable types.

{
  'reaction' => 'Reaction', 'sample' => 'Sample', 'molecule' => 'Molecule',
  'wellplate' => 'Wellplate', 'screen' => 'Screen', 'research_plan' => 'Research Plan',
  'device_description' => 'Device Description', 'cell_line' => 'Cell Line'
}.freeze
COLUMNS =

Curated, scalar columns exposed per type: [column/method, label, unit_column?]

{
  'reaction' => [
    %w[name Name], %w[short_label], ['status', 'Status'], ['role', 'Role'],
    ['rxno', 'Reaction Type'], ['conditions', 'Conditions'],
    ['duration', 'Duration'], %w[solvent Solvent]
  ],
  'sample' => [
    %w[name Name], %w[short_label], ['external_label', 'External Label'],
    ['sum_formula', 'Sum Formula'], ['molecular_mass', 'Molecular Mass'],
    %w[purity Purity], %w[density Density],
    ['real_amount_value', 'Real Amount', 'real_amount_unit'],
    ['target_amount_value', 'Target Amount', 'target_amount_unit'],
    %w[location Location]
  ],
  'molecule' => [
    ['iupac_name', 'IUPAC Name'], ['sum_formular', 'Sum Formula'],
    ['molecular_weight', 'Molecular Weight'],
    ['exact_molecular_weight', 'Exact Molecular Weight'],
    ['cano_smiles', 'Canonical SMILES'], ['melting_point', 'Melting Point'],
    ['boiling_point', 'Boiling Point'], %w[density Density]
  ],
  'wellplate' => [
    %w[name Name], %w[short_label], ['description', 'Description'],
    %w[width Width], %w[height Height]
  ],
  'screen' => [
    %w[name Name], ['description', 'Description'], %w[result Result],
    %w[collaborator Collaborator], ['conditions', 'Conditions'],
    %w[requirements Requirements]
  ],
  'research_plan' => [
    %w[name Name], %w[short_label]
  ],
  'device_description' => [
    %w[name Name], %w[short_label], ['serial_number', 'Serial Number'],
    ['device_class', 'Device Class'], ['operation_mode', 'Operation Mode'],
    ['application_name', 'Application Name'], ['vendor_url', 'Vendor URL'],
    %w[institute Institute], %w[building Building], %w[room Room]
  ],
  'cell_line' => [
    ['material_name', 'Cell line name'], ['name', 'Name of specific sample'],
    %w[short_label], %w[description Description],
    %w[amount Amount unit], %w[passage Passage],
    %w[contamination Contamination]
  ]
}.freeze
PROPERTY_TYPES =

Property field types whose value is a scalar worth surfacing inline.

%w[
  text textarea number integer select select-multi checkbox
  datetime system-defined formula-field text-formula ontology-select
].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(el_klass, el_id, el_type = nil) ⇒ LinkedElement

Returns a new instance of LinkedElement.



98
99
100
101
102
# File 'lib/labimotion/libs/linked_element.rb', line 98

def initialize(el_klass, el_id, el_type = nil)
  @el_klass = el_klass.to_s
  @el_type = el_type.to_s
  @el_id = el_id
end

Class Method Details

.available_for_klass(el_klass) ⇒ Object

The attributes a user can pick for a klass with no element instance to read values from — the settings screen picks defaults before any link exists. Same shape as #available, minus 'value'/'value_system'.



164
165
166
167
168
169
170
# File 'lib/labimotion/libs/linked_element.rb', line 164

def self.available_for_klass(el_klass)
  el_klass = el_klass.to_s
  return column_attributes_for_klass(el_klass) if MODELS.key?(el_klass)

  klass = Labimotion::ElementKlass.find_by(name: el_klass)
  klass.present? ? property_attributes_for_klass(klass) : []
end

.permit_targetsObject

The standard ELN types a drag field can link, for a settings screen that has to enumerate them. MODELS is the authority here, not the wider permit-target list: 'element' and 'generic_grid' are permit targets but not linkable types.



218
219
220
# File 'lib/labimotion/libs/linked_element.rb', line 218

def self.permit_targets
  MODELS.keys.map { |key| { 'key' => key, 'label' => PERMIT_TARGET_LABELS[key] || key.humanize } }
end

.strip_defaults(properties) ⇒ Object

Remove attributes injected from a user's default (marked IS_DEFAULT by PropertiesEntity) from a properties hash on its way to the database. They are display-only: persisting them would freeze today's default into the element as an explicit per-instance selection, and a later change to the default would then stop reaching this link.



177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/labimotion/libs/linked_element.rb', line 177

def self.strip_defaults(properties)
  layers = properties.is_a?(Hash) ? properties[Labimotion::Prop::LAYERS] : nil
  return properties unless layers.is_a?(Hash)

  layers.each_value do |layer|
    fields = layer.is_a?(Hash) ? layer[Labimotion::Prop::FIELDS] : nil
    next unless fields.is_a?(Array)

    fields.each { |field| strip_field_defaults(field) }
  end
  properties
end

Instance Method Details

#availableObject

All attributes the user can pick, each with its current value.



139
140
141
142
143
144
# File 'lib/labimotion/libs/linked_element.rb', line 139

def available
  return generic_attributes if generic?
  return column_attributes if column_based?

  []
end

#column_based?Boolean

Returns:

  • (Boolean)


119
120
121
# File 'lib/labimotion/libs/linked_element.rb', line 119

def column_based?
  MODELS.key?(target_key)
end

#generic?Boolean

Returns:

  • (Boolean)


123
124
125
# File 'lib/labimotion/libs/linked_element.rb', line 123

def generic?
  generic_klass?(target_key)
end

#recordObject



127
128
129
130
131
132
133
134
135
136
# File 'lib/labimotion/libs/linked_element.rb', line 127

def record
  return @record if defined?(@record)

  @record =
    if column_based?
      MODELS[target_key].safe_constantize&.find_by(id: @el_id)
    elsif generic?
      Labimotion::Element.find_by(id: @el_id)
    end
end

#resolve(selected) ⇒ Object

Refresh the values/labels of a previously stored selection. Selections whose source attribute no longer exists are kept but flagged as missing; for an unknown/unsupported el_klass the selection is returned untouched.



149
150
151
152
153
154
155
156
157
158
159
# File 'lib/labimotion/libs/linked_element.rb', line 149

def resolve(selected)
  return selected unless selected.is_a?(Array)
  return selected unless generic? || column_based?

  index = available.index_by { |a| [a['source'], a['key']] }
  selected.map do |attr|
    next attr unless attr.is_a?(Hash)

    index[[attr['source'], attr['key']]] || attr.merge('value' => nil, 'missing' => true)
  end
end

#target_keyObject

drag_element keeps the linked class in el_klass; drag_sample / drag_molecule keep it in el_type (and may omit el_klass). Resolve one effective dispatch key.



106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/labimotion/libs/linked_element.rb', line 106

def target_key
  return @target_key if defined?(@target_key)

  @target_key =
    if MODELS.key?(@el_klass) || generic_klass?(@el_klass)
      @el_klass
    elsif MODELS.key?(@el_type)
      @el_type
    else
      ''
    end
end