class Shamu::Entities::EntityLookupService

Looks up entities from compiled {EntityPath} strings allowing references to be stored as opaque values in an external service and later looked up without knowing which services are required to look up the entities.

Useful for implementing a `node` field in a Relay GraphQL endpoint or resolving entities in an {Auditing::AuditingService audit log}.

{#lookup} maps entity types to the service class used to look up entities of that type. The services must implement the well known `#lookup` method that takes an array of ids as an argument.

If there is no entry mapping in the `entity_map` provided to the constructor then EntityLookupService will attempt to locate a service named {Entities}Service in the same namespace as the entity type.

The EntityLookupService should be configured as a per-request singleton in Scorpion with all custom entity mappings set.

“` Scorpion.prepare do

capture Shamu::Entities::EntityLookupService do |scorpion|
  scorpion.new( Shamu::Entities::EntityLookupService, { "User" => Users::ExternalUsersService }, {} )
end

end “`

Attributes

entity_map_cache[R]

Public Class Methods

new( entity_map = nil ) click to toggle source
Calls superclass method
# File lib/shamu/entities/entity_lookup_service.rb, line 33
def initialize( entity_map = nil )
  entity_map ||= {}
  @entity_map_cache = Hash.new do |hash, entity_type|
    hash[ entity_type ] = entity_map[ entity_type ] \
                          || entity_map[ entity_type.to_s ] \
                          || find_implicit_service_class( entity_type.to_s )
  end
  super()
end

Public Instance Methods

ids( entities ) click to toggle source

Map the given entities to their {EntityPath} that can later be used to {#lookup} the given entity.

# File lib/shamu/entities/entity_lookup_service.rb, line 53
def ids( entities )
  Array.wrap( entities ).map do |entity|
    EntityPath.compose_entity_path( [ entity ] )
  end
end
lookup( *ids ) click to toggle source

Look up all the entities from their composed {EntityPath}.

@param [Array<String>] ids an array of {EntityPath} strings. @return [EntityList<Entity>] the entities in the same order as the given ids.

# File lib/shamu/entities/entity_lookup_service.rb, line 74
def lookup( *ids ) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
  types = {}

  # Decompose entity paths and group by entity type
  ids.each do |composed|
    path = EntityPath.decompose_entity_path( composed )
    fail "Only root entities can be restored" unless path.size == 1
    type, id = path.first

    types[ type ] ||= { paths: [], ids: [] }

    types[ type ][ :paths ] << composed
    types[ type ][ :ids ]   << id
  end

  # Short-circuit if we only have one entity type
  if types.size == 1
    service_class = service_class_for( types.first.first )
    service = scorpion.fetch service_class

    return service.lookup( *types.first.last[ :ids ] )
  end

  # Lookup all entities in batches
  hydrated = types.map do |type, map|
    service_class = service_class_for( type )
    service = scorpion.fetch service_class

    entities = service.lookup( *map[ :ids ] )

    Hash[ map[ :paths ].zip( entities ) ]
  end

  # Map found entities back to their original input order
  mapped = ids.map do |id|
    found = nil
    hydrated.each do |map|
      break if found = map[ id ]
    end

    found
  end

  Entities::List.new mapped
end
record_ids( ids ) click to toggle source

Map the encoded ids back to their raw record IDs discarding any type information.

@param [Array<String>] ids an array of ids encoded with {#ids}.

# File lib/shamu/entities/entity_lookup_service.rb, line 63
def record_ids( ids )
  Array.wrap( ids ).map do |id|
    EntityPath.decompose_entity_path( id ).first.last.to_model_id
  end
end
service_class_for( entity_type ) click to toggle source

Gets the class of the service used to look up entities of the given type. Use a scorpion to get an instance of the service class.

@return [Shamu::Services::Service] a service that implements `#lookup`.

# File lib/shamu/entities/entity_lookup_service.rb, line 47
def service_class_for( entity_type )
  entity_map_cache[ entity_type.to_sym ]
end

Private Instance Methods

find_implicit_service_class( entity_type ) click to toggle source
# File lib/shamu/entities/entity_lookup_service.rb, line 124
def find_implicit_service_class( entity_type )
  namespace = entity_type.deconstantize
  type      = entity_type.demodulize

  service_name = [
                   namespace,
                   "#{ type.pluralize }Service"
                 ].join( "::" )

  service_name.constantize
end