我有时间对此进行了更深入的研究,并觉得我现在有了更好的理解。
我将按顺序列出用户在 elixir 应用中调用 Repo.insert 时发生的步骤。
步骤 1. 调用 Repo.insert
AppName.Repo.insert(%AppName.Comments{comment: "hi"})
步骤 2.AppName.Repo 模块
defmodule AppName.Repo do
use Ecto.Repo, otp_app: :app_name, adapter: adapter_name
end
(这是 phoenix 应用程序的默认设置)
use Ecto.Repo 允许在该模块中定义的所有函数在调用它的模块中使用。这意味着当我们调用 AppName.Repo.insert 时,它会进入我们的模块,看到没有定义为 insert 的函数,看到 use marco,检查该模块,看到一个名为 insert 的函数并调用该函数(这是不完全是它的工作原理,但我觉得它解释得很好)。
步骤 3. Ecto.Repo 模块
def insert(struct, opts \\ []) do
Ecto.Repo.Schema.insert(__MODULE__, struct, opts)
end
Where function is defined
第 4 步。Ecto.Repo.Schema 模块
4.1
# if a changeset was passed in
def insert(name, %Changeset{} = changeset, opts) when is_list(opts) do
do_insert(name, changeset, opts)
end
# if a struct was passed in
# This will be called in this example
def insert(name, %{__struct__: _} = struct, opts) when is_list(opts) do
do_insert(name, Ecto.Changeset.change(struct), opts)
end
Where function is defined
此步骤确保以变更集的形式传递给do_insert 的数据。
4.2
do_insert(name, Ecto.Changeset.change(struct), opts)
不粘贴整个函数,因为它很长。
Where function is defined
此函数执行大量数据操作并检查错误。如果一切顺利,它最终会调用apply 函数
4.3
defp apply(changeset, adapter, action, args) do
case apply(adapter, action, args) do # <---- Kernel.apply/3
{:ok, values} ->
{:ok, values}
{:invalid, _} = constraints ->
constraints
{:error, :stale} ->
opts = List.last(args)
case Keyword.fetch(opts, :stale_error_field) do
{:ok, stale_error_field} when is_atom(stale_error_field) ->
stale_message = Keyword.get(opts, :stale_error_message, "is stale")
changeset = Changeset.add_error(changeset, stale_error_field, stale_message, [stale: true])
{:error, changeset}
_other ->
raise Ecto.StaleEntryError, struct: changeset.data, action: action
end
end
end
Where function is defined
此apply/4 函数使用module、function name 和arguments 调用Kernel.apply/3 函数。在我们的例子中,模块是AdapterName,函数是:insert。
这就是我们的适配器发挥作用的地方:D(最终)。
第 5 步。适配器名称
上面的apply/3 函数调用将我们带到我们创建的适配器。
defmodule AdapterName do
# Inherit all behaviour from Ecto.Adapters.SQL
use Ecto.Adapters.SQL, driver: :postgrex, migration_lock: "FOR UPDATE"
end
这个模块中没有定义插入函数,但是因为它是 'using' Ecto.Adapters.SQL 让我们接下来看看这个模块。
第 6 步。Ecto.Adapters.SQL 模块
defmodule Ecto.Adapters.SQL do
...
@conn __MODULE__.Connection
...
@impl true
def insert(adapter_meta, %{source: source, prefix: prefix}, params,
{kind, conflict_params, _} = on_conflict, returning, opts) do
{fields, values} = :lists.unzip(params)
sql = @conn.insert(prefix, source, fields, [fields], on_conflict, returning)
Ecto.Adapters.SQL.struct(adapter_meta, @conn, sql, :insert, source, [], values ++ conflict_params, kind, returning, opts)
end
...
end
@conn 被定义为module attribute 并且只是current calling module (MODULE) + .Connection。
调用模块,如第 5 点所述,是 AdapterName
这意味着在insert 函数中,下面这行...
@conn.insert(prefix, source, fields, [fields], on_conflict, returning)
与
相同
AdapterName.Connection.insert(prefix, source, fields, [fields], on_conflict, returning)
由于我们的adapter 与postgres adapter 相同,因此它会将我们带到下一个函数。
步骤 7.AdapterName.Connection
def insert(prefix, table, header, rows, on_conflict, returning) do
values =
if header == [] do
[" VALUES " | intersperse_map(rows, ?,, fn _ -> "(DEFAULT)" end)]
else
[?\s, ?(, intersperse_map(header, ?,, "e_name/1), ") VALUES " | insert_all(rows, 1)]
end
["INSERT INTO ", quote_table(prefix, table), insert_as(on_conflict),
values, on_conflict(on_conflict, header) | returning(returning)]
end
Where the function is defined
为了在已经太长的答案中保存一些文本,我不会详细说明。这个函数实际上并不接受我们传递给Repo.insert 的参数(回到第一组)。
如果我们想编辑参数,我们需要在 AdapterName 模块中进行。我们需要定义自己的 insert 函数,使其不再调用第 6 步中定义的 insert 函数。
步骤 8. AdapterName - 定义我们自己的插入。
为简单起见,我们将把第 6 步中定义的 insert 复制到我们的 AdapterName 模块中。然后我们可以修改该函数以更新我们认为合适的参数。
如果我们这样做,我们最终会得到一个类似...的函数
def insert(adapter_meta, %{source: source, prefix: prefix}, params, on_conflict, returning, opts) do
Keyword.replace!(params, :comment, "I am the adapter and I control this database. Hahaha (evil laugh)") # <---- changing the comment like we wanted :D
{kind, conflict_params, _} = on_conflict
{fields, values} = :lists.unzip(params)
sql = @conn.insert(prefix, source, fields, [fields], on_conflict, returning)
Ecto.Adapters.SQL.struct(adapter_meta, @conn, sql, :insert, source, [], values ++ conflict_params, kind, returning, opts)
end
这现在插入了我们最初想要的不同值。
希望有人觉得这很有帮助。