biagidp
biagidp

Reputation: 2185

control flow for custom validation in phoenix schema

I'm trying to implement some validations in a schema for a phoenix app I'm working on. I have an attribute on my schema, :details, that holds (among other things) a map that holds (among other things) data about questions and answers. An example:

%{
  question: "What's for dinner?",
  options:[
    %{option: "Chicken", selected: false},
    %{option: "Fish", selected: true}
  ]
}

The schema also has a :type attribute, which is used to describe the contents of the :details map. I've written the following validation to ensure that the :details map contains an array of options when the :type is "dialogue".

def validate_dialogue_options(changeset) do
  validate_change(changeset, :details, fn :details, details ->
    if(changeset.changes.type == "dialogue") do 
      cond do
        Map.has_key?(details, :options) ->
          []
        true ->
          [details: "must include options for dialogue events"]
      end
    else
      []
    end
  end)
end

This works, but it feels really cumbersome. I'm fairly new to elixir, so I'm sure there's a much more concise way to accomplish the above. Can anyone recommend a more streamlined way to implement the above?

Thanks!

BONUS: I'm currently working on an additional validation to ensure at least one option in the details map is selected. Right now I'm using the below which works, but I'd be interested in a more concise implementation!

Enum.member?(Enum.map(details.options, fn(x) -> x.selected end), true)

Upvotes: 1

Views: 192

Answers (1)

Dogbert
Dogbert

Reputation: 222158

The first part can be simplified using pattern matching. We're interested in two terms, changeset.changes.type and details so we match on a tuple of those terms.

validate_change changeset, :details, fn :details, details ->
  case {changeset.changes.type, details} do
    {"dialogue", %{options: _}} -> []
    {"dialogue", _} -> [details: "must include options for dialogue events"]
    _ -> []
  end
end

The second one can be simplified using Enum.any?/2

Enum.any?(details.options, fn x -> x.selected end)

or even

Enum.any?(details.options, & &1.selected)

Upvotes: 4

Related Questions