我有一个包含这样的地图的列表:
[
%{"AM" => "01", "D9" => ""},
%{"AM" => "02", "D8" => ""}
]
在数据库中还有一个包含AM01和AM02列的表。
我想这样保存地图:
____________________________________________________________
|___________________________Table X__________________________|
|_________Column_AM01__________|_________Column_AM02_________|
| %{"AM" => "01", "D9" => ""} | %{"AM" => "02", "D8" => ""} |
|______________________________|_____________________________|
如何将这些地图保存在您各自的表中?
答案 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" => ""}
]
...然后使用下面指定的变更集验证,您仍将获得第二张地图的插入,并且列AM01
和AM02
将为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