向 Ecto 模型添加一个随机且唯一的字段
Adding a random and unique field to Ecto model
我想在 Ecto 模型中有一个独特的领域。该字段应包含一个 随机字符串 ,我可以轻松生成它(例如,参见 )。但是,我想避免生成字符串并检查它是否已经存在于数据库中,因为这会使我面临竞争条件。
我想让它重试插入,直到找到唯一的字符串。但是我该怎么做呢?它应该在 changeset/2
函数内吗?
defmodule LetsPlan.Event do
use LetsPlan.Web, :model
schema "events" do
field :name, :string
field :from, Ecto.DateTime
field :to, Ecto.DateTime
field :slug, :string
timestamps
end
@required_fields ~w(from to)
@optional_fields ~w(slug)
def changeset(model, params \ :empty) do
model
|> cast(params, @required_fields, @optional_fields)
|> unique_constraint(:slug)
end
end
根据 @tkowal 的建议,我写了以下内容。在模型模块中:
def changeset(model, params \ :empty) do
unless params == :empty do
params = params |> cast_date("from") |> cast_date("to")
end
model
|> cast(params, @required_fields, @optional_fields)
|> unique_constraint(:slug)
end
defp cast_date(params, key) do
params |> Map.update(key, nil, &Utils.to_ecto_date/1)
end
在控制器中:
def create(conn, %{"event" => params}) do
params = Map.put(params, "slug", Utils.random_string(10))
changeset = Event.changeset(%Event{}, params)
case Repo.insert(changeset) do
{:ok, event} ->
conn
|> put_flash(:info, "Event created successfully")
|> redirect(to: event_path(conn, :show, event.slug))
{:error, changeset} ->
if Keyword.has_key? changeset.errors, :slug do
create(conn, %{"event" => params})
else
render conn, "new.html", changeset: changeset
end
end
end
欢迎各种反馈!
已经 4 个月了,我猜你已经明白了。您应该根据您正在执行的操作和用于 "read" 目的的基本变更集创建不同的变更集。
显式 > 隐式
您的模型可能会变成这样:
defmodule App.Classified do
@rules_create %{
:required_fields => ~w(tenant_id firstname lastname email password password_confirmation phone birthday description),
:optional_fields => ~w(),
}
@rules_update %{
:required_fields => ~w(firstname lastname email phone birthday description),
:optional_fields => ~w()
}
def changeset(model, params \ :empty) do
model
|> cast(params, [], [])
end
@doc """
Changeset when you create a new classified
"""
def create_changeset(model, params \ :empty) do
model
|> cast(params, @rules_create.required_fields, @rules_create.optional_fields)
|> validate_length(:description, min: 280)
|> validate_length(:password, min: 6)
|> validate_confirmation(:password)
|> unique_constraint(:email)
|> hash_password
|> make_token
|> make_search
end
@doc """
Changeset when you update an classified
"""
def update_changeset(model, params \ :empty) do
model
|> cast(params, @rules_update.required_fields, @rules_update.optional_fields)
|> validate_length(:description, min: 280)
|> make_search
end
end
defmodule App.User.Slug do
import Ecto.Changeset, only: [unsafe_validate_unique: 3, change: 2]
def build_slug(changeset) do
slug = your_fn_to_build_slug(changeset.username)
make_sure_unique(slug)
end
defp make_sure_unique(slug, attempt \ 1) do
slug = if attempt > 1, do: "#{slug}-#{attempt}", else: slug
changeset = change(%User{}, slug: slug)
changeset = unsafe_validate_unique(changeset, [:slug], App.Repo)
if is_slug_unique(changeset) do
slug
else
make_sure_unique(slug, attempt + 1)
end
end
defp is_slug_unique(%Ecto.Changeset{valid?: true}), do: true
defp is_slug_unique(_), do: false
end
model
|> cast(params, @required_fields, @optional_fields)
|> App.User.Slug.build_slug
|> other_validations_you_need
请注意,unsafe_validate_unique
不能保证它是唯一的,尽管由于比赛条件。但应该在 99% 的情况下对你有效。
我想在 Ecto 模型中有一个独特的领域。该字段应包含一个 随机字符串 ,我可以轻松生成它(例如,参见
我想让它重试插入,直到找到唯一的字符串。但是我该怎么做呢?它应该在 changeset/2
函数内吗?
defmodule LetsPlan.Event do
use LetsPlan.Web, :model
schema "events" do
field :name, :string
field :from, Ecto.DateTime
field :to, Ecto.DateTime
field :slug, :string
timestamps
end
@required_fields ~w(from to)
@optional_fields ~w(slug)
def changeset(model, params \ :empty) do
model
|> cast(params, @required_fields, @optional_fields)
|> unique_constraint(:slug)
end
end
根据 @tkowal 的建议,我写了以下内容。在模型模块中:
def changeset(model, params \ :empty) do
unless params == :empty do
params = params |> cast_date("from") |> cast_date("to")
end
model
|> cast(params, @required_fields, @optional_fields)
|> unique_constraint(:slug)
end
defp cast_date(params, key) do
params |> Map.update(key, nil, &Utils.to_ecto_date/1)
end
在控制器中:
def create(conn, %{"event" => params}) do
params = Map.put(params, "slug", Utils.random_string(10))
changeset = Event.changeset(%Event{}, params)
case Repo.insert(changeset) do
{:ok, event} ->
conn
|> put_flash(:info, "Event created successfully")
|> redirect(to: event_path(conn, :show, event.slug))
{:error, changeset} ->
if Keyword.has_key? changeset.errors, :slug do
create(conn, %{"event" => params})
else
render conn, "new.html", changeset: changeset
end
end
end
欢迎各种反馈!
已经 4 个月了,我猜你已经明白了。您应该根据您正在执行的操作和用于 "read" 目的的基本变更集创建不同的变更集。
显式 > 隐式
您的模型可能会变成这样:
defmodule App.Classified do
@rules_create %{
:required_fields => ~w(tenant_id firstname lastname email password password_confirmation phone birthday description),
:optional_fields => ~w(),
}
@rules_update %{
:required_fields => ~w(firstname lastname email phone birthday description),
:optional_fields => ~w()
}
def changeset(model, params \ :empty) do
model
|> cast(params, [], [])
end
@doc """
Changeset when you create a new classified
"""
def create_changeset(model, params \ :empty) do
model
|> cast(params, @rules_create.required_fields, @rules_create.optional_fields)
|> validate_length(:description, min: 280)
|> validate_length(:password, min: 6)
|> validate_confirmation(:password)
|> unique_constraint(:email)
|> hash_password
|> make_token
|> make_search
end
@doc """
Changeset when you update an classified
"""
def update_changeset(model, params \ :empty) do
model
|> cast(params, @rules_update.required_fields, @rules_update.optional_fields)
|> validate_length(:description, min: 280)
|> make_search
end
end
defmodule App.User.Slug do
import Ecto.Changeset, only: [unsafe_validate_unique: 3, change: 2]
def build_slug(changeset) do
slug = your_fn_to_build_slug(changeset.username)
make_sure_unique(slug)
end
defp make_sure_unique(slug, attempt \ 1) do
slug = if attempt > 1, do: "#{slug}-#{attempt}", else: slug
changeset = change(%User{}, slug: slug)
changeset = unsafe_validate_unique(changeset, [:slug], App.Repo)
if is_slug_unique(changeset) do
slug
else
make_sure_unique(slug, attempt + 1)
end
end
defp is_slug_unique(%Ecto.Changeset{valid?: true}), do: true
defp is_slug_unique(_), do: false
end
model
|> cast(params, @required_fields, @optional_fields)
|> App.User.Slug.build_slug
|> other_validations_you_need
请注意,unsafe_validate_unique
不能保证它是唯一的,尽管由于比赛条件。但应该在 99% 的情况下对你有效。