Tom
Tom

Reputation: 1982

Remove trailing and leading whitespaces with Ecto changeset

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?

Upvotes: 7

Views: 2923

Answers (3)

xxdavid
xxdavid

Reputation: 59

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

Upvotes: 2

mrroot5
mrroot5

Reputation: 1971

Why not simply use :trim?

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

Docs validate_required/3.

Upvotes: 0

zwippie
zwippie

Reputation: 15515

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])

Upvotes: 24

Related Questions