Ecto validate_required "one of"

In some, admittedly relatively rare cases when I needed to validate presence of an attribute unless another attribute is already present I used to do

validate_presence_of :the_attribute, unless:  <another_attribute.present?> 

That’s obviously in “the other” framework’s syntax. How do you guys do such things with Ecto? Do we have something remotely similar for Ecto? Or custom validations and manual checking to the rescue?

1 Like

Just have a pipe-able function e.g.

def validate_unless_other_present(changeset, required_field, the_other_field) do
  case Changeset.get_field(changeset, the_other_field) do
    nil -> Changeset.validate_required(changeset, required_field) # the other field is not present, validate away
    _value -> changeset # the other field is present, skip validation
  end
end

And put it inside your changeset function(s) that need to make use of that logic e.g.

def changeset(data, params) do
  data
  |> Changeset.change(params)
  |> validate_required(@required_fields) # the optionally-required field from above must NOT be here
  |> cast_assoc(...)
  |> cast_embed(...)
  |> validate_unless_other_present(:the_attribute, :another_attribute)
end

And you should be good to go.

4 Likes

Yeah one of the things I really like about how changesets work as a datastructure is that instead of needing to reinvent their own control flow you can just use the language level controls to compose it as desired.

I see. That’s the custom validation path I mentioned but you wrapped it up so nicely - thanks!

Consider marking a comment as a solution – it helps future readers by surfacing such questions higher in the search function. I get no “points” or “reputation” from it.

Happy to help! :023:

Yep, definitely. Also Ecto.Multi.

Elixir opened my eyes about accumulating state and/or future actions in one place and then get/set/do everything in one call at the end. I’ve been happily applying this way of work in my other work as well – mainly Rust and some Golang. And it does help there as well.

I do it gladly™ :wink: And if it gave you points or reputation I’d do it as much or even more gladly