How do I insert items with associations into my database via iex?

N.B. I don’t seem to have permission to post in the Phoenix section :woman_shrugging:

Background

I’m learning Phoenix and my relational database skills are a bit rusty to say the least. I’m trying to create a data structure for creating musical chord progressions, whereby a Progression has a many to many association with a Chord. I’m loosely following this guide. To do this I have the following schema (please tell me if my design is generally awful by the way!):

# create_chords migration
defmodule Chordwitch.Repo.Migrations.CreateChords do
  use Ecto.Migration

  def change do
    create table(:chords) do
      add :numeral, :string
      add :extension, :string
      add :tonality, :string
      add :function, :string
      add :comments, :text
      add :altered, :boolean, default: false, null: false

      timestamps()
    end
  end
end
# lib/chords/chord.ex
defmodule Chordwitch.Chords.Chord do
  use Ecto.Schema
  import Ecto.Changeset
  alias Progressions.Progression

  schema "chords" do
    field :altered, :boolean, default: false
    field :comments, :string
    field :extension, :string
    field :function, :string
    field :numeral, :string
    field :tonality, Ecto.Enum, values: [:major, :minor, :neutral]
    many_to_many :progressions, Progression, join_through: "progression_chords"

    timestamps()
  end

  @doc false
  def changeset(chord, attrs) do
    chord
    |> cast(attrs, [:numeral, :extension, :tonality, :function, :comments, :altered])
    |> validate_required([:numeral, :extension, :tonality, :function, :comments, :altered])
  end
end

# create_progressions migration
defmodule Chordwitch.Repo.Migrations.CreateProgressions do
  use Ecto.Migration

  def change do
    create table(:progressions) do
      add :name, :string
      add :tags, {:array, :string}

      timestamps()
    end
  end
end
# lib/progressions/progression.ex
defmodule Chordwitch.Progressions.Progression do
  use Ecto.Schema
  import Ecto.Changeset
  alias Chords.Chord

  schema "progressions" do
    field :name, :string
    field :tags, {:array, :string}
    many_to_many :chords, Chord, join_through: "progression_chords"

    timestamps()
  end

  @doc false
  def changeset(progression, attrs) do
    progression
    |> cast(attrs, [:name, :tags])
    |> validate_required([:name, :tags])
  end
end

As you can see I’ve also created a relationship table for progression_chords with many_to_many associations in both my chord and progression schema. The migration for progression_chords looks like this:

defmodule Chordwitch.Repo.Migrations.CreateProgressionChords do
  use Ecto.Migration

  def change do
    create table(:progression_chords) do
      add :progression_id, references(:progressions)
      add :chord_id, references(:chords)
      add :index, :integer
    end
  end
end

I’m trying to test this out by running my application in iex and doing the following:

alias Chordwitch.Chords.Chord
Chordwitch.Chords.Chord
iex(2)> alias Chordwitch.Progressions.Progression
Chordwitch.Progressions.Progression
iex(3)> alias Chordwitch.Repo
Chordwitch.Repo
iex(4)> p = %Progression{name: "iex Rock"}
%Chordwitch.Progressions.Progression{
  __meta__: #Ecto.Schema.Metadata<:built, "progressions">,
  id: nil,
  name: "iex Rock",
  tags: nil,
  chords: #Ecto.Association.NotLoaded<association :chords is not loaded>,
  inserted_at: nil,
  updated_at: nil
}
iex(5)> p = Repo.insert!(p)

Problem

When I run that last command and try and insert my progression (before building any associations), I get the following error:

** (UndefinedFunctionError) function Chords.Chord.__schema__/1 is undefined (module Chords.Chord is not available)
    Chords.Chord.__schema__(:primary_key)
    (ecto 3.10.1) lib/ecto/changeset/relation.ex:155: Ecto.Changeset.Relation.change/3
    (ecto 3.10.1) lib/ecto/changeset/relation.ex:526: anonymous fn/4 in Ecto.Changeset.Relation.surface_changes/3
    (elixir 1.14.4) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
    (ecto 3.10.1) lib/ecto/changeset/relation.ex:513: Ecto.Changeset.Relation.surface_changes/3
    (ecto 3.10.1) lib/ecto/repo/schema.ex:344: Ecto.Repo.Schema.do_insert/4
    (ecto 3.10.1) lib/ecto/repo/schema.ex:273: Ecto.Repo.Schema.insert!/4

This error is the reason I have alias ed Chord in the Progression module and vice versa, it’s not something that was in the guide.

Sorry for the long post but any pointers would be appreciated.

Welcome!

New users need to have their posts validated by a moderator as an anti spam measure.

defmodule Chordwitch.Progressions.Progression do
  use Ecto.Schema
  import Ecto.Changeset
  alias Chords.Chord

This is your bug. You have done alias Chords.Chord and therefore this line here many_to_many :chords, Chord aliases to many_to_many :chords, Chords.Chord, but the module is Chordwitch.Chords.Chord

You should have alias Chordwitch.Chords.Chord instead, or just don’t alias and use the full module name.

D’oh! Yes of course that was it, and I should have known as I’d aliased correctly in iex! But thanks very much for the quick reply to a confused newbie :slight_smile: