DEV Community

Cover image for πŸ’Ž ANN: omniauth-identity v3.1.5 (Hanami/ROM Support)
Peter H. Boling
Peter H. Boling

Posted on

πŸ’Ž ANN: omniauth-identity v3.1.5 (Hanami/ROM Support)

3.1.5 - 2025-10-13

  • TAG: v3.1.5
  • COVERAGE: 93.58% -- 437/467 lines in 14 files
  • BRANCH COVERAGE: 81.00% -- 81/100 branches in 14 files
  • 92.39% documented

Added

  • Adapter support for Hanami and ROM
  • Complete YARD documentation
  • kettle-dev for easier maintenance & dev tooling

The one where omniauth-identity gains a rom adapter. Since rom is the default DB adapter for Hanami, this also makes it hanami-ready.

class Identity
  include OmniAuth::Identity::Models::Rom

  # Configure the ROM container and relation using the DSL (no `self.`):
  rom_container -> { MyDatabase.rom } # accepts a proc or a container object
  rom_relation_name :identities # optional, defaults to :identities
  owner_relation_name :owners  # optional, for loading associated owner
  # Uses OmniAuth::Identity::Model.auth_key to set the auth key (defaults to :email)
  auth_key :email
  # Optional: override the password digest field name (defaults to :password_digest)
  password_field :password_digest
end
Enter fullscreen mode Exit fullscreen mode

Find a complete example after the funding info.

Support & Funding Info

I am a full-time FLOSS maintainer. If you find my work valuable I ask that you become a sponsor. Every dollar helps!

πŸ₯° Support FLOSS work πŸ₯° Get access to "Sponsors" channel on Galtzo FLOSS Discord πŸ‘‡οΈ Live Chat on Discord
Sponsor Me on Github Liberapay Goal Progress Buy me a coffee Donate at ko-fi.com Donate on PayPal Donate on Polar

As an example I'll use the code straight from the specs.

Test Harness

ROM_DB = if RUBY_ENGINE == "jruby"
  require "jdbc/sqlite3"
  require "sequel"
  Sequel.connect("jdbc:sqlite::memory:rom")
else
  require "sqlite3"
  require "sequel"
  Sequel.connect("sqlite::memory:rom")
end

require "rom"
require "rom-sql"

# Define the ROM relations
class RomTestIdentities < ROM::Relation[:sql]
  schema(:rom_test_identities) do
    attribute :id, ROM::SQL::Types::Serial
    attribute :email, ROM::SQL::Types::String
    attribute :login, ROM::SQL::Types::String
    attribute :password_digest, ROM::SQL::Types::String
    attribute :pwd_hash, ROM::SQL::Types::String
    attribute :owner_id, ROM::SQL::Types::Integer
  end
end

class RomTestOwners < ROM::Relation[:sql]
  schema(:rom_test_owners) do
    attribute :id, ROM::SQL::Types::Serial
    attribute :name, ROM::SQL::Types::String
  end
end

# Set up ROM container
ROM_CONFIG = ROM::Configuration.new(:sql, ROM_DB)
ROM_CONFIG.register_relation(RomTestIdentities)
ROM_CONFIG.register_relation(RomTestOwners)
ROM_CONTAINER = ROM.container(ROM_CONFIG)

class RomTestIdentity
  include OmniAuth::Identity::Models::Rom

  # Configure via the new DSL (no `self.`): accepts a value or a proc
  rom_container -> { ROM_CONTAINER }
  rom_relation_name :rom_test_identities
  # Use the standard Model.auth_key API to configure the auth key
  auth_key :email
  password_field :password_digest

  # Provide a simple reader for the :login attribute for specs that use a
  # non-standard auth key (e.g. `auth_key :login`). The ROM adapter stores the
  # underlying tuple in @identity_data, so delegate to it here.
  def login
    @identity_data && @identity_data[:login]
  end
end
Enter fullscreen mode Exit fullscreen mode

Test

RSpec.describe(OmniAuth::Identity::Models::Rom, :sqlite3) do
  before(:all) do
    # Create the tables
    ROM_DB.create_table(:rom_test_identities) do
      primary_key :id
      String :email, null: false
      String :password_digest, null: false
      # Add the login column to support tests that use a non-standard auth_key
      String :login
      Integer :owner_id
    end

    ROM_DB.create_table(:rom_test_owners) do
      primary_key :id
      String :name, null: false
    end
  end

  before do
    # Clear the tables before each test
    ROM_DB[:rom_test_identities].delete
    ROM_DB[:rom_test_owners].delete
  end

  describe "model", type: :model do
    subject(:model_klass) { RomTestIdentity }

    describe "::authenticate" do
      it "authenticates with correct password" do
        # Insert test data
        password_digest = BCrypt::Password.create("password")
        identity_data = {email: "test@example.com", password_digest: password_digest}
        ROM_CONTAINER.relations[:rom_test_identities].insert(identity_data)

        authenticated = model_klass.authenticate({email: "test@example.com"}, "password")
        expect(authenticated).to(be_a(RomTestIdentity))
        expect(authenticated.email).to(eq("test@example.com"))
      end

      it "authenticates with custom auth_key (login) when auth_key is set to :login" do
        original = model_klass.auth_key
        model_klass.auth_key(:login)

        begin
          # Insert test data using login field
          password_digest = BCrypt::Password.create("password")
          identity_data = {login: "bob", email: "bob@example.com", password_digest: password_digest}
          ROM_CONTAINER.relations[:rom_test_identities].insert(identity_data)

          authenticated = model_klass.authenticate({login: "bob"}, "password")
          expect(authenticated).to(be_a(RomTestIdentity))
          expect(authenticated.login).to(eq("bob"))
        ensure
          model_klass.auth_key(original)
        end
      end

      it "returns false with incorrect password" do
        # Insert test data
        password_digest = BCrypt::Password.create("password")
        identity_data = {email: "test@example.com", password_digest: password_digest}
        ROM_CONTAINER.relations[:rom_test_identities].insert(identity_data)

        authenticated = model_klass.authenticate({email: "test@example.com"}, "wrong")
        expect(authenticated).to(be(false))
      end

      it "returns false for non-existent user" do
        authenticated = model_klass.authenticate({email: "nonexistent@example.com"}, "password")
        expect(authenticated).to(be(false))
      end
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Photo by Ryunosuke Kikuno on Unsplash

Top comments (0)