{"id":239,"date":"2018-10-29T16:06:42","date_gmt":"2018-10-29T16:06:42","guid":{"rendered":"https:\/\/packlane.com\/blog\/?p=239"},"modified":"2018-10-29T16:06:42","modified_gmt":"2018-10-29T16:06:42","slug":"using-ecto-changeset-for-data-validation","status":"publish","type":"post","link":"https:\/\/packlane.com\/blog\/using-ecto-changeset-for-data-validation\/","title":{"rendered":"Using Ecto.Changeset for data validation"},"content":{"rendered":"<!DOCTYPE html PUBLIC \"-\/\/W3C\/\/DTD HTML 4.0 Transitional\/\/EN\" \"http:\/\/www.w3.org\/TR\/REC-html40\/loose.dtd\">\n<?xml encoding=\"utf-8\" ?><html><body><p><a href=\"https:\/\/hexdocs.pm\/ecto\/\"><code>Ecto<\/code><\/a>, while commonly used as a database wrapper, can also serve as a powerful data validation tool thanks to its <a href=\"https:\/\/hexdocs.pm\/ecto\/Ecto.Changeset.html\"><code>Ecto.Changeset<\/code><\/a>.<\/p>\n<p>Let&rsquo;s say you have a list of movie data that you want to clean up and put into a nice <code>%Movie{}<\/code> struct that would consist of the following fields:<\/p>\n<ul>\n<li><code>title<\/code>, a string,<\/li>\n<li><code>runtime<\/code>, an integer representing the movie runtime in minutes,<\/li>\n<li><code>cast<\/code>, a list of strings for each actor name,<\/li>\n<li><code>releases<\/code>, a map of the release dates for different countries.<\/li>\n<\/ul>\n<p>While we could use Ecto&rsquo;s <a href=\"https:\/\/hexdocs.pm\/ecto\/Ecto.Schema.html#schema\/2\"><code>schema\/2<\/code><\/a> macro to define this, we will instead use a simple struct&sup1; as we won&rsquo;t actually be persisting this data to a database&sup2;.<\/p>\n<pre><code class=\"elixir\">defmodule Movie do\r\n  defstruct [:title, :runtime, :cast, :releases]\r\n\r\n  @type_constraints %{\r\n    title: :string,\r\n    runtime: :integer,\r\n    cast: {:array, :string},\r\n    releases: {:map, :date}\r\n  }\r\nend\r\n<\/code><\/pre>\n<p>You can check the list of available types <a href=\"https:\/\/hexdocs.pm\/ecto\/Ecto.Schema.html#module-primitive-types\">here<\/a>, and if you can&rsquo;t find what you need you can always define a <a href=\"https:\/\/hexdocs.pm\/ecto\/Ecto.Type.html\">custom type<\/a>.<\/p>\n<p>Now that we have defined our constraints, we want a function that&rsquo;ll take a regular map, validate it and return a <code>%Movie{}<\/code> if the input data is valid, or an error if it isn&rsquo;t.<\/p>\n<p>The first thing we&rsquo;ll do is filter out any field from the input map that isn&rsquo;t part of our <code>%Movie{}<\/code> struct or that can&rsquo;t be converted to the right type. The <a href=\"https:\/\/hexdocs.pm\/ecto\/Ecto.Changeset.html#cast\/4\"><code>cast\/4<\/code><\/a> function allows us to do just that:<\/p>\n<pre><code class=\"elixir\">cast(data, params, permitted, opts \\\\ [])\r\n<\/code><\/pre>\n<p><code>data<\/code> is our container, so here we&rsquo;ll be using an empty <code>%Movie{}<\/code> struct&sup3;. Since we&rsquo;re not using <a href=\"https:\/\/hexdocs.pm\/ecto\/Ecto.Schema.html#schema\/2\"><code>schema\/2<\/code><\/a> we&rsquo;ll need to add the type constraints here too: <code>{%Movie{}, @type_constraints}<\/code>. <code>params<\/code> is the data we want to filter and validate, <code>permitted<\/code> is a list of the keys we want to allow in <code>params<\/code>. <code>opts<\/code> allows to pass <code>empty_values:<\/code> (for example you might want to set this to [[], &ldquo;&rdquo;] to make empty lists and empty strings nil) but we won&rsquo;t be using it here. It defaults to <code>empty_values: [\"\"]<\/code>.<\/p>\n<p>Let&rsquo;s add a function to our <code>Movie<\/code> module to create a new <code>%Movie{}<\/code> from a map of params:<\/p>\n<pre><code class=\"elixir\">defmodule Movie do\r\n  import Ecto.Changeset\r\n\r\n  ...\r\n\r\n  @allowed_keys Map.keys(@type_constraints) # We want to allow every key that has a type constraint\r\n\r\n  def new(movie_data) do\r\n    {%__MODULE__{}, @type_constraints}\r\n    |&gt; cast(movie_data, @allowed_keys)\r\n  end\r\nend\r\n<\/code><\/pre>\n<p>We can now test it and make sure it works as expected:<\/p>\n<pre><code class=\"elixir\">iex&gt; Movie.new(%{title: \"The Hitchhiker's Guide to the Galaxy\", runtime: \"109\", cast: [\"Zooey Deschanel\", \"Martin Freeman\"], releases: %{\"UK\" =&gt; \"2005-04-28\", \"KR\" =&gt; \"2005-08-26\"}})\r\n#Ecto.Changeset&lt;\r\n  action: nil,\r\n  changes: %{\r\n    cast: [\"Zooey Deschanel\", \"Martin Freeman\"],\r\n    releases: %{},\r\n    runtime: 109,\r\n    title: \"The Hitchhiker's Guide to the Galaxy\"\r\n  },\r\n  errors: [],\r\n  data: #Movie&lt;&gt;,\r\n  valid?: true\r\n&gt;\r\n<\/code><\/pre>\n<p>Ecto uses <code>%Changeset{}<\/code> to track changes in the data, which is really helpful when you want to persist your data to a database using Ecto or to get a list of validation errors, but not really useful for valid data in our case. Thankfully, <a href=\"https:\/\/hexdocs.pm\/ecto\/Ecto.Changeset.html#apply_changes\/1\"><code>apply_changes\/1<\/code><\/a> can help us with that:<\/p>\n<pre><code class=\"elixir\">defmodule Movie do\r\n  ...\r\n\r\n  def new(movie_data) do\r\n    case changeset(movie_data) do\r\n      %{valid?: true} = changeset -&gt; {:ok, apply_changes(changeset)}\r\n      changeset -&gt; {:error, changeset}\r\n    end\r\n  end\r\n\r\n  def changeset(params) do\r\n    {%__MODULE__{}, @type_constraints}\r\n    |&gt; cast(params, @allowed_keys)\r\n  end\r\nend\r\n<\/code><\/pre>\n<p>The <code>%Changeset{}<\/code> struct has a <code>valid?<\/code> flag that we can use to check if our data is valid. <code>apply_changes\/1<\/code> will happily ignore it and return the underlying struct with our new data regardless of this flag&#8308;, so we have to check that it&rsquo;s actually set to <code>true<\/code> before calling it. Let&rsquo;s try that again:<\/p>\n<pre><code class=\"elixir\">iex&gt; Movie.new(%{title: \"The Hitchhiker's Guide to the Galaxy\", runtime: \"109\", cast: [\"Zooey Deschanel\", \"Martin Freeman\"], releases: %{\"UK\" =&gt; \"2005-04-28\", \"KR\" =&gt; \"2005-08-26\"}})\r\n{:ok,\r\n %Movie{\r\n   cast: [\"Zooey Deschanel\", \"Martin Freeman\"],\r\n   releases: %{\"KR\" =&gt; &sim;D[2005-08-26], \"UK\" =&gt; &sim;D[2005-04-28]},\r\n   runtime: 109,\r\n   title: \"The Hitchhiker's Guide to the Galaxy\"\r\n }}\r\niex&gt; Movie.new(%{runtime: \"not a number\", releases: %{\"UK\" =&gt; \"not a date\"}})\r\n{:error,\r\n #Ecto.Changeset&lt;\r\n   action: nil,\r\n   changes: %{},\r\n   errors: [\r\n     releases: {\"is invalid\", [type: {:map, :date}, validation: :cast]},\r\n     runtime: {\"is invalid\", [type: :integer, validation: :cast]}\r\n   ],\r\n   data: #Movie&lt;&gt;,\r\n   valid?: false\r\n &gt;}\r\n<\/code><\/pre>\n<p>As you can see, <code>cast\/3<\/code> took care of converting <code>runtime<\/code> to an integer and our release dates to <a href=\"https:\/\/hexdocs.pm\/elixir\/Kernel.html#sigil_D\/2\"><code>%Date{}<\/code> structs<\/a>, returning a list of errors when some of the data was invalid.<\/p>\n<p>Now that we have our <code>new\/1<\/code> function working, let&rsquo;s add some validation!<\/p>\n<p>First, we want to make sure our movies always have a title and a runtime, so we&rsquo;ll use <a href=\"https:\/\/hexdocs.pm\/ecto\/Ecto.Changeset.html#validate_required\/3\"><code>validate_required\/3<\/code><\/a> to help us with that:<\/p>\n<pre><code class=\"elixir\">defmodule Movie do\r\n  @required_keys [:title, :runtime]\r\n\r\n  ...\r\n\r\n  def changeset(params) do\r\n    {%__MODULE__{}, @type_constraints}\r\n    |&gt; cast(params, @allowed_keys)\r\n    |&gt; validate_required(@required_keys)\r\n  end\r\nend\r\n<\/code><\/pre>\n<p>And that&rsquo;s it! Ecto will return the changeset with <code>valid?<\/code> flag set to <code>false<\/code> if either of these two fields is missing in our data. There are a lot of validation functions in <a href=\"https:\/\/hexdocs.pm\/ecto\/Ecto.Changeset.html\"><code>Ecto.Changeset<\/code><\/a> to add constraints on all sorts of data. For example, we could use <a href=\"https:\/\/hexdocs.pm\/ecto\/Ecto.Changeset.html#validate_number\/3\"><code>validate_number\/3<\/code><\/a> to ensure the <code>runtime<\/code> is always greater than 30 minutes and shorter than 5 hours, or <code>validate_length\/3<\/code> to make sure there&rsquo;s at least one actor in <code>cast<\/code>.<\/p>\n<p>Sometimes, though, you&rsquo;ll want to have validation rules that a specific to your use case. For this, Ecto provides an helper function <a href=\"https:\/\/hexdocs.pm\/ecto\/Ecto.Changeset.html#validate_change\/3\"><code>validate_change\/3<\/code><\/a> that we can use to define custom validations.<\/p>\n<p>Let&rsquo;s say we want to allow setting the IMDB page for each movie. Ecto doesn&rsquo;t have a URL validator so we&rsquo;re going to define a custom one and use it in our <code>changeset\/1<\/code> function:<\/p>\n<pre><code class=\"elixir\">defmodule Movie do\r\n  ...\r\n\r\n  defstruct [:title, :runtime, :cast, :releases, :imdb_url]\r\n\r\n  @type_constraints %{\r\n    ...\r\n    imdb_url: :string\r\n  }\r\n\r\n  ...\r\n\r\n  def changeset(params) do\r\n    {%__MODULE__{}, @type_constraints}\r\n    &#9474;&gt; cast(params, @allowed_keys)\r\n    &#9474;&gt; validate_required(@required_keys)\r\n    &#9474;&gt; validate_imdb_url()\r\n  end\r\n\r\n  defp validate_imdb_url(changeset) do\r\n    validate_change(changeset, :imdb_url, fn :imdb_url, value -&gt;\r\n      case URI.parse(value) do\r\n        %URI{host: \"imdb.com\"} -&gt; []\r\n        %URI{host: \"www.imdb.com\"} -&gt; []\r\n        _ -&gt; [imdb_url: \"is not a valid URL\"]\r\n      end\r\n    end)\r\n  end\r\nend\r\n<\/code><\/pre>\n<p><a href=\"https:\/\/hexdocs.pm\/ecto\/Ecto.Changeset.html#validate_change\/3\"><code>validate_change\/3<\/code><\/a> expects a changeset, the name of the field you want to validate and a function that will be passed the name of the field and its value and that should return a list of validation errors if any.<\/p>\n<p>Note that the custom validation function will only be called if there&rsquo;s a change for the given field and if the new value isn&rsquo;t <code>nil<\/code>. Since we have set the type of <code>imdb_url<\/code> to <code>:string<\/code> the value passed to our validation function will always be a string.<\/p>\n<p>Let&rsquo;s give it a try:<\/p>\n<pre><code class=\"elixir\">iex&gt; Movie.new(%{title: \"The Hitchhiker's Guide to the Galaxy\", runtime: \"109\", cast: [\"Zooey Deschanel\", \"Martin Freeman\"], releases: %{\"UK\" =&gt; \"2005-04-28\", \"KR\" =&gt; \"2005-08-26\"}, imdb_url: \"https:\/\/www.imdb.com\/title\/tt0371724\"})\r\n{:ok,\r\n %Movie{\r\n   ...\r\n }}\r\niex&gt; Movie.new(%{title: \"The Hitchhiker's Guide to the Galaxy\", runtime: \"109\", cast: [\"Zooey Deschanel\", \"Martin Freeman\"], releases: %{\"UK\" =&gt; \"2005-04-28\", \"KR\" =&gt; \"2005-08-26\"}, imdb_url: \"https:\/\/www.not-imdb.com\/\"})\r\n{:error,\r\n #Ecto.Changeset&lt;\r\n   action: nil,\r\n   changes: %{\r\n     cast: [\"Zooey Deschanel\", \"Martin Freeman\"],\r\n     imdb_url: \"https:\/\/www.not-imdb.com\/\",\r\n     releases: %{\"KR\" =&gt; &sim;D[2005-08-26], \"UK\" =&gt; &sim;D[2005-04-28]},\r\n     runtime: 109,\r\n     title: \"The Hitchhiker's Guide to the Galaxy\"\r\n   },\r\n   errors: [imdb_url: {\"is not a valid URL\", []}],\r\n   data: #Movie&lt;&gt;,\r\n   valid?: false\r\n &gt;}\r\n<\/code><\/pre>\n<p>Et voil&agrave;!<\/p>\n<ol>\n<li>Note that you don&rsquo;t actually need to define a struct and could just use a map instead.<\/li>\n<li><code>schema\/2<\/code> will add a <a href=\"https:\/\/hexdocs.pm\/ecto\/Ecto.Schema.Metadata.html\"><code>__meta__<\/code><\/a> field that&rsquo;s mostly here to help with data persistance.<\/li>\n<li>We could also pass a non-empty struct, which is nice if you need to update existing data.<\/li>\n<li><a href=\"https:\/\/hexdocs.pm\/ecto\/Ecto.Changeset.html#apply_action\/2\"><code>apply_action\/2<\/code><\/a> would take care of this but requires an <a href=\"https:\/\/hexdocs.pm\/ecto\/Ecto.Changeset.html#module-changeset-actions\">action<\/a> to be given, and that doesn&rsquo;t make much sense in our case. You would probably want to use it when working with <a href=\"https:\/\/hexdocs.pm\/phoenix_html\/Phoenix.HTML.Form.html\">Phoenix form<\/a>, as they require an action to be set in order to display errors.<\/li>\n<\/ol>\n<p>PS: Looking to join a collaborative remote engineering team writing Elixir and React? We&rsquo;re often hiring, go check out our listings at https:\/\/packlane.com\/jobs<\/p>\n<\/body><\/html>\n","protected":false},"excerpt":{"rendered":"<p>Ecto, while commonly used as a database wrapper, can also serve as a powerful data validation tool thanks to its Ecto.Changeset. Let&rsquo;s say you have a list of movie data that you want to clean up and put into a nice %Movie{} struct that would consist of the following fields: title, a string, runtime, an [&hellip;]<\/p>\n","protected":false},"author":12357,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_et_pb_use_builder":"","_et_pb_old_content":"","_et_gb_content_width":"","inline_featured_image":false,"footnotes":""},"categories":[1],"tags":[9],"class_list":["post-239","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-engineering"],"_links":{"self":[{"href":"https:\/\/packlane.com\/blog\/wp-json\/wp\/v2\/posts\/239","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/packlane.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/packlane.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/packlane.com\/blog\/wp-json\/wp\/v2\/users\/12357"}],"replies":[{"embeddable":true,"href":"https:\/\/packlane.com\/blog\/wp-json\/wp\/v2\/comments?post=239"}],"version-history":[{"count":0,"href":"https:\/\/packlane.com\/blog\/wp-json\/wp\/v2\/posts\/239\/revisions"}],"wp:attachment":[{"href":"https:\/\/packlane.com\/blog\/wp-json\/wp\/v2\/media?parent=239"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/packlane.com\/blog\/wp-json\/wp\/v2\/categories?post=239"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/packlane.com\/blog\/wp-json\/wp\/v2\/tags?post=239"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}