Using Ecto, I’ve been able to attach to legacy database tables and I’ve been able to define a custom primary key, e.g. “something_id”, but the name of the key doesn’t get aliased as “id”. This behavior is different than the other fields defined using the “scope” option.
Consider the following modules:
defmodule Something.SomethingCtx.SomethingBase do
defmacro __using__(_) do
quote do
use Ecto.Schema
@primary_key {:legacy_id, :integer, autogenerate: false}
end
end
end
defmodule Something.SomethingCtx.Something do
use Something.SomethingCtx.SomethingBase
import Ecto.Changeset
alias Something.SomethingCtx.Something
@derive {Phoenix.Param, key: :legacy_id}
# Weird table name
schema "legacy_weird_tablename" do
field :foo, :integer, [source: :legacy_foo]
field :bar, :string, [source: :poorly_named_column]
end
@doc false
def changeset(%Something{} = menuitem, attrs) do
menuitem
|> cast(attrs, [:legacy_id, :foo, :bar])
|> validate_required([:legacy_id, :foo, :bar])
end
end
By using the “source” option, now I can internally refer to the legacy column names using a nice, short, name (:foo and :bar in the above example, instead of legacy_foo or poorly_named_column.
However, I still need to use “legacy_id”. E.g. in my JSON view, I have to reference the something.legacy_id property instead of having a nice “id” alias.
Is it possible to “alias” the custom primary key?
(And secondarily, did I do the above correctly? I’m pretty shaky here, but so far I’ve been able to attach to legacy database tables using code like the above).
Well, I figured out my own problem, so I’m going to post the solution here as a reference.
First, creating the “base” module was not necessary – although the Ecto docs demonstrate that technique for other cases, it’s not necessary just because your database table uses a non-standard primary key. Here’s what the module looks like, pared down for simplicity:
defmodule MyApp.MyCtx.Myschema do
use Ecto.Schema
import Ecto.Changeset
@primary_key {:something_id, :integer, autogenerate: false, source: :something_id}
@derive {Phoenix.Param, key: :something_id}
#
schema "legacy_table_name" do
field :id, :integer, [source: :something_id] # <--- seems redundant, but it exposes the primary key column as "id"
field :foo, :string, [source: :legacy_foo]
# ... etc ...
end
# ...changeset, etc...
end
Adding the @primary_key {...} bit makes things work, but your views will still need to be adjusted because the record object won’t have a standard “id” property. So the trick was to simply add “id” as a field and reference its “source” column like we do for all the other fields. That way your views can use the “id” property as expected (the legacy something_id property will be there too, but meh… it’s not hurting anything)
Hope that my solution represents a valid approach – it seems to have worked.
Ah, that does work, thank you! That’s much simpler.
I think it might be nice to add a couple more examples to the Ecto docs for these types of use-cases. Surely I’m not the only one who was confused by this and failed to arrive at a “best-practices” solution. Thoughts?