Ecto belongs_to changeset在两个关系中插入插入

时间:2016-09-13 08:32:52

标签: elixir phoenix-framework ecto changeset

简介

我在Ecto中遇到关系问题。我有两个模式用户角色,我希望能够在数据库中将带有外键的新用户插入角色表。

问题是每次我尝试插入新用户时,它与 Role 表没有关系( role_id 列在中为null用户表)。我尝试构建一个关联以创建关系,但它不起作用。

当前代码

我的用户架构是:

  schema "user" do
    field :first_name, :string
    field :last_name, :string
    field :email, :string
    field :encrypted_password, :string
    field :password, :string, virtual: true

    belongs_to :role, TestApp.Role

    timestamps()
  end

角色架构:

  schema "role" do
    field :name, :string

    timestamps()

    has_many :users, TestApp.User
  end

我将用户变更集定义如下:

  @required_fields ~w(first_name last_name email password)
  @optional_fields ~w(encrypted_password)

  @required_update_fields ~w()
  @optional_update_fields ~w(first_name last_name email password encrypted_password role)


  def create_changeset(struct, params \\ :empty) do
    changeset(struct, params, @required_fields, @optional_fields)
  end

  def update_changeset(struct, params \\ :empty) do
    changeset(struct, params, @required_update_fields, @optional_update_fields)
  end

  defp changeset(struct, params \\ :empty, required_field, optional_fiels) do
    struct
    |> cast(params, required_field, optional_fiels)
    |> validate_format(:email, ~r/@/, message: "invalid format")
    |> validate_length(:password, min: 5)
    |> validate_confirmation(:password, message: "password does not match")
    |> unique_constraint(:email, message: "email already taken")
    |> generate_encrypted_password
  end

角色变更集:

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

在我的 UserController 中,我有一个创建方法

  def create(conn, %{"user" => user_params}) do
    changeset = User.create_changeset(%User{}, user_params)

    case User.insert(changeset) do
      {:ok, user} ->
        conn
        |> put_status(:ok)
        |> render(TestApp.UserRender, "show.json", user: user)
      {:error, changeset} ->
        handle_user_creation_validation_error(conn, changeset: changeset)
    end
  end

经过测试的解决方案(未成功)

我测试了几个选项,下面的列表只是我到目前为止尝试过的一个例子(尝试次数太多)。它根本不需要编译,我只是将其作为信息发布。

我尝试在 User 中创建一个 insert 方法来构建一个assoc并保存更改集。

  def insert(changeset) do
    if changeset.valid? do
      IEx.pry
      TestApp.Role.find_by_name("user")
      |> Ecto.build_assoc(:users)
      |> Ecto.change(changeset.params)
      |> Repo.insert
    else
      {:error, changeset}
    end
  end 

我也尝试过这个角色并构建一个关联

TestApp.Role.find_by_name("user")
|> TestApp.Repo.preload(:users)
|> Ecto.build_assoc(:users)
|> Ecto.Changeset.put_assoc(:users, user)
|> Repo.insert!

我在Ecto文档中看到了这个例子

  def insert(changeset) do
    Repo.transaction fn ->
      TestApp.Role.find_by_name("user")
      |> TestApp.Repo.preload(:users)
      |> Ecto.Changeset.put_assoc(:users, [changeset])
      |> Repo.insert
  end

当前状态

我能够在用户角色之间创建关系,但每次我插入一个新的用户一个新的角色< / em>角色也被插入(我很震惊,它每次只创建一个one_to_one关系!)。

我知道有一种正确的方法,但我想知道它是否意味着首先从角色预先加载:users。我不认为这是一个可以接受的解决方案,希望有人可以解释关于关系,预加载和保存变更集的所有这些。

解决方案

好吧,似乎有必要将FK作为参数发送到 User 变更集中,因此我必须使用role更改param字段role_id

param body请求示例可能是:

{
    "user" : {
        "first_name": "first name",
        "last_name": "last name",
        "email": "test@email",
        "password": "longtestpassword",
        "password_confirmation": "longtestpassword",
        "role_id": 1
    }
} 

我还在变更集验证管道中添加了cast_assoc

|> cast_assoc(:role)

User.insert/1实施已删除,控制器会调用Repo.insert/1

感谢您的帮助!

2 个答案:

答案 0 :(得分:1)

更改

@optional_update_fields ~w(... role)

为:

@optional_update_fields ~w(... role_id)

然后只在您的参数中发送role_id而不是角色字段地图

答案 1 :(得分:0)

通常我用这些方式处理这种情况:

mtcars %>% group_by(am) %>% do(rs = modelr::bootstrap(., 1000)) %>% group_by(am) %>% unnest %>% group_by(am, .id) %>% do(model = lm(mpg~wt, data = as.data.frame(.$strap))) %>% tidy(model) %>% ggplot(aes(estimate)) + geom_histogram() + facet_grid(am ~ term, scales = "free_x", labeller = label_both) 方面:

  1. 直接将user(肯定role_id)放在Role.id中,因此在Repo.insert(用户)之后,此新用户将User changese attrs ForeignKey 1}},像这样:
  2. Role
    1. build_assoc,实际上就像上一个一样,只需按下&#34; role_id&#34; 进入def changeset(%User{}=user, %{.., role_id: certain_role_id}=attrs) do user |> cast(attrs, [...,:role_id] |> validate_require([:role_id, ...]) ,但更美丽..
    2. user方面:

        role中使用的
      1. put_assoc,它将Role changeset管理与当前users相关联的所有role。由于put_assoc(:users, [user_changeset_lists])不要检查数据,所以最好做put_assoc手册;像这样:
      2. changeset
        1. cast_assoc喜欢为def changeset(%Role{}=role, new_user) do users = Repo.preload(role, :users) users = user ++ User.changeset(new_user) role |> cast(..) |> validate_required(..) |> put_assoc(:users, users) end 添加user而不会影响相关的role,而user会自动调用cast_assoc,如下所示:< / LI>
          User.changeset