一次将一个模型保存在父对象的多个关联中
Save one model in multiple associations for parent object at once
我有两个模型 - Organization
和 User
组织有很多用户,也属于一个所有者(所有者也是 User
模特)。
我有组织的注册表单,用户可以在其中为组织输入 name
,为 user
输入 name
和 password
(user
是organization
的嵌套对象)。成功注册后,我需要有一个 Organization
和关联的 owner
(User
模型),而且,这个 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 %>
看来 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
我有两个模型 - Organization
和 User
组织有很多用户,也属于一个所有者(所有者也是 User
模特)。
我有组织的注册表单,用户可以在其中为组织输入 name
,为 user
输入 name
和 password
(user
是organization
的嵌套对象)。成功注册后,我需要有一个 Organization
和关联的 owner
(User
模型),而且,这个 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 %>
看来 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