DEV Community

Marcos Vinicius O. Silveira
Marcos Vinicius O. Silveira

Posted on

Validando associações com Ecto: put_assoc vs cast_assoc

Recentemente fazendo algumas refatorações de código, identifiquei um problema onde a validação de alguns changesets não estavam funcionando, e olhando a implementação tudo estava aparentemente correto:

def create_user(params) do
  User
  |> User.changeset(params)
  |> put_assoc(:personal_info, params.personal_info)
  |> Repo.create()
end
Enter fullscreen mode Exit fullscreen mode

Debugando o código, vi que o problema era que ao executar o create_user, o changeset do personal_info não era executado. Então fui ver na documentação, e vi que a put_assoc exige que os parâmetros tenham sido validados pelo changeset antes da chamada da função, então o que eu precisava fazer era o seguinte:

def create_user(params) do
  User
  |> User.changeset(params)
  |> put_assoc(:personal_info, build_personal_info(params))
  |> Repo.create()
end

defp build_personal_info(%{
  personal_info: personal_info
}) do
 PersonalInfo
 |> PersonalInfo.changeset(personal_info)
end
Enter fullscreen mode Exit fullscreen mode

E dessa forma a validação passou a ser feita da maneira esperada. Porém, eu adicionei mais uma função privada no meu contexto. Então, eu fui atrás de uma forma melhor de resolver esse problema. A solução foi utilizar a cast_assoc diretamente no changeset do User, pois ela faz a relação dos dados e trata as informações com o changeset. E como os parâmetros já possuíam a chave personal_info, eu não precisei informar qual parâmetro
deveria ser validado, pois a função já faz isso:

  defmodule MyApp.Accounts.User do
    ...

    schema "users" do
      ...
      # Atente a necessidade da relação estar declarada no schema
      has_one(:personal_info, PersonalInfo, on_delete: delete_all)
    end

    def changeset(user, attrs)
      user
      |> cast(attrs, @optional_fields ++ @required_fields)
      |> validate_required(@required_fields)
      |> cast_assoc(:personal_info, required: true)
  end
Enter fullscreen mode Exit fullscreen mode

Dessa forma, eu não precisei fazer nenhuma chamada no Contexto, nem criar uma nova função para validar os dados de PersonalInfo ao salvar dados de uma nova pessoa usuária.

Top comments (2)

Collapse
 
andregmdias profile image
André Luiz GM Dias

Excelente post ! Uma dúvida, qual era o erro que você obtinha anteriormente a essa nova abordagem ? Ou não havia erro, foi uma abordagem diferente e mais segura que optou por fazer ?

Collapse
 
viniciussilveira profile image
Marcos Vinicius O. Silveira

Então, nesse caso em específico não chegou a dar nenhum erro, pois os dados eram validados no frontend antes de enviar pra API. Mas caso chegasse alguma coisa invalida, iria dar erro do banco de dados e a API iria retornar 500.