How to change an ecto change set before embedding it in a repo?

I'm really new to Phoenix / Elixir, and I'm trying to wrap my head around change sets.

I understand that it contains a set of changes that are used to create or update a model.

What I would like to know is how and how I can change the change before pushing it into the database.

My usage example:

  • I have a form that allows people to create new artists in the database.
  • In this form there is a field of specialty.
  • Before creating an artist, I want to split the specialty field into "," to save it as an array of strings

I'm not even sure if this is possible by directly modifying the set of changes due to immutability restrictions, but I could create another set of changes to insert into the repo.

Any suggestion is welcome and feel free to point out bad practices or stupid things that I can do!

EDIT the following comment: I am looking at something like:

defp put_specialty_array(changeset) do case changeset do %Ecto.Changeset{valid?: true, changes: %{specialty: spec}} -> put_change(changeset, :specialty, String.split(spec, ",")) _ -> changeset end end 
+6
source share
1 answer

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.

+10
source

All Articles