Class: Shamu::Services::Service

Inherits:
Object
  • Object
show all
Includes:
Scorpion::Object
Defined in:
lib/shamu/services/service.rb

Overview

...

Well Known Methos

  • list( list_scope ) - lists all of the entities matching the requested list scope. Often apply Entities::ListScope::Paging or other filters.

    def list( list_scope )
      list_scope = UserListScope.coerce! list_scope
      entity_list Models::User.by_list_scope( list_scope ) do |record|
        scorpion.fetch UserEntity, record: record
      end
    end
    
  • lookup( *ids ) - matches a given list of ids to their entities or a Entities::NullEntity for ids that can't be found. The lookup method is typically used to resolve related resources between services - similar to associations in an ActiveRecord model. Use #entity_lookup_list to transform a list of records or external resources to a lookup list of entities.

    def lookup( *ids )
      entity_lookup_list Models::User.where( id: ids ), ids, NullEntity.for( UserEntity ) do |record|
        scorpion.fetch UserEntity, record: record
      end
    end
    
  • find( id ) - finds a single entity with the given id, raising NotFoundError if the resource cannot be found. If the service also implements lookup then this can be implemented by simply aliasing find to #find_by_lookup.

    def find( id )
      find_by_lookup( id )
    end
    
  • report( report_scope ) - Compile a report including metrics, and master/detail data that make take longer to gather than a standard list request.

    def report( report_scope = nil )
      report_scope = UserReportScope.coerce! report_scope
      scorpion.fetch UserReport, report_scope
    end
    

Instance Method Summary collapse

Instance Method Details

#cache_for(dependency_service = nil, key: :id, entity: nil, coerce: :not_set) ⇒ Entities::IdentityCache

Get the Entities::IdentityCache for the given Entities::Entity class.

