Skip to content

Commit

Permalink
Track Views on Posts (firestormforum#86)
Browse files Browse the repository at this point in the history
  • Loading branch information
knewter authored Jun 29, 2017
1 parent 03511ed commit 222607a
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 13 deletions.
64 changes: 59 additions & 5 deletions lib/firestorm_web/forums/forums.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ defmodule FirestormWeb.Forums do

import Ecto.{Query, Changeset}, warn: false
alias FirestormWeb.{Repo, Notifications}
alias FirestormWeb.Forums.{User, Category, Thread, Post, Watch}
alias FirestormWeb.Forums.{
User,
Category,
Thread,
Post,
Watch,
View
}

@doc """
Returns the list of users.
Expand Down Expand Up @@ -264,10 +271,9 @@ defmodule FirestormWeb.Forums do
posts_count = length(thread.posts)
completely_read? =
if user do
# TODO: Return true if the user has completely read this thread -
# i.e., if each post has a corresponding view in the database for the
# user
false
# FIXME: This is insanely inefficient, lol?
thread.posts
|> Enum.all?(fn(post) -> post |> viewed_by?(user) end)
else
false
end
Expand Down Expand Up @@ -542,4 +548,52 @@ defmodule FirestormWeb.Forums do
|> cast(attrs, [:assoc_id, :user_id])
|> validate_required([:assoc_id, :user_id])
end

@doc """
Indicate a user viewed a post:
iex> %User{} |> view(%Post{})
{:ok, %Post{}}
"""
def view(%User{} = user, %Post{} = post) do
post
|> Ecto.build_assoc(:views, %{user_id: user.id})
|> view_changeset(%{})
|> Repo.insert()
end

@doc """
Determine if a user has viewed a given viewable (Post, etc):
iex> %Post{} |> viewed_by?(%User{})
false
"""
def viewed_by?(viewable, user = %User{}) do
view_count(viewable, user) > 0
end

def view_count(viewable) do
viewable
|> views()
|> Repo.aggregate(:count, :id)
end
defp view_count(viewable, user = %User{}) do
viewable
|> views()
|> where([f], f.user_id == ^user.id)
|> Repo.aggregate(:count, :id)
end

defp views(viewable) do
viewable
|> Ecto.assoc(:views)
end

defp view_changeset(%View{} = view, attrs) do
view
|> cast(attrs, [:assoc_id, :user_id])
|> validate_required([:assoc_id, :user_id])
end
end
4 changes: 3 additions & 1 deletion lib/firestorm_web/forums/post.ex
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
defmodule FirestormWeb.Forums.Post do
use Ecto.Schema
alias FirestormWeb.Forums.{User, Thread}
alias FirestormWeb.Forums.{User, Thread, View}

schema "forums_posts" do
field :body, :string
belongs_to :thread, Thread
belongs_to :user, User
has_many :views, {"forums_posts_views", View}, foreign_key: :assoc_id
many_to_many :viewers, User, join_through: "forums_posts_views", join_keys: [assoc_id: :id, user_id: :id]

timestamps()
end
Expand Down
17 changes: 17 additions & 0 deletions lib/firestorm_web/forums/view.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
defmodule FirestormWeb.Forums.View do
@moduledoc """
A `View` is a polymorphic representation that a user viewed a thing in our
system at a specified time.
"""

use Ecto.Schema

schema "abstract table: views" do
# This will be used by associations on each "concrete" table
field :assoc_id, :integer
field :user_id, :integer

timestamps()
end
end

2 changes: 1 addition & 1 deletion lib/firestorm_web/web/controllers/category_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ defmodule FirestormWeb.Web.CategoryController do

threads =
category
|> Forums.list_threads()
|> Forums.list_threads(current_user(conn))

render(conn, "show.html", category: category, threads: threads)
end
Expand Down
9 changes: 9 additions & 0 deletions lib/firestorm_web/web/controllers/thread_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ defmodule FirestormWeb.Web.ThreadController do
false
end

# Record a view for each post in this thread
if current_user(conn) do
for post <- thread.posts do
conn
|> current_user()
|> Forums.view(post)
end
end

render(conn, "show.html", thread: thread, category: category, first_post: first_post, posts: posts, watched: watched)
end

Expand Down
11 changes: 11 additions & 0 deletions priv/repo/migrations/20170629061022_add_forums_posts_views.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule FirestormWeb.Repo.Migrations.AddForumsPostsViews do
use Ecto.Migration

def change do
create table(:forums_posts_views) do
add :assoc_id, references(:forums_posts)
add :user_id, references(:forums_users)
timestamps()
end
end
end
10 changes: 10 additions & 0 deletions test/feature/categories_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,21 @@ defmodule FirestormWeb.Feature.CategoriesTest do
{:ok, _post} = Forums.create_post(otp_is_cool, user, %{body: "Yup"})

session
|> log_in_as(user)
|> visit(category_path(Endpoint, :show, elixir.id))
|> all(threads(1))
|> List.first()
|> assert_has(thread_title("OTP is cool"))
|> assert_has(thread_posts_count(2, completely_read?: false))

session
|> log_in_as(user)
|> visit(category_thread_path(Endpoint, :show, elixir.id, otp_is_cool.id))
|> visit(category_path(Endpoint, :show, elixir.id))
|> all(threads(1))
|> List.first()
|> assert_has(thread_title("OTP is cool"))
|> assert_has(thread_posts_count(2, completely_read?: true))
end

def create_categories(titles) do
Expand Down
6 changes: 0 additions & 6 deletions test/feature/threads_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,4 @@ defmodule FirestormWeb.Feature.ThreadsTest do
end
{:ok, categories}
end

defp log_in_as(session, user) do
session
|> visit("/")
|> Browser.set_cookie("current_user", user.id)
end
end
15 changes: 15 additions & 0 deletions test/forums_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,21 @@ defmodule FirestormWeb.ForumsTest do
end
end

describe "viewing a post" do
setup [:create_user, :create_category, :create_thread]

test "viewing a post", %{thread: thread, user: user} do
thread =
thread
|> Repo.preload(:posts)

[first_post] = thread.posts

{:ok, _view} = user |> Forums.view(first_post)
assert first_post |> Forums.viewed_by?(user)
end
end

def create_category(_) do
category = fixture(:category, @create_category_attrs)
{:ok, category: category}
Expand Down
11 changes: 11 additions & 0 deletions test/support/feature_case.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
defmodule FirestormWeb.Web.FeatureCase do
use ExUnit.CaseTemplate

defmodule Helpers do
use Wallaby.DSL

def log_in_as(session, user) do
session
|> visit("/")
|> Browser.set_cookie("current_user", user.id)
end
end

using do
quote do
use Wallaby.DSL
Expand All @@ -11,6 +21,7 @@ defmodule FirestormWeb.Web.FeatureCase do
import Ecto.Query

import FirestormWeb.Web.Router.Helpers
import FirestormWeb.Web.FeatureCase.Helpers

alias FirestormWeb.Forums.{
User,
Expand Down

0 comments on commit 222607a

Please sign in to comment.