app/models/service_store.rb
# A ServiceStore is a collection of umlaut service definitions, with identifiers.
#
# There is one default global one that is typically used; originally this
# was all neccesarily global state, but we refactored to be an actual object,
# although there's still a default global one used, with class methods
# that delegate to it, for backwards compat and convenience.
#
# By default, a ServiceStore loads service definitions from
# Rails.root/config/umlaut_services.yml . Although in testing,
# or for future architectural expansion, services can be manually supplied
# instead.
#
# A ServiceStore instantiates services from definitions, by id,
# ServiceStore.instantiate_service!("our_sfx")
#
# A ServiceStore's cached service definitions can be reset, useful in
# testing: ServiceStore.reset!
#
# They'll then be lazily reloaded on next access, unless manually set.
class ServiceStore
@@global_service_store = ServiceStore.new
def self.global_service_store
@@global_service_store
end
# certain class methods all default to global default ServiceStore,
# for global ServiceStore. For convenience, and backwards-compat.
[ :config, :"config=", :service_definitions, :service_definition_for,
:'instantiate_service!', :'reset!' ].each do |method|
self.define_singleton_method(method) do |*args|
global_service_store.send(method, *args)
end
end
# Returns complete hash loaded from config/umlaut_services.yml
# Passes through ERB first, allowing ERB in umlaut_services.yml
def config
# cache hash loaded from YAML, ensure it has the keys we expect.
unless defined? @services_config_list
yaml_path = File.expand_path("config/umlaut_services.yml", Rails.root)
@services_config_list = if File.exists? yaml_path
YAML::load(ERB.new(File.open(yaml_path).read).result)
else
{}
end
@services_config_list["default"] ||= {}
@services_config_list["default"]["services"] ||= {}
end
return @services_config_list
end
# Manually set a config hash, as would normally be found serialized
# in config/umlaut_services.yml. Useful in testing. All keys
# should be strings!!
#
# Needs to have the somewhat cumbersome expected structure:
# ["default"]["services"] => { "service_id" => definition_hash }
def config=(hash)
reset!
@services_config_list = hash
end
# Returns hash keyed by unique service name, value service definition
# hash.
def service_definitions
unless defined? @service_definitions
@service_definitions = {}
config.each_pair do |group_name, group|
if group["services"]
# Add the group name to each service
# in the group
group["services"].each_pair do |service_id, service|
service["group"] = group_name
end
# Merge the group's services into the service definitions.
@service_definitions.merge!( group["services"] )
end
end
# set service_id key in each based on hash key
@service_definitions.each_pair do |key, hash|
hash["service_id"] = key
end
end
return @service_definitions
end
# Reset cached service definitions. They'll be lazily loaded when asked for,
# typically by being looked up from disk again. Typically used for testing.
def reset!
remove_instance_variable "@service_definitions" if defined? @service_definitions
remove_instance_variable "@services_config_list" if defined? @services_config_list
end
def service_definition_for(service_id)
return service_definitions[service_id]
end
# pass in array of service group ids. eg. ["group1", "-group2"]
#
# Returns a list of service definition hashes.
#
# Start with default group(s). Remove any that are mentioned with "-group_id" in
# the group list, add in any that are mentioned with "group_id"
def determine_services(specified_groups = [])
services = {}
activated_service_groups = self.config.select do |group_id, group_definition|
((group_id == "default" || group_definition["default"] == true) ||
specified_groups.include?(group_id)) &&
! specified_groups.include?("-#{group_id}")
end
activated_service_groups.each_pair do |group_id, group_definition|
services.merge! (group_definition["services"] || {})
end
# Remove any disabled services
services.reject! {|service_id, hash| hash && hash["disabled"] == true}
return services
end
# pass in string unique key OR a service definition hash,
# and a current UmlautRequest.
# get back instantiated Service object.
#
# If string service_id is passed in, but is not defined in application services,
# a ServiceStore::NoSuchService exception will be raised.
def instantiate_service!(service, request)
definition = service.kind_of?(Hash) ? service : service_definition_for(service.to_s)
raise NoSuchService.new("Service '#{service}'' does not exist in umlaut-services.yml") if definition.nil?
className = definition["type"] || definition["service_id"]
classConst = Kernel.const_get(className)
service = classConst.new(definition)
service.request = request
return service
end
class NoSuchService < RuntimeError ; end
end