{"id":17038660,"url":"https://github.com/wbotelhos/voting","last_synced_at":"2025-04-12T13:52:55.129Z","repository":{"id":56897643,"uuid":"116704795","full_name":"wbotelhos/voting","owner":"wbotelhos","description":":+1: A Binomial proportion confidence interval voting system with scope and cache enabled","archived":false,"fork":false,"pushed_at":"2020-03-06T16:55:43.000Z","size":47,"stargazers_count":8,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-26T08:37:12.194Z","etag":null,"topics":["rating","vote","voter","voting","voting-system"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wbotelhos.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-01-08T17:06:54.000Z","updated_at":"2024-11-11T20:04:37.000Z","dependencies_parsed_at":"2022-08-21T02:20:28.549Z","dependency_job_id":null,"html_url":"https://github.com/wbotelhos/voting","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wbotelhos%2Fvoting","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wbotelhos%2Fvoting/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wbotelhos%2Fvoting/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wbotelhos%2Fvoting/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wbotelhos","download_url":"https://codeload.github.com/wbotelhos/voting/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248576397,"owners_count":21127385,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["rating","vote","voter","voting","voting-system"],"created_at":"2024-10-14T08:57:26.327Z","updated_at":"2025-04-12T13:52:55.110Z","avatar_url":"https://github.com/wbotelhos.png","language":"Ruby","funding_links":["https://www.patreon.com/wbotelhos"],"categories":[],"sub_categories":[],"readme":"# Voting\n\n[![Build Status](https://travis-ci.org/wbotelhos/voting.svg)](https://travis-ci.org/wbotelhos/voting)\n[![Gem Version](https://badge.fury.io/rb/voting.svg)](https://badge.fury.io/rb/voting)\n[![Maintainability](https://api.codeclimate.com/v1/badges/dcdf51e3093148c5ac6a/maintainability)](https://codeclimate.com/github/wbotelhos/voting/maintainability)\n[![Patreon](https://img.shields.io/badge/donate-%3C3-brightgreen.svg)](https://www.patreon.com/wbotelhos)\n\nA Binomial proportion confidence interval voting system with scope and cache enabled.\n\n## Description\n\nVoting uses **Binomial proportion confidence interval** to calculate the voting. Inspired on [Evan Miller Article](https://www.evanmiller.org/how-not-to-sort-by-average-rating.html) and used by [Reddit](https://redditblog.com/2009/10/15/reddits-new-comment-sorting-system), [Yelp](https://www.yelpblog.com/2011/02/the-most-romantic-city-on-yelp-is), [Digg](web.archive.org/web/20110415020820/http://about.digg.com/blog/algorithm-experiments-better-comments) and probably other, Voting gives you cache and option to work with scopes over a **binary** voting system.\n\nIf you are looking for **5 stars** voting system, check [Rating](https://github.com/wbotelhos/rating) :star2:\n\n## Install\n\nAdd the following code on your `Gemfile` and run `bundle install`:\n\n```ruby\ngem 'voting'\n```\n\nRun the following task to create a Voting migration:\n\n```bash\nrails g voting:install\n```\n\nThen execute the migrations to create the tables `voting_votes` and `voting_votings`:\n\n```bash\nrake db:migrate\n```\n\n## Usage\n\nJust add the callback `voting` to your model:\n\n```ruby\nclass Author \u003c ApplicationRecord\n  voting\nend\n```\n\nNow this model can vote or receive votes.\n\n### vote\n\nYou can vote on some resource using integer or string as value:\n\n```ruby\nauthor_1 = Author.first\nauthor_2 = Author.last\nresource = Comment.last\n\nauthor_1.vote(resource, 1)  # +1 vote\nauthor_2.vote(resource, -1) # -1 vote\n```\n\n### status\n\nThis mehod will return the status of a `Voting::Vote` object as `positive`, `negative` or `none`.\n\n```ruby\nauthor   = Author.first\nresource = Comment.last\n\nauthor.vote(resource, 1).status # 'positive'\nauthor.vote(resource, -1)       # 'negative'\nauthor.vote(resource, -1)       # 'none'\n```\n\n### voting\n\nA voted resource exposes a cached data about it state:\n\n```ruby\nresource = Comment.last\n\nresource.voting\n```\n\nIt will return a `Voting::Voting` object that keeps:\n\n`author`: the author of this vote;\n\n`estimate`: the Binomial proportion confidence interval value;\n\n`negative`: the sum of negative votes for this resource;\n\n`positive`: the sum of positive votes for this resource;\n\n`resource`: the self object that received this vote;\n\n`scopeable`: the object used as scope;\n\n### vote_for\n\nYou can retrieve the vote an author gave to a specific resource:\n\n```ruby\nauthor   = Author.last\nresource = Comment.last\n\nauthor.vote_for resource\n```\n\nIt will return a `Voting::Vote` object that keeps:\n\n`author`: the author of vote;\n\n`resource`: the resource that received the vote;\n\n`negative`: the -1 value when vote was negative;\n\n`positive`: the 1 value when vote was positive;\n\n`scopeable`: the object used as scope;\n\n### voted?\n\nMaybe you want just to know if some author already voted some resource and receive `true` or `false`:\n\n```ruby\nauthor   = Author.last\nresource = Comment.last\n\nauthor.voted? resource\n```\n\nIf you want to know if the vote was `positive` or `negative`, just pass a symbol about it:\n\n```ruby\nauthor.voted? resource, :negative\nauthor.voted? resource, :positive\n```\n\n### votes\n\nYou can retrieve all votes received by some resource:\n\n```ruby\nresource = Article.last\n\nresource.votes\n```\n\nIt will return a collection of `Voting::Vote` object.\n\n### voted\n\nIn the same way you can retrieve all votes that some author made:\n\n```ruby\nauthor = Author.last\n\nauthor.voted\n```\n\nIt will return a collection of `Voting::Vote` object.\n\n### order_by_voting\n\nYou can list resource ordered by voting data:\n\n```ruby\nComment.order_by_voting\n```\n\nIt will return a collection of resource ordered by `estimate desc` as default.\nThe order column and direction can be changed:\n\n```ruby\nComment.order_by_voting :negative, :asc\n```\n\nIt will return a collection of resource ordered by `Voting::Voting` table data.\n\n### Records\n\nMaybe you want to recover all records, so you can add the suffix `_records` on relations:\n\n```ruby\nauthor   = Author.last\nresource = Comment.last\n\nauthor.vote resource, 1\n\nauthor.voting_records\n\nauthor.voted_records\n\ncomment.voting_records\n```\n\n### As\n\nIf you have a model that will only be able to vote but not to receive a vote, configure it as `author`.\nAn author model still can be voted, but won't generate a Voting record with all values as zero to warm up the cache.\n\n```ruby\nvoting as: :author\n```\n\n### Alias\n\nYou can to use alias to directly call `vote` with positive or negative data.\n\n```ruby\nauthor   = Author.last\nresource = Comment.last\n\nauthor.up   resource # +1\nauthor.down resource # -1\n```\n\n#### Options\n\n`down`: makes a negative vote;\n\n`up`: makes a positive vote;\n\n### Toggle\n\nThe toggle functions works out of box, so if you vote up twice or vote twice down, the vote will be canceled.\nWhen you do that, the vote record is **not destroyed** instead, it receives zero on `negative` and `positive` column.\n\n### Scope\n\nAll methods accepts a `scope` param to be persisted with the vote or to be searched:\n\n```ruby\ncategory = Category.last\n\nauthor.down     resource,            scope: category\nauthor.up       resource,            scope: category\nauthor.vote     resource, 1,         scope: category\nauthor.vote_for resource,            scope: category\nauthor.voted    resource,            scope: category\nauthor.voted?   resource, :negative, scope: category\nauthor.voted?   resource, :positive, scope: category\n\nresource.votes  scope: category\nauthor  .voted  scope: category\nresource.votign scope: category\n```\n\n### Scoping\n\nIf you need to warm up a record with scope, you need to setup the `scoping` relation.\n\n```ruby\nclass Resource \u003c ApplicationRecord\n  voting scoping: :categories\nend\n```\n\nNow, when a resource is created, the cache will be generated for each related `category` as `scopeable`.\n\n### References\n\n- [Evan Miller](https://www.evanmiller.org/how-not-to-sort-by-average-rating.html)\n- [Jonathan Landy](http://efavdb.com/ranking-revisited)\n- [Wilson Score Interval](https://en.wikipedia.org/wiki/Binomial_proportion_confidence_interval#Wilson_score_interval)\n- [How to Count Thumb-Ups and Thumb-Downs](http://www.dcs.bbk.ac.uk/~dell/publications/dellzhang_ictir2011.pdf)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwbotelhos%2Fvoting","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwbotelhos%2Fvoting","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwbotelhos%2Fvoting/lists"}