AlbertGazizov/ioc_rb

View on GitHub
lib/ioc_rb/bean_factory.rb

Summary

Maintainability
A
25 mins
Test Coverage
require 'ioc_rb/scopes'
require 'ioc_rb/scopes/singleton_scope'
require 'ioc_rb/scopes/prototype_scope'
require 'ioc_rb/scopes/request_scope'

# Instantiates beans according to their scopes
class IocRb::BeanFactory
  attr_reader :const_loader

  # Constructor
  # @param beans_metadata_storage [BeansMetadataStorage] storage of bean metadatas
  def initialize(const_loader, beans_metadata_storage)
    @const_loader           = const_loader
    @beans_metadata_storage = beans_metadata_storage
    @singleton_scope        = IocRb::Scopes::SingletonScope.new(self)
    @prototype_scope        = IocRb::Scopes::PrototypeScope.new(self)
    @request_scope          = IocRb::Scopes::RequestScope.new(self)
  end

  # Get bean from the container by it's +name+.
  # According to the bean scope it will be newly created or returned already
  # instantiated bean
  # @param [Symbol] bean name
  # @return bean instance
  # @raise MissingBeanError if bean with the specified name is not found
  def get_bean(name)
    bean_metadata = @beans_metadata_storage.by_name(name)
    unless bean_metadata
      raise IocRb::Errors::MissingBeanError, "Bean with name :#{name} is not defined"
    end
    get_bean_with_metadata(bean_metadata)
  end

  # Get bean by the specified +bean metadata+
  # @param [BeanMetadata] bean metadata
  # @return bean instance
  def get_bean_with_metadata(bean_metadata)
    get_scope_by_metadata(bean_metadata).get_bean(bean_metadata)
  end

  # Create new bean instance according
  # to the specified +bean_metadata+
  # @param [BeanMetadata] bean metadata
  # @return bean instance
  # @raise MissingBeanError if some of bean dependencies are not found
  def create_bean_and_save(bean_metadata, beans_storage)
    if bean_metadata.bean_class.is_a?(Class)
      bean_class = bean_metadata.bean_class
    else
      bean_class = const_loader.load_const(bean_metadata.bean_class)
      bean_metadata.fetch_attrs!(bean_class)
    end
    bean = bean_metadata.instance ? bean_class.new : bean_class
    if bean_metadata.has_factory_method?
      set_bean_dependencies(bean, bean_metadata)
      bean = bean.send(bean_metadata.factory_method)
      beans_storage[bean_metadata.name] = bean
    else
      # put to container first to prevent circular dependencies
      beans_storage[bean_metadata.name] = bean
      set_bean_dependencies(bean, bean_metadata)
    end

    bean
  end

  # Delete bean from the container by it's +name+.
  # @param [Symbol] bean name
  # @raise MissingBeanError if bean with the specified name is not found
  def delete_bean(name)
    bean_metadata = @beans_metadata_storage.by_name(name)
    unless bean_metadata
      raise IocRb::Errors::MissingBeanError, "Bean with name :#{name} is not defined"
    end
    get_scope_by_metadata(bean_metadata).delete_bean(bean_metadata)
  end

  private

  def set_bean_dependencies(bean, bean_metadata)
    bean_metadata.attrs.each do |attr|
      bean_metadata = @beans_metadata_storage.by_name(attr.ref)
      unless bean_metadata
        raise IocRb::Errors::MissingBeanError, "Bean with name :#{attr.ref} is not defined, check #{bean.class}"
      end
      case bean_metadata.scope
      when :singleton
        bean.send("#{attr.name}=", get_bean(attr.ref))
      when :prototype
        bean.instance_variable_set(:@_ioc_rb_bean_factory, self)
        bean.define_singleton_method(attr.name) do
          @_ioc_rb_bean_factory.get_bean(attr.ref)
        end
      when :request
        bean.instance_variable_set(:@_ioc_rb_bean_factory, self)
        bean.define_singleton_method(attr.name) do
          @_ioc_rb_bean_factory.get_bean(attr.ref)
        end
      end
    end
  end

  def get_scope_by_metadata(bean_metadata)
    case bean_metadata.scope
    when :singleton
      @singleton_scope
    when :prototype
      @prototype_scope
    when :request
      @request_scope
    else
      raise IocRb::Errors::UnsupportedScopeError, "Bean with name :#{bean_metadata.name} has unsupported scope :#{bean_metadata.scope}"
    end
  end

end