Run time error ; cannot encode association

cannot encode association :taggables from VampDev.User to JSON because the association was not loaded. Please make sure you have preloaded the association or remove it from the data to be encoded

I am getting above error. I am trying to associate two table and could not able to connect (using MySQL). Please help to solve this issue :frowning: Where to load the association ?

example code :slight_smile:

# web/models.user.ex
defmodule VampDev.User do
  use VampDev.Web, :model
  @primary_key {:id, :string, []}
  schema "users" do
    field :name, :string
    field :country, :string
    many_to_many :tags, VampDev.Tags, join_through: "taggables"
  end
end 

# web/models.user_controller.ex
defmodule VampDev.UserController do
  use VampDev.Web, :controller
  
  def index(conn, _params) do
	   user = Repo.all(VampDev.User)

	   json conn, user
  end
end

Thanks

Click the arrows for more details.

The Problem

Ecto eschews expectations for explicitness.

When working with associations, there’s a certain level of ambiguity about what data we should get back.

[details=Ecto expects us to resolve ambiguity]Here are some possible expectations a developer might have about what Ecto will return for the :tags:

  1. :tags is nil because we didn’t preload the association. But really, :tags is not a column in our "users" table, so it doesn’t mean the same thing as nil would for the other fields.
  2. :tags is [] because we didn’t preload the association. But a user with no tags will also have :tags as []. How would we know the difference?
  3. :tags is [] because Ecto automatically preloaded the association, but there are no tags for that User.

Instead we get a pseudo-nil value, an %Ecto.Association.NotLoaded{} struct. That way, each developer can determine how they want to proceed.[/details]

We must ask Ecto to either fetch the data for the association or exclude it, but if we do nothing, we will get %Ecto.Association.NotLoaded{}.

The Solution

Assuming we want the tags to be apart of the JSON, we simply add Repo.preload(:tags) to the code:

# web/models.user_controller.ex
defmodule VampDev.UserController do
  use VampDev.Web, :controller

  def index(conn, _params) do
    user =
      VampDev.User
      |> Repo.all()
      |> Repo.preload(:tags)

    json conn, user
  end
end

[details=Solution without tags]Now, if we don’t want the tags - there are a few things we can do. Probably the easiest is to explicitly tell Ecto what to return:

# web/models.user_controller.ex
defmodule VampDev.UserController do
  use VampDev.Web, :controller
  # consider creating a separate module for queries instead of importing Ecto.Query in every controller 
  import Ecto.Query, warn: false

  def index(conn, _params) do
    user =
      VampDev.User
      |> select([:name, :country])
      |> Repo.all()

    json conn, user
  end
end

[/details]

We’re also getting to a point where these queries should be in a different module. It might be a good idea to consider putting these queries in a separate module.

Hi thanks for your reply. After adding the tags, i am getting an below error. Please advice me how to fix it.

no function clause matching in Ecto.Repo.Preloader.preload/4

This is my user model and using many to many relations.

web/models.user.ex

defmodule VampDev.User do
use Ecto.Schema
use VampDev.Web, :model
@primary_key {:id, :string, []}
schema “users” do
field :name, :string
field :country, :string
many_to_many :tags, VampDev.Tags, join_through: “taggables”
end
end

Wow. This is what I get for not checking my work before posting.

Sorry for the inconvenience. Repo.all() should be called first and then Repo.preload(:tags), see below:

# web/models.user_controller.ex
defmodule VampDev.UserController do
  use VampDev.Web, :controller

  def index(conn, _params) do
    user =
      VampDev.User
      |> Repo.all()
      |> Repo.preload(:tags)

    json conn, user
  end
end

Hi Ryh,

Thanks for the quick reply. Still i am getting an error. check the below error.

Protocol.UndefinedError at GET /api/v1/users
protocol Ecto.Queryable not implemented for VampDev.Tags, the given module does not exist. This protocol is implemented for: Atom, BitString, Ecto.Query, Ecto.SubQuery, Tuple

Check the below table details

CREATE TABLE users (
id varchar(100) NOT NULL,
name varchar(255) DEFAULT NULL,
country varchar(255) DEFAULT NULL,
created_date varchar(255) DEFAULT NULL,
updated_date varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Taggables:

CREATE TABLE taggables (
id varchar(100) NOT NULL,
user_id varchar(255) DEFAULT NULL,
tag_id varchar(255) DEFAULT NULL,
created_date varchar(255) DEFAULT NULL,
updated_date varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Tags:

CREATE TABLE tags (
id varchar(100) NOT NULL,
name varchar(255) DEFAULT NULL,
slug varchar(255) DEFAULT NULL,
type varchar(255) DEFAULT NULL,
created_date varchar(255) DEFAULT NULL,
updated_date varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

I just wanted to get the tags for every user through taggables. Please advice me !

Check my below model.

web/models.tag.ex

defmodule VampDev.Tag do
use Ecto.Schema
use VampDev.Web, :model
@primary_key {:id, :string, []}
schema “tags” do
field :name, :string
field :slug, :string
field :type, :string
end
end


web/models.taggable.ex

defmodule VampDev.Taggable do

use VampDev.Web, :model
@primary_key {:id, :string, []}

schema “taggables” do
#field :user_id, :string
field :tag_id, :string
belongs_to :user, VampDev.User, references: :id, type: :string

end
end

defmodule VampDev.User do
  use Ecto.Schema
  use VampDev.Web, :model
  @primary_key {:id, :string, []}
  schema "users" do
    field :name, :string
    field :country, :string 
    many_to_many :tags, VampDev.Tags, join_through: "taggables"
    #                   ^~~~~~~~~~~~
  end
end

Try correcting VampDev.Tags to VampDev.Tag in your User schema and see if that fixes the problem. Looks like it could be a typo.

Yeah. Sorry. I did not notice the mistakes. Now its working fine . Thanks!

The sample out put.

// 20171106052218
// http://localhost:4000/api/v1/users

[
{
“tags”: [
{
“slug”: “photo”,
“id”: “a7d41c25-0452-47eb-b400-7237b2ff2c6d”
}
],
“name”: “Alberto Giubilini”,
“id”: “042807cc-26e1-4122-9235-23412eacaa79”,
“country”: “Singapore”
},


How to render the json object based upon below JS? Please suggest how to handle it.

Front End:

(function init() {
get(’/users’).then(response => {
return response.json();
}).then(json => {
var includedMap = {};
json.included.forEach(item => {
if (!includedMap[item.type]) {
includedMap[item.type] = {};
}
includedMap[item.type][item.id] = item;
});
json.data.forEach(item => {
if (item.type && item.type.toLowerCase() === ‘user’ && item.relationships.taggables.data.length > 0) {
var tagsHtml = item.relationships.taggables.data.map(taggableRelationship => {
if (taggableRelationship && taggableRelationship.type === ‘taggable’) {
var taggable = includedMap[taggableRelationship.type][taggableRelationship.id];
if (taggable && taggable.relationships && taggable.relationships.tag && taggable.relationships.tag.data) {
var tag = includedMap[taggable.relationships.tag.data.type][taggable.relationships.tag.data.id];
if (tag) {
return <span class="tag label label-default"">${tag.attributes.name}</span>;
}
}
}
return ‘’;
}).join(’’);
var name = item.attributes[‘full-name’];
var country = item.attributes.country || ‘the earth’;
$("#main-list").append(<li class="list-group-item"><span class="name"><strong>${name}</strong> from ${country}</span><div class="tags">${tagsHtml}</div></li>);
}
})
});
})();

Thanks