将地图保存到数据库表中的相应列

时间:2019-07-05 14:37:15

标签: elixir ecto

我有一个包含这样的地图的列表:

 [
   %{"AM" => "01", "D9" => ""},
   %{"AM" => "02", "D8" => ""}
 ]

在数据库中还有一个包含AM01和AM02列的表。

我想这样保存地图:

 ____________________________________________________________
|___________________________Table X__________________________|
|_________Column_AM01__________|_________Column_AM02_________|
| %{"AM" => "01", "D9" => ""}  | %{"AM" => "02", "D8" => ""} |
|______________________________|_____________________________|

如何将这些地图保存在您各自的表中?

1 个答案:

答案 0 :(得分:1)

以下是相关的代码完整文件:

  def insert_list([]), do: :ok
  def insert_list( [ %{"AM" => value}=whole_map | tail ] ) do
    column_name = "AM#{value}"
    changeset = Foo.Item.changeset(
                      %Foo.Item{}, 
                      %{column_name => whole_map}
                )
    IO.inspect changeset
    {:ok, _item} = @repo.insert(changeset)
    insert_list(tail)
  end

  def go do
    maps =  [
      %{"AM" => "01", "D9" => ""},
      %{"AM" => "02", "D8" => ""}
    ]

    insert_list(maps)
  end

在iex中:

~/phoenix_apps/foo$ iex -S mix
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Compiling 1 file (.ex)
Interactive Elixir (1.8.2) - press Ctrl+C to exit (type h() ENTER for help)

iex(1)> Foo.go
#Ecto.Changeset<
  action: nil,
  changes: %{AM01: %{"AM" => "01", "D9" => ""}},
  errors: [],
  data: #Foo.Item<>,
  valid?: true
>
#Ecto.Changeset<
  action: nil,
  changes: %{AM02: %{"AM" => "02", "D8" => ""}},
  errors: [],
  data: #Foo.Item<>,
  valid?: true
>
[debug] QUERY OK db=11.9ms decode=2.4ms queue=2.7ms
INSERT INTO "items" ("AM01","inserted_at","updated_at") VALUES ($1,$2,$3) RETURNING "id" [%{"AM" => "01", "D9" => ""}, ~N[2019-07-06 17:16:23], ~N[2019-07-06 17:16:23]]
[debug] QUERY OK db=2.7ms queue=0.4ms
INSERT INTO "items" ("AM02","inserted_at","updated_at") VALUES ($1,$2,$3) RETURNING "id" [%{"AM" => "02", "D8" => ""}, ~N[2019-07-06 17:16:23], ~N[2019-07-06 17:16:23]]
:ok

iex(2)> Foo.list_items
[debug] QUERY OK source="items" db=6.8ms queue=0.5ms
SELECT i0."id", i0."title", i0."AM01", i0."AM02", i0."inserted_at", i0."updated_at" FROM "items" AS i0 []
[
  %Foo.Item{
    AM01: %{"AM" => "01", "D9" => ""},
    AM02: nil,
    __meta__: #Ecto.Schema.Metadata<:loaded, "items">,
    id: 1,
    inserted_at: ~N[2019-07-06 17:16:23],
    title: nil,
    updated_at: ~N[2019-07-06 17:16:23]
  },
  %Foo.Item{
    AM01: nil,
    AM02: %{"AM" => "02", "D8" => ""},
    __meta__: #Ecto.Schema.Metadata<:loaded, "items">,
    id: 2,
    inserted_at: ~N[2019-07-06 17:16:23],
    title: nil,
    updated_at: ~N[2019-07-06 17:16:23]
  }
]

请注意,如果您的列表如下所示:

[
 %{"AM" => "01", "D9" => ""},
 %{"AM" => "03", "D8" => ""},
 %{"AM" => "02", "D8" => ""}
]

...然后使用下面指定的变更集验证,您仍将获得第二张地图的插入,并且列AM01AM02将为nil。您可以防止第二张地图触发插入,如下所示:

if changeset.changes == %{} do
  insert_list(tail)
else
  case @repo.insert(changeset) do
    {:ok, _item} -> :ok
    {:error, error} -> IO.puts "Insert error: #{error}"
  end
  insert_list(tail)
end

因为第二张地图的变更集看起来像这样:

#Ecto.Changeset<action: nil, 
     changes: %{}, 
     errors: [], 
     data: #Foo.Item<>, valid?: true>

第一个地图的变更集如下:

#Ecto.Changeset<action: nil, 
     changes: %{AM01: %{"AM" => "01", "D9" => ""}}, 
     errors: [], 
     data: #Foo.Item<>, valid?: true>

或者,您可以根据需要添加其他验证。例如,如果必须显示AM**列之一的值,那么如果attrs映射中的两个键都缺失({{1}的第二个参数) }函数):

changeset()

第二张地图将产生以下错误输出:

  def changeset(%Foo.Item{}=item, attrs \\ %{}) do
    item
    |> cast(attrs, [:title, :AM01, :AM02])
    |> validate_required_either_AM_column()
 #   |> validate_required([:title])
 #   |> validate_length(:title, min: 3)

  end


  defp validate_required_either_AM_column(%Ecto.Changeset{} = changeset) do
    IO.puts "inside validate_required: changeset=#{inspect changeset}"
    am01 = get_change(changeset, :AM01)
    am02 = get_change(changeset, :AM02)

    case am01 || am02 do
      nil ->  # The || operator will return nil when both am01 and am02 are nil
          add_error(changeset, :AM01, "either this column or :AM02 must be present")
          |> add_error(:AM02, "either this column or :AM01 must be present")
      _ ->  
          changeset
    end

foo / lib / foo.ex:

***Insert error: #Ecto.Changeset<action: :insert, 
changes: %{}, 
errors: [AM02: {"either this column or :AM01 must be present", []}, 
         AM01: {"either this column or :AM02 must be present", []}],
data: #Foo.Item<>, valid?: false>

foo / lib / foo / repo.ex:

defmodule Foo do
  alias Foo.Item

  @repo Foo.Repo

  def list_items do
    @repo.all(Item)
  end

  def insert_item(attrs) do
    Foo.Item
    |> struct(attrs)
    |> @repo.insert()
  end

  def delete_item(%Foo.Item{} = item) do
    @repo.delete(item)
  end

  def insert_list([]), do: :ok
  def insert_list( [ %{"AM" => value}=whole_map | tail ] ) do
    column_name = "AM#{value}"
    changeset = Foo.Item.changeset(
                      %Foo.Item{}, 
                      %{column_name => whole_map}
                )

    IO.inspect changeset
    {:ok, _item} = @repo.insert(changeset)
    insert_list(tail)
  end

  def go do
    maps =  [
     %{"AM" => "01", "D9" => ""},
     %{"AM" => "02", "D8" => ""}
    ]

    insert_list(maps)
  end

end

foo / lib / foo / item.ex:

defmodule Foo.Repo do
  use Ecto.Repo,
    otp_app: :foo,
    adapter: Ecto.Adapters.Postgres
end

foo / priv / repo / migrations /...._ create_items.exs:

defmodule Foo.Item do
  use Ecto.Schema
  import Ecto.Changeset

  schema "items" do
    field :title, :string
    field :AM01, :map
    field :AM02, :map

    timestamps()
  end

  def changeset(%Foo.Item{}=item, attrs \\ %{}) do
    item
    |> cast(attrs, [:title, :AM01, :AM02])
#    |> validate_required([:title])
#    |> validate_length(:title, min: 3)
#    ...
#    ...
  end


end