Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
ed0f7d3
Adding some documentation!
dasunpubudumal Aug 14, 2025
5b9431e
[skip ci] Adding some documentation!
dasunpubudumal Aug 14, 2025
87503b4
[skip ci] Adding some documentation!
dasunpubudumal Aug 14, 2025
7484922
Starting Settions and Configuration V2 mixins.
dasunpubudumal Aug 15, 2025
e9a64df
Fix linting issues
dasunpubudumal Aug 18, 2025
1fbe003
Fix linting issues
dasunpubudumal Aug 18, 2025
239458f
Redoing the settings module
dasunpubudumal Aug 18, 2025
0f356ff
Redoing the settings module
dasunpubudumal Aug 18, 2025
d9f17e6
[skip ci] Adding comments
dasunpubudumal Aug 18, 2025
741672a
[skip ci] Adding more configuration stuff
dasunpubudumal Aug 18, 2025
31e5575
[skip ci] Adding more configuration stuff
dasunpubudumal Aug 19, 2025
887a183
[skip ci] Adding more configuration stuff
dasunpubudumal Aug 19, 2025
a68b52d
Refactor search_helper to use children method for accessing purposes
dasunpubudumal Aug 19, 2025
f50a9f4
Enhance CustomConfiguration to dynamically define getter and setter m…
dasunpubudumal Aug 19, 2025
f9d574f
Refactor CreationBehaviour and Settings to improve configuration hand…
dasunpubudumal Aug 19, 2025
ed2ac0c
Refactor accessor method in Settings module to handle children for co…
dasunpubudumal Aug 19, 2025
950887b
Refactor compatible_purposes method to use lazy enumeration for impro…
dasunpubudumal Aug 19, 2025
f50a9bb
Refactor search_helper to remove unnecessary children method calls fo…
dasunpubudumal Aug 19, 2025
b25ba84
Changing test.yml
dasunpubudumal Aug 20, 2025
a8b7910
Changing the metaprogramming logic to dynamically add mutator methods
dasunpubudumal Aug 20, 2025
d01d39f
Changing the metaprogramming logic to dynamically add mutator methods
dasunpubudumal Aug 20, 2025
27a16c5
Updating search helper
dasunpubudumal Aug 20, 2025
497b718
Trying to fix the donor pooling yaml frustratingly!!!
dasunpubudumal Aug 20, 2025
831e592
Fixing yet another config issue.
dasunpubudumal Aug 20, 2025
77b16ff
Fixing yet another config issue.
dasunpubudumal Aug 21, 2025
d17c47e
Fixing yet another config issue.
dasunpubudumal Aug 21, 2025
acf5e2e
Fixing yet another config issue.
dasunpubudumal Aug 21, 2025
90c0e17
Fixing yet another config issue.
dasunpubudumal Aug 21, 2025
20c1e74
Fixing yet another config issue.
dasunpubudumal Aug 21, 2025
1fe05e6
Fixing yet another config issue.
dasunpubudumal Aug 21, 2025
4172ad5
Merge remote-tracking branch 'origin/develop' into Y25-283---As-a-tea…
dasunpubudumal Aug 26, 2025
5ad1a96
Fixing yet another config issue.
dasunpubudumal Aug 26, 2025
d6150bc
Trying CI out again
dasunpubudumal Aug 27, 2025
0eab969
Fix prettier
dasunpubudumal Aug 27, 2025
1adecd1
Fixing yet another config issue
dasunpubudumal Aug 27, 2025
07634e9
Fixing a linting issue
dasunpubudumal Aug 27, 2025
9de6f71
Fixing yet another config issue
dasunpubudumal Aug 27, 2025
c0104e1
Fixing yet another config issue
dasunpubudumal Aug 27, 2025
03c14af
Merge remote-tracking branch 'origin/develop' into Y25-283---As-a-tea…
dasunpubudumal Aug 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/helpers/search_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@

module SearchHelper # rubocop:todo Style/Documentation
def stock_plate_uuids
Settings.purposes.select { |_uuid, config| config.input_plate }.keys
Settings.purposes.select { |_uuid, config| config[:input_plate] }.keys
end

def self.stock_plate_names
Settings.purposes.values.select(&:input_plate).map(&:name)
Settings.purposes.values.select { |item| item[:input_plate] == true }.pluck(:name)
end

# Returns purpose names of stock plates using stock_plate flag instead of input_plate.
def self.stock_plate_names_with_flag
Settings.purposes.values.select(&:stock_plate).map(&:name)
Settings.purposes.values.select { |item| item[:stock_plate] == true }.pluck(:name)
end

