Where should I transform params before cast?

Hi,

I have a String param coming from the client, but it should be a NaiveDateTime in my schema, because of that I am thinking about transforming it before passing it to the cast function, is it fine?

  def changeset(my_struct, attrs) do
    my_struct
    |> my_transformation_func(attrs) # transform type string to NaiveDateTime
    |> cast(attrs, [:my_date])
    |> ...
  end

Or maybe it is better to transform at the controller or context levels?

The Ecto.Changeset doc says:

The functions cast/4 and change/2 are the usual entry points for creating changesets.

1 Like

Turning values from “external” shapes (like strings) to “application” shapes (like structs) is part of what Ecto.Changeset.cast does - it uses the fields declared in the schema to decide how to do that.

What does the input string look like? Standard shapes may “just work”, or you could define a custom Ecto.Type to do special parsing.

1 Like

Oh, makes sense, but I didn’t find a place in the docs that says how string dates are cast into Elixir datatypes by Ecto.Changeset.cast, you know where it is?

In my case, my strings come from a select input and seem like this: 1-hour, 12-hour, 1-day, etc… I was trying to transform it into a NaiveDateTime by adding them to the current NaiveDateTime (doing some specific parsing).

A custom Ecto.Type may help me to parse it directly via cast as you said, but if standard shapes may just work I think it could be an easier way to reach the result.

There are at least a couple schools of thought on if/where to do this. a) parse/validate at the boundary (LiveVIew, controller, etc) and then pass structured data to your core. b) pass the user/api data straight through to your changeset functions.

edit: interesting thoughts on the topic by Sasa

3 Likes

Not sure I fully understand the interface but assuming that these are literals that will be transformed into actual date times, why not “let” the FE handle it using simple option labels/values?

1 Like

You can split this process into two parts:

  • params normalization (do it in interface layer)
  • params validation (do it in core layer)

You can give Towards Maintainable Elixir: The Core and the Interface a read. I believe then you’ll have the idea how to do this.

1 Like