使用模式之间的构建关联

Use build association between schemas

我有以下架构:

第一个:

schema "countries_codes" do

    # Country code based on ISO-2
    field :iso,        :string
    field :name,       :string
    has_many :country, Country

    timestamps

  end

  def changeset(struct, params \ %{}) do

    struct
    |> cast(params, [:iso, :name])
    |> validate_required([:iso, :name])
    |> validate_length(:iso, max: 2)

  end

第二个:

schema "languages_codes" do

    # Language code based on ISO-2
    field :iso,        :string
    field :name,       :string
    has_many :country, Country

    timestamps

  end

  def changeset(struct, params \ %{}) do

    struct
    |> cast(params, [:iso, :name])
    |> validate_required([:iso, :name])
    |> validate_length(:iso, max: 2)

  end

第三个:

schema "countries" do

    belongs_to :country_iso,  CountryCode
    belongs_to :language_iso, LanguageCode
    field :name,              :string

    timestamps

  end

  def changeset(struct, params \ %{}) do

    struct
    |> cast(params, [:country_iso, :language_iso, :name])
    |> cast_assoc(:country_iso)
    |> validate_required([:country_iso, :language_iso, :name])

  end

如您在第三个 table 中所见,第一个和第二个字段属于其他架构。

当我 运行 country 变更集函数时,我得到:

** (RuntimeError) casting assocs with cast/3 is not supported, use cast_assoc/3 instead
    (ecto) lib/ecto/changeset.ex:440: Ecto.Changeset.type!/2
    (ecto) lib/ecto/changeset.ex:415: Ecto.Changeset.process_param/8
    (elixir) lib/enum.ex:1247: Enum."-map_reduce/3-lists^mapfoldl/2-0-"/3
    (ecto) lib/ecto/changeset.ex:391: Ecto.Changeset.do_cast/7
    (busiket) web/models/country.ex:20: Busiket.Country.changeset/2
    (elixir) lib/enum.ex:1184: Enum."-map/2-lists^map/1-0-"/2
    priv/repo/seeds.exs:48: (file)
    (elixir) lib/code.ex:363: Code.require_file/2
    (mix) lib/mix/tasks/run.ex:71: Mix.Tasks.Run.run/1
    (mix) lib/mix/task.ex:296: Mix.Task.run_task/3
    (mix) lib/mix/cli.ex:58: Mix.CLI.run_task/2
    (elixir) lib/code.ex:363: Code.require_file/2

我的问题是,我不知道如何为模式编写变更集函数 countries

要修复错误,请在架构 countries.

中删除 :country_iso:language_iso 表单 cast/3

更新模型 Country

defmodule MyApp.Country do
  use Ecto.Schema
  import Ecto.Changeset

  schema "countries" do
    field :name, :string
    belongs_to :country_iso, MyApp.CountryCode
    belongs_to :language_iso, MyApp.LanguageCode

    timestamps()
  end

  def changeset(struct, params \ %{}) do
    struct
    |> cast(params, [:name, :country_iso_id, :language_iso_id])
    |> cast_assoc(:country_iso)
    |> cast_assoc(:language_iso)
    |> unique_constraint(:country_iso_id)
    |> unique_constraint(:language_iso_id)
    |> validate_required([:name])
  end
end

用新的 CountryCodeLanguageCode 创建一个新的 Country:

changeset = Country.changeset(%Country{}, %{
  name: "United Kingdom",
  country_iso: %{iso: "GB", name: "United Kingdom"},
  language_iso: %{iso: "en", name: "English"}
})
Repo.insert! changeset

无法插入具有重复关联的变更集

Repo.insert! %CountryCode{iso: "GB", name: "United Kingdom"}

changeset = Country.changeset(%Country{}, %{
  name: "United Kingdom",
  country_iso: %{iso: "GB", name: "United Kingdom 2"},
  language_iso: %{iso: "en", name: "English"}
})
{:error, changeset} = Repo.insert changeset
changeset.valid? # => false
changeset.changes[:country_iso].errors[:iso] # => {"has already been taken", []}

