Reputation: 8062
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
Upvotes: 1
Views: 270
Reputation: 847
*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.
Upvotes: 1