Can I have with has_one also access to the original column?

I am using Ecto 3.
I have the following two models with has_one relation between them

defmodule MyApp.Foo do
  use Ecto.Schema
  import Ecto.Changeset
  alias MyApp.Bar

  schema "foos" do
    field :name, :string
    has_one :bar, Bar

    timestamps()
  end

  @doc false
  def changeset(foo, attrs) do
    foo
    |> cast(attrs, [:name])
    |> validate_required([:name])
    |> cast_assoc(:bar, required: false)
  end
end

defmodule MyApp.Bar do
  use Ecto.Schema
  import Ecto.Changeset
  alias MyApp.Foo

  schema "bars" do
    field :name, :string

    has_many :foos, Foo, on_delete: :nilify_all

    timestamps()
  end

  @doc false
  def changeset(bar, attrs) do
    bar
    |> cast(attrs, [:name])
    |> validate_required([:name])
  end
end

I have problem to get access to the original column that links Foo to Bar.
I have some context in which I do not have bar loaded when I have foo, but I cannot do foo.bar_id.
The belongs_to has the option for define_field: false and to have the key as field :bar_id, Ecto.UUID, however this option is not available for has_one. Is there another way to expose it and to allow code as follow.

if Ecto.assoc_loaded?(foo.bar) do
    foo.bar
else
    foo.bar_id
end

Any ideas how can achieve that and keep has_one :bar, Bar

The relationship should likely be belongs_to <-> has_one/has_many, not has_one <-> has_many.

The relation is has_one, because it can be null and the key is in the bar_id is in Foo.
Anyway, I found work around with using different field name as follow

defmodule MyApp.Foo do
  schema "foos" do
    field :name, :string
    field :internal_bar_id, Ecto.UUID, source: :bar_id
    has_one :bar, Bar

    timestamps()
  end
end

Actually I end up with using the same name as this

defmodule MyApp.Foo do
  schema "foos" do
    field :name, :string
    field :bar_id, Ecto.UUID, source: :bar_id
    has_one :bar, Bar

    timestamps()
  end
end

I use this bar_id only as read field.

Then Foo should define belongs_to :bar, Bar, not has_one :bar, Bar.

belongs_to is for the side with the whatever_id column and has_one/has_many for the other end of the relationship.

2 Likes

I see, it works as well without this additional mapping.

defmodule MyApp.Foo do
  schema "foos" do
    field :name, :string
    belongs_to :bar, Bar

    timestamps()
  end
end