Multiple many-to-many relationships for the same model?

Using Ecto v2.2.6, phoenix 1.3

I have a scenario in which a user can create posts, and then other users can like posts. Users have a one-to-many relationship with posts via creation, and a many-to-many relationship with likes, via a linking table.

Setup:

mix phx.gen.json Account User users name:string
mix phx.gen.json Content Post posts title:string content:text user_id:references:users
mix phx.gen.json Content Like likes user_id:references:users post_id:references:posts

Schemas:

  schema "users" do
    field :name, :string

    has_many :posts, SocialNetwork.Content.Post, foreign_key: :users_id
    many_to_many :posts, SocialNetwork.Content.Post, join_through: "likes"

    timestamps()
  end

  schema "posts" do
    field :content, :string
    field :title, :string

    belongs_to :user, SocialNetwork.Accounts.User
    many_to_many :users, SocialNetwork.Accounts.User, join_through: "likes"

    timestamps()
  end

  schema "likes" do
    belongs_to :user, SocialNetwork.Accounts.User
    belongs_to :post, SocialNetwork.Content.Post

    timestamps()
  end

When I run mix phx.server, I get this error:

== Compilation error in file lib/social_network/account/user.ex ==
** (ArgumentError) field/association :posts is already set on schema

Is there a way that I can set up more than one association to the same schema, but through a different context?

The problem is that you have two :posts keys in your schema. How would you differentiate between a post a user likes and a post a user created?

For many_to_many and has_many, you can actually use any key you want.

  schema "users" do
    field :name, :string

    has_many :authored_posts, SocialNetwork.Content.Post, foreign_key: :users_id
    many_to_many :liked_posts, SocialNetwork.Content.Post, join_through: "likes"

    timestamps()
  end
3 Likes

@ryh That solved the problem, thank you!

One more question (please let me know if I should start another OP): if I create the “likes” entity with just a migration and a schema, I am able to seed it with something like this:

Repo.insert!(Like.changeset(
  %Like{}, %{
    user_id: 4,
    post_id: 8
  }
))

However, if I create something similar with phx.gen.json Content Like likes... and try to seed it the same way, nothing goes in.

%MyApp.Content.Like{__meta__: #Ecto.Schema.Metadata<:loaded, "likes">,
  id: 1, inserted_at: ~N[2017-10-07 19:36:34.095408],
  post: #Ecto.Association.NotLoaded<association :post is not loaded>,
  post_id: nil, updated_at: ~N[2017-10-07 19:36:34.095411],
  user: #Ecto.Association.NotLoaded<association :user is not loaded>,
  user_id: nil},

What do I have to do to make a linking table created with phx.gen.json seed correctly?

If I remember correctly, the changeset function that phx.gen.json creates does not have casts for entity_id. Check to see if your Like.changeset/2 casts :post_id and :user_id.

If that doesn’t work, we can try something else.

1 Like

It worked! I wish it did those casts automatically. For linking tables, you have to manually tell your changeset to cast those id’s.

schema "likes" do
  belongs_to :user, SocialNetwork.Accounts.User
  belongs_to :post, SocialNetwork.Content.Post

  timestamps()
end

def changeset(%Like{} = like, attrs) do
  like
  |> cast(attrs, [:user_id, :post_id])
  |> validate_required([:user_id, :post_id])
end