Using dry-effects to implement current_account
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
endThis 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
endThe 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
endIf 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
endThis 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
endThis 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 😁.