tnederlof

tnederlof

I have been thinking of the most efficent way to setup a schema to take Users and associate them with one another when they “friend” each other. I found a thread from 2017 with some good ideas (Best-practise question - follow and friends posts in ecto - #6 by kokolegorille), however there was some pretty intense queries that have to be done on the Friendship table (to check both directions). Is there a more efficient (from a querying perspective)? Right now I have a User and a Friendship schema where in Friendship there is a “friend_a_id” and a “friend_b_id”.

Any thoughts would be greatly appreciated!

Showing Posts 1 to 8

stefanchrobot

stefanchrobot

Two options come to mind:

  1. Store the friendship in sorted order, i.e. friend_a_id < friend_b_id. On the boundary of the context, always make sure to sort the ids before querying.
  2. Store the relationship twice. When unfriending, make sure to delete both entries.
josefrichter

josefrichter

What exactly are you trying to do? Just for full context. What kind of queries you need to run in your case?

This is not necessarily an Elixir specific topic, so there should be ton of resources outside our world. I remember reading a few years ago about the Twitter schema, don’t remember exactly the details, but do remember that they were duplicating a lot of data by basically building and writing a timeline for each user..

tnederlof

tnederlof OP

Thats a good point about not really being Elixir specific. I think I will expand out my search for how others have accomplished this and try a few things first. I will come back with the schema I have tried and my actual query code/use cases. As I am new to Elixir and Ecto I trying to make sure I learn some of the idiomatic ways of approaching problems.

josefrichter

josefrichter

Here is Redis way, described in detail. Lots of logic to infer from there https://redislabs.com/ebook/part-2-core-concepts/chapter-8-building-a-simple-social-network/

josefrichter

josefrichter

Oh btw you might want to have a look at graph databases like OrientDB (very simple, friendly syntax) or Neo4j (market leader, but a bit more complex)

chungwong

chungwong

If you are working on relationships, a graph database is really what you want.

tnederlof

tnederlof OP

Thank you all for the help so far. On further reflection in terms of what I am trying to do I realized its not a true friendship model. I really just want to keep track of all of the users that have been invited and accepted by say User 1, then do the same for User 2, 3, etc. I dont need to keep track of if User 4 is connected to User 2 who is then connected to User 1. I will likely change the table name from Friendship but for now I am going to leave it and get the functionality I need working first. Its more like keeping track of which users are subscribed to a specific user.

So I have created the following code so far which gets me pretty close I think, however I am getting the error below because I put timestamp columns via my migration but put_assoc doesnt seem to add them. Any help getting the timestamps added and also comments on better ways to accomplish this would be most helpful!
** (Postgrex.Error) ERROR 23502 (not_null_violation) null value in column "inserted_at" violates not-null constraint

User Schema

defmodule App.Users.User do
  use Ecto.Schema
  import Ecto.Changeset

  schema "users" do
    field :name, :string
    field :phone_number, :string
    field :username, :string
    field :email, :string, null: false
 
    many_to_many :friends, User,
      join_through: "friendships",
      join_keys: [from_user_id: :id, to_user_id: :id]

    timestamps()
  end

  @doc false
  def changeset(user_or_changeset, attrs) do
    required_fields = [:username, :name, :email, :phone_number]

    user_or_changeset
    |> cast(attrs, required_fields)
    |> validate_required(required_fields)
  end

  def changeset_friend(changeset, friend_user) do
      changeset
      |> put_assoc(:friends, [friend_user])
  end

end

User Migration

defmodule App.Repo.Migrations.CreateUsers do
  use Ecto.Migration

  def change do
    create table(:users) do
      add :name, :string, null: false
      add :username, :string, null: false
      add :email, :string, null: false
      add :phone_number, :string, null: false

      timestamps()
    end

    create unique_index(:users, [:username, :email, :phone_number])
  end
end

Friendship Schema

defmodule App.Users.Friendship do

  use Ecto.Schema
  import Ecto.Changeset

  schema "friendships" do

    timestamps()
  end
end

Friendship Migration

defmodule App.Repo.Migrations.AddCreateFriendshipTable do
  use Ecto.Migration

  def change do
    create table(:friendships, primary_key: false) do
      add :from_user_id, references(:users)
      add :to_user_id, references(:users)
      add :accepted, :boolean, default: false

      timestamps()
    end
  end
end

Users Context Module

defmodule App.Users do
  import Ecto.Query, warn: false
  alias App.Repo
  alias App.Users.User

  def add_friend(user, friend_user) do
    user
    |> Repo.preload(:friends)
    |> Ecto.Changeset.change()
    |> User.changeset_friend(friend_user)
    |> Repo.update()
  end

end
f0rest8

f0rest8

Hi, so I’m not sure if the error you are getting is for say, perhaps: not working on a reset database so there are entries that exist and therefore would have null entries for your not null constraints.

But, if that’s not the case, then I believe the reason is because you have named your “friendships” table as such on your many_to_many join_through call.

If you change it to naming the schema file, then you will be able to specify the timestamps as you do. Currently, you have removed the schema file by calling the table by its string name “friendships”. Thus you don’t technically have a schema (as far as Ecto is concerned, I think), so you cannot add the timestamps via the schema.

To use your implementation of the timestamps, I would try naming your schema file:

join_through: App.Users.Friendship

Then, your timestamps should get added.

Another option is to set your timestamps default at the database level (sounds more complicated). This is coming from Dashbit’s wonderful Little Ecto Cookbook resource.

In case that’s not the issue for you, then sorry (that’s just what had jumped out at me from looking through your code snippet).

— All posts loaded —

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
Trolleger
What approach to take when sending live updates to “random” users Hi! I have a question, I have a little chat app, and when I create a DM...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
matt-savvy
Anyone here using Honeybadger? My Honeybadger account is being overwhelmed with noise from some bots. Seeing a lot of Bandit.HTTPError...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews