Remove trailing and leading whitespaces with Ecto changeset
Asked Answered
C

3

7

Is there a built-in way in Ecto to remove trailing and leading whitespaces of fields in Ecto.Changeset when calling changeset/2 before inserting it to the database?


At the moment, I am adding two custom functions to my schema for data filtering to improve data integrity:

defp trim_fields(changeset, fields) do
  Enum.reduce(fields, changeset, &trim(&2, &1))
end

defp trim(changeset, field) do
  if get_change(changeset, field) do
    update_change(changeset, field, &String.trim/1)
  else
    changeset
  end
end

The function(s) can then be piped in the changeset/2 function with, e.g.

def changeset(%Customer{} = customer, attrs) do
  |> cast(attrs, [:first_name, :last_name])
  |> validate_required([:first_name], [:last_name])
  |> trim_fields([:first_name, :last_name])
end

Since I think this is a common use-case I was wondering if there isn't a function that already provides this functionality?

If this functionality is not yet provided in Ecto, then from the Ectos API point of view, it would be convenient to add such functions and by naming them filter_trim and filter_... I guess?

Congregational answered 10/4, 2018 at 9:27 Comment(0)
F
23

I think it's best to trim input before running validations. Also, update_change will only perform the change if there is a change for the given key.

This leads to slightly more streamlined code:

changeset
|> cast(attrs, [:first_name, :last_name])
|> update_change(:first_name, &String.trim/1)
|> update_change(:last_name, &String.trim/1)
|> validate_required([:first_name, :last_name])
Flite answered 10/4, 2018 at 11:55 Comment(1)
I overlooked the fact that update_change only executes the given function when there is a change for the given key. Thanks!Congregational
D
2

I would like to extend zwippie's great answer.

As the documentation points out, the value may be nil if the field was cleared. Thus, I'd use something like this:

def changeset(%Customer{} = customer, attrs) do
  customer
  |> cast(attrs, [:first_name, :last_name])
  |> update_change(:first_name, &trim/1)
  |> update_change(:last_name, &trim/1)
  |> validate_required([:first_name, :last_name])
end

defp trim(binary) when is_binary(binary), do: String.trim(binary)
defp trim(nil), do: nil

And if you have many fields you want to do the trimming on, I would consider defining an auxiliary function update_changes/3 (similar to your trim_fields/2):

def changeset(%Customer{} = customer, attrs) do
  customer
  |> cast(attrs, [:first_name, :last_name])
  |> update_changes([:first_name, :last_name], &trim/1)
  |> validate_required([:first_name, :last_name])
end

defp update_changes(changeset, keys, function) do
  Enum.reduce(keys, changeset, fn key, set -> update_change(set, key, function) end)
end
Dorian answered 9/11, 2023 at 12:27 Comment(1)
I would prefer this one.Neutralism
S
0

Why not simply use :trim?

changeset
|> cast(attrs, [:first_name, :last_name])
|> validate_required([:first_name, :last_name], [trim: true])

Docs validate_required/3.

Stunner answered 30/11, 2022 at 17:10 Comment(1)
According to the docs, the trim option for validate_required/3 is only removing whitespace for the validation, (so that multiple whitespaces like " " would still count as empty value), and the option already defaults to true. So trim: true does not actually change the cast value and keeps surrounding whitespace.Words

© 2022 - 2024 — McMap. All rights reserved.