I believe what you are looking for is a custom Ecto.Type. I do this all the time, and it works great! It would 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 right type
add :tags, {:array, :string}
Finally in your schema specify the field type that you created.
field :tags, MyApp.Tags
Then you can just add it as a field in your changeset. If cast of your type returns :error
, then the changeset will have an error something like {:tags, ["is invalid"]}
. You don't have to worry about any processing of the field in your model or controller then. If the user posts a string array for the value or just a comma separated string, it will work.
If you need to save the value to the database in a different format, you would just change the return value of def type
and ensure that def dump
returns a value of that type and that def load
can read a value of that type to whatever internal representation you want. One common pattern is to define a struct for the internal representation so that you can make your own implementation of Poison's to_json
that could even return a simple string. One example might be a LatLng type that encodes to 12.12345N,123.12345W
in json, stores as some GIS type in postgres, but has a struct like %LatLng{lat: 12.12345, lng: -123.12345}
that lets you do some simple math in elixir. The DateTime formats work a lot like that (there is a struct for elixir, a tuple format for the db driver and an ISO format for json).
I think this works really well for password fields, btw. You can squash the JSON representation, use a struct to represent the algorithm, parameters to the algorithm separate salt from hash or whatever else makes life easy. In your code, to update a password, it would just be Ecto.Changeset.change(user, password: "changeme")
.
I realize this is a 6mo old question and you've probably found something, but I ended up here from a google search, and assume others will, too.
registration_changeset
is callingput_pass_hash
) – AbMvirtual
attribute that's the string inputed by the user (similar to how thepassword
attribute is a virtual attribute in the example I shared). – AbM