Issues/misunderstanding of Ecto foreign keys/associations

Hi guys, first time posting on the forum so let me know if there’s anything I can improve on!

Currently the issue I’m having is inserting foreign keys into a repo. The Patient repo requires keys from the Gp, CDoctor, and Pharmacy repos to be inserted. Currently, a primary key is generated on each of the Gp, CDoctor and Pharmacy repos, and this value is then passed through assigns to the patient form. However upon submitting the form, errors appear in the changeset:

errors: [gp_id: {“is invalid”, [type: :id, validation: :cast
pharm_id: {“is invalid”, [type: :id, validation: :cast]}]

Included is the schema for GP (CDoctor and Pharmacy are almost indentical so need no need for all of them) and the patient schema.

Patient schema:

defmodule CmsV1.Patient do
  use CmsV1.Web, :model

  @primary_key {:id, :binary_id, autogenerate: true}
  @derive {Phoenix.Param, key: :id}
  schema "patients" do
    field :ph_number, :integer
    field :name, :binary
    field :address, :binary
    field :ppsn, :binary
    field :date_of_birth, Ecto.Date
    field :gender, :string
    field :medical_card_present, :boolean, default: false
    field :medical_card_number, :binary
    field :medical_card_expiry, Ecto.Date
    field :mobile_number, :string
    field :landline_number, :string
    field :nok_name, :string
    field :nok_address, :string
    field :nok_mobile_number, :string
    field :nok_landline_number, :string
    field :active, :boolean, default: false
    field :active_details, :string
    belongs_to :gps, CmsV1.Gp, foreign_key: :gp_id
    belongs_to :cdoctors, CmsV1.Cdoctor, foreign_key: :cdoctor_id
    belongs_to :pharms, CmsV1.Pharm, foreign_key: :pharm_id

    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """

  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:ph_number, :name, :address, :ppsn, :date_of_birth, :gender, :medical_card_present, :medical_card_number, :medical_card_expiry, :mobile_number, :landline_number, :nok_name, :nok_address, :nok_mobile_number, :nok_landline_number, :active, :active_details, :gp_id, :cdoctor_id, :pharm_id])
    |> validate_required([:ph_number, :name, :address, :ppsn, :date_of_birth, :gender, :medical_card_present, :medical_card_number, :medical_card_expiry, :mobile_number, :landline_number, :nok_name, :nok_address, :nok_mobile_number, :nok_landline_number, :active, :active_details])
    |> assoc_constraint(:gps)
    |> assoc_constraint(:cdoctors)
    |> assoc_constraint(:pharms)
  end
end

GP schema:

defmodule CmsV1.GP do
  use CmsV1.Web, :model

  @primary_key {:id, :binary_id, autogenerate: true}
  @derive {Phoenix.Param, key: :id}
  schema "gps" do
    field :name, :string
    field :address, :string
    field :mobile_number, :string
    field :landline_number, :string
    field :fax, :string
    field :email, :string
    field :training_level, :string
    has_many :patients, CmsV1.Patient


    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:name, :address, :mobile_number, :landline_number, :fax, :email, :training_level])
    |> validate_required([:name, :address, :mobile_number, :landline_number, :fax, :email, :training_level])
  end
end

Let me know if anyone can spot anything that may be askew, and thank you!

Welcome, I can see some problems with your design…

First, what version of Phoenix are You on? This is previous version 1.2.

use CmsV1.Web, :model

Now with version 1.3 it should be

  use Ecto.Schema
  import Ecto.Changeset

Second, I don’t see any fields with association key.

You use

belongs_to :gps, CmsV1.Gp, foreign_key: :gp_id

But there is no field like this in your schema. You should have

field :gp_id, :integer

You should as well have :cdoctor_id and :pharm_id in the field list.

If You follow any blogpost, it might be outdated.

Thanks kokolegorille,

I’m can confirm I’m currently using 1.2.

Regarding the belongs_to, should the omittance of specifying the foreign key cause it to default to :id (read that somewhere)

Yeah, you don’t need to include the field if you’ve got the belongs to.

@r_donoghue can you provide an example of the data you’re passing into the changeset, and the code around it?

Sure, heres some sample parameters being passed:

   [debug] Processing by CmsV1.PatientController.create/2
  Parameters: %{"_csrf_token" => "M38dCFhgcWcdawIcWDw8MTE+OQpzNgAApHXZnYHLW9nzwsjxkOKaEQ==", "_utf8" => "Ô£ô", "patient" => %{"a
ctive" => "true", "active_details" => "test", "address" => "test", "date_of_birth" => %{"day" => "1", "month" => "1", "year" =>
"2013"}, "doctor_id" => "16f91368-f667-4f07-9a2a-5a1d3fc97214", "gender" => "test", "gp_id" => "66d48056-86c4-49b0-b4f4-1f015be9
4841", "landline_number" => "test", "medical_card_expiry" => %{"day" => "1", "month" => "1", "year" => "2013"}, "medical_card_nu
mber" => "test", "medical_card_present" => "true", "mobile_number" => "test", "name" => "test", "nok_address" => "test", "nok_la
ndline_number" => "test", "nok_mobile_number" => "test", "nok_name" => "test", "ph_number" => "111111", "pharm_id" => "200776be-
d5f2-4f71-bba8-328a47efba55", "ppsn" => "test"}}

sample from patient controller

def new(conn, _params) do
    changeset = Patient.changeset(%Patient{})
    doctors = Repo.all(CDoctor) |> Enum.map(&{&1.name, &1.id})
    gps = Repo.all(GP) |> Enum.map(&{&1.name, &1.id})
    pharms = Repo.all(Pharmacy) |> Enum.map(&{&1.name, &1.id})
    render(conn, "new.html", changeset: changeset, doctors: doctors, gps: gps, pharms: pharms)
  end

  def create(conn, %{"patient" => patient_params}) do
    changeset = Patient.changeset(%Patient{}, patient_params)
    IO.inspect(changeset)
    case Repo.insert(changeset) do
      {:ok, _patient} ->
        conn
        |> put_flash(:info, "Patient created successfully.")
        |> redirect(to: patient_path(conn, :index))
      {:error, changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end

patient form

<%= form_for @changeset, @action, fn f -> %>
  <%= if @changeset.action do %>
    <div class="alert alert-danger">
      <p>Oops, something went wrong! Please check the errors below.</p>
    </div>
  <% end %>

  <div class="form-group">
    <%= label f, :ph_number, class: "control-label" %>
    <%= number_input f, :ph_number, class: "form-control" %>
    <%= error_tag f, :ph_number %>
  </div>

  <div class="form-group">
    <%= label f, :name, class: "control-label" %>
    <%= text_input f, :name, class: "form-control" %>
    <%= error_tag f, :name %>
  </div>

  <div class="form-group">
    <%= label f, :address, class: "control-label" %>
    <%= text_input f, :address, class: "form-control" %>
    <%= error_tag f, :address %>
  </div>

  <div class="form-group">
    <%= label f, :ppsn, class: "control-label" %>
    <%= text_input f, :ppsn, class: "form-control" %>
    <%= error_tag f, :ppsn %>
  </div>

  <div class="form-group">
    <%= label f, :date_of_birth, class: "control-label" %>
    <%= date_select f, :date_of_birth, class: "form-control" %>
    <%= error_tag f, :date_of_birth %>
  </div>

  <div class="form-group">
    <%= label f, :gender, class: "control-label" %>
    <%= text_input f, :gender, class: "form-control" %>
    <%= error_tag f, :gender %>
  </div>

  <div class="form-group">
    <%= label f, :medical_card_present, class: "control-label" %>
    <%= checkbox f, :medical_card_present, class: "form-control" %>
    <%= error_tag f, :medical_card_present %>
  </div>

  <div class="form-group">
    <%= label f, :medical_card_number, class: "control-label" %>
    <%= text_input f, :medical_card_number, class: "form-control" %>
    <%= error_tag f, :medical_card_number %>
  </div>

  <div class="form-group">
    <%= label f, :medical_card_expiry, class: "control-label" %>
    <%= date_select f, :medical_card_expiry, class: "form-control" %>
    <%= error_tag f, :medical_card_expiry %>
  </div>

  <div class="form-group">
    <%= label f, :mobile_number, class: "control-label" %>
    <%= text_input f, :mobile_number, class: "form-control" %>
    <%= error_tag f, :mobile_number %>
  </div>

  <div class="form-group">
    <%= label f, :landline_number, class: "control-label" %>
    <%= text_input f, :landline_number, class: "form-control" %>
    <%= error_tag f, :landline_number %>
  </div>

  <div class="form-group">
    <%= label f, :nok_name, class: "control-label" %>
    <%= text_input f, :nok_name, class: "form-control" %>
    <%= error_tag f, :nok_name %>
  </div>

  <div class="form-group">
    <%= label f, :nok_address, class: "control-label" %>
    <%= text_input f, :nok_address, class: "form-control" %>
    <%= error_tag f, :nok_address %>
  </div>

  <div class="form-group">
    <%= label f, :nok_mobile_number, class: "control-label" %>
    <%= text_input f, :nok_mobile_number, class: "form-control" %>
    <%= error_tag f, :nok_mobile_number %>
  </div>

  <div class="form-group">
    <%= label f, :nok_landline_number, class: "control-label" %>
    <%= text_input f, :nok_landline_number, class: "form-control" %>
    <%= error_tag f, :nok_landline_number %>
  </div>

  <div class="form-group">
    <%= label f, :active, class: "control-label" %>
    <%= checkbox f, :active, class: "form-control" %>
    <%= error_tag f, :active %>
  </div>

  <div class="form-group">
    <%= label f, :active_details, class: "control-label" %>
    <%= text_input f, :active_details, class: "form-control" %>
    <%= error_tag f, :active_details %>
  </div>

  <div class="form-group">
    <%= label f, :doctor_id, class: "control-label" %>
    <%= select f, :doctor_id, @doctors, class: "form-control" %>
    <%= error_tag f, :doctor_id %>
  </div>
  <div class="form-group">
    <%= label f, :gp_id, class: "control-label" %>
    <%= select f, :gp_id, @gps, class: "form-control" %>
    <%= error_tag f, :gp_id %>
  </div>
  <div class="form-group">
    <%= label f, :pharm_id, class: "control-label" %>
    <%= select f, :pharm_id, @pharms, class: "form-control" %>
    <%= error_tag f, :pharm_id %>
  </div>

  <div class="form-group">
    <%= submit "Submit", class: "btn btn-primary" %>
  </div>
<% end %>

Hope this helps.

UPDATE: I changed the belongs_to reference to omitt the foreign key, and receive the following error:

unknown field gp_id. Only fields, embeds and associations (except :through ones) are supported in changesets