【问题标题】:Adding a random and unique field to Ecto model向 Ecto 模型添加随机且唯一的字段
【发布时间】:2019-01-10 20:13:42
【问题描述】:

我想在 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

【问题讨论】:

  • 是的,在 changeset 函数和控制器中设置约束,当其他字段准备好时,生成 slug,将其放入变更集中并尝试保存。然后匹配三种情况 a) 它工作 -> 继续 b) 关于 slug 的 changeset.error -> 递归调用自身以生成 slug 并重试 c) 其他错误 -> 处理或出现在 GUI 中。
  • @tkowal 好的,我明白了,但我有一个问题:如何区分错误?例如,我怎么知道插入失败是因为 slug 还是有其他错误?
  • @tkowal 没关系。看了Ecto的源码,发现错误放在changeset.error。你说了,但我之前没听懂。

标签: elixir phoenix-framework ecto


【解决方案1】:

已经 4 个月了,所以我猜你想通了。您应该创建不同的变更集,具体取决于您正在执行的操作以及用于“读取”目的的基本变更集。

显式 > 隐式

你的模型可能会这样结束:

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

【讨论】:

【解决方案2】:

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

欢迎各种反馈!

【讨论】:

    【解决方案3】:
    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% 的情况。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-17
      • 2017-07-14
      • 2017-06-06
      • 2012-10-05
      相关资源
      最近更新 更多