需要将 unique_constraint(:iso) 添加到 CountryCode 变更集中以使其像那样工作。

插入具有现有关联的变更集

country_iso = Repo.insert! %CountryCode{iso: "GB", name: "United Kingdom"}
changeset = Country.changeset(%Country{}, %{
  name: "United Kingdom",
  country_iso_id: country_iso.id,
  language_iso: %{iso: "en", name: "English"}
})
Repo.insert! changeset
country = Repo.one Country
country.country_iso_id == country_iso.id # => true

考虑使用列 iso 作为表 country_codeslanguage_codes

的主键

查看更多 - Ecto Custom Primary Keys

迁移:

defmodule MyApp.Repo.Migrations.CreateCountryCode do
  use Ecto.Migration

  def change do
    create table(:countries_codes, primary_key: false) do
      add :iso, :string, size: 2, primary_key: true
      add :name, :string

      timestamps()
    end
  end
end

defmodule MyApp.Repo.Migrations.CreateLanguageCode do
  use Ecto.Migration

  def change do
    create table(:languages_codes, primary_key: false) do
      add :iso, :string, size: 2, primary_key: true
      add :name, :string

      timestamps()
    end
  end
end

defmodule MyApp.Repo.Migrations.CreateCountry do
  use Ecto.Migration

  def change do
    create table(:countries) do
      add :name, :string
      add :country_iso, references(:countries_codes, column: :iso, type: :string, on_delete: :nothing)
      add :language_iso, references(:languages_codes, column: :iso, type: :string, on_delete: :nothing)

      timestamps()
    end

    create unique_index(:countries, [:country_iso, :language_iso])
  end
end

型号:

defmodule MyApp.CountryCode do
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key {:iso, :string, []}
  schema "countries_codes" do
    field :name, :string
    has_many :countries, MyApp.Country

    timestamps()
  end

  def changeset(struct, params \ %{}) do
    struct
    |> cast(params, [:iso, :name])
    |> validate_required([:iso, :name])
    |> validate_length(:iso, max: 2)
    |> unique_constraint(:iso)
  end
end

defmodule MyApp.LanguageCode do
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key {:iso, :string, []}
  schema "languages_codes" do
    field :name, :string
    has_many :countries, MyApp.Country

    timestamps()
  end

  def changeset(struct, params \ %{}) do
    struct
    |> cast(params, [:iso, :name])
    |> validate_required([:iso, :name])
    |> validate_length(:iso, max: 2)
    |> unique_constraint(:iso)
  end
end

defmodule MyApp.Country do
  use Ecto.Schema
  import Ecto.Changeset

  schema "countries" do
    field :name, :string
    belongs_to :country_code, MyApp.CountryCode, foreign_key: :country_iso, references: :iso, type: :string
    belongs_to :language_code, MyApp.LanguageCode, foreign_key: :language_iso, references: :iso, type: :string

    timestamps()
  end

  def changeset(struct, params \ %{}) do
    struct
    |> cast(params, [:name, :country_iso, :language_iso])
    |> validate_required([:name, :country_iso, :language_iso])
    |> foreign_key_constraint(:country_iso)
    |> foreign_key_constraint(:language_iso)
  end
end

注意 foreign_key_constraint/3 函数 - 当使用不存在的 country_isolanguage_iso 时,它会为 Country 变更集添加验证错误。

我还将关联名称更改为 country_codelanguage_code

假设有一些国家代码和语言代码:

Repo.insert! %CountryCode{iso: "GB", name: "United Kingdom"}
Repo.insert! %LanguageCode{iso: "en", name: "English"}

创建国家变更集并插入数据库:

changeset = Country.changeset(%Country{}, %{name: "United Kingdom", country_iso: "GB", language_iso: "en"})
Repo.insert! changeset