How to refuse nil on put_assoc/3?

Hello. I do have some working code but I would like to know if there is a better option.

I have a schema “countries”

  @primary_key {:code, :string, []}
  @derive {Phoenix.Param, key: :code}
  schema "countries" do
    field :name, :string
  end

and a schema “user_profiles”

schema "user_profiles" do
  # ... things
   belongs_to :country, Country, references: :code,
      foreign_key: :country_code, type: :string
end

Now when I want to create a new user profile I do

    country_struct = Repo.get(Country, country)

    profile =
      %UserProfile{}
      |> UserProfile.registration_changeset(params)
      |> put_assoc(:country, country_struct, required: true)
      |> IO.inspect

    user =
      User.registration_changeset(%User{}, params, key)
      |> Changeset.put_assoc(:user_profile, 

The country variable is a string, the two digits country code. For example “GR”. If the user submits a country code that doesn’t exist in the database, the country_struct variable becomes nil.

When I pass that variable to put_assoc/3 it proceeds happily, even if the assoc is nil. The changeset remains valid. This is how put_assoc/3 is supposed to work.

Looking from the Ecto.Changeset source, passing required: true in the options doesn’t seem to have any effect.

What I did to fix this is to change the line that gets the country from the DB to

    country_struct = Repo.get(Country, country) || :error

So put_assoc/3 receives the %Country{} struct or :error and the changeset becomes invalid in case no country with the given id is found in the database.

Is there any better way to do this according to Ecto design?

Will produce a validation error when the country struct is nil.

2 Likes

Thanks. I had tried everything except that.

Just wanted to clarify this answer against the documentation of the validate/3 function; which seems a bit contradictory. It says …

Do not use this function to validate associations are required, instead pass the :required option to cast_assoc/3

However, the documentation references cast_assoc/3 though … does anyone have any input on this?

cast_assoc with required: is better when the association is managed as part of the parent struct.

put_assoc is better when each side is managed independently. You could just pattern match on the value being passed to put_assoc to confirm it is not nil, but validate_required is a pretty convenient hack :grin: