Elixir Ecto: 复合(多列)唯一约束的Changeset校验

我们有这么一张表的迁移脚本 20160718132517_create_vehicle_change_owner_table.exs 描述了机动车过户的记录

defmodule ElectricProto.Repo.Migrations.CreateVehicleChangeOwnerTable do
  use Ecto.Migration

  def up do
    create table(:vehicle_change_owner) do
      add :vehicle_id,    :string  # 车牌号
      add :old_user,      :integer # 原车主
      add :new_user,      :integer # 现车主
      add :description,   :string  # 备注
      timestamps
    end
    # 这里定义了多列唯一索引
    create index(:vehicle_change_owner, [:vehicle_id, :old_user, :new_user], unique: true)
  end

  def down do
    drop table(:vehicle_change_owner)
  end
end

这种情况下, 我们需要明确指定索引的名字, 如果你在创建索引的时候没有显示指定名字, 那么默认的名称为:

vehicle_change_owner_vehicle_id_old_user_new_user_index

其命名规则为:

表名称_所有字段名称通过下划线连接_index

在变更集中的数据验证

def changeset(struct, params \\ %{}) do
  struct
  |> cast(params, [
    :vehicle_id,
    :old_user,
    :new_user,
    :description
  ])
  |> validate_required([
    :vehicle_id,
    :old_user,
    :new_user,
    :description
  ])
  |> unique_constraint(
    :username, 
    name: :vehicle_change_owner_vehicle_id_old_user_new_user_index, 
    message: "不能插入重复的过户记录"
  )
end

vehicle_change_owner_vehicle_id_old_user_new_user_index 这个名字太长, 我们可以在迁移脚本中明确指定名称来缩短一些

create index(
  :vehicle_change_owner, 
  [:vehicle_id, :old_user, :new_user], 
  name: :vehicle_change_owner_index
)

注意

MySQL 中对值的比较是不区分大小写的, PostgreSQL 默认是区分大小写的, 因此在校验唯一性约束的时候如果需要不区分大小写的比较, 可以用 Ecto.Changeset.update_change/3来明确把的把值转换为小写

cast(data, params, [:email], [])
|> update_change(:email, &String.downcase/1)
|> unique_constraint(:email)

最后展示一个插入数据的例子

def api_post(params) do
  changeset = changeset(%__MODULE__{}, params)
  if changeset.valid? do
    case Repo.insert(changeset) do
      {:ok, struct} ->
        Resp.responses.status_201
        |> Map.put(:result, %{last_inserted_id: struct.id})
      {:error, changeset} ->
        errors = Enum.map(changeset.errors, fn error ->
          {_key, {msg, _opts}} = error
          msg
        end)
        Resp.responses.status_400
        |> Map.put(:error, %{messages: errors})
    end
  end
end

api_post 函数的 params 参数在Phoenix 框架中被包装成一个Map

你可能感兴趣的:(ecto,elixir)