def self.purpose_config_for_purpose_name(purpose_name)
Expand Down
8 changes: 4 additions & 4 deletions app/models/concerns/presenters/creation_behaviour.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def compatible_tube_rack_purposes

# Eventually this will end up on our labware_creators/creations module
def purposes_of_type(type)
compatible_purposes.select { |_uuid, purpose| purpose.asset_type == type }
compatible_purposes.select { |_uuid, purpose| purpose[:asset_type] == type }
end

def construct_buttons(scope)
Expand All @@ -34,9 +34,9 @@ def construct_buttons(scope)
parent_uuid: uuid,
parent: labware,
purpose_uuid: purpose_uuid,
name: purpose_settings.name,
type: purpose_settings.asset_type,
filters: purpose_settings.filters || {}
name: purpose_settings[:name],
type: purpose_settings[:asset_type],
filters: purpose_settings[:filters] || {}
)
end
.force
Expand Down
7 changes: 4 additions & 3 deletions app/models/concerns/presenters/robot_controlled.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ def suitable_robots
end

def suitable_for_labware?(config)
config
.beds
.detect { |_bed, bed_config| bed_config.purpose == purpose_name && bed_config.states.include?(labware.state) }
config[:beds]
.detect do |_bed, bed_config|
bed_config[:purpose] == purpose_name && bed_config[:states].include?(labware.state)
end
.present?
end

Expand Down
2 changes: 1 addition & 1 deletion app/models/concerns/presenters/submission_behaviour.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Include in a presenter to add support for creating a submission
module Presenters::SubmissionBehaviour
def each_submission_option
purpose_config.submission_options.each do |button_text, options|
purpose_config[:submission_options].each do |button_text, options|
submission_options = options.to_hash
submission_options[:asset_groups] = asset_groups
submission_options[:labware_barcode] = labware.labware_barcode.human
Expand Down
4 changes: 2 additions & 2 deletions app/models/labware_creators/merged_plate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def labware_wells
# @return [Array] Purpose name strings.
#
def expected_source_purposes
Settings.purposes.dig(@purpose_uuid, :merged_plate).source_purposes
Settings.purposes.dig(@purpose_uuid, :merged_plate)[:source_purposes]
end

#
Expand All @@ -38,7 +38,7 @@ def expected_source_purposes
# @return [String] Some descriptive text.
#
def help_text
Settings.purposes.dig(@purpose_uuid, :merged_plate).help_text
Settings.purposes.dig(@purpose_uuid, :merged_plate)[:help_text]
end

def barcodes=(barcodes)
Expand Down
2 changes: 1 addition & 1 deletion app/models/labware_creators/multi_stamp_tubes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def request_hash(transfer)
# Returns: a hash containing the submission parameters
# Adds: errors if there is more than one submission specified
def configured_params
submission_options_from_config = purpose_config.submission_options
submission_options_from_config = purpose_config[:submission_options]

# if there's more than one appropriate submission, we can't know which one to choose,
# so don't create one.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ def generate_control_locations_from_purpose_config
control_locations = []
list_of_controls.count.times do |control_index|
control = list_of_controls[control_index]
if control.fixed_location?
if control[:fixed_location].present?
# use the location specified in the purpose config for this control
control_locations.push(control.fixed_location)
control_locations.push(control[:fixed_location])
else
# sample a random parent well and fetch its location (child not created yet)
control_locations.push(parent.wells.sample.position['name'])
Expand All @@ -92,13 +92,13 @@ def generate_control_locations_from_purpose_config

def check_control_rules_from_config(control_locations)
list_of_rules.each do |rule|
case rule.type
case rule[:type]
when 'not'
# locations must not match this combination of wells (order is important)
return false if control_locations == rule.value
return false if control_locations == rule[:value]
when 'well_exclusions'
# locations must not be in this list well locations (exclusions)
return false if control_locations.any? { |location| rule.value.include?(location) }
return false if control_locations.any? { |location| rule[:value].include?(location) }
else
# check for unrecognised rule type
raise StandardError, "Unrecognised control locations rule type from purpose config #{rule.type}"
Expand Down
6 changes: 3 additions & 3 deletions app/models/presenters/plate_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,14 @@ def csv_file_links
links =
purpose_config
.fetch(:file_links, [])
.select { |link| can_be_enabled?(link&.states) }
.select { |link| can_be_enabled?(link[:states]) }
.map do |link|
[
link.name,
link[:name],
[
:limber_plate,
:export,
{ id: link.id, limber_plate_id: human_barcode, format: :csv, **link.params || {} }
{ id: link[:id], limber_plate_id: human_barcode, format: :csv, **link[:params] || {} }
]
]
end
Expand Down
2 changes: 1 addition & 1 deletion app/models/presenters/presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def content_title
end

