coen.bakker
Exclusive OR in Ecto: validating that only one of two fields is given
I have a table for a collection of tasks. Only one of the fields :expiration_time and :expiration_all_day? may have a value (i.e. not be null/nil). Also, at least one should be provided.
Was making a start with implementing this, but can’t figure out how to use multiple fields in create constraint. I expected to be able to do this:
defmodule TodayTodo.Repo.Migrations.CreateTasks do
use Ecto.Migration
def change do
create table(:tasks, primary_key: false) do
add :id, :uuid, primary_key: true
add :description, :string
add :expiration_date, :date
add :expiration_time, :time
add :expiration_all_day?, :boolean # Prob throws
timestamps()
end
create(
constraint(
:tasks,
:validate_time_or_all_day,
check: "(expiration_time NOT NULL) OR (expiration_all_day? NOT NULL)"
)
)
end
end
defmodule TodayTodo.Tasks.Task do
use Ecto.Schema
import Ecto.Changeset
@primary_key {:id, :binary_id, autogenerate: true}
schema "tasks" do
field :description, :string
field :expiration_date, :date
field :expiration_time, :time
field :expiration_all_day?, :boolean
timestamps()
end
@doc false
def changeset(task, attrs) do
task
|> cast(attrs, [:id, :description, :expiration_date, :expiration_time, :expiration_all_day?])
|> validate_required([:description, :expiration_date])
|> check_constraint(:expiration_time, name: :validate_time_or_all_day)
end
end
But I get:
20:19:10.714 [info] create table tasks
20:19:10.718 [info] create check constraint validate_time_or_all_day on table tasks
** (Postgrex.Error) ERROR 42601 (syntax_error) syntax error at or near "NOT"
query: ALTER TABLE "tasks" ADD CONSTRAINT "validate_time_or_all_day" CHECK ((expiration_time IS NOT NULL) OR (expiration_all_day? IS NOT NULL))
(ecto_sql 3.8.3) lib/ecto/adapters/sql.ex:932: Ecto.Adapters.SQL.raise_sql_call_error/1
(elixir 1.13.3) lib/enum.ex:1593: Enum."-map/2-lists^map/1-0-"/2
(ecto_sql 3.8.3) lib/ecto/adapters/sql.ex:1024: Ecto.Adapters.SQL.execute_ddl/4
(ecto_sql 3.8.3) lib/ecto/migration/runner.ex:352: Ecto.Migration.Runner.log_and_execute_ddl/3
(ecto_sql 3.8.3) lib/ecto/migration/runner.ex:117: anonymous fn/6 in Ecto.Migration.Runner.flush/0
(elixir 1.13.3) lib/enum.ex:2396: Enum."-reduce/3-lists^foldl/2-0-"/3
(ecto_sql 3.8.3) lib/ecto/migration/runner.ex:116: Ecto.Migration.Runner.flush/0
(ecto_sql 3.8.3) lib/ecto/migration/runner.ex:289: Ecto.Migration.Runner.perform_operation/3
What am I missing?
EDIT: I changed the code blocks above, because I copied an old version of my code initially.
Most Liked
al2o3cr
You need to escape field names that have special characters like ? - Ecto normally takes care of this, but the SQL given to check is passed through verbatim.
evadne
A simple solution would be to change the design completely and simplify:
- Store expiration_date disallowing NULL
- Store expiration_time which is a time and can be NULL, if the value is NULL then the task is all day
al2o3cr
The second argument to constraint should be a string or atom for the constraint name, but you’re passing a list of atoms.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance










