【问题标题】:Phoenix Ecto: foreign key not being insertedPhoenix Ecto:未插入外键
【发布时间】:2016-12-17 01:59:19
【问题描述】:

我正在插入一个模型 A,其中包含另一个模型 B 的外键。

defmodule MyApp.ModelA do
  use MyApp.Web, :model

  schema "model_a" do
    field :type, :string, null: false
    field :data, :string, null: false
    belongs_to :model_b, MyApp.ModelB
    timestamps()
  end

  @required_fields ~w(type data)
  @optional_fields ~w()

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, @required_fields, @optional_fields)
    |> assoc_constraint(:model_b)
  end
end

和模型 B:

defmodule MyApp.ModelB do
  use MyApp.Web, :model

  schema "model_b" do
    field :username, :string
    field :pass, :string
    has_many :model_a, MyApp.ModelA
    timestamps()
  end

  @required_fields ~w(username pass)
  @optional_fields ~w()

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, @required_fields, @optional_fields)
    |> cast_assoc(:model_a)
    |> validate_required([])
  end
end

模型 B 存在,因为我可以通过 Repo.all(ModelB) 获得它。

模型 A 变更集已成功验证,当我打印模型 A 变更集结构时,我可以看到 model_b_id 值。

但是插入时,引用没有插入。虽然我可以在打印模型 A 的变更集时看到它,但在 MySQL 日志中,该字段完全缺失,不在 INSERT 查询中。

我玩了一点,如果我强制这个引用字段在 MySQL 表中不为空,那么当作为 Repo.insert(...)响应,尽管模型 B 存在于数据库中。

【问题讨论】:

  • 变更集验证不检查外键验证,因此在实际尝试插入之前不会出现“不存在”错误。您能否发布模型的架构、您运行的代码和 SQL 日志?
  • 对不起,也许我没有解释自己。我得到“不存在”作为 Repo.insert(...) 的响应。对于这种情况,变更集验证确实总是可以正常工作。
  • 您确定您在变更集中看到model_b_id 吗?根据您刚刚发布的代码,您的 @optional_fields 为空。您可以尝试将model_b_id 添加到@optional_fields 吗?
  • 是的,如果我在创建和验证变更集后打印变更集,我绝对可以看到它。我在变更集的 model_b_id 字段中看到了 ModelB 行的 id。注意模型A中的这个引用字段是在迁移中添加的,所以在原始表定义中没有定义,但我认为它没有什么关系。我创建了一个额外的新字段,并且能够为其设置一个值。因此,在迁移中定义它应该没有任何关系。
  • 我已经尝试在 required_fields 中添加这个 model_b_id 字段(以及第二次尝试在 optional_fields 中),我一直遇到同样的问题,但是变更集验证没有抱怨,所以这意味着字段值实际上是放。问题是当 Repo.insert(..).

标签: elixir phoenix-framework ecto


【解决方案1】:

我对这个问题的回答非常感兴趣。

我已经失去了超过 7 个小时的确切条件,即“我知道该行已创建,为什么更改集不让我存储引用?”

我想我会建议任何有同样挫败感的人检查您的数据库迁移参考。

我确定我已经对它们进行了三次检查,但确实如此,就像睡了一夜好觉后的第二天一样清晰。我设置了对错误表的引用。

希望这可以节省一些时间。

【讨论】:

    【解决方案2】:

    我认为 Ecto 不会为您从结构中提取 ID 字段 - 我一直在做这样的事情:

    defmodule MyApp.ModelA do
      use MyApp.Web, :model
    
      schema "model_a" do
        field :type, :string, null: false
        field :data, :string, null: false
        belongs_to :model_b, MyApp.ModelB
        timestamps()
      end
    
      @required_fields ~w(type data model_b_id)
      @optional_fields ~w()
    
      @doc """
      Method head to define what's coming.
      """
      def changeset(model, params \\ :empty)
    
      @doc """
      Catches the case where you're passing in a model_b struct instead of an integer.
      Just makes a recursive call with the correct type.
      """
      def changeset(model, %{model_b_id: nil, agency: %MyApp.ModelB{} = model_b} = params) do
        changeset(model, %{params | model_b_id: model_b.id})
      end
    
      @doc """
      I normally use assoc_constraint on the table index itself like so
      """
      def changeset(struct, params \\ %{}) do
        struct
        |> cast(params, @required_fields, @optional_fields)
        |> assoc_constraint(:model_b, name: :model_as_model_b_id_fkey)
      end
    end
    

    【讨论】:

      【解决方案3】:

      对我有用的是:

      model_b = Repo.get(MyApp.ModelB, 2)
      model_a_changeset = Ecto.build_assoc(model_b, :model_as,
          type: "Model type",
          data: "Model data"
      )
      Repo.insert! model_a_changeset
      

      (数字2是model_b的id示例,你要弄清楚如何检索正确的父id)

      有时您需要在更改集上明确指定外键,如下所示:

      model_a_changeset = Ecto.build_assoc(model_b, :model_as,
          type: "Model type",
          data: "Model data",
          model_b_id: model_b.id
      )
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-10-26
        • 1970-01-01
        • 1970-01-01
        • 2015-12-11
        相关资源
        最近更新 更多