def default_printer
@default_printer ||= Settings.printers[purpose_config.default_printer_type]
@default_printer ||= Settings.printers[purpose_config[:default_printer_type]]
end

def default_label_count
Expand Down
12 changes: 6 additions & 6 deletions app/models/presenters/tube_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ def custom_metadata_fields
end

def sequencescape_submission
return nil if purpose_config.submission.empty?
return nil if purpose_config[:submission].empty?

s = SequencescapeSubmission.new(purpose_config.submission.to_hash.merge(assets: [labware.uuid]))
s = SequencescapeSubmission.new(purpose_config[:submission].to_hash.merge(assets: [labware.uuid]))
yield s if block_given?
s
end
Expand All @@ -72,15 +72,15 @@ def transfer_volumes?
def csv_file_links
purpose_config
.fetch(:file_links, [])
.select { |link| can_be_enabled?(link&.states) }
.select { |link| can_be_enabled?(link[:states]) }
.map do |link|
format_extension = link.format || 'csv'
format_extension = link[:format] || 'csv'
[
link.name,
link[:name],
[
:limber_tube,
:tubes_export,
{ id: link.id, limber_tube_id: human_barcode, format: format_extension, **link.params || {} }
{ id: link[:id], limber_tube_id: human_barcode, format: format_extension, **link[:params] || {} }
]
]
end
Expand Down
5 changes: 3 additions & 2 deletions app/models/presenters/tube_rack_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ def tube_failing_applicable?
def csv_file_links
purpose_config
.fetch(:file_links, [])
.select { |link| can_be_enabled?(link&.states) }
.select { |link| can_be_enabled?(link[:states]) }
.map do |link|
[link.name, [:limber_tube_rack, :tube_racks_export, { id: link.id, limber_tube_rack_id: uuid, format: :csv }]]
[link[:name],
[:limber_tube_rack, :tube_racks_export, { id: link[:id], limber_tube_rack_id: uuid, format: :csv }]]
end
end

Expand Down
86 changes: 86 additions & 0 deletions config/initializers/custom_configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# frozen_string_literal: true

# Parent configuration mixin.
module Settings
# Configuration for the application, including loaders for various configurations.

# This will create class-level functions so that they could be accessed like
# Settings.searches, Settings.transfer_templates, etc.
class CustomConfiguration
def initialize(config_hash)
@children = config_hash.with_indifferent_access

# When you invoke, Settings.configuration.searches in settings.rb, it would invoke static
# methods declared through this block.
@children.each do |key, value|
# For every getter method, it creates a method that returns an Item instance
# with the key and value from the configuration hash.
define_singleton_method(key) { Item.new(key, value) }
# For every setter method, it creates a method that sets the value in the children hash.
# This allows you to do Settings.configuration.searches = new_value.
define_singleton_method("#{key}=") { |new_value| @children[key] = new_value }
end
end

# Configuration item. It recursively builds a structure
# that allows for nested configurations to be accessed easily.
# It includes Enumerable to allow iteration over its children.
#
# Each child can be a nested configuration or a simple value.
# This allows for a flexible and dynamic configuration structure.
#
# @example
# config = Settings.configuration
# config.searches.some_child.some_grandchild
# config.searches.some_child.some_grandchild = new_value
#
class Item
include Enumerable

attr_reader :children, :configuration

# rubocop:disable Metrics/MethodLength
def initialize(configuration, children = {})
@children = children
@configuration = configuration

# Dynamically defines getter and setter methods for each child key in the configuration.
#
# For each key-value pair in the children hash:
# - If the value is a nested hash (ActiveSupport::HashWithIndifferentAccess),
# it creates a getter method that returns a new Item instance, allowing for recursive access.
# - If the value is a simple value, it creates a getter method that returns the value directly.
# - For every key, it also creates a setter method to update the value in the children hash.
#
# This enables flexible, dot-notation access and assignment for deeply nested configuration structures.
#
# Example:
# config = Settings.configuration
# config.searches.some_child.some_grandchild # Access nested value
# config.searches.some_child.some_grandchild = val # Set nested value
children.each do |key, child|
if child.is_a?(ActiveSupport::HashWithIndifferentAccess)
next if respond_to?(key)

child_item = Item.new(configuration, child)
define_singleton_method(key) { child_item }
else
define_singleton_method(key) { child }
end
define_singleton_method("#{key}=") { |new_value| @children[key] = new_value }
end
end
# rubocop:enable Metrics/MethodLength

delegate :[]=, to: :@children

def fetch(key, default = nil)
@children.fetch(key, default)
end

def each(...)
children.each(...)
end
end
end
end
Loading
Loading