Packages

Implements vote / like / follow functionality for Ecto models. Inspired from Acts as Votable

Current section

Files

Jump to
votex lib votex votable.ex
Raw

lib/votex/votable.ex

defmodule Votex.Votable do
@moduledoc """
Defines a Votable Model
A Votable model will expose the required methods to enable voting functionality
Typically be used by models like Post, Image, Answer etc.
## Example
defmodule Post do
use Ecto.Schema
use Votex.Votable
schema "posts" do
field :title, :string
field :views, :integer, default: 0
end
end
"""
import Ecto.Query
import Votex.Core
alias Votex.{Vote, Votable, DB, CleanupBehaviour}
defmacro __using__(_opts) do
quote do
@behaviour unquote(__MODULE__)
@behaviour CleanupBehaviour
defdelegate vote_by(votable, voter), to: Votable
defdelegate unvote_by(votable, voter), to: Votable
defdelegate votes_for(votable), to: Votable
defdelegate cleanup_votes(result), to: Votable
end
end
@doc """
Primary method to cast a vote
## Example
{:ok, vote} = post |> Post.vote_by user
"""
def vote_by(votable, voter) do
{votable_type, voter_type} = extract_fields(votable, voter)
result =
%{
votable_id: votable.id,
votable_type: votable_type,
voter_id: voter.id,
voter_type: voter_type
}
|> create_vote
calculate_cached_fields_for_votable(get_module(votable_type), votable_type, votable.id, true)
result
end
@doc """
Primary method to remove a vote
## Example
{:ok, vote} = post |> Post.unvote_by user
"""
def unvote_by(votable, voter) do
{votable_type, voter_type} = extract_fields(votable, voter)
vote =
Vote
|> where(votable_type: ^votable_type)
|> where(votable_id: ^votable.id)
|> where(voter_type: ^voter_type)
|> where(voter_id: ^voter.id)
|> DB.repo().one
case vote do
%{} = v ->
calculate_cached_fields_for_votable(
get_module(votable_type),
votable_type,
votable.id,
false
)
v |> DB.repo().delete
nil ->
raise "Vote not present"
end
end
@doc """
Get a list of votes on votable record
## Example
votes = post |> Post.votes_for
"""
def votes_for(votable) do
{votable_type, _} = extract_fields(votable, nil)
Vote
|> where(votable_type: ^votable_type)
|> where(votable_id: ^votable.id)
|> DB.repo().all
|> preload_votes
end
@doc """
Clean up votes after a votable record is deleted
## Example
Repo.delete(user) |> User.cleanup_votes
"""
def cleanup_votes({status, %{} = payload}) do
case status do
:ok ->
{votable_type, _} = extract_fields(payload, nil)
Vote
|> where(votable_type: ^votable_type)
|> where(votable_id: ^payload.id)
|> DB.repo().delete_all
_ ->
{status, payload}
end
end
@doc """
Reserved for internal use
"""
def children() do
for({module, _} <- :code.all_loaded(), do: module)
|> Enum.filter(&is_child?/1)
end
# Private
defp get_module(type) do
children()
|> convert_modules_list_to_map
|> Map.get(type)
end
defp is_child?(module) do
module.module_info[:attributes]
|> Keyword.get(:behaviour, [])
|> Enum.member?(__MODULE__)
end
defp create_vote(%{} = vote) do
%Vote{}
|> Vote.changeset(vote)
|> DB.repo().insert
end
end