I have this situation where I have two talbes. One “user_profile” and one “countries”. “user_profile” has a foreign key to “countries”. Here are the migrations:
def change do
create table(:user_profiles, primary_key: false) do
# ... more columns
add :country_code, references(:countries, [column: :code, type: :string]), size: 2, null: false
timestamps()
end
create table(:countries, primary_key: false) do
add :code, :string, size: 2, primary_key: true
add :name, :string, null: false
end
and the “user_profiles” schema:
schema "user_profiles" do
# ... more fields
belongs_to :country, Country, references: :code,
foreign_key: :country_code, type: :string
timestamps()
end
@required_fields ~W(birth_date area_of_residence)a
@optional_fields ~W(first_name last_name)a
def registration_changeset(struct, params \\ %{}) do
struct
|> cast(params, @required_fields ++ @optional_fields)
|> validate_required(@required_fields)
|> assoc_constraint(:country)
end
The params map that get passed to the changeset is
%{
area_of_residence: "Emerald City",
birth_date: ~D[1990-12-01],
country_code: "GR",
email: "voger@example.com",
first_name: "John",
has_accepted_tos: true,
is18_or_older: true,
last_name: "Doe",
password: "123456",
password_confirmation: "123456",
username: "voger5"
}
And the changeset reasonably doesn’t contain any change for the country field.
#Ecto.Changeset<action: nil,
changes: %{area_of_residence: "Emerald City", birth_date: ~D[1990-12-01],
first_name: "John", last_name: "Doe"}, errors: [],
data: #Mysite.Accounts.UserProfile<>, valid?: true>
Now when I do a Repo.insert! in the changeset I get this exception
** (exit) an exception was raised:
** (Postgrex.Error) ERROR 23502 (not_null_violation): null value in column "country_code" violates not-null constraint
table: user_profiles
column: country_code
Failing row contains (22, John, Doe, 1990-12-01, null, 0, Emerald City, 2017-08-09 13:39:07.684616, 2017-08-09 13:39:07.68465, null).
One simple solution would be to just Repo.get the country from the database and do a put_assoc to the changeset. However I am curious how it can be done without having to call the database. Just cast the field and send it.