gorav
Validation on association set
I have a registrations model and participants model as follows:
schema "registrations" do
field :conf_number, :string
field :status, :integer, default: 1
belongs_to :event, Event
has_many :participants, Participant
timestamps()
end
schema "participants" do
field :accommodation_choice, :integer, default: 1
field :age, :integer
field :amount_paid, :integer, default: 0
field :arrival_time, :integer, default: 1
field :city, :string
field :conf_number, :integer
...
field :primary, :boolean, default: false
...
field :admin_comment, :string
belongs_to :registration, Registration
belongs_to :country, Country
belongs_to :payment, Payment
timestamps()
end
I am using nested resources to save the registration with participants. I want to include a validation in changeset that there should be one participant with primary boolean as true while saving the registration. What is the best way to achieve this?
I have create_changeset and changeset (for update) with some logic running but both of them call the following common_changeset in the end:
defp common_changeset(changeset) do
changeset
|> validate_required([:conf_number, :event, :status])
|> cast_assoc(:participants, required: true)
|> unique_constraint(:conf_number)
end
I want to be able to add something like check_primary method to the common_changeset pipeline as last step. Thanks.
Most Liked
gorav
Solved the first requirement as follows:
@spec check_primary(Ecto.Changeset.t()) :: Ecto.Changeset.t()
defp check_primary(%{valid?: true} = changeset) do
registration = apply_changes(changeset)
case Enum.count(registration.participants, fn participant -> participant.primary end) do
1 -> changeset
_ -> add_error(changeset, :base, "One primary registrant required")
end
end
defp check_primary(changeset), do: changeset
not sure if this is the best approach though. @idi527 thanks for all your help.
idi527
Maybe something like Ecto.Multi might help. It would look like this
alias Ecto.Multi
@spec create_registration(%{binary => term} | %{atom => term}) :: {:ok, %{registration: %Registration{}, check_primary: :ok}} | {:error, :registration | :check_primary, Ecto.Changeset.t | :error, changes_so_far :: %{optional(atom) => term}}
def create_registration(attrs) do
Multi.new()
|> Multi.insert(:registration, Registration.changeset(%Registration{}, attrs))
|> Multi.run(:check_primary, fn %{registration: registration} -> check_primary(registration) end)
|> Repo.transaction()
end
which would rollback if check_primary(registration) returns :error.
Checking them separately does not help.
Why? I thought it’d work … Can you show the code that you’ve used?
I need to compute fees for the participant but that can happen only after arrival_date and departure_date changes, if any, are applied to the record
That also could be done either in a multi or through a changeset, I prefer changesets:
@spec put_fees(Ecto.Changeset.t) :: Ecto.Changeset.t
defp put_fees(%{valid?: true, changes:%{arrival_date: arrival_date, departure_date: departure_date}} = changeset) do
put_change(changeset, :fees, compute_fees(arrival_date, departure_date))
end
defp put_fees(changeset), do: changeset







