Repository: elixir-ecto/ecto
Pull request #2071 (2017-05-22 14:23:22)

Add prefix constraint name checking
Feature request: https://groups.google.com/forum/#!msg/elixir-ecto/vwzgldHKNMM/TZzBYO9LBgAJ

Reasoning:
I am using Citus which is a Postgres extension that provides sharding capabilities. When indices are propagated to worker nodes, an integer is appended to the end of the index name. This means that I will have a normal name that Ecto can guess, but random integers at the end which differ between environments and runnings of the index being built. If Ecto supported prefix, I would set it to prefix mode and not have a custom name at all.


sb8244: To ensure sufficient coverage vs suffix:

```
➜  ecto git:(sb8244-prefix-constraints) grep -R "suffix" test
test/ecto/changeset_test.exs:    changeset = change(%Post{}) |> unique_constraint(:title, name: :whatever, match: :suffix, message: "is taken")
test/ecto/changeset_test.exs:           [%{type: :unique, field: :title, constraint: "whatever", match: :suffix, error: {"is taken", []}}]
test/ecto/repo_test.exs:    test "are mapped to repo constraint violation using suffix match" do
test/ecto/repo_test.exs:        |> Ecto.Changeset.unique_constraint(:foo, name: "custom_foo_index", match: :suffix)
```
ebert[bot]: Hello, @sb8244! This is your first Pull Request that will be reviewed by [Ebert](https://ebertapp.io/), an automatic Code Review service. It will leave comments on this diff with potential issues and style violations found in the code as you push new commits. You can also see all the issues found on this Pull Request on its [review page](https://ebertapp.io/github/elixir-ecto/ecto/pulls/2071). Please check our [documentation](https://ebertapp.io/docs) for more information. 

Patch:
diff --git a/lib/ecto/changeset.ex b/lib/ecto/changeset.ex
index 0d93b5e3..6f95918c 100644
--- a/lib/ecto/changeset.ex
+++ b/lib/ecto/changeset.ex
@@ -269,7 +269,7 @@ defmodule Ecto.Changeset do
   }
 
   @relations [:embed, :assoc]
-  @match_types [:exact, :suffix]
+  @match_types [:exact, :suffix, :prefix]
   @actions [:insert, :update, :delete, :replace]
 
   @doc """
diff --git a/lib/ecto/repo/schema.ex b/lib/ecto/repo/schema.ex
index 74cda050..fa359829 100644
--- a/lib/ecto/repo/schema.ex
+++ b/lib/ecto/repo/schema.ex
@@ -488,6 +488,7 @@ defmodule Ecto.Repo.Schema do
             case {c.type, c.constraint,  c.match} do
               {^type, ^constraint, :exact} -> true
               {^type, cc, :suffix} -> String.ends_with?(constraint, cc)
+              {^type, cc, :prefix} -> String.starts_with?(constraint, cc)
               _ -> false
             end
           end)


Test patch:
diff --git a/test/ecto/changeset_test.exs b/test/ecto/changeset_test.exs
index 3c8d342c..046bd612 100644
--- a/test/ecto/changeset_test.exs
+++ b/test/ecto/changeset_test.exs
@@ -1032,6 +1032,10 @@ defmodule Ecto.ChangesetTest do
     assert changeset.constraints ==
            [%{type: :unique, field: :title, constraint: "whatever", match: :suffix, error: {"is taken", []}}]
 
+    changeset = change(%Post{}) |> unique_constraint(:title, name: :whatever, match: :prefix, message: "is taken")
+    assert changeset.constraints ==
+           [%{type: :unique, field: :title, constraint: "whatever", match: :prefix, error: {"is taken", []}}]
+
     assert_raise ArgumentError, ~r/invalid match type: :invalid/, fn ->
       change(%Post{}) |> unique_constraint(:title, name: :whatever, match: :invalid, message: "is taken")
     end
diff --git a/test/ecto/repo_test.exs b/test/ecto/repo_test.exs
index 1513ebf3..080b99f5 100644
--- a/test/ecto/repo_test.exs
+++ b/test/ecto/repo_test.exs
@@ -364,6 +364,16 @@ defmodule Ecto.RepoTest do
       refute changeset.valid?
     end
 
+    test "are mapped to repo constraint violation using prefix match" do
+      my_schema = %MySchema{id: 1}
+      changeset =
+        put_in(my_schema.__meta__.context, {:invalid, [unique: "foo_table_custom_foo_index"]})
+        |> Ecto.Changeset.change(x: "foo")
+        |> Ecto.Changeset.unique_constraint(:foo, name: "foo_table_custom_foo", match: :prefix)
+      assert {:error, changeset} = TestRepo.insert(changeset)
+      refute changeset.valid?
+    end
+
     test "may fail to map to repo constraint violation on name" do
       my_schema = %MySchema{id: 1}
       changeset =