Elixir Phoenix - 添加列到时间戳

时间:2017-01-31 17:34:24

标签: elixir phoenix-framework ecto

我有一个凤凰应用程序,在所有模型中都使用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

1 个答案:

答案 0 :(得分:0)

您有两种选择:

<强> 1 即可。考虑来自Ecto.Schema扩展 timestamps和帮助器中Ecto.Migration的{​​{3}}函数,您将在模型和迁移中导入。尝试按照源代码中实现的方式实现列。

这种方法可能:

    由于挖掘和研究源代码,
  • 需要更多时间
  • 因为更好地了解源代码而更有益
  • 更难实施并确保您不会犯错误

<强> 2 即可。使用辅助工具中的功能或宏,而不是扩展timestamps的功能。它会:

  • 花费更少的时间和精力
  • 更容易实施

请记住您希望这些字段不仅存在于您的模型中,还存储在数据库中,这意味着您必须在迁移中添加这些字段,或者准备一个基于以下内容添加它们的脚本:来自数据库的当前值。