# Votex
**Implements :thumbsup: vote / :heart: like / follow functionality for Ecto models in Elixir**
> Inspired from [Acts as Votable][acts_as_votable] :star: in Ruby on Rails
[acts_as_votable]: https://github.com/ryanto/acts_as_votable
## Features
- Any model can be voted
- Any model can vote
- Supports self referential voting
- Easy to understand syntax
## Installation
Add Votex to your project dependencies `mix.exs`
``` elixir
defp deps do
[{:votex, "~> 0.3.0"}]
end
```
Specify your root project's repo in config
``` eixir
config :votex, Votex.DB,
repo: MyApp.Repo
```
Votex needs a table in DB to store votes information. Install votex and generate votex schema migration
``` shell
mix deps.get
mix votex.gen.migration
mix ecto.migrate
```
## Usage
### Configure Models
``` elixir
defmodule User do
use Ecto.Schema
use Votex.Voter
end
defmodule Post do
use Ecto.Schema
use Votex.Votable
end
```
### Vote / Unvote
``` elixir
post |> Post.vote_by user
user |> User.voted_for? post
# true
post |> Post.votes_for |> length
# 1
post |> Post.votes_for
[
%{
id: 1,
votable_id: 3,
votable_type: "posts",
voter: %Sample.User{
id: 5,
name: "John"
},
voter_id: 5,
voter_type: "users"
}
]
post |> Post.unvote_by user
```
### Self Referential Vote
``` elixir
defmodule User do
use Ecto.Schema
use Votex.Voter
use Votex.Votable
end
user2 |> User.vote_by user1
# {:ok, _}
```
### Cleanup
Since polymorphic associations are not supported in Ecto and callbacks are deprecated, orphan votes need to be cleared when a parent entity is destroyed. Therefore, you need to call cleanup_votes when you delete a Voter or a Votable record.
``` elixir
# Delete user
Repo.delete(user) |> User.cleanup_votes
# Delete post
Repo.delete(post) |> Post.cleanup_votes
```
### Cache Support
Some fields like total number of votes on a post can be cached in posts table to avoid extra calls to DB. Votex will update the field if present in schema.
``` elixir
defmodule Post do
use Ecto.Schema
use Votex.Votable
schema "posts" do
field(:cached_votes_for_total, :integer)
end
@fields ~w(cached_votes_for_total)a
# Publicly accessible changeset for Votex to update field
def changeset(post, attrs) do
post
|> cast(attrs, @fields)
|> validate_required(@fields)
end
end
```
The posts table will track total votes from now on
``` elixir
post |> Post.vote_by user
post = Repo.get(Post, 1)
post.cached_votes_for_total
# 5
```