From 65cabe71072cd1f05d18fb42c9ca219955a9b993 Mon Sep 17 00:00:00 2001 From: Josh Adams Date: Mon, 19 Jun 2017 14:53:25 -0500 Subject: [PATCH] Add ability to watch a thread --- lib/firestorm_web/forums/forums.ex | 57 ++++++++++++++++++- lib/firestorm_web/forums/thread.ex | 4 +- lib/firestorm_web/forums/watch.ex | 16 ++++++ ...70619194021_add_forums_threads_watches.exs | 11 ++++ test/forums_test.exs | 9 +++ 5 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 lib/firestorm_web/forums/watch.ex create mode 100644 priv/repo/migrations/20170619194021_add_forums_threads_watches.exs diff --git a/lib/firestorm_web/forums/forums.ex b/lib/firestorm_web/forums/forums.ex index 93060807..1871eab7 100644 --- a/lib/firestorm_web/forums/forums.ex +++ b/lib/firestorm_web/forums/forums.ex @@ -6,7 +6,7 @@ defmodule FirestormWeb.Forums do import Ecto.{Query, Changeset}, warn: false alias FirestormWeb.{Repo, Notifications} alias Ecto.Multi - alias FirestormWeb.Forums.{User, Category, Thread, Post} + alias FirestormWeb.Forums.{User, Category, Thread, Post, Watch} @doc """ Returns the list of users. @@ -412,4 +412,59 @@ defmodule FirestormWeb.Forums do |> preload([p], [thread: [:category]]) |> Repo.paginate(page: page) end + + @doc """ + Have a user watch a thread: + + iex> %User{} |> watch(%Thread{}) + {:ok, %Watch{}} + + """ + def watch(%User{} = user, %Thread{} = thread) do + thread + |> Ecto.build_assoc(:watches, %{user_id: user.id}) + |> watch_changeset(%{}) + |> Repo.insert() + end + + @doc """ + Determine if a user is watching a given watchable (Thread, etc): + + iex> %Thread{} |> watched_by?(%User{}) + false + + """ + def watched_by?(watchable, user = %User{}) do + watch_count(watchable, user) > 0 + end + + def watcher_ids(watchable) do + watchable + |> watches() + |> select([f], f.user_id) + |> Repo.all + end + + def watch_count(watchable) do + watchable + |> watches() + |> Repo.aggregate(:count, :id) + end + defp watch_count(watchable, user = %User{}) do + watchable + |> watches() + |> where([f], f.user_id == ^user.id) + |> Repo.aggregate(:count, :id) + end + + defp watches(watchable) do + watchable + |> Ecto.assoc(:watches) + end + + defp watch_changeset(%Watch{} = watch, attrs) do + watch + |> cast(attrs, [:assoc_id, :user_id]) + |> validate_required([:assoc_id, :user_id]) + end end diff --git a/lib/firestorm_web/forums/thread.ex b/lib/firestorm_web/forums/thread.ex index 65e1a702..22de7d8a 100644 --- a/lib/firestorm_web/forums/thread.ex +++ b/lib/firestorm_web/forums/thread.ex @@ -1,7 +1,7 @@ defmodule FirestormWeb.Forums.Thread do use Ecto.Schema - alias FirestormWeb.Forums.{Category, Post} + alias FirestormWeb.Forums.{Category, Post, Watch, User} alias FirestormWeb.Forums.Slugs.ThreadTitleSlug schema "forums_threads" do @@ -9,6 +9,8 @@ defmodule FirestormWeb.Forums.Thread do field :slug, ThreadTitleSlug.Type belongs_to :category, Category has_many :posts, Post + has_many :watches, {"forums_threads_watches", Watch}, foreign_key: :assoc_id + many_to_many :watchers, User, join_through: "forums_threads_watches", join_keys: [assoc_id: :id, user_id: :id] timestamps() end diff --git a/lib/firestorm_web/forums/watch.ex b/lib/firestorm_web/forums/watch.ex new file mode 100644 index 00000000..3a359a15 --- /dev/null +++ b/lib/firestorm_web/forums/watch.ex @@ -0,0 +1,16 @@ +defmodule FirestormWeb.Forums.Watch do + @moduledoc """ + A `Watch` is a polymorphic representation that a user is watching a thing in our system. + """ + + use Ecto.Schema + import Ecto.Changeset + + schema "abstract table: watches" do + # This will be used by associations on each "concrete" table + field :assoc_id, :integer + field :user_id, :integer + + timestamps() + end +end diff --git a/priv/repo/migrations/20170619194021_add_forums_threads_watches.exs b/priv/repo/migrations/20170619194021_add_forums_threads_watches.exs new file mode 100644 index 00000000..eaaf4ff7 --- /dev/null +++ b/priv/repo/migrations/20170619194021_add_forums_threads_watches.exs @@ -0,0 +1,11 @@ +defmodule FirestormWeb.Repo.Migrations.AddForumsThreadsWatches do + use Ecto.Migration + + def change do + create table(:forums_threads_watches) do + add :assoc_id, references(:forums_threads) + add :user_id, references(:forums_users) + timestamps() + end + end +end diff --git a/test/forums_test.exs b/test/forums_test.exs index b2ebbcf2..b58c6b3c 100644 --- a/test/forums_test.exs +++ b/test/forums_test.exs @@ -230,6 +230,15 @@ defmodule FirestormWeb.ForumsTest do end end + describe "watching a thread" do + setup [:create_user, :create_category, :create_thread] + + test "watching a thread", %{thread: thread, user: user} do + {:ok, _watch} = user |> Forums.watch(thread) + assert thread |> Forums.watched_by?(user) + end + end + def create_category(_) do category = fixture(:category, @create_category_attrs) {:ok, category: category}