1
votes

I'm new to Elixir and Phoenix (less than 10 days) but very excited about it and like many others I come from a Rails background.

I understand Ecto is not AR and callbacks have been deprecated or removed but I need to add a custom validation that should only happen on creation and needs to perform a query.

Here's what my Reservation model basically looks like.

schema "reservations" do
  field :ends_at, :utc_datetime
  field :name, :string, null: false
  field :starts_at, :utc_datetime
  field :user_id, :id
end

and then I have another schema Slot, which looks like this:

schema "slots" do
  field :ends_at, :utc_datetime
  field :name, :string, null: false
  field :starts_at, :utc_datetime
  field :admin_id, :id
end

Whenever I'm adding a new reservation, I need to query my DB to check if there are any slots with matching ends_at and starts_at. If there are, I need to prevent the record from being saved and add an error to it (similar to what in Rails we accomplish with throw :abort and errors.add).

Can someone please shed a light on this? What's the Ecto way of doing this?

Best regards

1

1 Answers

1
votes

*edit: added examples using separate changesets for creation and updation

You can add a custom validation function in your changeset validation chain and do DB queries in it.

Haven't run this code, but something like this should work

# separate changeset for creation
def create_changeset(struct, params) do
  struct
  |> cast(params, [...list of fields...])
  |> validate_unique([:name]) # lets say it has to be unique
  |> validate_slots # -- custom validation
end

# separate changeset for updation, no slot-check
def update_changeset(struct, params) do
  struct
  |> cast(params, [...list of fields...])
  |> validate_unique([:name]) # lets say it has to be unique
end


def validate_slots(changeset) do
    starts_at = get_field(changeset, :starts_at)
    ends_at = get_field(changeset, :ends_at)
    slots = Repo.all(from s in Slot, where: s.starts_at == ^starts_at and s.ends_at == ^ends_at)

    if Enum.empty?(slots) do
      changeset
    else
      add_error( changeset, :starts_at, "has slot with similar starts_at/ends_at")
    end
end

#---- using the changesets
# creation
%Reservation{} |> Reservation.create_changeset(params) |> Repo.insert()

# updation
%Reservation{} |> Reservation.update_changeset(params) |> Repo.update()

Although, from the look of it, you should probably normalize your starts_at and ends_at into a separate table called booking_time_frame or something and add unique indexes to it.

Or you might end up with more types of bookings and then have to check starts_at/ends_at across 3 tables and so on.