Class: SenroUsecaser::Container

Inherits:
Object
  • Object
show all
Defined in:
lib/senro_usecaser/container.rb

Overview

DI Container with namespace support

Examples:

Basic usage

container = SenroUsecaser::Container.new
container.register(:logger, Logger.new)
container.resolve(:logger) # => Logger instance

With namespaces

container = SenroUsecaser::Container.new
container.register(:logger, Logger.new)

container.namespace(:admin) do
  register(:user_repository, AdminUserRepository.new)
end

# From admin namespace, can resolve both admin and root dependencies
container.resolve_in(:admin, :user_repository) # => AdminUserRepository
container.resolve_in(:admin, :logger)          # => Logger (from root)

Defined Under Namespace

Classes: DuplicateRegistrationError, ResolutionError

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(parent: nil) ⇒ Container

: (?parent: Container?) -> void



51
52
53
54
55
# File 'lib/senro_usecaser/container.rb', line 51

def initialize(parent: nil)
  @parent = parent
  @registrations = {} #: Hash[String, untyped]
  @current_namespace = [] #: Array[Symbol]
end

Instance Attribute Details

#parent ⇒ Object (readonly)

Returns the parent container if any

: () -> Container?



226
227
228
# File 'lib/senro_usecaser/container.rb', line 226

def parent
  @parent
end

Instance Method Details

#clear! ⇒ Object

Clears all registrations

: () -> void



231
232
233
# File 'lib/senro_usecaser/container.rb', line 231

def clear!
  @registrations.clear
end

#keys ⇒ Object

Returns all registered keys (including parent keys)

: () -> Array



209
210
211
212
213
214
# File 'lib/senro_usecaser/container.rb', line 209

def keys
  own_keys = @registrations.keys
  return own_keys unless @parent

  (own_keys + @parent.keys).uniq
end

#namespace(name) ⇒ Object

Creates a namespace scope for registering dependencies

: ((Symbol | String)) { () -> void } -> void

Examples:

container.namespace(:admin) do
  register(:user_repository, AdminUserRepository.new)

  namespace(:reports) do
    register(:generator, ReportGenerator.new)
  end
end


199
200
201
202
203
204
# File 'lib/senro_usecaser/container.rb', line 199

def namespace(name, &)
  @current_namespace.push(name.to_sym)
  instance_eval(&) # steep:ignore BlockTypeMismatch
ensure
  @current_namespace.pop
end

#own_keys ⇒ Object

Returns only keys registered in this container (excluding parent)

: () -> Array



219
220
221
# File 'lib/senro_usecaser/container.rb', line 219

def own_keys
  @registrations.keys
end

#register(key, value = nil, &block) ⇒ Object

Registers a dependency in the current namespace

: (Symbol, ?untyped) ?{ (Container) -> untyped } -> void

Examples:

With value (returns same value every time)

container.register(:logger, Logger.new)

With block (lazy evaluation, called every time, receives container)

container.register(:database) { |container| Database.connect }

Raises:

  • (ArgumentError)


82
83
84
85
86
87
88
89
90
# File 'lib/senro_usecaser/container.rb', line 82

def register(key, value = nil, &block)
  raise ArgumentError, "Provide either a value or a block, not both" if value && block
  raise ArgumentError, "Provide either a value or a block" if value.nil? && block.nil?

  full_key = build_key(key)
  check_duplicate_registration!(full_key)

  @registrations[full_key] = block || ->(_) { value }
end

#register_lazy(key, &block) ⇒ Object

Registers a lazy dependency (block is called every time on resolve)

: [T] (Symbol) { (Container) -> T } -> void

Examples:

container.register_lazy(:connection) { |c| Database.connect }

With dependency resolution

container.register_lazy(:user_repository) do |container|
  UserRepository.new(current_user: container.resolve(:current_user))
end

Raises:

  • (ArgumentError)


103
104
105
106
107
108
109
110
# File 'lib/senro_usecaser/container.rb', line 103

def register_lazy(key, &block)
  raise ArgumentError, "Block is required for register_lazy" unless block

  full_key = build_key(key)
  check_duplicate_registration!(full_key)

  @registrations[full_key] = block
end

#register_singleton(key, &block) ⇒ Object

Registers a singleton dependency (block is called once and cached)

: [T] (Symbol) { (Container) -> T } -> void

Examples:

container.register_singleton(:database) { |c| Database.connect }
container.resolve(:database) # => same instance every time

With dependency resolution

container.register_singleton(:service) do |container|
  Service.new(logger: container.resolve(:logger))
end

Raises:

  • (ArgumentError)


124
125
126
127
128
129
130
131
# File 'lib/senro_usecaser/container.rb', line 124

def register_singleton(key, &block)
  raise ArgumentError, "Block is required for register_singleton" unless block

  full_key = build_key(key)
  check_duplicate_registration!(full_key)

  @registrations[full_key] = SingletonRegistration.new(block)
end

#registered?(key) ⇒ Boolean

Checks if a dependency is registered

: (Symbol) -> bool

Returns:

  • (Boolean)


165
166
167
# File 'lib/senro_usecaser/container.rb', line 165

def registered?(key)
  registered_in?(current_namespace_path, key)
end

#registered_in?(namespace, key) ⇒ Boolean

Checks if a dependency is registered in a specific namespace or its ancestors

: ((Symbol | String | Array), Symbol) -> bool

Returns:

  • (Boolean)


172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/senro_usecaser/container.rb', line 172

def registered_in?(namespace, key)
  namespace_parts = normalize_namespace(namespace)

  (namespace_parts.length + 1).times do |i|
    current_parts = namespace_parts[0, namespace_parts.length - i] || []
    full_key = build_key_with_namespace(current_parts, key)
    return true if @registrations.key?(full_key)
  end

  # Check parent container if available
  return @parent.registered_in?(namespace, key) if @parent

  false
end

#resolve(key) ⇒ Object

Resolves a dependency from the current namespace or its ancestors

: [T] (Symbol) -> T

Examples:

container.resolve(:logger)


139
140
141
# File 'lib/senro_usecaser/container.rb', line 139

def resolve(key)
  resolve_in(current_namespace_path, key)
end

#resolve_in(namespace, key) ⇒ Object

Resolves a dependency from a specific namespace or its ancestors

: [T] ((Symbol | String | Array), Symbol) -> T

Examples:

container.resolve_in(:admin, :logger)
container.resolve_in("admin::reports", :generator)


150
151
152
153
154
155
156
157
158
159
160
# File 'lib/senro_usecaser/container.rb', line 150

def resolve_in(namespace, key)
  registration = find_registration(namespace, key)

  unless registration
    raise ResolutionError,
          "Dependency #{key.inspect} not found in namespace #{namespace.inspect} or its ancestors"
  end

  # Always invoke with self (the resolving container) for proper scoping
  invoke_registration(registration)
end

#scope(&block) ⇒ Object

Creates a scoped child container that inherits from this container

: () ?{ () -> void } -> Container

Examples:

scoped = container.scope do
  register(:current_user, current_user)
end
scoped.resolve(:current_user) # => current_user
scoped.resolve(:logger)       # => resolved from parent


67
68
69
70
71
# File 'lib/senro_usecaser/container.rb', line 67

def scope(&block)
  child = Container.new(parent: self)
  child.instance_eval(&block) if block # steep:ignore BlockTypeMismatch
  child
end