Skip to content

Commit 5d53d6f

Browse files
Scope the primary-key -> _id rename to the outermost document
The MongoDB convention of renaming the schema's primary-key field to `_id` was being applied recursively. Once `Conversions.from_ecto_pk/2` descended into a nested map (for example an `embeds_one` sub-document), the parent schema's `pk` value was still threaded through, so any nested field named `:id` was also rewritten to `:_id`. That meant an embedded value like %{jurisdiction: %{id: "MD", title: "Maryland"}} was stored as {jurisdiction: {_id: "MD", title: "Maryland"}} even when the embedded schema declared its own `@primary_key {:id, :string, autogenerate: false}`. Queries that filter on `jurisdiction.id` (the common shape for apps that put a string identifier on a sub-document) return nothing, and the wire format diverges from what other clients (Ruby driver, Node driver, etc.) write for the same model. The pk -> _id rename is only meaningful at the top of a document. Below that, the field names are user-controlled and should be passed through unchanged. This patch makes the recursive call inside `document/2` and `document/3` pass `nil` for the pk argument, so the rename only fires for the outermost document. `Conversions.to_ecto_pk/2` (the read path) is left as-is for now. Its rename of nested `"_id"` -> `Atom.to_string(pk)` only matters when a sub-document actually contains an `_id` field, which is uncommon in hand-written documents (Mongo only auto-adds `_id` to top-level docs). A symmetric read-side change can follow separately if there's appetite.
1 parent 6e114c0 commit 5d53d6f

1 file changed

Lines changed: 14 additions & 2 deletions

File tree

lib/mongo_ecto/conversions.ex

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,25 @@ defmodule Mongo.Ecto.Conversions do
7474

7575
defp document(doc, pk) do
7676
map(doc, fn {key, value} ->
77-
pair(key, value, pk, &from_ecto_pk(&1, pk))
77+
# Recurse with `nil` so the parent schema's primary key isn't
78+
# accidentally rewritten when it appears inside a nested document
79+
# (e.g. an `embeds_one` whose embedded schema has its own `:id`
80+
# field). Without this guard, an embed like
81+
#
82+
# %{jurisdiction: %{id: "MD", title: "Maryland"}}
83+
#
84+
# gets serialized as `jurisdiction._id`, which breaks queries that
85+
# filter on `jurisdiction.id`. The pk → _id remap is only meaningful
86+
# at the top of the document.
87+
pair(key, value, pk, &from_ecto_pk(&1, nil))
7888
end)
7989
end
8090

8191
defp document(doc, params, pk) do
8292
map(doc, fn {key, value} ->
83-
pair(key, value, pk, &inject_params(&1, params, pk))
93+
# Same rationale as `document/2`: scope the pk → _id rename to the
94+
# outer document. See the comment in `document/2`.
95+
pair(key, value, pk, &inject_params(&1, params, nil))
8496
end)
8597
end
8698

0 commit comments

Comments
 (0)