CtrlK
BlogDocsLog inGet started
Tessl Logo

igmarin/hanakai-yaku

Curated library of atomic skills and personas for Hanami, dry-rb, and ROM Ruby development. Covers actions, slices, repositories, relations, changesets, providers, DI, operations, TDD, CLI, views, routing, validation, and 10 orchestration personas. Shared Ruby process skills have moved to ruby-core-skills. Uses Markdown + Front-matter architecture.

95

1.20x
Quality

95%

Does it follow best practices?

Impact

96%

1.20x

Average score across 45 eval scenarios

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

SKILL.mdskills/providers/implement-di/

name:
implement-di
license:
MIT
type:
atomic
description:
Inject dependencies through Hanami's `auto_inject` using `Deps["provider_key"]` — never call `Hanami.app["key"]` outside of providers, dependencies must be pre-registered by a provider using a descriptive dot-namespaced snake_case key, inject via constructor (no custom `initialize`), pass `instance_double` test stubs through `.new(keyword:)` in specs, validate resolution to catch `Dry::Container::Error` on unregistered keys, and access dependencies by their last key segment — for example `Deps["repos.user_repo"]` → call `user_repo`. Use when adding DI to actions, operations, or repositories. Trigger words: dependency injection, DI, auto_inject, Deps, inject, dry-system, constructor injection, test with DI.
metadata:
{"version":"1.0.0","user-invocable":"true"}

Implementing Dependency Injection

Inject dependencies through the constructor using Hanami's auto_inject — never call the container directly.

Quick Reference

  • Pattern: include Deps["provider_key"] in actions, operations, or repositories.
  • Testing: Pass test doubles through the constructor in specs.
  • Providers: Dependencies must be registered by a provider first.
  • Rule: Never call Hanami.app["key"] outside of providers.

HARD-GATE

DO NOT call the container directly (`Hanami.app["key"]`) outside of providers.
DO NOT use global state or class-level constants for dependencies.
DO inject dependencies through the constructor — use `include Deps[...]`.

Core Process

  1. Verify the provider — ensure the dependency is registered in a provider under a descriptive key.
  2. Add injection in the consuming class:
    module Api
      module Actions
        module Users
          class Create < Api::Action
            include Deps["operations.users.create_user"]
    
            def handle(req, res)
              result = create_user.call(req.params.to_h)
              # ...
            end
          end
        end
      end
    end
  3. For operations, inject repositories and other operations:
    module Users
      class CreateUser < Dry::Operation
        include Deps["repositories.user_repo", "operations.notifications.send_welcome"]
    
        def call(input)
          # ...
        end
      end
    end
  4. For repositories, inject ROM if needed:
    module Api
      module Repositories
        class UserRepo < ROM::Repository[:users]
          # Auto-injected if using auto_registration
        end
      end
    end
  5. Validate resolution — after adding the injection, confirm the key resolves correctly:
    bundle exec hanami console
    # Then in the console:
    # Hanami.app["operations.users.create_user"]  # should return the registered object
    If the key is unregistered, dry-system raises Dry::Container::Error: Nothing registered with the key. Check that the provider file exists, the key matches exactly (dot-namespaced, snake_case), and auto_registration covers the file path.
  6. Test with stubs — inject test doubles through the constructor:
    RSpec.describe Api::Actions::Users::Create do
      let(:create_user) { instance_double(Users::CreateUser, call: Dry::Monads::Success(user)) }
      # Pass each dep as a keyword arg; use instance_double (not double) so RSpec
      # validates the method exists on the real class.
      # For failure paths, return Dry::Monads::Failure(...) to test error branches.
      # For multiple deps: described_class.new(repo:, notifier:)
      let(:action) { described_class.new(create_user:) }
    
      it "calls the operation with request params" do
        result = action.call({ "user" => { "email" => "test@example.com" } })
        expect(create_user).to have_received(:call).with(hash_including("user"))
      end
    end

For testing DI patterns, see TESTING_DI.md.

Integration

SkillWhen to chain
configure-providersRegister the dependency before injecting it
load-contextDiscover existing DI conventions before adding new ones
hanami-setupPart of the project onboarding workflow

skills

providers

README.md

tile.json