【问题标题】:Conversion error with a custom DateRange Ecto type自定义 DateRange Ecto 类型的转换错误
【发布时间】:2017-11-11 21:43:34
【问题描述】:

我在编写自定义 Ecto 类型时遇到问题。它由%Postgrex.Range{} 类型支持。 代码是

defmodule Foo.Ecto.DateRange do

  @behaviour Ecto.Type

  def type, do: :daterange

  def cast(%{"lower" => lower, "upper" => upper}) do
    new_lower = Date.from_iso8601! lower
    new_upper = Date.from_iso8601! upper
    {:ok, Date.range(new_lower, new_upper)}
  end

  def cast(%Date.Range{}=range) do
    {:ok, range}
  end

  def cast(_), do: :error

  def load(%Postgrex.Range{lower: lower, upper: upper}) do
    {:ok, Date.range(lower, upper)}
  end

  def load(_), do: :error

  def dump(%Date.Range{}=range) do
    {:ok, %Postgrex.Range{lower: range.first, upper: range.last}}
  end

  def dump(_), do: :error
end

迁移是

  def change do
    create table(:users) do
      add :email,             :string, null: false
      add :username,          :string
      add :name,              :string, null: false
      add :password_hash,     :text,   null: false
      add :period,            :daterange
      timestamps()
    end

用户架构是

schema "users" do
  field :username,         :string
  field :name,             :string
  field :email,            :string
  field :password_hash,    :string
  field :password,         :string, virtual: true
  field :period,           Foo.Ecto.DateRange

我的seeds.exs 中有问题的代码是这个:

today    = Date.utc_today()

{:ok, user2} = create_user %{name: "Gloubi Boulga",
  email: "gloub@boul.ga", password: "xptdr32POD?é23PRK*efz",
  period: Date.range(today, Timex.shift(today, months: 2))
}

最后,错误是这个:

* (CaseClauseError) no case clause matching: {~D[2017-11-04]}
    (ecto) lib/ecto/adapters/postgres/datetime.ex:40: Ecto.Adapters.Postgres.TypeModule.encode_value/2
    (ecto) /home/tchoutri/dev/Projects/Foo/deps/postgrex/lib/postgrex/type_module.ex:717: Ecto.Adapters.Postgres.TypeModule.encode_params/3
[…]
priv/repo/seeds.exs:33: anonymous fn/0 in :elixir_compiler_1.__FILE__/1

当然,我不明白为什么会发生这种转换,这非常令人沮丧,特别是考虑到创建一个由%Postgrex.Range{} 支持的自定义 Ecto 类型应该有点微不足道。

编辑:我在 cast 函数中添加了一些 Logger.debug,我可以看到

[debug] Casting new_date #DateRange<~D[2017-11-11], ~D[2018-01-11]> 

出现和

%Postgrex.Range{lower: ~D[2017-11-11], lower_inclusive: true, upper: ~D[2018-01-11], upper_inclusive: true}

在dump 函数中。

【问题讨论】:

    标签: date elixir ecto postgrex


    【解决方案1】:

    在 %Postgrex.Range{} 中,当前版本的 Postgrex (0.13.3) 需要 %Postgrex.Date{}s。见相关测试here。

    但是,如链接中所示,%Postgrex.Date{} 在下一版本中已弃用,您应该从 0.14 开始使用 %Date{}(仍在开发中)。

    【讨论】:

      【解决方案2】:

      我今天遇到了这个。我希望这仍然有帮助:

      def dump(%Date.Range{} = range) do
        {:ok, %Postgrex.Range{lower: Date.to_erl(range.first), upper: Date.to_erl(range.last)}}
      end
      

      【讨论】:

        【解决方案3】:

        这就是我最终得到的结果:

        defmodule DateRange do
          @moduledoc false
        
          @behaviour Ecto.Type
        
          @doc """
          Does use the `:tsrange` postgrex type.
          """
          def type, do: :daterange
        
          @doc """
          Can cast various formats:
              # Simple maps (default to `[]` semantic like Date.range)
              %{"lower" => "2015-01-23", "upper" => "2015-01-23"}
              # Postgrex range with Date structs for upper and lower bound
              %Postgrex.Range{lower: #Date<2015-01-23>, upper: #Date<2015-01-23>}
          """
          def cast(%Date.Range{first: lower, last: upper}),  do: cast(%{lower: lower, up
        per: upper})
        
          def cast(%{"lower" => lower, "upper" => upper}), do: cast(%{lower: lower, uppe
        r: upper})
        
          def cast(%Postgrex.Range{lower: %Date{}, upper: %Date{}} = range), do: {:ok, r
        ange}
        
          def cast(%{lower: %Date{} = lower, upper: %Date{} = upper}) do
            {:ok, %Postgrex.Range{lower: lower, upper: upper}}
          end
        
          def cast(%{lower: lower, upper: upper}) do
            try do
              with {:ok, new_lower, 0} <- Date.from_iso8601(lower),
                   {:ok, new_upper, 0} <- Date.from_iso8601(upper) do
                {:ok, %Postgrex.Range{lower: new_lower, upper: new_upper}}
              else
                _ -> :error
              end
            rescue
              FunctionClauseError -> :error
            end
          end
        
          def cast(_), do: :error
        
          @end_of_times ~D[9999-12-31]
          @start_of_times ~D[0000-01-01]
          defp canonicalize_bounds(date, inclusive, offset, infinite_bound) do
            with {:ok, date} <- Date.from_erl(date) do
              case inclusive do
                false -> {:ok, Timex.shift(date, days: offset)}
                true -> {:ok, date}
              end
            else
              ^inclusive = false when is_nil(date) -> {:ok, infinite_bound}
              _ -> :error
            end
          end
        
          @doc """
          Does load the postgrex returned range and converts data back to Date structs.
          """
          def load(%Postgrex.Range{lower: lower, lower_inclusive: lower_inclusive,
                                   upper: upper, upper_inclusive: upper_inclusive}) do
            with {:ok, lower} <- canonicalize_bounds(lower, lower_inclusive, 1,  @start_
        of_times),
                 {:ok, upper} <- canonicalize_bounds(upper, upper_inclusive, -1, @end_of
        _times) do
        
              {:ok, Date.range(lower, upper)}
            else
              _ -> :error
            end
          end
        
          def load(_), do: :error
        
          @doc """
          Does convert the Date bounds into erl format for the db.
          """
          def dump(%Postgrex.Range{lower: %Date{} = lower, upper: %Date{} = upper} = range) do
            with {:ok, lower} <- Ecto.DataType.dump(lower),
                 {:ok, upper} <- Ecto.DataType.dump(upper) do
              {:ok, %{range | lower: lower, upper: upper}}
            else
              _ -> :error
            end
          end
        
          def dump(_), do: :error
        end
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-22
          • 2016-11-27
          • 2019-11-18
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多