sanger/sequencescape

View on GitHub
app/resources/api/v2/pick_list_resource.rb

Summary

Maintainability
A
1 hr
Test Coverage
A
91%
# frozen_string_literal: true

module Api
  module V2
    # @todo This documentation does not yet include a detailed description of what this resource represents.
    # @todo This documentation does not yet include detailed descriptions for relationships, attributes and filters.
    # @todo This documentation does not yet include any example usage of the API via cURL or similar.
    #
    # @note Access this resource via the `/api/v2/pick_lists/` endpoint.
    #
    # Provides a JSON:API representation of {PickList}.
    #
    # For more information about JSON:API see the [JSON:API Specifications](https://jsonapi.org/format/)
    # or look at the [JSONAPI::Resources](http://jsonapi-resources.com/) package for Sequencescape's implementation
    # of the JSON:API standard.
    class PickListResource < BaseResource
      # Constants...
      PERMITTED_PICK_ATTRIBUTES = %i[source_receptacle_id study_id project_id].freeze
      PERMITTED_LABWARE_PICK_ATTRIBUTES = %i[source_labware_id source_labware_barcode study_id project_id].freeze

      # model_name / model_hint if required

      # Associations

      # Attributes
      attribute :created_at, readonly: true
      attribute :updated_at, readonly: true
      attribute :state, readonly: true
      attribute :links, readonly: true

      attribute :pick_attributes
      attribute :labware_pick_attributes, writeonly: true
      attribute :asynchronous

      # Filters

      # Custom methods
      # These shouldn't be used for business logic, and a more about
      # I/O and isolating implementation details.

      # JSON API v1.0 doesn't have native support for creating nested resources
      # in a single request. In addition, as {PickList::Pick picks} are not backed
      # by real database records yet we could expect to run into issues anyway.
      # So we just expose our pick attributes. However, as we can't expect to
      # receive actual receptacles/studies/projects over the API, we convert them
      # from the ids. The RecordCache allows us to do this with a single database
      # query.
      def pick_attributes=(picks)
        # Extract and look up records here before passing through
        cache = PickList::RecordCache::ByReceptacle.new(picks)
        @model.pick_attributes = picks.map { |pick| cache.convert(pick.permit(PERMITTED_PICK_ATTRIBUTES)) }
      rescue KeyError => e
        # We'll see this if one of the attributes passed in doesn't match an actual record,
        # such as a non-existent study id.
        raise JSONAPI::Exceptions::BadRequest, e.message
      end

      # This provides an alternative API for passing in a list of labware, either by
      # ids or barcodes. This avoids the need to make additional requests for the receptacle
      # ids. We keep this as a separate accessor to avoid the confusion of passing in a list
      # of 12 picks, and receiving more back.
      def labware_pick_attributes=(labware_picks)
        # Extract and look up records here before passing through
        cache = PickList::RecordCache::ByLabware.new(labware_picks)
        @model.pick_attributes =
          labware_picks.flat_map { |pick| cache.convert(pick.permit(PERMITTED_LABWARE_PICK_ATTRIBUTES)) }
      rescue KeyError => e
        # We'll see this if one of the attributes passed in doesn't match an actual record,
        # such as a non-existent study id.
        raise JSONAPI::Exceptions::BadRequest, e.message
      end

      def pick_attributes
        @model.pick_attributes.map do |pick|
          {
            source_receptacle_id: pick[:source_receptacle].id,
            study_id: pick[:study]&.id,
            project_id: pick[:project]&.id
          }
        end
      end

      # Class method overrides
    end
  end
end