Is this the proper way to create an associated record B when creating a new record A?

Let me explain:

I have Engagements and Contacts. Contacts have a cusotm not auto-generated primary key which is the mobile number. Engagments have contact_id field to associate engagements to contacts.

Site vistors create engagements only (they don’t register as contacts by themselves first). I have put together this code in the engagement.ex schema definition:

  schema "engagements" do
    field :at, :naive_datetime
    field :mcr, :integer
    field :entity_id, :id
    field :contact_id, :id
    field :name, :string, virtual: true
    timestamps()
  end

  @doc false
  def changeset(%Engagement{} = engagement, attrs) do
    engagement
    |> cast(attrs, [:at, :mcr, :entity_id, :contact_id, :name])
    |> register_contact
    |> validate_required([:mcr])
  end

  defp register_contact(changeset) do
    mobile_number = get_field(changeset, :contact_id)
    name = get_field(changeset, :name)

    case Repo.get(Hub.Contacts.Contact, mobile_number) do
      nil ->  create_contact(mobile_number, name)
      record -> IO.puts "Already exists!"   
    end

    changeset

  end

  defp create_contact(mobile, name) do
    IO.puts "Create new contact!"  
    Hub.Contacts.create_contact(%{id: mobile, name: name, place_id: 1})
  end

Basically, when submiting new engagement by visitors, the code creates new contact in the contacts table if the mobile number (primary key for contacts) has not been registered before. And then populates the contact_id field in engagements with the mobile number.

I am putting the code here for review and recommendations. Maybe there is a better way to do this? Thank you.

Maybe you can change your schema a bit

  schema "engagements" do
    field :at, :naive_datetime
    field :mcr, :integer
    field :entity_id, :id
    # field :contact_id, :id  
    belongs_to(:contact, Contact)
    field :name, :string, virtual: true
    timestamps()
  end

and then pass %Contact{} attrs on %Engagement{} creation? You would need to add a cast_assoc in your engagement changeset for it.

Or you can use Ecto.Multi.

3 Likes