一次为父对象保存多个关联中的一个模型

时间:2017-09-23 23:22:05

标签: elixir phoenix-framework ecto

我有两个模型 - OrganizationUser 组织有很多用户,也属于一个所有者(所有者也是User模型)。

我有组织的注册表单,用户可以为组织输入name,为name输入passworduseruser的嵌套对象形式为organization)。注册成功后,我需要Organization与关联的ownerUser模型),此user应属于此组织。基本上,创建的用户应该是owner,并且在注册后应该在has_many :users关联列表中。

我已成功使用用户作为所有者关联创建组织,但我希望用户在创建后也能处于has_many :users关联。在Rails中,我只想在这种情况下为用户添加after_create回调和设置organization_id,但由于ecto没有回调,我该怎么办呢?我已经阅读了Ecto.Multi,但我不确定它是否正确(对我来说看起来有点矫枉过正)并且不确定它是如何用于这种情况的。

以防万一,这是我的模型代码和注册表单。

defmodule HappyClient.Organization do
  use HappyClient.Web, :model

  schema "organizations" do
    field :name, :string
    belongs_to :owner, HappyClient.User, foreign_key: :owner_id
    has_many :users, HappyClient.User

    timestamps()
  end

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

  def registration_changeset(struct, params) do
    struct
    |> changeset(params)
    |> cast_assoc(:owner, required: true, with: &HappyClient.User.registration_changeset/2)
  end
end

用户模型

defmodule HappyClient.User do
  use HappyClient.Web, :model

  schema "users" do
    field :email, :string
    field :name, :string
    field :password_hash, :string
    field :is_admin, :boolean, default: false
    field :is_operator, :boolean, default: false
    belongs_to :organization, HappyClient.Organization

    field :password, :string, virtual: true

    timestamps()
  end

  @required_fields ~w(email name)a
  @optional_fields ~w(is_admin is_operator)a

  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, @required_fields)
    |> validate_required([:email, :name])
    |> validate_format(:email, ~r/@/)
    |> assoc_constraint(:organization)
  end

  def registration_changeset(struct, params) do
    struct
    |> changeset(Map.merge(params, %{"is_admin" => true}))
    |> cast(params, [:password], [])
    |> validate_required([:password])
    |> validate_length(:password, min: 6, max: 100)
    |> hash_password
  end

  defp hash_password(changeset) do
    case changeset do
      %Ecto.Changeset{valid?: true,
                      changes: %{password: password}} ->
        put_change(changeset,
                   :password_hash,
                   Comeonin.Bcrypt.hashpwsalt(password))
      _ ->
        changeset
    end
  end
end

注册表格

<h1>Registration</h1>

<%= form_for @changeset, organization_path(@conn, :create), fn f -> %>
  <%= if @changeset.action do %>
    <div class="alert alert-danger">
      <p>There are some errors</p>
    </div>
  <% end %>
  <div class="form-group">
    <%= text_input f, :name, placeholder: "Organization Name", class: "form-control" %>
    <%= error_tag f, :name %>
  </div>

  <div class="form-group">
    <%= inputs_for f, :owner, fn of -> %>
      <%= text_input of, :name, placeholder: "Name" %>
      <%= error_tag of, :name %>
      <%= text_input of, :email, placeholder: "Email" %>
      <%= error_tag of, :email %>
      <%= password_input of, :password, placeholder: "Password" %>
      <%= error_tag of, :password %>

    <% end %>
  </div>
  <%= submit "Create Organization", class: "btn btn-primary" %>
<% end %>

1 个答案:

答案 0 :(得分:0)

似乎Ecto.Multi并不难用于我的情况。

这是我所做的,而且效果很好

changeset = %Organization{} |> Organization.registration_changeset(params)

multi = Multi.new
|> Multi.insert(:organization, changeset)
|> Multi.run(:user, fn %{organization: organization} ->
  organization
  |> Repo.preload(:users)
  |> Changeset.change
  |> Changeset.put_assoc(:users, [organization.owner])
  |> Repo.update
end)

case Repo.transaction(multi) do
  {:ok, %{organization: organization}} ->
    conn
    |> put_flash(:info, "#{organization.name} created!")
    |> redirect(to: organization_path(conn, :show))
  {:error, :organization, changeset, %{}} ->
    render(conn, "new.html", changeset: changeset)
end