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
    

Dependencies collapse

Instance Method Summary collapse

Instance Attribute Details

#loggerShamu::Logger

Returns the IO to dump logging info to.

Returns:



71
# File 'lib/shamu/services/service.rb', line 71

attr_dependency :logger, Shamu::Logger

Instance Method Details

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

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

Parameters:

  • 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:



282
283
284
285
286
287
288
# File 'lib/shamu/services/service.rb', line 282

def cache_for( key: :id, entity: nil, coerce: :not_set )
  coerce = coerce_method( coerce, key )

  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.

  • 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:



308
309
310
311
312
313
314
315
316
317
318
# File 'lib/shamu/services/service.rb', line 308

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:

  • record (Object)

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

Yield Returns:

Returns:



108
109
110
111
112
113
114
115
116
# File 'lib/shamu/services/service.rb', line 108

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

  Entities::List.new LazyTransform.new( 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:

  • record (Object)

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

Yield Returns:

Returns:



162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/shamu/services/service.rb', line 162

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

#error(attribute, message) ⇒ ErrorResult

Return an error #result from a service request.

Returns:

  • (ErrorResult)


350
351
352
353
354
# File 'lib/shamu/services/service.rb', line 350

def error( *args )
  Result.new.tap do |r|
    r.errors.add( *args )
  end
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:

Raises:



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

def find_by_lookup( id )
  entity = lookup( id ).first
  raise Shamu::NotFoundError unless entity.present?
  entity
end

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

Perform a lazy #lookup_association and only load the entity if its actually dereferenced by the caller.

Parameters:

  • id (Object)

    of the associated Entities::Entity to find.

  • service (Service)

    used to locate the associated resource.

Returns:



265
266
267
268
269
# File 'lib/shamu/services/service.rb', line 265

def lazy_association( id, service, &block )
  LazyAssociation.new( id ) do
    lookup_association id, service, &block
  end
end

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

Find an associated entity from a dependent service. Attempts to effeciently 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_entity( record, records = nil )
  owner = lookup_association record.owner_id, users_service 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.

Returns:



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/shamu/services/service.rb', line 240

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

  cache = cache_for( entity: service )
  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

#result(*values, request: nil, entity: nil) ⇒ Result

Parameters:

  • values (Array<Object,#errors>)

    an array of objects that represent the result of the service call. If they respond to #errors those errors will be included in #errors on the result object itself.

  • request (Request)

    submitted to the service. If :not_set, uses the first Request object found in the values.

  • entity (Entities::Entity)

    submitted to the service. If :not_set, uses the first Entity object found in the values.

Returns:



340
341
342
# File 'lib/shamu/services/service.rb', line 340

def result( *args )
  Result.new( *args )
end