Changeset validations with the rules of a picker (see Koyomi.Rules).
Give the same rules to the component and to the changeset, so that the browser and the server never disagree:
@stay_rules Koyomi.Rules.new(min: :today, max_days: 14)
def changeset(booking, attrs) do
booking
|> cast(attrs, [:stay])
|> validate_required([:stay])
|> Koyomi.Changeset.validate_range(:stay, @stay_rules)
endEach validation checks the field only when it has a value (a change or
the data's own value), and adds at most one error. The error has
validation: :koyomi and kind: (the reason, see Koyomi.Rules.error/0)
in its keys, and the values of the message (min, max, count,
date...), so that translate_error/1 of a Phoenix app can translate
it. count is set for the messages with a number, for plural forms.
Options
:message- a message for every error, or a map of messages by kind (%{too_long: "a stay is two weeks at most"}); the kinds that the map does not name keep the default message.
Summary
Functions
Casts a date and time that a person gave in a time zone into a
DateTime in UTC.
Returns the default error messages, by kind.
Validates a :date field (or a Koyomi.Month or Koyomi.Week field):
its day must be allowed by the rules.
Validates a list of days (Koyomi.Dates or {:array, :date}): each
day, and the count of days.
Validates a date and time field (:utc_datetime, :naive_datetime and
their _usec forms): its day, its time of day in the time zone of the
rules, and the bounds.
Validates a range of days.
Validates a :time field against :min_time, :max_time and :step.
Functions
@spec cast_datetime(Ecto.Changeset.t(), map(), atom(), String.t(), keyword()) :: Ecto.Changeset.t()
Casts a date and time that a person gave in a time zone into a
DateTime in UTC.
The date and time picker with a time_zone posts an ISO 8601 string
with the offset of the zone ("2027-07-15T14:30:00+01:00"), which a
:utc_datetime field casts on its own. This function also reads a
date and time without an offset ("2027-03-15T14:30", as the browser's
own input posts it when JavaScript is off) as a wall clock time in
time_zone. Call it instead of listing the field in cast/4:
booking
|> cast(attrs, [:name])
|> Koyomi.Changeset.cast_datetime(attrs, :starts_at, "Europe/Lisbon")A wall clock time that the zone skips (the hour the clocks go forward)
gets the error :gap; a time that it repeats (the hour the clocks go
back) takes the first of the two instants, or the second with
ambiguous: :later, or gets the error :ambiguous with
ambiguous: :error. A zone other than UTC needs a time zone database
(see Calendar). params may have string or atom keys.
Returns the default error messages, by kind.
Examples
iex> Koyomi.Changeset.messages().disabled
"is not available"
@spec validate_date( Ecto.Changeset.t(), atom(), Koyomi.Rules.t() | keyword(), keyword() ) :: Ecto.Changeset.t()
Validates a :date field (or a Koyomi.Month or Koyomi.Week field):
its day must be allowed by the rules.
Examples
iex> types = %{due: :date}
iex> changeset = Ecto.Changeset.cast({%{}, types}, %{"due" => "2027-03-13"}, [:due])
iex> changeset = Koyomi.Changeset.validate_date(changeset, :due, disabled_weekdays: [6, 7])
iex> changeset.errors
[due: {"is not available", [validation: :koyomi, kind: :disabled, date: ~D[2027-03-13]]}]
@spec validate_dates( Ecto.Changeset.t(), atom(), Koyomi.Rules.t() | keyword(), keyword() ) :: Ecto.Changeset.t()
Validates a list of days (Koyomi.Dates or {:array, :date}): each
day, and the count of days.
@spec validate_datetime( Ecto.Changeset.t(), atom(), Koyomi.Rules.t() | keyword(), keyword() ) :: Ecto.Changeset.t()
Validates a date and time field (:utc_datetime, :naive_datetime and
their _usec forms): its day, its time of day in the time zone of the
rules, and the bounds.
@spec validate_range( Ecto.Changeset.t(), atom() | {atom(), atom()}, Koyomi.Rules.t() | keyword(), keyword() ) :: Ecto.Changeset.t()
Validates a range of days.
field is a Koyomi.DateRange field (a Date.Range), or a {from, to}
tuple of two :date fields; with two fields, the error goes on from
for a rule of its day and on to otherwise.
Examples
iex> types = %{check_in: :date, check_out: :date}
iex> params = %{"check_in" => "2027-03-01", "check_out" => "2027-03-20"}
iex> changeset = Ecto.Changeset.cast({%{}, types}, params, [:check_in, :check_out])
iex> changeset = Koyomi.Changeset.validate_range(changeset, {:check_in, :check_out}, max_days: 14)
iex> changeset.errors
[check_out: {"must be at most %{count} days", [validation: :koyomi, kind: :too_long, count: 14, max_days: 14]}]
@spec validate_time( Ecto.Changeset.t(), atom(), Koyomi.Rules.t() | keyword(), keyword() ) :: Ecto.Changeset.t()
Validates a :time field against :min_time, :max_time and :step.