Skip to content

Postgrex encoding errors with AshOban and Multi tenancy #150

@awestbro

Description

@awestbro

Code of Conduct

  • I agree to follow this project's Code of Conduct

AI Policy

  • I agree to follow this project's AI Policy, or I agree that AI was not used while creating this issue.

Versions

elixir 1.19.4-otp-28
erlang 28.2

Operating system

macOS

Current Behavior

Defining Postgrex types like the documentation describes leads to encoding errors when creating resources with ash_oban and multitenancy.

postgrex_types.ex:

Postgrex.Types.define(
  MyApp.PostgrexTypes,
  [AshPostgres.Extensions.Vector] ++ Ecto.Adapters.Postgres.extensions(),
  []
)

config.exs:

config :my_app, MyApp.Repo, types: MyApp.PostgrexTypes

insight.ex:

defmodule MyApp.Feedback.Insight do
  require Ash.Query

  use Ash.Resource,
    otp_app: :my_app,
    domain: MyApp.Feedback,
    data_layer: AshPostgres.DataLayer,
    extensions: [AshAi, AshOban],
    authorizers: [Ash.Policy.Authorizer]
  
  multitenancy do
    strategy :context
  end

  vectorize do
    full_text do
      name :context_vector

      text fn record ->
        """
        Name: #{record.name}
        Description: #{record.description || ""}
        """
      end

      used_attributes [:name, :description]
    end

    strategy :ash_oban
    embedding_model MyApp.AI.OpenAIEmbeddingModel
    ash_oban_trigger_name :vectorize_insight_trigger
  end

  oban do
    triggers do
      trigger :vectorize_insight_trigger do
        action :ash_ai_update_embeddings
        queue :ash_ai_vectorization
        worker_read_action :read
        worker_module_name __MODULE__.AshOban.Worker.UpdateEmbeddings
        scheduler_module_name __MODULE__.AshOban.Scheduler.UpdateEmbeddings
        scheduler_cron false
        list_tenants fn -> MyApp.Repo.all_tenants() end
        timeout :timer.minutes(1)
        max_attempts 3
      end
    end
  end
    
end

Creating the resource:

insight =
      MyApp.Feedback.create_insight!(%{name: "New Insight"},
        tenant: socket.assigns.current_tenant # An organization struct
      )

Error:

[error] GenServer #PID<0.1777.0> terminating
** (Ash.Error.Unknown) 
Bread Crumbs:
  > Exception raised in: MyApp.Feedback.Insight.create

Unknown Error

* ** (Protocol.UndefinedError) protocol Jason.Encoder not implemented for MyApp.Accounts.Organization (a struct), Jason.Encoder protocol must always be explicitly implemented.

If you own the struct, you can derive the implementation specifying which fields should be encoded to JSON:

    @derive {Jason.Encoder, only: [....]}
    defstruct ...

It is also possible to encode all fields, although this should be used carefully to avoid accidentally leaking private information when new fields are added:

    @derive Jason.Encoder
    defstruct ...

Finally, if you don't own the struct you want to encode to JSON, you may use Protocol.derive/3 placed outside of any module:

    Protocol.derive(Jason.Encoder, NameOfTheStruct, only: [...])
    Protocol.derive(Jason.Encoder, NameOfTheStruct)


Got value:

    %MyApp.Accounts.Organization{
      id: "abc7c0ac-1ebd-42d6-8195-3b92081f862f",
      name: "Organization",
      subdomain: #Ash.CiString<"org">,
      logo: "/images/tenant/org_logo.jpg",
      inserted_at: ~U[2025-12-05 17:47:32.371315Z],
      updated_at: ~U[2025-12-05 17:47:32.371315Z],
      user_count: #Ash.NotLoaded<:aggregate, field: :user_count>,
      users_join_assoc: #Ash.NotLoaded<:relationship, field: :users_join_assoc>,
      organization_memberships: #Ash.NotLoaded<:relationship, field: :organization_memberships>,
      users: #Ash.NotLoaded<:relationship, field: :users>,
      __meta__: #Ecto.Schema.Metadata<:loaded, "organizations">
    }

  (jason 1.4.4) lib/jason.ex:213: Jason.encode_to_iodata!/2
  (my_app 0.1.0) deps/postgrex/lib/postgrex/type_module.ex:1084: MyApp.PostgrexTypes.encode_params/3
  (postgrex 0.21.1) lib/postgrex/query.ex:75: DBConnection.Query.Postgrex.Query.encode/3
  (db_connection 2.8.1) lib/db_connection.ex:1446: DBConnection.encode/5
  (db_connection 2.8.1) lib/db_connection.ex:1546: DBConnection.run_prepare_execute/5
  (db_connection 2.8.1) lib/db_connection.ex:769: DBConnection.parsed_prepare_execute/5
  (db_connection 2.8.1) lib/db_connection.ex:761: DBConnection.prepare_execute/4
  (ecto_sql 3.13.2) lib/ecto/adapters/postgres/connection.ex:102: Ecto.Adapters.Postgres.Connection.prepare_execute/5
  (ecto_sql 3.13.2) lib/ecto/adapters/sql.ex:1004: Ecto.Adapters.SQL.execute!/5
  (ecto_sql 3.13.2) lib/ecto/adapters/sql.ex:996: Ecto.Adapters.SQL.execute/6
  (ecto 3.13.5) lib/ecto/repo/queryable.ex:241: Ecto.Repo.Queryable.execute/4
  (ecto 3.13.5) lib/ecto/repo/queryable.ex:19: Ecto.Repo.Queryable.all/3
  (ecto 3.13.5) lib/ecto/repo/queryable.ex:163: Ecto.Repo.Queryable.one/3
  (oban 2.20.2) lib/oban/engines/basic.ex:546: Oban.Engines.Basic.fetch_job/3
  (oban 2.20.2) lib/oban/engines/basic.ex:433: Oban.Engines.Basic.insert_unique/3
  (my_app 0.1.0) lib/my_app/repo.ex:2: anonymous fn/1 in MyApp.Repo."transaction (overridable 1)"/2
  (ecto 3.13.5) lib/ecto/repo/transaction.ex:11: anonymous fn/3 in Ecto.Repo.Transaction.transact/4
  (ecto_sql 3.13.2) lib/ecto/adapters/sql.ex:1458: anonymous fn/3 in Ecto.Adapters.SQL.checkout_or_transaction/4
  (db_connection 2.8.1) lib/db_connection.ex:1046: DBConnection.transaction/3
  (oban 2.20.2) lib/oban/repo.ex:156: Oban.Repo.transaction/4
    (jason 1.4.4) lib/jason.ex:213: Jason.encode_to_iodata!/2
    (my_app 0.1.0) deps/postgrex/lib/postgrex/type_module.ex:1084: MyApp.PostgrexTypes.encode_params/3
    (postgrex 0.21.1) lib/postgrex/query.ex:75: DBConnection.Query.Postgrex.Query.encode/3
    (db_connection 2.8.1) lib/db_connection.ex:1446: DBConnection.encode/5
    (db_connection 2.8.1) lib/db_connection.ex:1546: DBConnection.run_prepare_execute/5
    (db_connection 2.8.1) lib/db_connection.ex:769: DBConnection.parsed_prepare_execute/5
    (db_connection 2.8.1) lib/db_connection.ex:761: DBConnection.prepare_execute/4
    (ecto_sql 3.13.2) lib/ecto/adapters/postgres/connection.ex:102: Ecto.Adapters.Postgres.Connection.prepare_execute/5
    (ecto_sql 3.13.2) lib/ecto/adapters/sql.ex:1004: Ecto.Adapters.SQL.execute!/5
    (ecto_sql 3.13.2) lib/ecto/adapters/sql.ex:996: Ecto.Adapters.SQL.execute/6
    (ecto 3.13.5) lib/ecto/repo/queryable.ex:241: Ecto.Repo.Queryable.execute/4
    (ecto 3.13.5) lib/ecto/repo/queryable.ex:19: Ecto.Repo.Queryable.all/3
    (ecto 3.13.5) lib/ecto/repo/queryable.ex:163: Ecto.Repo.Queryable.one/3
    (oban 2.20.2) lib/oban/engines/basic.ex:546: Oban.Engines.Basic.fetch_job/3
    (oban 2.20.2) lib/oban/engines/basic.ex:433: Oban.Engines.Basic.insert_unique/3
    (my_app 0.1.0) lib/my_app/repo.ex:2: anonymous fn/1 in MyApp.Repo."transaction (overridable 1)"/2
    (ecto 3.13.5) lib/ecto/repo/transaction.ex:11: anonymous fn/3 in Ecto.Repo.Transaction.transact/4
    (ecto_sql 3.13.2) lib/ecto/adapters/sql.ex:1458: anonymous fn/3 in Ecto.Adapters.SQL.checkout_or_transaction/4
    (db_connection 2.8.1) lib/db_connection.ex:1046: DBConnection.transaction/3
    (oban 2.20.2) lib/oban/repo.ex:156: Oban.Repo.transaction/4

I'm unsure how to debug this one - or where the right place for a fix would be. Any guidance?

Reproduction

No response

Expected Behavior

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions