向Ecto模型添加随机和唯一字段

时间:2016-03-21 08:41:56

标签: elixir phoenix-framework ecto

我想在Ecto模型中拥有一个独特的字段。该字段应包含随机字符串,我可以轻松生成(例如,请参阅here)。但是,我想避免生成字符串并检查它是否已经存在于数据库中,因为这会让我遇到竞争条件。

我想让它重试插入,直到找到一个唯一的字符串。但我该怎么办?它应该在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

3 个答案:

答案 0 :(得分:2)

已经4个月了,所以我想你已经明白了。您应该根据您正在执行的操作创建不同的变更集,并为"读取"目的。

明确>隐式

你的模特最终可能就是这样:

{{1}}

答案 1 :(得分:0)

根据 @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

欢迎各种反馈!

答案 2 :(得分:0)

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%的情况下为您工作。