Parameters:

  • dependency_service (Service#entity_class) (defaults to: nil)

    the dependent Shamu::Services::Service to cache results from. Must respond to #entity_class that returns the Entities::Entity class to cache.

  • entity (Class) (defaults to: nil)

    the type of entity that will be cached. Only required if the service manages multiple entities.

  • key (Symbol, #call) (defaults to: :id)

    the attribute on the entity, or a custom block used to obtain the cache key from an entity.

  • coerce (Symbol, #call) (defaults to: :not_set)

    a method that can be used to coerce key values to the same type (eg :to_i). If not set, automatically uses :to_i if key is an 'id' attribute.

Returns:



300
301
302
303
304
305
306
307
308
# File 'lib/shamu/services/service.rb', line 300

def cache_for( dependency_service = nil, key: :id, entity: nil, coerce: :not_set )
  coerce = coerce_method( coerce, key )
  entity ||= dependency_service
  entity = entity.entity_class if entity.respond_to?( :entity_class )

  cache_key        = [ entity, key, coerce ]
  @entity_caches ||= {}
  @entity_caches[ cache_key ] ||= scorpion.fetch( Entities::IdentityCache, coerce )
end

#cached_lookup(ids, match: :id, coerce: :not_set, entity: nil) {|missing_ids| ... } ⇒ Object

Caches the results of looking up the given ids in an Entities::IdentityCache and only fetches the records that have not yet been cached.

Examples:


def lookup( *ids )
  cached_lookup( ids ) do |missing_ids|
    entity_lookup_list( Models::User.where( id: missing_ids ), missing_ids, UserEntity::Missing )
  end
end

Parameters:

  • ids (Array)

    to fetch.

  • dependency_service (Service#entity_class)

    the dependent Shamu::Services::Service to cache results from. Must respond to #entity_class that returns the Entities::Entity class to cache.

  • entity (Class) (defaults to: nil)

    the type of entity that will be cached. Only required if the service manages multiple entities.

  • key (Symbol, #call)

    the attribute on the entity, or a custom block used to obtain the cache key from an entity.

  • coerce (Symbol, #call) (defaults to: :not_set)

    a method that can be used to coerce key values to the same type (eg :to_i). If not set, automatically uses :to_i if key is an 'id' attribute.

Yields:

  • (missing_ids)

Yield Parameters:

  • missing_ids (Array)

    that have not been cached yet.

Yield Returns:



328
329
330
331
332
333
334
335
336
337
338
# File 'lib/shamu/services/service.rb', line 328

def cached_lookup( ids, match: :id, coerce: :not_set, entity: nil, &lookup )
  coerce      = coerce_method( coerce, match )
  ids         = ids.map( &coerce ) if coerce
  cache       = cache_for( key: match, coerce: coerce, entity: entity )
  missing_ids = cache.uncached_keys( ids )

  cache_entities( cache, match, missing_ids, &lookup ) if missing_ids.any?

  entities = ids.map { |id| cache.fetch( id ) || fail( Shamu::NotFoundError ) }
  Entities::List.new( entities )
end

#entity_list(records) {|record| ... } ⇒ Entities::List

Takes a raw enumerable list of records and transforms them to a proper Entities::List.

As simple as the method is, it also serves as a hook for mixins to add additional behavior when processing lists.

If a block is not provided, looks for a method build_entities( records ) that maps a set of records to their corresponding entities.

Parameters:

  • records (Enumerable)

    the raw list of records.

Yields:

  • (record)

Yield Parameters:

  • records (Enumerable<Object>)

    the raw values from the list to transform to an Entities::Entity.

Yield Returns:

Returns:



93
94
95
96
97
98
99
100
101
# File 'lib/shamu/services/service.rb', line 93

def entity_list( records, &transformer )
  return Entities::List.new [] unless records
  unless transformer
    fail "Either provide a block or add a private method `def build_entities( records )` to #{ self.class.name }." unless respond_to?( :build_entities, true ) # rubocop:disable Metrics/LineLength
    transformer ||= method( :build_entities )
  end

  build_entity_list build_records_transform( records, &transformer )
end

#entity_lookup_list(records, ids, null_class, match: :id, coerce: :not_set) {|record| ... } ⇒ Entities::List

Match a list of records with the ids used to look up those records. Uses a Entities::NullEntity if the id doesn't have a matching record.

Examples:

def lookup( *ids )
  records = Models::Favorite.all.where( id: ids )
  entity_lookup_list records, ids, NullEntity.for( FavoriteEntity ) do |record|
    scorpion.fetch FavoriteEntity, { record: record }
  end
end

def lookup_by_name( *names )
  records = Models::Favorite.all.where( :name.in( names ) )

  entity_lookup_list records, names, NullEntity.for( FavoriteEntity ), match: :name do |record|
    scorpion.fetch FavoriteEntity, { record: record }
  end
end

def lookup_by_lowercase( *names )
  records = Models::Favorite.all.where( :name.in( names.map( &:downcase ) ) )
  matcher = ->( record ) { record.name.downcase }

  entity_lookup_list records, names, NullEntity.for( FavoriteEntity ), match: matcher do |record|
    scorpion.fetch FavoriteEntity, { record: record }
  end
end

Parameters:

  • records (Enumerable)

    matching the requested ids.

  • ids (Array<Integer>)

    of records found.

  • null_class (Class)

    to use when an id doesn't have a matching record.

  • match (Symbol, #call(record)) (defaults to: :id)

    the attribute or a Proc used to extract the id used to compare records.

  • coerce (Symbol, #call) (defaults to: :not_set)

    a method that can be used to coerce id values to the same type (eg :to_i). If not set, automatically uses :to_model_id if match is an 'id' attribute.

Yields:

  • (record)

Yield Parameters:

  • records (Enumerable<Object>)

    the raw values from the list to transform to an Entities::Entity.

Yield Returns:

Returns:



155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/shamu/services/service.rb', line 155

def entity_lookup_list( records, ids, null_class, match: :id, coerce: :not_set, &transformer )
  matcher = entity_lookup_list_matcher( match )
  coerce  = coerce_method( coerce, match )
  ids     = ids.map( &coerce ) if coerce

  list = entity_list records, &transformer
  matched = ids.map do |id|
    list.find { |e| matcher.call( e ) == id } || scorpion.fetch( null_class, id: id )
  end

  Entities::List.new( matched )
end

#find_by_lookup(id) ⇒ Entities::Entity

For services that expose a standard lookup method, find_by_lookup looks up a single entity and raises NotFoundError if the entity is nil or a Entities::NullEntity.

A find method can then be implemented in terms of the lookup method.

Examples:


class Example < Services::Service
  def lookup( *ids )
    # do something to find the entity
  end

  def find( id )
    find_by_lookup( id )
  end
end

Parameters:

  • id (Integer)

    of the entity.

Returns:



212
213
214
215
216
# File 'lib/shamu/services/service.rb', line 212

def find_by_lookup( id )
  entity = lookup( id ).first
  not_found!( id ) unless entity.present?
  entity
end

#lazy_association(id, entity_class, &block) ⇒ LazyAssociation<Entity>

Build a proxy object that delays yielding to the block until a method on the association is invoked.

Examples:

user = lazy_association 10, Users::UserEntity do
         expensive_lookup_user.find( 10 )
       end

user.id   # => 10 expensive lookup not performed
user.name # => "Trump" expensive lookup executed, cached, then
          #    method invoked on real object

Parameters:

  • id (Integer)

    of the resource.

  • entity_class (Class)

    of the resource.

Returns:



280
281
282
283
284
# File 'lib/shamu/services/service.rb', line 280

def lazy_association( id, entity_class, &block )
  return nil if id.nil?

  LazyAssociation.class_for( entity_class ).new( id, &block )
end

#lookup_association(id, service, cache, &block) ⇒ Entity

Find an associated entity from a dependent service. Attempts to efficiently handle multiple requests to lookup associations by caching all the associated entities when #lookup_association is used repeatedly when building an entity.

Examples:


def build_entities( records )
  cache = cache_for( entity: users_service )
  owner = lookup_association record.owner_id, users_service, cache do
            records.pluck( :owner_id ) if records
          end

  scorpion.fetch UserEntity, { record: record, owner: owner }
end

Parameters:

  • id (Object)

    of the associated Entities::Entity to find.

  • service (Service)

    used to locate the associated resource.

  • cache (IdentityCache)

    to store found associations.

Returns:



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/shamu/services/service.rb', line 246

def lookup_association( id, service, cache, &block )
  return unless id

  cache.fetch( id ) || begin
    if block_given? && ( ids = yield )
      service.lookup( *ids ).map do |entity|
        cache.add( entity.id, entity )
      end

      cache.fetch( id )
    else
      association = service.lookup( id ).first
      cache.add( association.id, association )
    end
  end
end