将地图保存到数据库中的相应列 table
Save maps to respective columns in database table
我有一个这样的地图列表:
[
%{"AM" => "01", "D9" => ""},
%{"AM" => "02", "D8" => ""}
]
并且在数据库中 table 包含列 AM01 和 AM02。
我想这样保存这张地图:
____________________________________________________________
|___________________________Table X__________________________|
|_________Column_AM01__________|_________Column_AM02_________|
| %{"AM" => "01", "D9" => ""} | %{"AM" => "02", "D8" => ""} |
|______________________________|_____________________________|
如何将这些地图保存在你们各自的 table 中?
下面是相关代码的完整文件:
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 (,,) 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 (,,) 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
将为零。您可以防止第二张地图触发这样的插入:
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
映射中缺少两个键([=26= 的第二个参数),则可以通过以下方式阻止插入] 函数):
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
第二张地图将产生此错误输出:
***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.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/repo.ex:
defmodule Foo.Repo do
use Ecto.Repo,
otp_app: :foo,
adapter: Ecto.Adapters.Postgres
end
foo/lib/foo/item.ex:
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
foo/priv/repo/migrations/...._create_items.exs:
defmodule Foo.Repo.Migrations.CreateItems do
use Ecto.Migration
def change do
create table("items") do
add :title, :string
add :AM01, :map
add :AM02, :map
timestamps()
end
end
end
我有一个这样的地图列表:
[
%{"AM" => "01", "D9" => ""},
%{"AM" => "02", "D8" => ""}
]
并且在数据库中 table 包含列 AM01 和 AM02。
我想这样保存这张地图:
____________________________________________________________
|___________________________Table X__________________________|
|_________Column_AM01__________|_________Column_AM02_________|
| %{"AM" => "01", "D9" => ""} | %{"AM" => "02", "D8" => ""} |
|______________________________|_____________________________|
如何将这些地图保存在你们各自的 table 中?
下面是相关代码的完整文件:
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 (,,) 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 (,,) 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
将为零。您可以防止第二张地图触发这样的插入:
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
映射中缺少两个键([=26= 的第二个参数),则可以通过以下方式阻止插入] 函数):
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
第二张地图将产生此错误输出:
***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.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/repo.ex:
defmodule Foo.Repo do
use Ecto.Repo,
otp_app: :foo,
adapter: Ecto.Adapters.Postgres
end
foo/lib/foo/item.ex:
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
foo/priv/repo/migrations/...._create_items.exs:
defmodule Foo.Repo.Migrations.CreateItems do
use Ecto.Migration
def change do
create table("items") do
add :title, :string
add :AM01, :map
add :AM02, :map
timestamps()
end
end
end