Hi, I’m running into a strange issue with custom primary key (autogenerated), has_many relation and preload.
I’m setting up 2 tables, one with a custom primary key that’s autogenerated.
Table 1
defmodule Pstspkr.Repo.Migrations.CreateUrls do
use Ecto.Migration
def change do
create table(:urls, primary_key: false) do
add :short_url, :string, primary_key: true
add :long_url, :string
timestamps()
end
end
end
Schema 1
defmodule Pstspkr.Url do
use Ecto.Schema
import Ecto.Changeset
alias Pstspkr.Ecto.ShortUrlId
@primary_key {:short_url, ShortUrlId, [autogenerate: true]}
@derive {Phoenix.Param, key: :short_url}
schema "urls" do
field :long_url, :string, null: false
has_many :visits, Pstspkr.Visit
timestamps()
end
@doc false
def changeset(url, attrs) do
url
|> cast(attrs, [:long_url])
|> validate_required([:long_url])
end
end
Table 2
defmodule Pstspkr.Repo.Migrations.CreateVisits do
use Ecto.Migration
def change do
create table(:visits) do
add :short_url, references("urls", column: :short_url, type: :string)
add :ip, :string
timestamps()
end
end
end
Schema 2
defmodule Pstspkr.Visit do
use Ecto.Schema
import Ecto.Changeset
schema "visits" do
field :ip, :string
belongs_to :url, Pstspkr.Url, [foreign_key: :short_url, references: :short_url, type: :string] #, define_field: false, type: :string
timestamps()
end
@doc false
def changeset(visit, attrs) do
visit
|> cast(attrs, [:short_url, :ip)
|> validate_required([:short_url, :ip])
end
end
Inserting in both tables goes fine, and in Postgress it all looks good.
But when I query this
Repo.all(from u in Url, preload: :visits)
I get an error
[debug] QUERY OK source="urls" db=0.4ms
SELECT u0."short_url", u0."long_url", u0."inserted_at", u0."updated_at" FROM "urls" AS u0 []
** (Ecto.QueryError) deps/ecto/lib/ecto/association.ex:621: field `url_short_url` in `where` does not exist in schema Pstspkr.Visit in query:
from v0 in Pstspkr.Visit,
where: v0.url_short_url in ^["AmlkTnzI", "Q6lRF4CG", "4zZFhO5B", "af9uxDVF", "nseqc29G", "_qvrCdok"],
order_by: [asc: v0.url_short_url],
select: {v0.url_short_url, v0}
But that is strange to me, because I specifically specified the foreign key in the Visits schema:
foreign_key: :short_url
I can see what goes wrong here internally, because looking at the docs foreign_key
defaults to _id
. So without a custom primary key one would do: belongs_to :url, Pstspkr.Url
And then the foreign_key would default to url_id
It looks like if you specify a custom foreign_key
, Ecto still concats the two: url
+ _
+ short_url
into url_short_url
, even though I’ve specified the foreign_key to be short_url
.
Am I doing something wrong or is this a bug?
Thanks!