我有一个凤凰应用程序,在所有模型中都使用timestamps
。但是我想扩展时间戳,这样每当我在模型中包含timestamps
时,它就会添加以下字段:
created_at (utc datetime)
updated_at (utc datetime)
created_at_utc (utc timestamp of created_at field)
updated_at_utc (utc timestamp of updated_at field)
如何扩展自动生成这些字段的时间戳功能(或生成新功能)?
更新
正如答案所示,我试图在我的助手db_helpers.ex
中扩展Ecto.Schema和Ecto.Migrations模块,如下所示:
defmodule Extension do
defmacro extends(module) do
module = Macro.expand(module, __CALLER__)
functions = module.__info__(:functions)
signatures = Enum.map functions, fn { name, arity } ->
args = Enum.map 0..arity, fn(i) ->
{ String.to_atom(<< ?x, ?A + i >>), [], nil }
end
{ name, [], tl(args) }
end
quote do
defdelegate unquote(signatures), to: unquote(module)
defoverridable unquote(functions)
end
end
end
defmodule Ecto.Schema do
import Extension
extends Ecto.Schema
@doc """
Generates `:inserted_at` and `:updated_at` timestamp fields.
The fields generated by this macro will automatically be set to
the current time when inserting and updating values in a repository.
## Options
* `:type` - the timestamps type, defaults to `:naive_datetime`.
* `:type_utc` - the utc timestamps type, defaults to `:utc_datetime`.
* `:usec` - sets whether microseconds are used in timestamps.
Microseconds will be 0 if false. Defaults to true.
* `:created_at` - the name of the column for insertion times or `false`
* `:updated_at` - the name of the column for update times or `false`
* `:created_at_utc` - the name of the column for utc insertion times or `false`
* `:updated_at_utc` - the name of the column for utc update times or `false`
* `:autogenerate` - a module-function-args tuple used for generating
both `inserted_at` and `updated_at` timestamps
All options can be pre-configured by setting `@timestamps_opts`.
"""
defmacro timestamps(opts \\ []) do
quote bind_quoted: binding() do
timestamps =
[created_at: :created_at, updated_at: :updated_at,
created_at_utc: :created_at_utc, inserted_at_utc: :inserted_at_utc,
type: :naive_datetime, type_utc: :utc_datetime,
usec: true]
|> Keyword.merge(@timestamps_opts)
|> Keyword.merge(opts)
type = Keyword.fetch!(timestamps, :type)
type_utc = Keyword.fetch!(timestamps, :type_utc)
precision = if Keyword.fetch!(timestamps, :usec), do: :microseconds, else: :seconds
autogen = timestamps[:autogenerate] || {Ecto.Schema, :__timestamps__, [type, precision]}
if created_at = Keyword.fetch!(timestamps, :created_at) do
Ecto.Schema.field(created_at, type, [])
Module.put_attribute(__MODULE__, :ecto_autogenerate, {created_at, autogen})
end
if updated_at = Keyword.fetch!(timestamps, :updated_at) do
Ecto.Schema.field(updated_at, type, [])
Module.put_attribute(__MODULE__, :ecto_autogenerate, {updated_at, autogen})
Module.put_attribute(__MODULE__, :ecto_autoupdate, {updated_at, autogen})
end
if created_at_utc = Keyword.fetch!(timestamps, :created_at_utc) do
Ecto.Schema.field(created_at_utc, type, [])
Module.put_attribute(__MODULE__, :ecto_autogenerate, {created_at_utc, autogen})
end
if updated_at_utc = Keyword.fetch!(timestamps, :updated_at_utc) do
Ecto.Schema.field(updated_at_utc, type, [])
Module.put_attribute(__MODULE__, :ecto_autogenerate, {updated_at_utc, autogen})
Module.put_attribute(__MODULE__, :ecto_autoupdate, {updated_at_utc, autogen})
end
end
end
end
defmodule Ecto.Migration do
import Extension
extends Ecto.Migration
@doc """
Adds `:created_at` and `:updated_at` timestamps columns.
Those columns are of `:naive_datetime` type, and by default
cannot be null. `opts` can be given to customize the generated
fields.
## Options
* `:created_at` - the name of the column for insertion times, providing `false` disables column
* `:updated_at` - the name of the column for update times, providing `false` disables column
* `:created_at_utc` - the name of the column for utc insertion times, providing `false` disables column
* `:updated_at_utc` - the name of the column for utc update times, providing `false` disables column
* `:type` - column type, defaults to `:naive_datetime`
* `:type_utc` - column type for utc, defaults to `:utc_datetime`
"""
def timestamps(opts \\ []) do
opts = Keyword.put_new(opts, :null, false)
{type, opts} = Keyword.pop(opts, :type, :naive_datetime)
{type_utc, opts} = Keyword.pop(opts, :type_utc, :utc_datetime)
{created_at, opts} = Keyword.pop(opts, :created_at, :created_at)
{updated_at, opts} = Keyword.pop(opts, :updated_at, :updated_at)
{created_at_utc, opts} = Keyword.pop(opts, :created_at_utc, :created_at_utc)
{updated_at_utc, opts} = Keyword.pop(opts, :updated_at_utc, :updated_at_utc)
if created_at != false, do: add(created_at, type, opts)
if updated_at != false, do: add(updated_at, type, opts)
if created_at_utc != false, do: add(created_at_utc, type_utc, opts)
if updated_at_utc != false, do: add(updated_at_utc, type_utc, opts)
end
end
但是,当我尝试运行mix ecto.migrate
时,我收到以下错误:
== Compilation error on file web/models/accounts.ex ==
** (CompileError) web/models/accounts.ex:4: undefined function schema/2
(stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
(elixir) lib/kernel/parallel_compiler.ex:117: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/1
extends
宏不应该使所有方法都可用。请注意,Extension
模块来自此处:https://gist.github.com/pavlos/c86858290743b4d4fed9
答案 0 :(得分:0)
您有两种选择:
<强> 1 即可。考虑来自Ecto.Schema
的扩展 timestamps和帮助器中Ecto.Migration
的{{3}}函数,您将在模型和迁移中导入。尝试按照源代码中实现的方式实现列。
这种方法可能:
<强> 2 即可。使用辅助工具中的新功能或宏,而不是扩展timestamps
的功能。它会:
请记住您希望这些字段不仅存在于您的模型中,还存储在数据库中,这意味着您必须在迁移中添加这些字段,或者准备一个基于以下内容添加它们的脚本:来自数据库的当前值。