Migration database constraint

2019-08-20 04:44发布

问题:

I'm writing a migration file and one of the fields I'd like to set a DB (Postgres) constraint for:

 def change do
   create table(:reviews) do
     add(:title, :string)
     add(:contents, :string)
     add(:stars, :integer)
     add(:user_id, references(:users), null: false)
     add(:user_id, references(:users), null: false)

     timestamps()
  end

Id like to set a constraint on the stars field to be integer values only 1-5. How can I do this with Ecto.Migration?

回答1:

How can I do this with Ecto.Migration

Ecto.Migration.create(Ecto.Migration.constraint/3)

foo/priv/repo/migations/....create_reviews.exs:

defmodule Foo.Repo.Migrations.CreateReviews do
  use Ecto.Migration

  def change do
    create table("reviews") do
      add :title, :string
      add :contents, :string
      add :stars, :integer

      timestamps()
    end

    create constraint("reviews",
                      :stars_range, 
                      check: "stars>=1 and stars<=5")
  end
end

foo/lib/foo/review.ex:

defmodule Foo.Review do
  use Ecto.Schema
  import Ecto.Changeset
  require Logger

  schema "reviews" do
    field :title, :string
    field :contents, :string
    field :stars, :integer

    timestamps()
  end

  def changeset(%Foo.Review{}=review, attrs \\ %{}) do
    review
    |> cast(attrs, [:title, :contents, :stars])
    |> check_constraint(
        :stars,
        name: :stars_range,
        message: "stars must be between 1 and 5 (inclusive)"
      )
  end

end

foo/lib/foo.ex:

defmodule Foo do
  alias Foo.Review
  require Logger

  @repo Foo.Repo

  def list_reviews do
    @repo.all(Review)
  end

  def insert_review(attrs) do
    changeset = Review.changeset(%Review{}, attrs)
    @repo.insert(changeset)
  end

  def delete_review(%Review{}=review) do
    @repo.delete(review)
  end

end

In iex:

~/phoenix_apps/foo$ iex -S mix
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.8.2) - press Ctrl+C to exit (type h() ENTER for help)

iex(1)> reviews = Foo.list_reviews()
[debug] QUERY OK source="reviews" db=1.0ms decode=4.0ms queue=1.5ms
SELECT r0."id", r0."title", r0."contents", r0."stars", r0."inserted_at", r0."updated_at" FROM "reviews" AS r0 []
[]

iex(2)> Foo.insert_review(%{title: "book", contents: "good", stars: 4})
[debug] QUERY OK db=5.0ms queue=2.4ms
INSERT INTO "reviews" ("contents","stars","title","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) RETURNING "id" ["good", 4, "book", ~N[2019-07-09 23:26:44], ~N[2019-07-09 23:26:44]]
{:ok,
 %Foo.Review{
   __meta__: #Ecto.Schema.Metadata<:loaded, "reviews">,
   contents: "good",
   id: 5,
   inserted_at: ~N[2019-07-09 23:26:44],
   stars: 4,
   title: "book",
   updated_at: ~N[2019-07-09 23:26:44]
 }}

iex(3)> Foo.insert_review(%{title: "movie", contents: "shite", stars: 0})
[debug] QUERY ERROR db=10.3ms queue=2.0ms
INSERT INTO "reviews" ("contents","stars","title","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) RETURNING "id" ["shite", 0, "movie", ~N[2019-07-09 23:27:13], ~N[2019-07-09 23:27:13]]
{:error,
 #Ecto.Changeset<
   action: :insert,
   changes: %{contents: "shite", stars: 0, title: "movie"},
   errors: [
     stars: {"stars must be between 1 and 5 (inclusive)",
      [constraint: :check, constraint_name: "stars_range"]}
   ],
   data: #Foo.Review<>,
   valid?: false
 >}

iex(4)> reviews = Foo.list_reviews()                                     
[
  %Foo.Review{
    __meta__: #Ecto.Schema.Metadata<:loaded, "reviews">,
    contents: "good",
    id: 5,
    inserted_at: ~N[2019-07-09 23:26:44],
    stars: 4,
    title: "book",
    updated_at: ~N[2019-07-09 23:26:44]
  }
]
[debug] QUERY OK source="reviews" db=4.7ms
SELECT r0."id", r0."title", r0."contents", r0."stars", r0."inserted_at", r0."updated_at" FROM "reviews" AS r0 []

iex(5)> Foo.insert_review(%{title: "theater", contents: "really, really good", stars: 6})
[debug] QUERY ERROR db=1.6ms queue=2.2ms
INSERT INTO "reviews" ("contents","stars","title","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) RETURNING "id" ["really, really good", 6, "theater", ~N[2019-07-09 23:28:07], ~N[2019-07-09 23:28:07]]
{:error,
 #Ecto.Changeset<
   action: :insert,
   changes: %{contents: "really, really good", stars: 6, title: "theater"},
   errors: [
     stars: {"stars must be between 1 and 5 (inclusive)",
      [constraint: :check, constraint_name: "stars_range"]}
   ],
   data: #Foo.Review<>,
   valid?: false
 >}

iex(6)> reviews = Foo.list_reviews()                                                     
[debug] QUERY OK source="reviews" db=0.5ms
SELECT r0."id", r0."title", r0."contents", r0."stars", r0."inserted_at", r0."updated_at" FROM "reviews" AS r0 []
[
  %Foo.Review{
    __meta__: #Ecto.Schema.Metadata<:loaded, "reviews">,
    contents: "good",
    id: 5,
    inserted_at: ~N[2019-07-09 23:26:44],
    stars: 4,
    title: "book",
    updated_at: ~N[2019-07-09 23:26:44]
  }

]