"associated schema does not exist" error

I’m making a notes app, and I don’t understand the following error:

warning: invalid association notes in schema Swiftmemo.Accounts.User: associated schema SwiftMemo.Core.Note does not exist
lib/swiftmemo/accounts/user.ex:1: Swiftmemo.Accounts.User (module)

My User schema looks like this.
defmodule Swiftmemo.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
alias Swiftmemo.Accounts.User

  schema "users" do
    field :email, :string
    field :provider, :string
    field :token, :string

    timestamps()

    has_many :notes, Swiftmemo.Core.Note
  end
end

If I alias the ‘Note’, the above error goes away:
defmodule Swiftmemo.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
alias Swiftmemo.Accounts.User
alias Swiftmemo.Core.Note # the alias that make the error go away.

  schema "users" do
    field :email, :string
    field :provider, :string
    field :token, :string

    timestamps()

    has_many :notes, Note
  end
end  

The “Note” looks like this:
defmodule Swiftmemo.Core.Note do
use Ecto.Schema
import Ecto.Changeset
alias Swiftmemo.Core.Note

  schema "notes" do
    field :body, :string
    field :title, :string

    timestamps()

    belongs_to :user, Swiftmemo.Accounts.User
  end
end

Is this expected behaviour?

Now that it compiles, I wasn’t getting the expected result in my ‘save’ function, and I started playing around in iex:

iex(1)> alias Swiftmemo.Core.Note
Swiftmemo.Core.Note
iex(2)> alias Swiftmemo.Accounts.User
Swiftmemo.Accounts.User
iex(3)> alias Swiftmemo.Repo
Swiftmemo.Repo
iex(4)> user = Repo.get(User, 1)
[debug] QUERY OK source="users" db=3.8ms queue=0.1ms
SELECT u0."id", u0."email", u0."provider", u0."token", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) [1]
%Swiftmemo.Accounts.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
 email: "-@-.com", id: 1,
 inserted_at: ~N[2017-09-17 21:57:04.203340],
 notes: #Ecto.Association.NotLoaded<association :notes is not loaded>,
 provider: "github", token: "-",
 updated_at: ~N[2017-09-17 21:57:04.207643]}
iex(5)> note1 = %{ "title" => "Test1", "body" => "This is test 1" }
%{"body" => "This is test 1", "title" => "Test1"}
iex(6)> note2 = %{ title: "Test2", body: "This is test 2" }
%{body: "This is test 2", title: "Test2"}
iex(7)> Ecto.build_assoc(user, :notes, note1)
%Swiftmemo.Core.Note{__meta__: #Ecto.Schema.Metadata<:built, "notes">,
 body: nil, id: nil, inserted_at: nil, title: nil, updated_at: nil,
 user: #Ecto.Association.NotLoaded<association :user is not loaded>, user_id: 1}
iex(8)> Ecto.build_assoc(user, :notes, note2)
%Swiftmemo.Core.Note{__meta__: #Ecto.Schema.Metadata<:built, "notes">,
 body: "This is test 2", id: nil, inserted_at: nil, title: "Test2",
 updated_at: nil,
 user: #Ecto.Association.NotLoaded<association :user is not loaded>, user_id: 1}

With note 1, using a map, the title/body is not inserted into the resulting struct.
With note 2, using a keyword list, the title/body is inserted into the resulting struct.

According to the documentation "You can also pass the attributes, which can be a map or a keyword list, to set the struct’s fields except the association key.
"

I’m pretty confused, as the above is surprising behaviour to me.

Any hints as to what I am doing wrong would be greatly appreciated!

Thank you

For the rest I don’t know, but at least here there is a typo…

SwiftMemo != Swiftmemo

BTW There is also …

note1 = %{ “title” => “Test1”, “body” => “This is test 1” }
note2 = %{ title: “Test2”, body: “This is test 2” }

You are not using the same key type. You should use atoms version (note2) with Struct.

Ecto.build_assoc(user, :notes, note1)
%Swiftmemo.Core.Note{meta: ecto.Schema.Metadata<:built, “notes”>,
body: nil, id: nil, inserted_at: nil, title: nil, updated_at: nil,
user: ecto.Association.NotLoaded, user_id: 1}

And here it is clear why note1 does not save, the struct is expecting map with atom key.
“key” is NOT equal to :key

With note 1, using a map, the title/body is not inserted into the resulting struct.
With note 2, using a keyword list, the title/body is inserted into the resulting struct.

What do You mean? They both are maps. A keyword list looks like that

[key: value, key2: value2] which is equivalent to [{:key, value}, {:key2, value2}]

I am very pleased to have it working - thank you very much for your help! Still very new to this, but finally I can have associated data.

My confusion with the maps is I was attempting to build_assoc(user, :notes, params_from_the_controller),
but the params have string keys. I now build_assoc(user, :notes) |> Note.changeset(params) and all is well.

Is there a rule of thumb to know that atom keys are required? I reread the help for build_assoc, and I didn’t find mention that the map must have atom keys, only that the function can be given a Map or keyword list.

Maps with string keys are most used for data coming from external data sources and should to be validated and changed to atom keys as early as possible. Ecto’s place for doing that is Ecto.Changeset. So you could expect Ecto.Changeset functions, which take any param-maps to work with string keys as well as atom keys. Other functions of ecto are not meant to work with data from external data sources and therefore will most likely work only with atom keys.