Сохранить карты в соответствующие столбцы в таблице базы данных - PullRequest
0 голосов
/ 05 июля 2019

У меня есть список с такими картами:

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

И в базе данных таблица с столбцами AM01 и AM02.

Я хочу сохранить эти карты следующим образом:

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

Как сохранить эти карты в соответствующей таблице?

1 Ответ

1 голос
/ 06 июля 2019

Ниже приведены полные файлы соответствующих кодов:

  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" => ""}
]

... тогда с проверками набора изменений, указанными ниже, вы все равно получите вставку для 2-й карты, и столбцы 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 (второй аргумент функции 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

Вторая карта выдаст эту ошибку:

***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 / Библиотека / 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 / Библиотека / Foo / repo.ex:

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

Foo / Библиотека / 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 / собств / репо / Миграции /...._ 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
...