I am building a clone of Instagram using only Ecto and I’m not really sure how to approach this feature. I have a table for likes
and the table is just foreign keys comment_id
, post_id
, and user_id
. In this manner I can associate a like to there resource and the user. The resource id’s should be provided in an either/or manner. A like can’t be associated with both the post and a comment at the same time:
id | comment_id | post_id | user_id
1 | abc | NULL | 123
2 | NULL. | xyz | 123
I initially added the below constraint on the table (id
's are UUID and I can’t cast a UUID to BOOLEAN hence the crazy SQL). I then go about applying the check_constraint/3
and I realize I can only apply this to a single key at a time so is this really the right way to go about doing this. I then looked at using that magic sauce called pattern matching. This IMO will make the constraint irrelevant, but I’m left wondering how to handle the other cases of the pattern matching in which neither resource id is included, no user is included, or all three relations are not include. I have a rough draft below. I’m still unravelling my JavaScript brain and remapping to Elixir so forgive my ignorance if this is super easy to solve.
# Constraint in migration
create constraint(:likes, :likes_comment_or_post, check: "(COALESCE((comment_id)::CHAR::INTEGER, 0) > 0)::INTEGER + (COALESCE((post_id)::CHAR::INTEGER, 0) > 0)::INTEGER = 1")
defmodule Ectogram.Like do
use Ectogram.Schema
import Ecto.Changeset
alias Ectogram.{Comment,Post,User}
schema "likes" do
belongs_to :comment, Comment
belongs_to :post, Post
belongs_to :user, User
timestamps()
end
def changeset(like, %{} = attrs) do
like
|> cast(attrs, ~w(user_id)a)
|> validate_required(~w(user_id)a)
|> add_error(:comment_id, "Must include comment_id or post_id.")
|> add_error(:post_id, "Must include post_id or comment_id.")
end
def changeset(like, %{comment_id: nil, post_id: nil, user_id: _} = attrs) do
like
|> cast(attrs, ~w(user_id)a)
|> validate_required(~w(user_id)a)
|> add_error(:comment_id, "Must include comment_id or post_id.")
|> add_error(:post_id, "Must include post_id or comment_id.")
end
def changeset(like, %{comment_id: _, post_id: nil, user_id: _} = attrs) do
like
|> cast(attrs, ~w(comment_id user_id)a)
|> validate_required(~w(comment_id user_id)a)
|> check_constraint(:comment_id, name: :likes_comment_or_post, message: "TODO")
|> assoc_constraint(:comment)
|> assoc_constraint(:user)
end
def changeset(like, %{comment_id: nil, post_id: _, user_id: _} = attrs) do
like
|> cast(attrs, ~w(post_id user_id)a)
|> validate_required(~w(post_id user_id)a)
|> check_constraint(:post_id, name: :likes_comment_or_post, message: "TODO")
|> assoc_constraint(:post)
|> assoc_constraint(:user)
end
end