{"id":13484361,"url":"https://github.com/ryanto/acts_as_votable","last_synced_at":"2025-05-14T05:10:31.123Z","repository":{"id":1268704,"uuid":"1207793","full_name":"ryanto/acts_as_votable","owner":"ryanto","description":"Votable ActiveRecord for Rails","archived":false,"fork":false,"pushed_at":"2022-12-12T17:27:52.000Z","size":278,"stargazers_count":1532,"open_issues_count":15,"forks_count":217,"subscribers_count":28,"default_branch":"master","last_synced_at":"2025-05-12T13:59:02.424Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ryanto.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2010-12-30T05:52:38.000Z","updated_at":"2025-04-26T15:36:34.000Z","dependencies_parsed_at":"2023-01-13T11:03:42.934Z","dependency_job_id":null,"html_url":"https://github.com/ryanto/acts_as_votable","commit_stats":null,"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanto%2Facts_as_votable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanto%2Facts_as_votable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanto%2Facts_as_votable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanto%2Facts_as_votable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ryanto","download_url":"https://codeload.github.com/ryanto/acts_as_votable/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254076848,"owners_count":22010611,"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":[],"created_at":"2024-07-31T17:01:23.032Z","updated_at":"2025-05-14T05:10:31.089Z","avatar_url":"https://github.com/ryanto.png","language":"Ruby","funding_links":[],"categories":["Ruby","ORM/ODM Extensions","ActiveRecord"],"sub_categories":[],"readme":"# Acts As Votable (aka Acts As Likeable)\r\n\r\n![Build status](https://github.com/ryanto/acts_as_votable/workflows/CI/badge.svg)\r\n\r\nActs As Votable is a Ruby Gem specifically written for Rails/ActiveRecord models.\r\nThe main goals of this gem are:\r\n\r\n- Allow any model to be voted on, like/dislike, upvote/downvote, etc.\r\n- Allow any model to be voted under arbitrary scopes.\r\n- Allow any model to vote.  In other words, votes do not have to come from a user,\r\n  they can come from any model (such as a Group or Team).\r\n- Provide an easy to write/read syntax.\r\n\r\n## Installation\r\n\r\n### Supported Ruby and Rails versions\r\n\r\n- Ruby \u003e= 2.5.0\r\n- Rails \u003e= 5.1\r\n\r\n### Install\r\n\r\nJust add the following to your Gemfile to install the latest release.\r\n\r\n```ruby\r\ngem 'acts_as_votable'\r\n```\r\n\r\nAnd follow that up with a ``bundle install``.\r\n\r\n### Database Migrations\r\n\r\nActs As Votable uses a votes table to store all voting information.  To\r\ngenerate and run the migration just use.\r\n\r\n```bash\r\nrails generate acts_as_votable:migration\r\nrails db:migrate\r\n```\r\n\r\nYou will get a performance increase by adding in cached columns to your model's\r\ntables.  You will have to do this manually through your own migrations.  See the\r\ncaching section of this document for more information.\r\n\r\n## Usage\r\n\r\n### Votable Models\r\n\r\n```ruby\r\nclass Post \u003c ApplicationRecord\r\n  acts_as_votable\r\nend\r\n\r\n@post = Post.new(name: 'my post!')\r\n@post.save\r\n\r\n@post.liked_by @user\r\n@post.votes_for.size # =\u003e 1\r\n```\r\n\r\n### Like/Dislike Yes/No Up/Down\r\n\r\nHere are some voting examples.  All of these calls are valid and acceptable.  The\r\nmore natural calls are the first few examples.\r\n\r\n```ruby\r\n@post.liked_by @user1\r\n@post.downvote_from @user2\r\n@post.vote_by voter: @user3\r\n@post.vote_by voter: @user4, vote: 'bad'\r\n@post.vote_by voter: @user5, vote: 'like'\r\n```\r\n\r\nBy default all votes are positive, so `@user3` has cast a 'good' vote for `@post`.\r\n\r\n`@user1`, `@user3`, and `@user5` all voted in favor of `@post`.\r\n\r\n`@user2` and `@user4` voted against `@post`.\r\n\r\nJust about any word works for casting a vote in favor or against post.  Up/Down,\r\nLike/Dislike, Positive/Negative... the list goes on-and-on.  Boolean flags `true` and\r\n`false` are also applicable.\r\n\r\nRevisiting the previous example of code.\r\n\r\n```ruby\r\n# positive votes\r\n@post.liked_by @user1\r\n@post.vote_by voter: @user3\r\n@post.vote_by voter: @user5, vote: 'like'\r\n\r\n# negative votes\r\n@post.downvote_from @user2\r\n@post.vote_by voter: @user2, vote: 'bad'\r\n\r\n# tally them up!\r\n@post.votes_for.size # =\u003e 5\r\n@post.weighted_total # =\u003e 5\r\n@post.get_likes.size # =\u003e 3\r\n@post.get_upvotes.size # =\u003e 3\r\n@post.get_dislikes.size # =\u003e 2\r\n@post.get_downvotes.size # =\u003e 2\r\n@post.weighted_score # =\u003e 1\r\n```\r\n\r\nActive Record scopes are provided to make life easier.\r\n\r\n```ruby\r\n@post.votes_for.up.by_type(User)\r\n@post.votes_for.down\r\n@user1.votes.up\r\n@user1.votes.down\r\n@user1.votes.up.for_type(Post)\r\n```\r\n\r\nOnce scoping is complete, you can also trigger a get for the\r\nvoter/votable\r\n\r\n```ruby\r\n@post.votes_for.up.by_type(User).voters\r\n@post.votes_for.down.by_type(User).voters\r\n\r\n@user.votes.up.for_type(Post).votables\r\n@user.votes.up.votables\r\n```\r\n\r\nYou can also 'unvote' a model to remove a previous vote.\r\n\r\n```ruby\r\n@post.liked_by @user1\r\n@post.unliked_by @user1\r\n\r\n@post.disliked_by @user1\r\n@post.undisliked_by @user1\r\n```\r\n\r\nUnvoting works for both positive and negative votes.\r\n\r\n### Examples with scopes\r\n\r\nYou can add a scope to your vote\r\n\r\n```ruby\r\n# positive votes\r\n@post.liked_by @user1, vote_scope: 'rank'\r\n@post.vote_by voter: @user3, vote_scope: 'rank'\r\n@post.vote_by voter: @user5, vote: 'like', vote_scope: 'rank'\r\n\r\n# negative votes\r\n@post.downvote_from @user2, vote_scope: 'rank'\r\n@post.vote_by voter: @user2, vote: 'bad', vote_scope: 'rank'\r\n\r\n# tally them up!\r\n@post.find_votes_for(vote_scope: 'rank').size # =\u003e 5\r\n@post.get_likes(vote_scope: 'rank').size # =\u003e 3\r\n@post.get_upvotes(vote_scope: 'rank').size # =\u003e 3\r\n@post.get_dislikes(vote_scope: 'rank').size # =\u003e 2\r\n@post.get_downvotes(vote_scope: 'rank').size # =\u003e 2\r\n\r\n# votable model can be voted under different scopes\r\n# by the same user\r\n@post.vote_by voter: @user1, vote_scope: 'week'\r\n@post.vote_by voter: @user1, vote_scope: 'month'\r\n\r\n@post.votes_for.size # =\u003e 2\r\n@post.find_votes_for(vote_scope: 'week').size # =\u003e 1\r\n@post.find_votes_for(vote_scope: 'month').size # =\u003e 1\r\n```\r\n\r\n### Adding weights to your votes\r\n\r\nYou can add weight to your vote. The default value is 1.\r\n\r\n```ruby\r\n# positive votes\r\n@post.liked_by @user1, vote_weight: 1\r\n@post.vote_by voter: @user3, vote_weight: 2\r\n@post.vote_by voter: @user5, vote: 'like', vote_scope: 'rank', vote_weight: 3\r\n\r\n# negative votes\r\n@post.downvote_from @user2, vote_scope: 'rank', vote_weight: 1\r\n@post.vote_by voter: @user2, vote: 'bad', vote_scope: 'rank', vote_weight: 3\r\n\r\n# tally them up!\r\n@post.find_votes_for(vote_scope: 'rank').sum(:vote_weight) # =\u003e 6\r\n@post.get_likes(vote_scope: 'rank').sum(:vote_weight) # =\u003e 6\r\n@post.get_upvotes(vote_scope: 'rank').sum(:vote_weight) # =\u003e 6\r\n@post.get_dislikes(vote_scope: 'rank').sum(:vote_weight) # =\u003e 4\r\n@post.get_downvotes(vote_scope: 'rank').sum(:vote_weight) # =\u003e 4\r\n```\r\n\r\n### The Voter\r\n\r\nYou can have your voters `acts_as_voter` to provide some reserve functionality.\r\n\r\n```ruby\r\nclass User \u003c ApplicationRecord\r\n  acts_as_voter\r\nend\r\n\r\n@user.likes @article\r\n\r\n@article.votes_for.size # =\u003e 1\r\n@article.get_likes.size # =\u003e 1\r\n@article.get_dislikes.size # =\u003e 0\r\n```\r\n\r\nTo check if a voter has voted on a model, you can use ``voted_for?``.  You can\r\ncheck how the voter voted by using ``voted_as_when_voted_for``.\r\n\r\n```ruby\r\n@user.likes @comment1\r\n@user.up_votes @comment2\r\n# user has not voted on @comment3\r\n\r\n@user.voted_for? @comment1 # =\u003e true\r\n@user.voted_for? @comment2 # =\u003e true\r\n@user.voted_for? @comment3 # =\u003e false\r\n\r\n@user.voted_as_when_voted_for @comment1 # =\u003e true, user liked it\r\n@user.voted_as_when_voted_for @comment2 # =\u003e false, user didnt like it\r\n@user.voted_as_when_voted_for @comment3 # =\u003e nil, user has yet to vote\r\n```\r\n\r\nYou can also check whether the voter has voted up or down.\r\n\r\n```ruby\r\n@user.likes @comment1\r\n@user.dislikes @comment2\r\n# user has not voted on @comment3\r\n\r\n@user.voted_up_on? @comment1 # =\u003e true\r\n@user.voted_down_on? @comment1 # =\u003e false\r\n\r\n@user.voted_down_on? @comment2 # =\u003e true\r\n@user.voted_up_on? @comment2 # =\u003e false\r\n\r\n@user.voted_up_on? @comment3 # =\u003e false\r\n@user.voted_down_on? @comment3 # =\u003e false\r\n```\r\n\r\nAliases for methods `voted_up_on?` and `voted_down_on?` are: `voted_up_for?`, `voted_down_for?`, `liked?` and `disliked?`.\r\n\r\nAlso, you can obtain a list of all the objects a user has voted for.\r\nThis returns the actual objects instead of instances of the Vote model.\r\nAll objects are eager loaded\r\n\r\n```ruby\r\n@user.find_voted_items\r\n\r\n@user.find_up_voted_items\r\n@user.find_liked_items\r\n\r\n@user.find_down_voted_items\r\n@user.find_disliked_items\r\n```\r\n\r\nMembers of an individual model that a user has voted for can also be\r\ndisplayed. The result is an ActiveRecord Relation.\r\n\r\n```ruby\r\n@user.get_voted Comment\r\n\r\n@user.get_up_voted Comment\r\n\r\n@user.get_down_voted Comment\r\n```\r\n\r\n### Registered Votes\r\n\r\nVoters can only vote once per model.  In this example the 2nd vote does not count\r\nbecause `@user` has already voted for `@shoe`.\r\n\r\n```ruby\r\n@user.likes @shoe\r\n@user.likes @shoe\r\n\r\n@shoe.votes_for.size # =\u003e 1\r\n@shoe.get_likes.size # =\u003e 1\r\n```\r\n\r\nTo check if a vote counted, or registered, use `vote_registered?` on your model\r\nafter voting.  For example:\r\n\r\n```ruby\r\n@hat.liked_by @user\r\n@hat.vote_registered? # =\u003e true\r\n\r\n@hat.liked_by =\u003e @user\r\n@hat.vote_registered? # =\u003e false, because @user has already voted this way\r\n\r\n@hat.disliked_by @user\r\n@hat.vote_registered? # =\u003e true, because user changed their vote\r\n\r\n@hat.votes_for.size # =\u003e 1\r\n@hat.get_positives.size # =\u003e 0\r\n@hat.get_negatives.size # =\u003e 1\r\n```\r\n\r\nTo permit duplicates entries of a same voter, use option duplicate. Also notice that this\r\nwill limit some other methods that didn't deal with multiples votes, in this case, the last vote will be considered.\r\n\r\n```ruby\r\n@hat.vote_by voter: @user, duplicate: true\r\n```\r\n\r\n## Caching\r\n\r\nTo speed up perform you can add cache columns to your votable model's table.  These\r\ncolumns will automatically be updated after each vote.  For example, if we wanted\r\nto speed up @post we would use the following migration:\r\n\r\n```ruby\r\nclass AddCachedVotesToPosts \u003c ActiveRecord::Migration\r\n  def change\r\n    change_table :posts do |t|\r\n      t.integer :cached_votes_total, default: 0\r\n      t.integer :cached_votes_score, default: 0\r\n      t.integer :cached_votes_up, default: 0\r\n      t.integer :cached_votes_down, default: 0\r\n      t.integer :cached_weighted_score, default: 0\r\n      t.integer :cached_weighted_total, default: 0\r\n      t.float :cached_weighted_average, default: 0.0\r\n    end\r\n\r\n    # Uncomment this line to force caching of existing votes\r\n    # Post.find_each(\u0026:update_cached_votes)\r\n  end\r\nend\r\n```\r\n\r\nIf you have a scope for your vote, let's say `subscribe`, your migration will be slightly different like below:\r\n\r\n```ruby\r\nclass AddCachedVotesToPosts \u003c ActiveRecord::Migration\r\n  def change\r\n    change_table :posts do |t|\r\n      t.integer :cached_scoped_subscribe_votes_total, default: 0\r\n      t.integer :cached_scoped_subscribe_votes_score, default: 0\r\n      t.integer :cached_scoped_subscribe_votes_up, default: 0\r\n      t.integer :cached_scoped_subscribe_votes_down, default: 0\r\n      t.integer :cached_weighted_subscribe_score, default: 0\r\n      t.integer :cached_weighted_subscribe_total, default: 0\r\n      t.float :cached_weighted_subscribe_average, default: 0.0\r\n\r\n      # Uncomment this line to force caching of existing scoped votes\r\n      # Post.find_each { |p| p.update_cached_votes(\"subscribe\") }\r\n    end\r\n  end\r\nend\r\n```\r\n\r\n`cached_weighted_average` can be helpful for a rating system, e.g.:\r\n\r\nOrder by average rating:\r\n\r\n```ruby\r\nPost.order(cached_weighted_average: :desc)\r\n```\r\n\r\nDisplay average rating:\r\n\r\n```erb\r\n\u003c%= post.weighted_average.round(2) %\u003e / 5\r\n\u003c!-- 3.5 / 5 --\u003e\r\n```\r\n\r\n## Votable model's `updated_at`\r\n\r\nYou can control whether `updated_at` column of votable model will be touched or\r\nnot by passing `cacheable_strategy` option to `acts_as_votable` method.\r\n\r\nBy default, `update` strategy is used. Pass `:update_columns` as\r\n`cacheable_strategy` if you don't want to touch model's `updated_at` column.\r\n\r\n```ruby\r\nclass Post \u003c ApplicationRecord\r\n  acts_as_votable cacheable_strategy: :update_columns\r\nend\r\n```\r\n\r\n## Testing\r\n\r\nAll tests follow the RSpec format and are located in the spec directory.\r\nThey can be run with:\r\n\r\n```bash\r\nrake spec\r\n```\r\n\r\n## License\r\n\r\nActs as votable is released under the [MIT License](http://www.opensource.org/licenses/MIT).\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fryanto%2Facts_as_votable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fryanto%2Facts_as_votable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fryanto%2Facts_as_votable/lists"}