After switching to rodauth I was not quite happy with my implementation of the current_account feature. So I decided to dig a bit deeper into the documentation and found a really neat solution.

I was already using dry-effects, but in a rather haphazard way. Looking at the documentation again, I decided to go the rack middleware route as well. If I put the new middleware after rodauth, I can grab the account_id from request.env[“rodauth”] and store it in the effects handler:

account_dry_effect.rb
# frozen_string_literal: true
require "dry/effects"

module MyApp
  class AccountDryEffect
    include Dry::Effects::Handler.Reader(:account)

    def initialize(app)
      @app = app
    end

    def call(env)
      with_account(detect_account(env)) do
        @app.call(env)
      end
    end

    def detect_account(env)
      if env["rodauth"].logged_in?
        _account = env["rodauth"].account_from_session

        Hanami.app["repos.account_repo"].by_id(env["rodauth"].account_id)
      end
    end
  end
end

This solves the first half of the puzzle. Getting the current account into other objects, though… That sounds like a job for dependency injection!

First, the object that is going to be injected, asically just a really small wrapper around Dry::Effects.Reader:

lib/my_app/current_account.rb
# frozen_string_literal: true

require "dry-effects"

module MyApp
  class CurrentAccount
    include Dry::Effects.Reader(:account)

    def id
      account&.id
    end

    def obj
      account
    end
  end
end

The really neat happens when we register the current_account provider to make this an injectable dependency:

config/providers.current_account.rb
Hanami.app.register_provider(:current_account) do
  start do
    register :current_account, MyApp::CurrentAccount.new
  end
end

If we need the current account somewhere, we can inject it via Deps:

account_repo.rb
# frozen_string_literal: true

module MyApp
  module Repos
    class AccountRepo < MyApp::DB::Repo
      include Deps["current_account"]

      // some code removed for brevity

      def update_locale(lang)
        accounts.where(id: current_account.id).changeset(:update, locale: lang.to_s).commit
      end

    end
  end
end

This way, it’s easy to stub out the current account in tests:

some_spec.rb
let(:account) { Factory.create(:account, password: password) }

around do |example|
  Hanami.app.container.stub("current_account", account) do
    example.run
  end
end

This feels a lot cleaner to me than putting the with_account calls all over the codebase where I needed them. I don’t know if this is the intended usage, but it’s working pretty fine so far 😁.