I believe you are looking for a custom Ecto.Type. I do this all the time and it works great! It will look something like this:
defmodule MyApp.Tags do @behaviour Ecto.Type def type, do: {:array, :string} def cast(nil), do: {:ok, nil} # if nil is valid to you def cast(str) when is_binary(str) do str |> String.replace(~r/\s/, "") # remove all whitespace |> String.split(",") |> cast end def cast(arr) when is_list(arr) do if Enum.all?(arr, &String.valid?/1), do: {:ok, arr}, else: :error end def cast(_), do: :error def dump(val) when is_list(val), do: {:ok, val} def dump(_), do: :error def load(val) when is_list(val), do: {:ok, val} def load(_), do: :error end
Then in your migration add a column with the correct type
add :tags, {:array, :string}
Finally, in your schema, specify the type of field you created.
field :tags, MyApp.Tags
Then you can simply add it as a field in your change set. If a listing of your type returns :error , then the change set will have an error similar to {:tags, ["is invalid"]} . Then you do not need to worry about any field processing in your model or controller. If the user sends a string array for the value or just a comma separated string, it will work.
If you need to save the value in the database in a different format, you simply change the return value of def type and make sure that def dump returns a value of this type and that def load can read the value from this type to any internal representation that you want. One common pattern is to define the structure of the internal representation so that you can make your own Poison to_json implementation that could even return a simple string. One example would be the LatLng type, which is encoded in 12.12345N,123.12345W in json, saves as some GIS type in postgres, but has a structure like %LatLng{lat: 12.12345, lng: -123.12345} , which allows you to do some simple math in the elixir. The DateTime format is very similar to this (for elixir there is a structure, a tuple format for the db driver, and an ISO format for json).
I think this works very well for password fields, by the way. You can squander the JSON representation, use the structure to represent the algorithm, the parameters for the algorithm that separates the salt from the hash, or something else that makes life easier. In your code, to update the password, it will just be Ecto.Changeset.change(user, password: "changeme") .
I understand that this is a 6 millionth question, and you probably found something, but I got here from a Google search and I suppose that others will also be.