From 418def161164089c6b8e4d5ed4116a831507b540 Mon Sep 17 00:00:00 2001 From: Aleksandr Koss Date: Mon, 30 Nov 2009 23:45:31 +0600 Subject: [PATCH 001/138] Fixed NameError: uninitialized constant RAILS_DEFAULT_LOGGER; added exception catcher --- lib/vote_fu.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/vote_fu.rb b/lib/vote_fu.rb index fb61d4a..8e46b20 100644 --- a/lib/vote_fu.rb +++ b/lib/vote_fu.rb @@ -5,4 +5,12 @@ ActiveRecord::Base.send(:include, Juixe::Acts::Voteable) ActiveRecord::Base.send(:include, PeteOnRails::Acts::Voter) ActiveRecord::Base.send(:include, PeteOnRails::VoteFu::Karma) -RAILS_DEFAULT_LOGGER.info "** vote_fu: initialized properly." + +success_message = '** vote_fu: initialized properly.' + +begin + RAILS_DEFAULT_LOGGER.info success_message +rescue NameError + Logger.new(STDOUT).info success_message +end + From 33032693bb4c694fe498740062f3c638057379ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Dro=C5=BCd=C5=BCy=C5=84ski?= Date: Wed, 24 Mar 2010 10:27:36 +0100 Subject: [PATCH 002/138] Version bump to 0.1.1 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 6e8bf73..17e51c3 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.0 +0.1.1 From ad00194cdd1e0ab316edeb8dc8c481419231123a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Dro=C5=BCd=C5=BCy=C5=84ski?= Date: Wed, 24 Mar 2010 10:37:09 +0100 Subject: [PATCH 003/138] Added the gemspec to the repository --- .gitignore | 3 +- objectreload-vote_fu.gemspec | 67 ++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 objectreload-vote_fu.gemspec diff --git a/.gitignore b/.gitignore index 3ee9716..01d0a08 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -*.gemspec -pkg/ \ No newline at end of file +pkg/ diff --git a/objectreload-vote_fu.gemspec b/objectreload-vote_fu.gemspec new file mode 100644 index 0000000..e93c641 --- /dev/null +++ b/objectreload-vote_fu.gemspec @@ -0,0 +1,67 @@ +# Generated by jeweler +# DO NOT EDIT THIS FILE DIRECTLY +# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = %q{objectreload-vote_fu} + s.version = "0.1.1" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnętrzak"] + s.date = %q{2010-03-24} + s.description = %q{VoteFu provides the ability to have multiple voting entities on an arbitrary number of models in ActiveRecord.} + s.email = %q{gems@objectreload.com} + s.extra_rdoc_files = [ + "README.markdown" + ] + s.files = [ + ".gitignore", + "CHANGELOG.markdown", + "MIT-LICENSE", + "README.markdown", + "Rakefile", + "VERSION", + "examples/routes.rb", + "examples/users_controller.rb", + "examples/voteable.html.erb", + "examples/voteable.rb", + "examples/voteables_controller.rb", + "examples/votes/_voteable_vote.html.erb", + "examples/votes/create.rjs", + "examples/votes_controller.rb", + "generators/vote_fu/templates/migration.rb", + "generators/vote_fu/templates/vote.rb", + "generators/vote_fu/vote_fu_generator.rb", + "lib/acts_as_voteable.rb", + "lib/acts_as_voter.rb", + "lib/has_karma.rb", + "lib/vote_fu.rb", + "rails/init.rb", + "test/vote_fu_test.rb" + ] + s.homepage = %q{http://github.com/objectreload/vote_fu} + s.rdoc_options = ["--charset=UTF-8"] + s.require_paths = ["lib"] + s.rubygems_version = %q{1.3.6} + s.summary = %q{Voting for ActiveRecord with multiple vote sources and advanced features.} + s.test_files = [ + "test/vote_fu_test.rb", + "examples/routes.rb", + "examples/users_controller.rb", + "examples/voteable.rb", + "examples/voteables_controller.rb", + "examples/votes_controller.rb" + ] + + if s.respond_to? :specification_version then + current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION + s.specification_version = 3 + + if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then + else + end + else + end +end + From 693029e8af44235d68632d752ce8ea5d652293f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Dro=C5=BCd=C5=BCy=C5=84ski?= Date: Thu, 22 Jul 2010 16:23:19 +0200 Subject: [PATCH 004/138] Removed the 'vote_fu inititalised properly message' --- lib/vote_fu.rb | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/vote_fu.rb b/lib/vote_fu.rb index 8e46b20..cdc97a7 100644 --- a/lib/vote_fu.rb +++ b/lib/vote_fu.rb @@ -5,12 +5,3 @@ ActiveRecord::Base.send(:include, Juixe::Acts::Voteable) ActiveRecord::Base.send(:include, PeteOnRails::Acts::Voter) ActiveRecord::Base.send(:include, PeteOnRails::VoteFu::Karma) - -success_message = '** vote_fu: initialized properly.' - -begin - RAILS_DEFAULT_LOGGER.info success_message -rescue NameError - Logger.new(STDOUT).info success_message -end - From ac77b185160f6742d0190d2ebe6231fdcc76bbe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Dro=C5=BCd=C5=BCy=C5=84ski?= Date: Thu, 22 Jul 2010 16:23:44 +0200 Subject: [PATCH 005/138] Version bump to 0.1.2 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 17e51c3..d917d3e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.1 +0.1.2 From b15dece3ec3ff859d3cc7514075c2395dd50cfc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Dro=C5=BCd=C5=BCy=C5=84ski?= Date: Thu, 22 Jul 2010 16:24:05 +0200 Subject: [PATCH 006/138] Updated gemspec --- objectreload-vote_fu.gemspec | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/objectreload-vote_fu.gemspec b/objectreload-vote_fu.gemspec index e93c641..d9fe4e7 100644 --- a/objectreload-vote_fu.gemspec +++ b/objectreload-vote_fu.gemspec @@ -5,11 +5,11 @@ Gem::Specification.new do |s| s.name = %q{objectreload-vote_fu} - s.version = "0.1.1" + s.version = "0.1.2" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnętrzak"] - s.date = %q{2010-03-24} + s.date = %q{2010-07-22} s.description = %q{VoteFu provides the ability to have multiple voting entities on an arbitrary number of models in ActiveRecord.} s.email = %q{gems@objectreload.com} s.extra_rdoc_files = [ @@ -37,13 +37,14 @@ Gem::Specification.new do |s| "lib/acts_as_voter.rb", "lib/has_karma.rb", "lib/vote_fu.rb", + "objectreload-vote_fu.gemspec", "rails/init.rb", "test/vote_fu_test.rb" ] s.homepage = %q{http://github.com/objectreload/vote_fu} s.rdoc_options = ["--charset=UTF-8"] s.require_paths = ["lib"] - s.rubygems_version = %q{1.3.6} + s.rubygems_version = %q{1.3.7} s.summary = %q{Voting for ActiveRecord with multiple vote sources and advanced features.} s.test_files = [ "test/vote_fu_test.rb", @@ -58,7 +59,7 @@ Gem::Specification.new do |s| current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION s.specification_version = 3 - if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then else end else From 5fd49014ac62de4095f14756db8b09394a03a19c Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Tue, 3 Aug 2010 17:17:27 +1000 Subject: [PATCH 007/138] Huge updates: - Renamed to ThumbsUp. - (Almost) all code updated for Rails 3 using AR/Arel. - Removed a bunch of extra junk. --- .gitignore | 1 - CHANGELOG.markdown | 7 + MIT-LICENSE | 23 +++ README.markdown | 151 +++------------- Rakefile | 48 +---- VERSION | 2 +- examples/routes.rb | 7 - examples/users_controller.rb | 76 -------- examples/voteable.html.erb | 8 - examples/voteable.rb | 10 -- examples/voteables_controller.rb | 117 ------------ examples/votes/_voteable_vote.html.erb | 23 --- examples/votes/create.rjs | 1 - examples/votes_controller.rb | 110 ------------ .../templates/migration.rb | 4 +- generators/thumbs_up/thumbs_up_generator.rb | 11 ++ generators/vote_fu/templates/vote.rb | 16 -- generators/vote_fu/vote_fu_generator.rb | 11 -- lib/active_record/vote.rb | 16 ++ lib/acts_as_voteable.rb | 168 ++++++++---------- lib/acts_as_voter.rb | 135 +++++++------- lib/has_karma.rb | 68 ------- lib/thumbs_up.rb | 7 + lib/vote_fu.rb | 7 - objectreload-vote_fu.gemspec | 68 ------- test/vote_fu_test.rb | 8 - thumbs_up.gemspec | 49 +++++ 27 files changed, 288 insertions(+), 864 deletions(-) delete mode 100644 .gitignore delete mode 100644 examples/routes.rb delete mode 100644 examples/users_controller.rb delete mode 100644 examples/voteable.html.erb delete mode 100644 examples/voteable.rb delete mode 100644 examples/voteables_controller.rb delete mode 100644 examples/votes/_voteable_vote.html.erb delete mode 100644 examples/votes/create.rjs delete mode 100644 examples/votes_controller.rb rename generators/{vote_fu => thumbs_up}/templates/migration.rb (90%) create mode 100644 generators/thumbs_up/thumbs_up_generator.rb delete mode 100644 generators/vote_fu/templates/vote.rb delete mode 100644 generators/vote_fu/vote_fu_generator.rb create mode 100644 lib/active_record/vote.rb delete mode 100644 lib/has_karma.rb create mode 100644 lib/thumbs_up.rb delete mode 100644 lib/vote_fu.rb delete mode 100644 objectreload-vote_fu.gemspec delete mode 100644 test/vote_fu_test.rb create mode 100644 thumbs_up.gemspec diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 01d0a08..0000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -pkg/ diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index c09e380..b3c7965 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -1,3 +1,10 @@ +2010-08-03 +========== +* Renamed to ThumbsUp from vote\_fu. +* Updated for Rails 3, using ActiveRecord/Arel. +* Cleaned up some dead code, some shitty code, and made a few methods take up quite a lot less memory and time (voters\_who\_voted). +* Removed some shitty example code - this gem is self-explanatory and straight-forward as-is. + 2010-02-04 ========== * Remove vote.rb and votes_controller.rb from gem lib diff --git a/MIT-LICENSE b/MIT-LICENSE index e8d49d5..1e60e98 100644 --- a/MIT-LICENSE +++ b/MIT-LICENSE @@ -1,3 +1,26 @@ +Copyright (c) 2010 Brady Bouchard (ldawn.com) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Major portions of this package were adapted from VoteFu, which is subject to the same license. Here is the original copyright notice for VoteFu: + Copyright (c) 2008 Peter Jackson (peteonrails.com) Permission is hereby granted, free of charge, to any person obtaining diff --git a/README.markdown b/README.markdown index b69e846..6e93cfe 100644 --- a/README.markdown +++ b/README.markdown @@ -1,48 +1,28 @@ -vote_fu +ThumbsUp ======= -Allows an arbitrary number of entites (including Users) to vote on models. +Allows an arbitrary number of entities (users, etc.) to vote on models. ### Mixins -This plugin introduces three mixins to your recipe book: +This plugin introduces two mixins to your recipe book: 1. **acts\_as\_voteable** : Intended for content objects like Posts, Comments, etc. 2. **acts\_as\_voter** : Intended for voting entities, like Users. -3. **has\_karma** : Intended for voting entities, or other objects that own the things you're voting on. ### Inspiration -This plugin started as an adaptation / update of act\_as\_voteable. It has grown different from that plugin in several ways: - -1. You can specify the model name that initiates votes. -2. You can, with a little tuning, have more than one entity type vote on more than one model type. -3. Adds "acts\_as\_voter" behavior to the initiator of votes. -4. Introduces some newer Rails features like named\_scope and :polymorphic keywords -5. Adds "has\_karma" mixin for identifying key content contributors - -### Difference between original vote_fu and our gem - -Generator creates model vote.rb in you application instead of keeping it in the gem lib. -We had bad experience with extending this class in application - unexpected things where having place. +This plugin started as an adaptation / update of vote\_fu for use with Rails 3. It adds some speed, removes some cruft, and is adapted for use with ActiveRecord / Arel in Rails 3. It maintains the awesomeness of the original vote\_fu. Installation ============ -Use either the plugin or the gem installation method depending on your preference. If you're not sure, the plugin method is simpler. Whichever you choose, create the migration afterward and run it to create the required model. -### Via plugin - ./script/plugin install git://github.com/objectreload/vote_fu.git +### Require the gem: -### Via gem -Add the following to your application's environment.rb: - config.gem "objectreload-vote_fu", :lib => 'vote_fu' + gem 'thumbs_up' -Install the gem: - rake gems:install +### Create and run the ThumbsUp migration: -### Create vote_fu migration and vote.rb model - ./script/generate vote_fu - -Run the migration: + rails generate thumbs_up rake db:migrate Usage @@ -50,15 +30,13 @@ Usage ## Getting Started -### Make your ActiveRecord model act as voteable. - +### Turn your AR model into something that can be voted upon. class Model < ActiveRecord::Base acts_as_voteable end - -### Make your ActiveRecord model(s) that vote act as voter. +### Turn your Users (or any other model) into voters. class User < ActiveRecord::Base acts_as_voter @@ -71,19 +49,16 @@ Usage ### To cast a vote for a Model you can do the following: #### Shorthand syntax - voter.vote_for(voteable) # Adds a +1 vote - voter.vote_against(voteable) # Adds a -1 vote - voter.vote(voteable, t_or_f) # Adds either +1 or -1 vote true => +1, false => -1 + voter.vote_for(voteable) # Adds a +1 vote + voter.vote_against(voteable) # Adds a -1 vote + voter.vote(voteable, vote) # Adds either a +1 or -1 vote: vote => true (+1), vote => false (-1) -#### ActsAsVoteable syntax -The old acts\_as\_voteable syntax is still supported: +### Querying votes - vote = Vote.new(:vote => true) - m = Model.find(params[:id]) - m.votes << vote - user.votes << vote +Did the first user vote for the Car with id = 2 already? -### Querying votes + u = User.first + u.voted_on?(Car.find(2)) #### Tallying Votes @@ -95,7 +70,7 @@ You can easily retrieve voteable object collections based on the properties of t :start_at => 2.weeks.ago, :end_at => 1.day.ago, :limit => 10, - :order => "items.name desc" + :order => "items.name DESC" }) This will select the Items with between 1 and 10,000 votes, the votes having been cast within the last two weeks (not including today), then display the 10 last items in an alphabetical list. @@ -123,102 +98,26 @@ And because the Vote Fu plugin will add the has_many votes relationship to your The mixin also provides these methods: voter.voted_for?(voteable) # True if the voter voted for this object. - voter.vote_count([true|false|"all"]) # returns the count of +1, -1, or all votes + voter.vote_count(:up | :down | :all) # returns the count of +1, -1, or all votes voteable.voted_by?(voter) # True if the voter voted for this object. @voters = voteable.voters_who_voted -#### Named Scopes - -The Vote model has several named scopes you can use to find vote details: - - @pete_votes = Vote.for_voter(pete) - @post_votes = Vote.for_voteable(post) - @recent_votes = Vote.recent(1.day.ago) - @descending_votes = Vote.descending - -You can chain these together to make interesting queries: - - # Show all of Pete's recent votes for a certain Post, in descending order (newest first) - @pete_recent_votes_on_post = Vote.for_voter(pete).for_voteable(post).recent(7.days.ago).descending - -### Experimental: Voteable Object Owner Karma -I have just introduced the "has\_karma" mixin to this package. It aims to assign a karma score to the owners of voteable objects. This is designed to allow you to see which users are submitting the most highly voted content. Currently, karma is only "positive". That is, +1 votes add to karma, but -1 votes do not detract from it. - - class User - has_many :posts - has_karma :posts - end - - class Post - acts_as_voteable - end - - # in your view, you can then do this: - Karma: <%= @user.karma %> - -This feature is in alpha, but useful enough that I'm releasing it. - ### One vote per user! -If you want to limit your users to a single vote on each item, take a look in lib/vote.rb. - - # Uncomment this to limit users to a single vote on each item. - # validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id] - -And if you want that enforced at the database level, look in the generated migration for your voteable: - - # If you want to enfore "One Person, One Vote" rules in the database, uncomment the index below - # add_index :votes, ["voter_id", "voter_type", "voteable_id", "voteable_type"], :unique => true, :name => "uniq_one_vote_only" -### Example Application +ThumbsUp by default only allows one vote per user. This can be changed by removing: -There is now a reference application available. Due to overwhelming demand for example -code and kickstart guides, I have open-sourced MyQuotable.com in order to provide an -easy-to-follow example of how to use VoteFu with RESTful Authentication, JRails, and -other popular plugins. To get the example code: +#### In vote.rb: - git clone git://github.com/peteonrails/myquotable.git + validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id] -There will be a screencast coming soon too. Contact me if you want to help. +#### In the migration: -Consideration -============= -If you like this software and use it, please consider recommending me on Working With Rails. - -I don't want donations: a simple up-vote would make my day. My profile is: [http://www.workingwithrails.com/person/12521-peter-jackson][4] - -To go directly to the "Recommend Me" screen: [http://www.workingwithrails.com/recommendation/new/person/12521-peter-jackson][5] + add_index :votes, ["voter_id", "voter_type", "voteable_id", "voteable_type"], :unique => true, :name => "uniq_one_vote_only" Credits ======= -#### Contributors - -* Bence Nagy, Budapest, Hungary -* Jon Maddox, Richmond, Virginia, USA - -#### Other works - -[Juixe - The original ActsAsVoteable plugin inspired this code.][1] - -[Xelipe - This plugin is heavily influenced by Acts As Commentable.][2] - -[1]: http://www.juixe.com/techknow/index.php/2006/06/24/acts-as-voteable-rails-plugin/ -[2]: http://github.com/jackdempsey/acts_as_commentable/tree/master - -More -==== - -Support: [Use my blog for support.][6] - - -[Documentation from the original acts\_as\_voteable plugin][3] - -[3]: http://www.juixe.com/techknow/index.php/2006/06/24/acts-as-voteable-rails-plugin/ -[4]: http://www.workingwithrails.com/person/12521-peter-jackson -[5]: http://www.workingwithrails.com/recommendation/new/person/12521-peter-jackson -[6]: http://blog.peteonrails.com - -Copyright (c) 2008 Peter Jackson, released under the MIT license +Basic structure and a good chunk of code is taken from Peter Jackson's work on ActsAsVoteable. \ No newline at end of file diff --git a/Rakefile b/Rakefile index 188b186..c2cba2c 100644 --- a/Rakefile +++ b/Rakefile @@ -5,53 +5,15 @@ require 'rake' begin require 'jeweler' Jeweler::Tasks.new do |gem| - gem.name = "objectreload-vote_fu" + gem.name = "thumbs_up" gem.summary = "Voting for ActiveRecord with multiple vote sources and advanced features." - gem.description = "VoteFu provides the ability to have multiple voting entities on an arbitrary number of models in ActiveRecord." - gem.email = "gems@objectreload.com" - gem.homepage = "http://github.com/objectreload/vote_fu" - gem.authors = ["Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnętrzak"] + gem.description = "ThumbsUp provides the ability to have multiple voting entities on an arbitrary number of models in ActiveRecord." + gem.email = "brady@ldawn.com" + gem.homepage = "http://github.com/brady8/thumbs_up" + gem.authors = ["Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnętrzak", "Brady Bouchard"] # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings end Jeweler::GemcutterTasks.new rescue LoadError puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler" -end - -require 'rake/testtask' -Rake::TestTask.new(:test) do |test| - test.libs << 'lib' << 'test' - test.pattern = 'test/**/*_test.rb' - test.verbose = true -end - -begin - require 'rcov/rcovtask' - Rcov::RcovTask.new do |test| - test.libs << 'test' - test.pattern = 'test/**/*_test.rb' - test.verbose = true - end -rescue LoadError - task :rcov do - abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov" - end -end - -task :test => :check_dependencies - -task :default => :test - -require 'rake/rdoctask' -Rake::RDocTask.new do |rdoc| - if File.exist?('VERSION') - version = File.read('VERSION') - else - version = "" - end - - rdoc.rdoc_dir = 'rdoc' - rdoc.title = "permissions_gem #{version}" - rdoc.rdoc_files.include('README*') - rdoc.rdoc_files.include('lib/**/*.rb') end \ No newline at end of file diff --git a/VERSION b/VERSION index d917d3e..0ea3a94 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.2 +0.2.0 diff --git a/examples/routes.rb b/examples/routes.rb deleted file mode 100644 index 3a620d6..0000000 --- a/examples/routes.rb +++ /dev/null @@ -1,7 +0,0 @@ - -map.resources :users do |user| - user.resources :votes - user.resources :voteable do |mv| - mv.resources :votes - end -end \ No newline at end of file diff --git a/examples/users_controller.rb b/examples/users_controller.rb deleted file mode 100644 index 091351c..0000000 --- a/examples/users_controller.rb +++ /dev/null @@ -1,76 +0,0 @@ -# I usually use the user class from restful_authentication as my principle voter class -# There are generally no changes required to support voting in this controller. - -class UsersController < ApplicationController - # Be sure to include AuthenticationSystem in Application Controller instead - include AuthenticatedSystem - - # Protect these actions behind an admin login - before_filter :admin_required, :only => [:suspend, :unsuspend, :destroy, :purge] - before_filter :find_user, :only => [:suspend, :unsuspend, :destroy, :purge, :show] - - before_filter :login_required, :only => [:index] - - # render new.html.erb - def new - end - - # GET /users/:id - def show - end - - - def create - cookies.delete :auth_token - @user = User.new(params[:user]) - @user.register! if @user.valid? - if @user.errors.empty? - self.current_user.forget_me if logged_in? - cookies.delete :auth_token - reset_session - flash[:notice] = "Thanks for signing up!" - else - render :action => 'new' - end - end - - def activate - unless params[:activation_code].blank? - self.current_user = User.find_by_activation_code(params[:activation_code]) - if logged_in? && !current_user.active? - current_user.activate! - flash[:notice] = "Signup complete!" - redirect_back_or_default('/') - else - flash[:error] = "Sorry, we couldn't find that activation code. Please cut and paste your activation code into the space at left." - end - end - # render activate.html.erb - end - - def suspend - @user.suspend! - redirect_to users_path - end - - def unsuspend - @user.unsuspend! - redirect_to users_path - end - - def destroy - @user.delete! - redirect_to users_path - end - - def purge - @user.destroy - redirect_to users_path - end - -protected - def find_user - @user = User.find(params[:id]) - end - -end diff --git a/examples/voteable.html.erb b/examples/voteable.html.erb deleted file mode 100644 index df35ff2..0000000 --- a/examples/voteable.html.erb +++ /dev/null @@ -1,8 +0,0 @@ -
- - ..... Show some fields ..... - -
- <%= render :partial => "votes/voteable_vote", :locals => {:voteable => @voteable} %> -
-
diff --git a/examples/voteable.rb b/examples/voteable.rb deleted file mode 100644 index 60e09db..0000000 --- a/examples/voteable.rb +++ /dev/null @@ -1,10 +0,0 @@ -class Voteable < ActiveRecord::Base - - belongs_to :user - - acts_as_voteable - - named_scope :descending, :order => "created_at DESC" - - -end \ No newline at end of file diff --git a/examples/voteables_controller.rb b/examples/voteables_controller.rb deleted file mode 100644 index 71dadd7..0000000 --- a/examples/voteables_controller.rb +++ /dev/null @@ -1,117 +0,0 @@ -# This example controller assumes you are using the User class from restful_authentication -# and a nested voteable resource. See routes.rb - - -class VoteablesController < ApplicationController - - before_filter :find_user - before_filter :login_required, :only => [:new, :edit, :destroy, :create, :update] - before_filter :must_own_voteable, :only => [:edit, :destroy, :update] - - # GET /users/:id/voteables - # GET /users/:id/voteables.xml - def index - @voteable = Voteable.descending - - respond_to do |format| - format.html # index.html.erb - format.xml { render :xml => @voteables } - end - end - - # GET /users/:id/voteables/1 - # GET /users/:id/voteables/1.xml - def show - @voteable = Voteable.find(params[:id]) - - respond_to do |format| - format.html # show.html.erb - format.xml { render :xml => @voteable } - end - end - - # GET /users/:id/voteables/new - # GET /users/:id/voteables/new.xml - def new - @voteable = Voteable.new - - respond_to do |format| - format.html # new.html.erb - format.xml { render :xml => @voteable } - end - end - - # GET /users/:id/voteables/1/edit - def edit - @voteable ||= Voteable.find(params[:id]) - end - - # POST /users/:id/voteables - # POST /users/:id/voteables.xml - def create - @voteable = Voteable.new(params[:voteable]) - @voteable.user = current_user - - respond_to do |format| - if @voteable.save - flash[:notice] = 'Voteable was successfully saved.' - format.html { redirect_to([@user, @voteable]) } - format.xml { render :xml => @voteable, :status => :created, :location => @voteable } - else - format.html { render :action => "new" } - format.xml { render :xml => @voteable.errors, :status => :unprocessable_entity } - end - end - end - - # PUT /users/:id/voteable/1 - # PUT /users/:id/voteable/1.xml - def update - @voteable = Voteable.find(params[:id]) - - respond_to do |format| - if @quote.update_attributes(params[:voteable]) - flash[:notice] = 'Voteable was successfully updated.' - format.html { redirect_to([@user, @voteable]) } - format.xml { head :ok } - else - format.html { render :action => "edit" } - format.xml { render :xml => @voteable.errors, :status => :unprocessable_entity } - end - end - end - - # DELETE /users/:id/voteable/1 - # DELETE /users/:id/voteable/1.xml - def destroy - @voteable = Voteable.find(params[:id]) - @voteable.destroy - - respond_to do |format| - format.html { redirect_to(user_voteables_url) } - format.xml { head :ok } - end - end - - private - def find_user - @user = User.find(params[:user_id]) - end - - def must_own_voteable - @voteable ||= Voteable.find(params[:id]) - @voteable.user == current_user || ownership_violation - end - - def ownership_violation - respond_to do |format| - flash[:notice] = 'You cannot edit or delete voteable that you do not own!' - format.html do - redirect_to user_path(current_user) - end - end - end - - - -end diff --git a/examples/votes/_voteable_vote.html.erb b/examples/votes/_voteable_vote.html.erb deleted file mode 100644 index bf2bdb1..0000000 --- a/examples/votes/_voteable_vote.html.erb +++ /dev/null @@ -1,23 +0,0 @@ -<% - # You can't vote if it is your quote, - # you are not logged in, - # or you have already voted on this item - - unless quote.user == current_user || - !logged_in? || - current_user.voted_on?(@voteable) -%> - - <%= link_to_remote "Up", - :url => user_voteable_votes_path(voteable.user, voteable, :vote => :true, :format => :rjs), - :method => :post - %> - / - <%= link_to_remote "Down", - :url => user_voteable_votes_path(voteable.user, voteable, :vote => :false, :format => :rjs), - :method => :post - %> - -<% end %> - -Votes: <%= voteable.votes_for - voteable.votes_against %> diff --git a/examples/votes/create.rjs b/examples/votes/create.rjs deleted file mode 100644 index ba91c89..0000000 --- a/examples/votes/create.rjs +++ /dev/null @@ -1 +0,0 @@ -page.replace_html "votes_#{@voteable.id}", :partial => "voteable_vote", :locals => {:voteable => @voteable} diff --git a/examples/votes_controller.rb b/examples/votes_controller.rb deleted file mode 100644 index 7bccad9..0000000 --- a/examples/votes_controller.rb +++ /dev/null @@ -1,110 +0,0 @@ -# An example controller for "votes" that are nested resources under users. See examples/routes.rb - -class VotesController < ApplicationController - - # First, figure out our nested scope. User or Voteable? - before_filter :find_votes_for_my_scope, :only => [:index] - - before_filter :login_required, :only => [:new, :edit, :destroy, :create, :update] - before_filter :must_own_vote, :only => [:edit, :destroy, :update] - before_filter :not_allowed, :only => [:edit, :update, :new] - - # GET /users/:user_id/votes/ - # GET /users/:user_id/votes.xml - # GET /users/:user_id/voteables/:voteable_id/votes/ - # GET /users/:user_id/voteables/:voteable_id/votes.xml - def index - respond_to do |format| - format.html # index.html.erb - format.xml { render :xml => @votes } - end - end - - # GET /users/:user_id/votes/1 - # GET /users/:user_id/votes/1.xml - # GET /users/:user_id/voteables/:voteable_id/votes/1 - # GET /users/:user_id/voteables/:voteable_id/1.xml - def show - @voteable = Vote.find(params[:id]) - - respond_to do |format| - format.html # show.html.erb - format.xml { render :xml => @vote } - end - end - - # GET /users/:id/votes/new - # GET /users/:id/votes/new.xml - # GET /users/:id/votes/new - # GET /users/:id/votes/new.xml - def new - # Not generally used. Most people want to vote via AJAX calls. - end - - # GET /users/:id/votes/1/edit - def edit - # Not generally used. Most people don't want to allow editing of votes. - end - - # POST /users/:user_id/voteables/:voteable_id/votes - # POST /users/:user_id/voteables/:voteable_id/votes.xml - def create - @voteable = Voteable.find(params[:quote_id]) - - respond_to do |format| - if current_user.vote(@voteable, params[:vote]) - format.rjs { render :action => "create", :vote => @vote } - format.html { redirect_to([@voteable.user, @voteable]) } - format.xml { render :xml => @voteable, :status => :created, :location => @voteable } - else - format.rjs { render :action => "error" } - format.html { render :action => "new" } - format.xml { render :xml => @vote.errors, :status => :unprocessable_entity } - end - end - end - - # PUT /users/:id/votes/1 - # PUT /users/:id/votes/1.xml - def update - # Not generally used - end - - # DELETE /users/:id/votes/1 - # DELETE /users/:id/votes/1.xml - def destroy - @vote = Vote.find(params[:id]) - @vote.destroy - - respond_to do |format| - format.html { redirect_to(user_votes_url) } - format.xml { head :ok } - end - end - - private - def find_votes_for_my_scope - if params[:voteable_id] - @votes = Vote.for_voteable(Voteable.find(params[:voteable_id])).descending - elsif params[:user_id] - @votes = Vote.for_voter(User.find(params[:user_id])).descending - else - @votes = [] - end - end - - def must_own_vote - @vote ||= Vote.find(params[:id]) - @vote.user == current_user || ownership_violation - end - - def ownership_violation - respond_to do |format| - flash[:notice] = 'You cannot edit or delete votes that you do not own!' - format.html do - redirect_to user_path(current_user) - end - end - end - -end diff --git a/generators/vote_fu/templates/migration.rb b/generators/thumbs_up/templates/migration.rb similarity index 90% rename from generators/vote_fu/templates/migration.rb rename to generators/thumbs_up/templates/migration.rb index 62052c9..0dcfee6 100644 --- a/generators/vote_fu/templates/migration.rb +++ b/generators/thumbs_up/templates/migration.rb @@ -1,10 +1,10 @@ -class VoteFuMigration < ActiveRecord::Migration +class ThumbsUpMigration < ActiveRecord::Migration def self.up create_table :votes, :force => true do |t| t.boolean :vote, :default => false t.references :voteable, :polymorphic => true, :null => false t.references :voter, :polymorphic => true - t.timestamps + t.timestamps end add_index :votes, ["voter_id", "voter_type"], :name => "fk_voters" diff --git a/generators/thumbs_up/thumbs_up_generator.rb b/generators/thumbs_up/thumbs_up_generator.rb new file mode 100644 index 0000000..75b1c6c --- /dev/null +++ b/generators/thumbs_up/thumbs_up_generator.rb @@ -0,0 +1,11 @@ +class ThumbsUpGenerator < ActiveRecord::Generators::Base + + source_root File.expand_path("../templates", __FILE__) + + def manifest + record do |m| + m.directory File.join('db', 'migrate') + m.migration_template 'migration.rb', 'db/migrate', :migration_file_name => 'thumbs_up' + end + end +end diff --git a/generators/vote_fu/templates/vote.rb b/generators/vote_fu/templates/vote.rb deleted file mode 100644 index 5a8bf15..0000000 --- a/generators/vote_fu/templates/vote.rb +++ /dev/null @@ -1,16 +0,0 @@ -class Vote < ActiveRecord::Base - - named_scope :for_voter, lambda { |*args| {:conditions => ["voter_id = ? AND voter_type = ?", args.first.id, args.first.type.name]} } - named_scope :for_voteable, lambda { |*args| {:conditions => ["voteable_id = ? AND voteable_type = ?", args.first.id, args.first.type.name]} } - named_scope :recent, lambda { |*args| {:conditions => ["created_at > ?", (args.first || 2.weeks.ago).to_s(:db)]} } - named_scope :descending, :order => "created_at DESC" - - # NOTE: Votes belong to the "voteable" interface, and also to voters - belongs_to :voteable, :polymorphic => true - belongs_to :voter, :polymorphic => true - - attr_accessible :vote, :voter, :voteable - - # Uncomment this to limit users to a single vote on each item. - # validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id] -end \ No newline at end of file diff --git a/generators/vote_fu/vote_fu_generator.rb b/generators/vote_fu/vote_fu_generator.rb deleted file mode 100644 index e0b7e6c..0000000 --- a/generators/vote_fu/vote_fu_generator.rb +++ /dev/null @@ -1,11 +0,0 @@ -class VoteFuGenerator < Rails::Generator::Base - - def manifest - record do |m| - m.directory File.join('db', 'migrate') - m.migration_template 'migration.rb', 'db/migrate', :migration_file_name => 'vote_fu_migration' - m.directory File.join('app', 'models') - m.template 'vote.rb', File.join('app', 'models', 'vote.rb') - end - end -end diff --git a/lib/active_record/vote.rb b/lib/active_record/vote.rb new file mode 100644 index 0000000..d170643 --- /dev/null +++ b/lib/active_record/vote.rb @@ -0,0 +1,16 @@ +class Vote < ActiveRecord::Base + + scope :for_voter, lambda { |*args| where(:voter_id => args.first.id, :voter_type => args.first.type.name) } + scope :for_voteable, lambda { |*args| where(:voteable_id => args.first.id, :voteable_type => args.first.type.name) } + scope :recent, lambda { |*args| where('created_at', (args.first || 2.weeks.ago)) } + scope :descending, order('created_at DESC') + + belongs_to :voteable, :polymorphic => true + belongs_to :voter, :polymorphic => true + + attr_accessible :vote, :voter, :voteable + + # Comment out the line below if you want to allow multiple votes per voter. + validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id] + +end \ No newline at end of file diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index 9e0e80b..656d415 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -1,114 +1,86 @@ -# ActsAsVoteable -module Juixe - module Acts #:nodoc: - module Voteable #:nodoc: +module ThumbsUp + module ActsAsVoteable #:nodoc: - def self.included(base) - base.extend ClassMethods + def self.included(base) + base.extend ClassMethods + end + + module ClassMethods + def acts_as_voteable + has_many :votes, :as => :voteable, :dependent => :nullify + + include ThumbsUp::ActsAsVoteable::InstanceMethods + extend ThumbsUp::ActsAsVoteable::SingletonMethods end + end - module ClassMethods - def acts_as_voteable - has_many :votes, :as => :voteable, :dependent => :nullify + module SingletonMethods - include Juixe::Acts::Voteable::InstanceMethods - extend Juixe::Acts::Voteable::SingletonMethods - end + # Calculate the vote counts for all voteables of my type. + # This method returns all voteables with at least one vote. + # The vote count for each voteable is available as #vote_count. + # + # Options: + # :start_at - Restrict the votes to those created after a certain time + # :end_at - Restrict the votes to those created before a certain time + # :conditions - A piece of SQL conditions to add to the query + # :limit - The maximum number of voteables to return + # :order - A piece of SQL to order by. Eg 'vote_count DESC' or 'voteable.created_at DESC' + # :at_least - Item must have at least X votes + # :at_most - Item may not have more than X votes + def tally(*args) + options = args.extract_options! + t = self.where("#{Vote.table_name}.voteable_type = '#{self.name}'") + # We join so that you can order by columns on the voteable model. + t = t.joins("LEFT OUTER JOIN #{Vote.table_name} ON #{self.table_name}.#{self.primary_key} = #{Vote.table_name}.voteable_id") + t = t.having("vote_count > 0") + t = t.group("#{Vote.table_name}.voteable_id") + t = t.limit(options[:limit]) if options[:limit] + t = t.where("#{Vote.table_name}.created_at >= ?", options[:start_at]) if options[:start_at] + t = t.where("#{Vote.table_name}.created_at <= ?", options[:end_at]) if options[:end_at] + t = t.where(options[:conditions]) if options[:conditions] + t = options[:order] ? t.order(options[:order]) : t.order("vote_count DESC") + t = t.having(["vote_count >= ?", options[:at_least]]) if options[:at_least] + t = t.having(["vote_count <= ?", options[:at_most]]) if options[:at_most] + t.select("#{self.table_name}.*, COUNT(#{Vote.table_name}.voteable_id) AS vote_count") end - - # This module contains class methods - module SingletonMethods - - # Calculate the vote counts for all voteables of my type. - def tally(options = {}) - find(:all, options_for_tally(options.merge({:order =>"count DESC" }))) - end - # - # Options: - # :start_at - Restrict the votes to those created after a certain time - # :end_at - Restrict the votes to those created before a certain time - # :conditions - A piece of SQL conditions to add to the query - # :limit - The maximum number of voteables to return - # :order - A piece of SQL to order by. Eg 'votes.count desc' or 'voteable.created_at desc' - # :at_least - Item must have at least X votes - # :at_most - Item may not have more than X votes - def options_for_tally (options = {}) - options.assert_valid_keys :start_at, :end_at, :conditions, :at_least, :at_most, :order, :limit + end - scope = scope(:find) - start_at = sanitize_sql(["#{Vote.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at] - end_at = sanitize_sql(["#{Vote.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at] + module InstanceMethods - type_and_context = "#{Vote.table_name}.voteable_type = #{quote_value(base_class.name)}" + def votes_for + Vote.where(:voteable_id => id, :voteable_type => self.class.name, :vote => true).count + end - conditions = [ - type_and_context, - options[:conditions], - start_at, - end_at - ] + def votes_against + Vote.where(:voteable_id => id, :voteable_type => self.class.name, :vote => false).count + end - conditions = conditions.compact.join(' AND ') - conditions = merge_conditions(conditions, scope[:conditions]) if scope + # The difference between votes for and votes against for this particular instance. + # Note, this is different than the class method, which instead returns all votes + # for a particular voteable_type. + def tally + votes_for - votes_against + end - joins = ["LEFT OUTER JOIN #{Vote.table_name} ON #{table_name}.#{primary_key} = #{Vote.table_name}.voteable_id"] - joins << scope[:joins] if scope && scope[:joins] - at_least = sanitize_sql(["COUNT(#{Vote.table_name}.id) >= ?", options.delete(:at_least)]) if options[:at_least] - at_most = sanitize_sql(["COUNT(#{Vote.table_name}.id) <= ?", options.delete(:at_most)]) if options[:at_most] - having = [at_least, at_most].compact.join(' AND ') - group_by = "#{Vote.table_name}.voteable_id HAVING COUNT(#{Vote.table_name}.id) > 0" - group_by << " AND #{having}" unless having.blank? + def votes_count + self.votes.size + end - { :select => "#{table_name}.*, COUNT(#{Vote.table_name}.id) AS count", - :joins => joins.join(" "), - :conditions => conditions, - :group => group_by - }.update(options) - end + def voters_who_voted + self.votes.map(&:voter).uniq end - - # This module contains instance methods - module InstanceMethods - def votes_for - Vote.count(:all, :conditions => [ - "voteable_id = ? AND voteable_type = ? AND vote = ?", - id, self.class.name, true - ]) - end - - def votes_against - Vote.count(:all, :conditions => [ - "voteable_id = ? AND voteable_type = ? AND vote = ?", - id, self.class.name, false - ]) - end - - # Same as voteable.votes.size - def votes_count - self.votes.size - end - - def voters_who_voted - voters = [] - self.votes.each { |v| - voters << v.voter - } - voters - end - - def voted_by?(voter) - rtn = false - if voter - self.votes.each { |v| - rtn = true if (voter.id == v.voter_id && voter.class.name == v.voter_type) - } - end - rtn - end - - + + def voted_by?(voter) + 0 < Vote.where( + :voteable_id => self.id, + :voteable_type => self.class.name, + :voter_type => voter.class.name, + :voter_id => voter.id + ).count end + end end -end +end \ No newline at end of file diff --git a/lib/acts_as_voter.rb b/lib/acts_as_voter.rb index 0f872f4..791cc05 100644 --- a/lib/acts_as_voter.rb +++ b/lib/acts_as_voter.rb @@ -1,75 +1,84 @@ -# ActsAsVoter -module PeteOnRails - module Acts #:nodoc: - module Voter #:nodoc: +module ThumbsUp #:nodoc: + module ActsAsVoter #:nodoc: - def self.included(base) - base.extend ClassMethods + def self.included(base) + base.extend ClassMethods + end + + module ClassMethods + def acts_as_voter + + # If a voting entity is deleted, keep the votes. + # has_many :votes, :as => :voter, :dependent => :nullify + # Destroy votes when a user is deleted. + has_many :votes, :as => :voter, :dependent => :destroy + + include ThumbsUp::ActsAsVoter::InstanceMethods + extend ThumbsUp::ActsAsVoter::SingletonMethods end + end + + # This module contains class methods + module SingletonMethods + end + + # This module contains instance methods + module InstanceMethods + + # Usage user.vote_count(:up) # All +1 votes + # user.vote_count(:down) # All -1 votes + # user.vote_count() # All votes - module ClassMethods - def acts_as_voter - has_many :votes, :as => :voter, :dependent => :nullify # If a voting entity is deleted, keep the votes. - include PeteOnRails::Acts::Voter::InstanceMethods - extend PeteOnRails::Acts::Voter::SingletonMethods + def vote_count(for_or_against = :all) + v = Vote.where(:voter_id => id).where(:voter_type => self.class.name) + v = case for_or_against + when :all then v + when :up then v.where(:vote => true) + when :down then v.where(:vote => false) end + v.count end - - # This module contains class methods - module SingletonMethods + + def voted_for?(voteable) + voted_which_way?(voteable, :up) end - - # This module contains instance methods - module InstanceMethods - - # Usage user.vote_count(true) # All +1 votes - # user.vote_count(false) # All -1 votes - # user.vote_count() # All votes - - def vote_count(for_or_against = "all") - where = (for_or_against == "all") ? - ["voter_id = ? AND voter_type = ?", id, self.class.name ] : - ["voter_id = ? AND voter_type = ? AND vote = ?", id, self.class.name, for_or_against ] - - Vote.count(:all, :conditions => where) - end - - def voted_for?(voteable) - 0 < Vote.count(:all, :conditions => [ - "voter_id = ? AND voter_type = ? AND vote = ? AND voteable_id = ? AND voteable_type = ?", - self.id, self.class.name, true, voteable.id, voteable.class.name - ]) - end - - def voted_against?(voteable) - 0 < Vote.count(:all, :conditions => [ - "voter_id = ? AND voter_type = ? AND vote = ? AND voteable_id = ? AND voteable_type = ?", - self.id, self.class.name, false, voteable.id, voteable.class.name - ]) - end - - def voted_on?(voteable) - 0 < Vote.count(:all, :conditions => [ - "voter_id = ? AND voter_type = ? AND voteable_id = ? AND voteable_type = ?", - self.id, self.class.name, voteable.id, voteable.class.name - ]) - end - - def vote_for(voteable) - self.vote(voteable, true) - end - - def vote_against(voteable) - self.vote(voteable, false) - end + def voted_against?(voteable) + voted_which_way?(voteable, :down) + end - def vote(voteable, vote) - vote = Vote.new(:vote => vote, :voteable => voteable, :voter => self) - vote.save - end + def voted_on?(voteable) + 0 < Vote.where( + :voter_id => self.id, + :voter_type => self.class.name, + :voteable_id => voteable.id, + :voteable_type => voteable.class.name + ).count + end + + def vote_for(voteable) + self.vote(voteable, true) + end + def vote_against(voteable) + self.vote(voteable, false) end + + def vote(voteable, vote) + Vote.create!(:vote => vote, :voteable => voteable, :voter => self) + end + + def voted_which_way?(voteable, direction) + raise ArgumentError, "expected :up or :down" unless [:up, :down].include?(direction) + 0 < Vote.where( + :voter_id => self.id, + :voter_type => self.class.name, + :vote => direction == :up ? true : false, + :voteable_id => voteable.id, + :voteable_type => voteable.class.name + ).count + end + end end -end +end \ No newline at end of file diff --git a/lib/has_karma.rb b/lib/has_karma.rb deleted file mode 100644 index 53473bd..0000000 --- a/lib/has_karma.rb +++ /dev/null @@ -1,68 +0,0 @@ -# Has Karma - -module PeteOnRails - module VoteFu #:nodoc: - module Karma #:nodoc: - - def self.included(base) - base.extend ClassMethods - class << base - attr_accessor :karmatic_objects - end - end - - module ClassMethods - def has_karma(voteable_type) - self.class_eval <<-RUBY - def karma_voteable - #{voteable_type.to_s.classify} - end - RUBY - include PeteOnRails::VoteFu::Karma::InstanceMethods - extend PeteOnRails::VoteFu::Karma::SingletonMethods - if self.karmatic_objects.nil? - self.karmatic_objects = [eval(voteable_type.to_s.classify)] - else - self.karmatic_objects.push(eval(voteable_type.to_s.classify)) - end - end - end - - # This module contains class methods - module SingletonMethods - - ## Not yet implemented. Don't use it! - # Find the most popular users - def find_most_karmic - find(:all) - end - - end - - # This module contains instance methods - module InstanceMethods - def karma(options = {}) - #FIXME cannot have 2 models imapcting the karma simultaneously - # count the total number of votes on all of the voteable objects that are related to this object - #2009-01-30 GuillaumeNM The following line is not SQLite3 compatible, because boolean are stored as 'f' or 't', not '1', or '0' - #self.karma_voteable.sum(:vote, options_for_karma(options)) - #self.karma_voteable.find(:all, options_for_karma(options)).length - karma_value = 0 - self.class.karmatic_objects.each do |object| - karma_value += object.find(:all, options_for_karma(object, options)).length - end - return karma_value - end - - def options_for_karma (object, options = {}) - #GuillaumeNM : 2009-01-30 Adding condition for SQLite3 - conditions = ["u.id = ? AND vote = ?" , self[:id] , true] - joins = ["inner join votes v on #{object.table_name}.id = v.voteable_id", "inner join #{self.class.table_name} u on u.id = #{object.name.tableize}.#{self.class.name.foreign_key}"] - { :joins => joins.join(" "), :conditions => conditions }.update(options) - end - - end - - end - end -end diff --git a/lib/thumbs_up.rb b/lib/thumbs_up.rb new file mode 100644 index 0000000..346bd5f --- /dev/null +++ b/lib/thumbs_up.rb @@ -0,0 +1,7 @@ +autoload :Vote, 'active_record/vote' + +require 'acts_as_voteable' +require 'acts_as_voter' + +ActiveRecord::Base.send(:include, ThumbsUp::ActsAsVoteable) +ActiveRecord::Base.send(:include, ThumbsUp::ActsAsVoter) \ No newline at end of file diff --git a/lib/vote_fu.rb b/lib/vote_fu.rb deleted file mode 100644 index cdc97a7..0000000 --- a/lib/vote_fu.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'acts_as_voteable' -require 'acts_as_voter' -require 'has_karma' - -ActiveRecord::Base.send(:include, Juixe::Acts::Voteable) -ActiveRecord::Base.send(:include, PeteOnRails::Acts::Voter) -ActiveRecord::Base.send(:include, PeteOnRails::VoteFu::Karma) diff --git a/objectreload-vote_fu.gemspec b/objectreload-vote_fu.gemspec deleted file mode 100644 index d9fe4e7..0000000 --- a/objectreload-vote_fu.gemspec +++ /dev/null @@ -1,68 +0,0 @@ -# Generated by jeweler -# DO NOT EDIT THIS FILE DIRECTLY -# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command -# -*- encoding: utf-8 -*- - -Gem::Specification.new do |s| - s.name = %q{objectreload-vote_fu} - s.version = "0.1.2" - - s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= - s.authors = ["Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnętrzak"] - s.date = %q{2010-07-22} - s.description = %q{VoteFu provides the ability to have multiple voting entities on an arbitrary number of models in ActiveRecord.} - s.email = %q{gems@objectreload.com} - s.extra_rdoc_files = [ - "README.markdown" - ] - s.files = [ - ".gitignore", - "CHANGELOG.markdown", - "MIT-LICENSE", - "README.markdown", - "Rakefile", - "VERSION", - "examples/routes.rb", - "examples/users_controller.rb", - "examples/voteable.html.erb", - "examples/voteable.rb", - "examples/voteables_controller.rb", - "examples/votes/_voteable_vote.html.erb", - "examples/votes/create.rjs", - "examples/votes_controller.rb", - "generators/vote_fu/templates/migration.rb", - "generators/vote_fu/templates/vote.rb", - "generators/vote_fu/vote_fu_generator.rb", - "lib/acts_as_voteable.rb", - "lib/acts_as_voter.rb", - "lib/has_karma.rb", - "lib/vote_fu.rb", - "objectreload-vote_fu.gemspec", - "rails/init.rb", - "test/vote_fu_test.rb" - ] - s.homepage = %q{http://github.com/objectreload/vote_fu} - s.rdoc_options = ["--charset=UTF-8"] - s.require_paths = ["lib"] - s.rubygems_version = %q{1.3.7} - s.summary = %q{Voting for ActiveRecord with multiple vote sources and advanced features.} - s.test_files = [ - "test/vote_fu_test.rb", - "examples/routes.rb", - "examples/users_controller.rb", - "examples/voteable.rb", - "examples/voteables_controller.rb", - "examples/votes_controller.rb" - ] - - if s.respond_to? :specification_version then - current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION - s.specification_version = 3 - - if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then - else - end - else - end -end - diff --git a/test/vote_fu_test.rb b/test/vote_fu_test.rb deleted file mode 100644 index 83bc088..0000000 --- a/test/vote_fu_test.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'test/unit' - -class VoteFuTest < Test::Unit::TestCase - # Replace this with your real tests. - def test_this_plugin - flunk - end -end diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec new file mode 100644 index 0000000..399ad4b --- /dev/null +++ b/thumbs_up.gemspec @@ -0,0 +1,49 @@ +# Generated by jeweler +# DO NOT EDIT THIS FILE DIRECTLY +# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = %q{thumbs_up} + s.version = "0.2.0" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wn\304\231trzak", "Brady Bouchard"] + s.date = %q{2010-08-03} + s.description = %q{ThumbsUp provides the ability to have multiple voting entities on an arbitrary number of models in ActiveRecord.} + s.email = %q{brady@ldawn.com} + s.extra_rdoc_files = [ + "README.markdown" + ] + s.files = [ + "CHANGELOG.markdown", + "MIT-LICENSE", + "README.markdown", + "Rakefile", + "VERSION", + "generators/thumbs_up/templates/migration.rb", + "generators/thumbs_up/thumbs_up_generator.rb", + "lib/active_record/vote.rb", + "lib/acts_as_voteable.rb", + "lib/acts_as_voter.rb", + "lib/has_karma.rb", + "lib/thumbs_up.rb", + "rails/init.rb" + ] + s.homepage = %q{http://github.com/brady8/thumbs_up} + s.rdoc_options = ["--charset=UTF-8"] + s.require_paths = ["lib"] + s.rubygems_version = %q{1.3.7} + s.summary = %q{Voting for ActiveRecord with multiple vote sources and advanced features.} + + if s.respond_to? :specification_version then + current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION + s.specification_version = 3 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + else + end + else + end +end + From 7aa70b194aff56f0b5cbf900db48b169f7229a74 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Wed, 4 Aug 2010 07:40:50 +1000 Subject: [PATCH 008/138] Fixed karma. Fixed generators for Rails 3. Fixed depreciations. All code updated for Rails 3. --- CHANGELOG.markdown | 39 +---------------- README.markdown | 39 +++++++++-------- Rakefile | 6 +-- VERSION | 2 +- generators/thumbs_up/thumbs_up_generator.rb | 11 ----- lib/active_record/vote.rb | 16 ------- lib/acts_as_voteable.rb | 7 ++-- .../thumbs_up/templates/migration.rb | 4 +- lib/generators/thumbs_up/templates/vote.rb | 16 +++++++ .../thumbs_up/thumbs_up_generator.rb | 27 ++++++++++++ lib/has_karma.rb | 42 +++++++++++++++++++ lib/thumbs_up.rb | 6 +-- rails/init.rb | 4 +- thumbs_up.gemspec | 19 +++++---- 14 files changed, 132 insertions(+), 106 deletions(-) delete mode 100644 generators/thumbs_up/thumbs_up_generator.rb delete mode 100644 lib/active_record/vote.rb rename {generators => lib/generators}/thumbs_up/templates/migration.rb (68%) create mode 100644 lib/generators/thumbs_up/templates/vote.rb create mode 100644 lib/generators/thumbs_up/thumbs_up_generator.rb create mode 100644 lib/has_karma.rb diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index b3c7965..a147679 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -3,40 +3,5 @@ * Renamed to ThumbsUp from vote\_fu. * Updated for Rails 3, using ActiveRecord/Arel. * Cleaned up some dead code, some shitty code, and made a few methods take up quite a lot less memory and time (voters\_who\_voted). -* Removed some shitty example code - this gem is self-explanatory and straight-forward as-is. - -2010-02-04 -========== -* Remove vote.rb and votes_controller.rb from gem lib - -2009-02-11 -========== -* Merge in xlash's bugfix for PostgreSQL and his has\_karma patch for multi-model support. - -2008-12-02 -========== -* Merge in maddox's README typo fix and his ActiveSupport.Dependency patch -* Merge in nagybence's updates that make the code usable as a Gem in addition to being a Rails plugin. -* Thanks for the bugfixes and proofreading, nagybence and maddox! -* Updated the gemplugin support to be compatible with maddox and nagybence's changes. -* Added details on the MyQuotable reference application. - -2008-07-20 -========== -* Protect against mass assignment misvotes using attr\_accessible -* Update acts\_as mixins to use self.class.name instead of the deprecated self.type.name - -2008-07-15 -========== -* Added examples directory -* Changed this file to markdown format for GitHub goodness -* Added a commented out unique index in the migration generator for "one person, one vote" -* Removed votes\_controller.rb from lib/ and moved to examples - -2008-07-10 -========== - -* Added a generator class for the migration. -* Implemented rails/init.rb -* Implemented capability to use any model as the initiator of votes. -* Implemented acts\_as\_voter methods. +* Removed example code. +* Fixed karma. \ No newline at end of file diff --git a/README.markdown b/README.markdown index 6e93cfe..77b1186 100644 --- a/README.markdown +++ b/README.markdown @@ -1,13 +1,15 @@ ThumbsUp ======= +A ridiculously straightforward and simple package 'o' code to enable voting in your application, a la stackoverflow.com, etc. Allows an arbitrary number of entities (users, etc.) to vote on models. ### Mixins -This plugin introduces two mixins to your recipe book: +This plugin introduces three mixins to your recipe book: 1. **acts\_as\_voteable** : Intended for content objects like Posts, Comments, etc. 2. **acts\_as\_voter** : Intended for voting entities, like Users. +3. **has\_karma** : Adds some helpers to acts\_as\_voter models for calculating karma. ### Inspiration @@ -30,9 +32,13 @@ Usage ## Getting Started -### Turn your AR model into something that can be voted upon. +### Turn your AR models into something that can be voted upon. - class Model < ActiveRecord::Base + class SomeModel < ActiveRecord::Base + acts_as_voteable + end + + class Question < ActiveRecord::Base acts_as_voteable end @@ -40,6 +46,10 @@ Usage class User < ActiveRecord::Base acts_as_voter + # The following line is optional, and tracks karma (up votes) for questions this user has submitted. + # Each question has a submitter_id column that tracks the user who submitted it. + # You can track any voteable model. + has_karma(:questions, :as => :submitter) end class Robot < ActiveRecord::Base @@ -85,23 +95,16 @@ This will select the Items with between 1 and 10,000 votes, the votes having bee :at_most - Item may not have more than X votes #### Lower level queries -ActiveRecord models that act as voteable can be queried for the positive votes, negative votes, and a total vote count by using the votes\_for, votes\_against, and votes\_count methods respectively. Here is an example: - - positiveVoteCount = m.votes_for - negativeVoteCount = m.votes_against - totalVoteCount = m.votes_count - -And because the Vote Fu plugin will add the has_many votes relationship to your model you can always get all the votes by using the votes property: - - allVotes = m.votes -The mixin also provides these methods: + positiveVoteCount = voteable.votes_for + negativeVoteCount = voteable.votes_against + plusminus = voteable.plusminus # Votes for minus votes against. - voter.voted_for?(voteable) # True if the voter voted for this object. - voter.vote_count(:up | :down | :all) # returns the count of +1, -1, or all votes + voter.voted_for?(voteable) # True if the voter voted for this object. + voter.vote_count(:up | :down | :all) # returns the count of +1, -1, or all votes - voteable.voted_by?(voter) # True if the voter voted for this object. - @voters = voteable.voters_who_voted + voteable.voted_by?(voter) # True if the voter voted for this object. + @voters = voteable.voters_who_voted ### One vote per user! @@ -120,4 +123,4 @@ ThumbsUp by default only allows one vote per user. This can be changed by removi Credits ======= -Basic structure and a good chunk of code is taken from Peter Jackson's work on ActsAsVoteable. \ No newline at end of file +Basic scaffold is from Peter Jackson's work on VoteFu / ActsAsVoteable. All code updated for Rails 3, cleaned up for speed and clarity, karma calculation fixed, and (hopefully) zero introduced bugs. \ No newline at end of file diff --git a/Rakefile b/Rakefile index c2cba2c..0befb23 100644 --- a/Rakefile +++ b/Rakefile @@ -6,11 +6,11 @@ begin require 'jeweler' Jeweler::Tasks.new do |gem| gem.name = "thumbs_up" - gem.summary = "Voting for ActiveRecord with multiple vote sources and advanced features." - gem.description = "ThumbsUp provides the ability to have multiple voting entities on an arbitrary number of models in ActiveRecord." + gem.summary = "Voting for ActiveRecord with multiple vote sources and karma calculation." + gem.description = "ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com." gem.email = "brady@ldawn.com" gem.homepage = "http://github.com/brady8/thumbs_up" - gem.authors = ["Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnętrzak", "Brady Bouchard"] + gem.authors = ["Brady Bouchard", "Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnętrzak"] # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings end Jeweler::GemcutterTasks.new diff --git a/VERSION b/VERSION index 0ea3a94..0c62199 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.0 +0.2.1 diff --git a/generators/thumbs_up/thumbs_up_generator.rb b/generators/thumbs_up/thumbs_up_generator.rb deleted file mode 100644 index 75b1c6c..0000000 --- a/generators/thumbs_up/thumbs_up_generator.rb +++ /dev/null @@ -1,11 +0,0 @@ -class ThumbsUpGenerator < ActiveRecord::Generators::Base - - source_root File.expand_path("../templates", __FILE__) - - def manifest - record do |m| - m.directory File.join('db', 'migrate') - m.migration_template 'migration.rb', 'db/migrate', :migration_file_name => 'thumbs_up' - end - end -end diff --git a/lib/active_record/vote.rb b/lib/active_record/vote.rb deleted file mode 100644 index d170643..0000000 --- a/lib/active_record/vote.rb +++ /dev/null @@ -1,16 +0,0 @@ -class Vote < ActiveRecord::Base - - scope :for_voter, lambda { |*args| where(:voter_id => args.first.id, :voter_type => args.first.type.name) } - scope :for_voteable, lambda { |*args| where(:voteable_id => args.first.id, :voteable_type => args.first.type.name) } - scope :recent, lambda { |*args| where('created_at', (args.first || 2.weeks.ago)) } - scope :descending, order('created_at DESC') - - belongs_to :voteable, :polymorphic => true - belongs_to :voter, :polymorphic => true - - attr_accessible :vote, :voter, :voteable - - # Comment out the line below if you want to allow multiple votes per voter. - validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id] - -end \ No newline at end of file diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index 656d415..a1fc151 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -57,10 +57,9 @@ def votes_against Vote.where(:voteable_id => id, :voteable_type => self.class.name, :vote => false).count end - # The difference between votes for and votes against for this particular instance. - # Note, this is different than the class method, which instead returns all votes - # for a particular voteable_type. - def tally + # You'll probably want to use this method to display how 'good' a particular voteable + # is, and/or sort based on it. + def plusminus votes_for - votes_against end diff --git a/generators/thumbs_up/templates/migration.rb b/lib/generators/thumbs_up/templates/migration.rb similarity index 68% rename from generators/thumbs_up/templates/migration.rb rename to lib/generators/thumbs_up/templates/migration.rb index 0dcfee6..26c3304 100644 --- a/generators/thumbs_up/templates/migration.rb +++ b/lib/generators/thumbs_up/templates/migration.rb @@ -10,8 +10,8 @@ def self.up add_index :votes, ["voter_id", "voter_type"], :name => "fk_voters" add_index :votes, ["voteable_id", "voteable_type"], :name => "fk_voteables" - # If you want to enfore "One Person, One Vote" rules in the database, uncomment the index below - # add_index :votes, ["voter_id", "voter_type", "voteable_id", "voteable_type"], :unique => true, :name => "uniq_one_vote_only" + # If you don't want to enforce "One Person, One Vote" rules in the database, comment out the index below. + add_index :votes, ["voter_id", "voter_type", "voteable_id", "voteable_type"], :unique => true, :name => "uniq_one_vote_only" end def self.down diff --git a/lib/generators/thumbs_up/templates/vote.rb b/lib/generators/thumbs_up/templates/vote.rb new file mode 100644 index 0000000..7b6f58d --- /dev/null +++ b/lib/generators/thumbs_up/templates/vote.rb @@ -0,0 +1,16 @@ +class Vote < ActiveRecord::Base + + scope :for_voter, lambda { |*args| where(["voter_id = ? AND voter_type = ?", args.first.id, args.first.class.name]) } + scope :for_voteable, lambda { |*args| where(["voteable_id = ? AND voteable_type = ?", args.first.id, args.first.class.name]) } + scope :recent, lambda { |*args| where(["created_at > ?", (args.first || 2.weeks.ago)]) } + scope :descending, order("created_at DESC") + + belongs_to :voteable, :polymorphic => true + belongs_to :voter, :polymorphic => true + + attr_accessible :vote, :voter, :voteable + + # Comment out the line below to allow multiple votes per user. + validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id] + +end \ No newline at end of file diff --git a/lib/generators/thumbs_up/thumbs_up_generator.rb b/lib/generators/thumbs_up/thumbs_up_generator.rb new file mode 100644 index 0000000..ebcead6 --- /dev/null +++ b/lib/generators/thumbs_up/thumbs_up_generator.rb @@ -0,0 +1,27 @@ +require 'rails/generators/active_record' + +class ThumbsUpGenerator < Rails::Generators::Base + + include Rails::Generators::Migration + + source_root File.expand_path('../templates', __FILE__) + + # Implement the required interface for Rails::Generators::Migration. + def self.next_migration_number(dirname) #:nodoc: + next_migration_number = current_migration_number(dirname) + 1 + if ActiveRecord::Base.timestamped_migrations + [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max + else + "%.3d" % next_migration_number + end + end + + def create_migration + migration_template 'migration.rb', File.join('db', 'migrate', 'thumbs_up_migration.rb') + end + + def move_vote_model + template 'vote.rb', File.join('app', 'models', 'vote.rb') + end + +end \ No newline at end of file diff --git a/lib/has_karma.rb b/lib/has_karma.rb new file mode 100644 index 0000000..84da880 --- /dev/null +++ b/lib/has_karma.rb @@ -0,0 +1,42 @@ +module ThumbsUp #:nodoc: + module Karma #:nodoc: + + def self.included(base) + base.extend ClassMethods + class << base + attr_accessor :karmic_objects + end + end + + module ClassMethods + def has_karma(voteable_type, options = {}) + include ThumbsUp::Karma::InstanceMethods + extend ThumbsUp::Karma::SingletonMethods + self.karmic_objects ||= {} + self.karmic_objects[voteable_type.to_s.classify.constantize] = (options[:as] ? options[:as].to_s.foreign_key : self.class.name.foreign_key) + end + end + + module SingletonMethods + + ## Not yet implemented. Don't use it! + # Find the most popular users + def find_most_karmic + find(:all) + end + + end + + module InstanceMethods + def karma(options = {}) + self.class.karmic_objects.collect do |object, fk| + v = object.where(["#{Vote.table_name}.vote = ?", true]).where(["#{self.class.table_name}.#{self.class.primary_key} = ?", self.id]) + v = v.joins("INNER JOIN #{Vote.table_name} ON #{Vote.table_name}.voteable_type = '#{object.to_s}' AND #{Vote.table_name}.voteable_id = #{object.table_name}.#{object.primary_key}") + v = v.joins("INNER JOIN #{self.class.table_name} ON #{self.class.table_name}.#{self.class.primary_key} = #{object.table_name}.#{fk}") + v.count + end.sum + end + end + + end +end diff --git a/lib/thumbs_up.rb b/lib/thumbs_up.rb index 346bd5f..b3d1f8a 100644 --- a/lib/thumbs_up.rb +++ b/lib/thumbs_up.rb @@ -1,7 +1,7 @@ -autoload :Vote, 'active_record/vote' - require 'acts_as_voteable' require 'acts_as_voter' +require 'has_karma' ActiveRecord::Base.send(:include, ThumbsUp::ActsAsVoteable) -ActiveRecord::Base.send(:include, ThumbsUp::ActsAsVoter) \ No newline at end of file +ActiveRecord::Base.send(:include, ThumbsUp::ActsAsVoter) +ActiveRecord::Base.send(:include, ThumbsUp::Karma) \ No newline at end of file diff --git a/rails/init.rb b/rails/init.rb index 7cbe35b..87b4e50 100644 --- a/rails/init.rb +++ b/rails/init.rb @@ -1,4 +1,4 @@ -RAILS_DEFAULT_LOGGER.info "** vote_fu: setting up load paths" +RAILS_DEFAULT_LOGGER.info "** thumbs_up: setting up load paths **" %w{ models controllers helpers }.each do |dir| path = File.join(File.dirname(__FILE__) , 'lib', dir) @@ -7,4 +7,4 @@ ActiveSupport::Dependencies.load_once_paths.delete(path) end -require 'vote_fu' \ No newline at end of file +require 'thumbs_up' \ No newline at end of file diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index 399ad4b..30ad4af 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -5,12 +5,12 @@ Gem::Specification.new do |s| s.name = %q{thumbs_up} - s.version = "0.2.0" + s.version = "0.2.1" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= - s.authors = ["Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wn\304\231trzak", "Brady Bouchard"] - s.date = %q{2010-08-03} - s.description = %q{ThumbsUp provides the ability to have multiple voting entities on an arbitrary number of models in ActiveRecord.} + s.authors = ["Brady Bouchard", "Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wn\304\231trzak"] + s.date = %q{2010-08-04} + s.description = %q{ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com.} s.email = %q{brady@ldawn.com} s.extra_rdoc_files = [ "README.markdown" @@ -21,20 +21,21 @@ Gem::Specification.new do |s| "README.markdown", "Rakefile", "VERSION", - "generators/thumbs_up/templates/migration.rb", - "generators/thumbs_up/thumbs_up_generator.rb", - "lib/active_record/vote.rb", "lib/acts_as_voteable.rb", "lib/acts_as_voter.rb", + "lib/generators/thumbs_up/templates/migration.rb", + "lib/generators/thumbs_up/templates/vote.rb", + "lib/generators/thumbs_up/thumbs_up_generator.rb", "lib/has_karma.rb", "lib/thumbs_up.rb", - "rails/init.rb" + "rails/init.rb", + "thumbs_up.gemspec" ] s.homepage = %q{http://github.com/brady8/thumbs_up} s.rdoc_options = ["--charset=UTF-8"] s.require_paths = ["lib"] s.rubygems_version = %q{1.3.7} - s.summary = %q{Voting for ActiveRecord with multiple vote sources and advanced features.} + s.summary = %q{Voting for ActiveRecord with multiple vote sources and karma calculation.} if s.respond_to? :specification_version then current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION From 8626ede0588b5751ab5900a65efc286f79aa7e96 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Wed, 4 Aug 2010 08:09:33 +1000 Subject: [PATCH 009/138] Version bump to 0.2.2 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 0c62199..ee1372d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.1 +0.2.2 From fd7211756e2b2d81696fff44ee50348fcf252a1d Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Fri, 6 Aug 2010 13:19:51 +1000 Subject: [PATCH 010/138] Added vote_exclusively_for and vote_exclusively_against. --- .gitignore | 1 + README.markdown | 9 ++++++--- lib/acts_as_voter.rb | 26 ++++++++++++++++++++++---- thumbs_up.gemspec | 6 +++--- 4 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c111b33 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.gem diff --git a/README.markdown b/README.markdown index 77b1186..00ef5e9 100644 --- a/README.markdown +++ b/README.markdown @@ -59,9 +59,12 @@ Usage ### To cast a vote for a Model you can do the following: #### Shorthand syntax - voter.vote_for(voteable) # Adds a +1 vote - voter.vote_against(voteable) # Adds a -1 vote - voter.vote(voteable, vote) # Adds either a +1 or -1 vote: vote => true (+1), vote => false (-1) + voter.vote_for(voteable) # Adds a +1 vote + voter.vote_against(voteable) # Adds a -1 vote + voter.vote(voteable, vote) # Adds either a +1 or -1 vote: vote => true (+1), vote => false (-1) + + voter.vote_exclusively_for(voteable) # Removes any previous votes by that particular voter, and votes for. + voter.vote_exclusively_for(voteable) # Removes any previous votes by that particular voter, and votes against. ### Querying votes diff --git a/lib/acts_as_voter.rb b/lib/acts_as_voter.rb index 791cc05..82fd854 100644 --- a/lib/acts_as_voter.rb +++ b/lib/acts_as_voter.rb @@ -57,15 +57,33 @@ def voted_on?(voteable) end def vote_for(voteable) - self.vote(voteable, true) + self.vote(voteable, { :direction => :up, :exclusive => false }) end def vote_against(voteable) - self.vote(voteable, false) + self.vote(voteable, { :direction => :down, :exclusive => false }) end - def vote(voteable, vote) - Vote.create!(:vote => vote, :voteable => voteable, :voter => self) + def vote_exclusively_for(voteable) + self.vote(voteable, { :direction => :up, :exclusive => true }) + end + + def vote_exclusively_against(voteable) + self.vote(voteable, { :direction => :down, :exclusive => true }) + end + + def vote(voteable, options = {}) + raise ArgumentError "you must specify :up or :down in order to vote" unless options[:direction] && [:up, :down].include?(options[:direction].to_sym) + if options[:exclusive] + Vote.where( + :voter_id => self.id, + :voter_type => self.class.name, + :voteable_id => voteable.id, + :voteable_type => voteable.class.name + ).map(&:destroy) + end + direction = (options[:direction].to_sym == :up ? true : false) + Vote.create!(:vote => direction, :voteable => voteable, :voter => self) end def voted_which_way?(voteable, direction) diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index 30ad4af..a6243c3 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -5,11 +5,11 @@ Gem::Specification.new do |s| s.name = %q{thumbs_up} - s.version = "0.2.1" + s.version = "0.2.2" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= - s.authors = ["Brady Bouchard", "Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wn\304\231trzak"] - s.date = %q{2010-08-04} + s.authors = ["Brady Bouchard", "Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnętrzak"] + s.date = %q{2010-08-06} s.description = %q{ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com.} s.email = %q{brady@ldawn.com} s.extra_rdoc_files = [ From d34dc5f060e95defd41512541c51916893c6d9af Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Fri, 6 Aug 2010 13:21:08 +1000 Subject: [PATCH 011/138] Version bump to 0.2.3 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index ee1372d..7179039 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.2 +0.2.3 From 65dc8c64f4c27245041aa463dc794978fa25c6d4 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Sat, 28 Aug 2010 00:32:25 +1000 Subject: [PATCH 012/138] Added :weight option to specify a multiplier when calculating karma. Added :exclusive option for voting, to first clear the voteable model instance of any previous votes (up or down) by a voter. --- README.markdown | 3 ++- lib/acts_as_voter.rb | 16 ++++++++++------ lib/has_karma.rb | 8 ++++---- thumbs_up.gemspec | 5 +++-- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/README.markdown b/README.markdown index 00ef5e9..dc908bc 100644 --- a/README.markdown +++ b/README.markdown @@ -48,8 +48,9 @@ Usage acts_as_voter # The following line is optional, and tracks karma (up votes) for questions this user has submitted. # Each question has a submitter_id column that tracks the user who submitted it. + # The option :weight value will be multiplied to any karma from that voteable model (defaults to 1). # You can track any voteable model. - has_karma(:questions, :as => :submitter) + has_karma(:questions, :as => :submitter, :weight => 0.5) end class Robot < ActiveRecord::Base diff --git a/lib/acts_as_voter.rb b/lib/acts_as_voter.rb index 82fd854..0b4beb8 100644 --- a/lib/acts_as_voter.rb +++ b/lib/acts_as_voter.rb @@ -75,17 +75,21 @@ def vote_exclusively_against(voteable) def vote(voteable, options = {}) raise ArgumentError "you must specify :up or :down in order to vote" unless options[:direction] && [:up, :down].include?(options[:direction].to_sym) if options[:exclusive] - Vote.where( - :voter_id => self.id, - :voter_type => self.class.name, - :voteable_id => voteable.id, - :voteable_type => voteable.class.name - ).map(&:destroy) + self.clear_votes(voteable) end direction = (options[:direction].to_sym == :up ? true : false) Vote.create!(:vote => direction, :voteable => voteable, :voter => self) end + def clear_votes(voteable) + Vote.where( + :voter_id => self.id, + :voter_type => self.class.name, + :voteable_id => voteable.id, + :voteable_type => voteable.class.name + ).map(&:destroy) + end + def voted_which_way?(voteable, direction) raise ArgumentError, "expected :up or :down" unless [:up, :down].include?(direction) 0 < Vote.where( diff --git a/lib/has_karma.rb b/lib/has_karma.rb index 84da880..2464226 100644 --- a/lib/has_karma.rb +++ b/lib/has_karma.rb @@ -13,7 +13,7 @@ def has_karma(voteable_type, options = {}) include ThumbsUp::Karma::InstanceMethods extend ThumbsUp::Karma::SingletonMethods self.karmic_objects ||= {} - self.karmic_objects[voteable_type.to_s.classify.constantize] = (options[:as] ? options[:as].to_s.foreign_key : self.class.name.foreign_key) + self.karmic_objects[voteable_type.to_s.classify.constantize] = [ (options[:as] ? options[:as].to_s.foreign_key : self.name.foreign_key), (options[:weight] ? options[:weight] : 1).to_f ] end end @@ -29,11 +29,11 @@ def find_most_karmic module InstanceMethods def karma(options = {}) - self.class.karmic_objects.collect do |object, fk| + self.class.karmic_objects.collect do |object, attr| v = object.where(["#{Vote.table_name}.vote = ?", true]).where(["#{self.class.table_name}.#{self.class.primary_key} = ?", self.id]) v = v.joins("INNER JOIN #{Vote.table_name} ON #{Vote.table_name}.voteable_type = '#{object.to_s}' AND #{Vote.table_name}.voteable_id = #{object.table_name}.#{object.primary_key}") - v = v.joins("INNER JOIN #{self.class.table_name} ON #{self.class.table_name}.#{self.class.primary_key} = #{object.table_name}.#{fk}") - v.count + v = v.joins("INNER JOIN #{self.class.table_name} ON #{self.class.table_name}.#{self.class.primary_key} = #{object.table_name}.#{attr[0]}") + (v.count.to_f * attr[1]).round end.sum end end diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index a6243c3..d664112 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -5,7 +5,7 @@ Gem::Specification.new do |s| s.name = %q{thumbs_up} - s.version = "0.2.2" + s.version = "0.2.3" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Brady Bouchard", "Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnętrzak"] @@ -16,7 +16,8 @@ Gem::Specification.new do |s| "README.markdown" ] s.files = [ - "CHANGELOG.markdown", + ".gitignore", + "CHANGELOG.markdown", "MIT-LICENSE", "README.markdown", "Rakefile", From 81973a46d25239573c21c4269d97dda991b6772f Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Sat, 28 Aug 2010 00:34:55 +1000 Subject: [PATCH 013/138] Version bump to 0.3.0 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 7179039..0d91a54 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.3 +0.3.0 From a516ccbe36fd9835addbe34974c7f3adea712fda Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Sat, 28 Aug 2010 00:38:47 +1000 Subject: [PATCH 014/138] Regen gemspec. --- thumbs_up.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index d664112..20bc4f9 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -5,11 +5,11 @@ Gem::Specification.new do |s| s.name = %q{thumbs_up} - s.version = "0.2.3" + s.version = "0.3.0" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Brady Bouchard", "Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnętrzak"] - s.date = %q{2010-08-06} + s.date = %q{2010-08-28} s.description = %q{ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com.} s.email = %q{brady@ldawn.com} s.extra_rdoc_files = [ From 2475814796230690a1d9f82ebb9171366852637d Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Mon, 6 Sep 2010 07:55:31 +1000 Subject: [PATCH 015/138] Voteables should destroy their votes when they are removed. --- lib/acts_as_voteable.rb | 2 +- lib/acts_as_voter.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index a1fc151..7f24724 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -7,7 +7,7 @@ def self.included(base) module ClassMethods def acts_as_voteable - has_many :votes, :as => :voteable, :dependent => :nullify + has_many :votes, :as => :voteable, :dependent => :destroy include ThumbsUp::ActsAsVoteable::InstanceMethods extend ThumbsUp::ActsAsVoteable::SingletonMethods diff --git a/lib/acts_as_voter.rb b/lib/acts_as_voter.rb index 0b4beb8..64573ae 100644 --- a/lib/acts_as_voter.rb +++ b/lib/acts_as_voter.rb @@ -9,6 +9,8 @@ module ClassMethods def acts_as_voter # If a voting entity is deleted, keep the votes. + # If you want to nullify (and keep the votes), you'll need to remove + # the unique constraint on the [ voter, voteable ] index in the database. # has_many :votes, :as => :voter, :dependent => :nullify # Destroy votes when a user is deleted. has_many :votes, :as => :voter, :dependent => :destroy From 37d072ca423ce0fa50cbf3cc633478ed2b39fb28 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Mon, 6 Sep 2010 08:02:36 +1000 Subject: [PATCH 016/138] Version bump to 0.3.1 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 0d91a54..9e11b32 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.0 +0.3.1 From c0a9b664ef919032fe018ef62ef9d9b3655c546a Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Mon, 6 Sep 2010 08:03:32 +1000 Subject: [PATCH 017/138] Updated gemspec. --- .gitignore | 1 + thumbs_up.gemspec | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index c111b33..6992551 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.gem +pkg/* diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index 20bc4f9..09c54b1 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -5,11 +5,11 @@ Gem::Specification.new do |s| s.name = %q{thumbs_up} - s.version = "0.3.0" + s.version = "0.3.1" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Brady Bouchard", "Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnętrzak"] - s.date = %q{2010-08-28} + s.date = %q{2010-09-06} s.description = %q{ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com.} s.email = %q{brady@ldawn.com} s.extra_rdoc_files = [ From 848bb601484ddb19d0dbdcbbd76c4def63749678 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Fri, 24 Sep 2010 19:08:11 +1000 Subject: [PATCH 018/138] Don't need to be so explicit with the conditionals. --- lib/acts_as_voter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/acts_as_voter.rb b/lib/acts_as_voter.rb index 64573ae..9516a43 100644 --- a/lib/acts_as_voter.rb +++ b/lib/acts_as_voter.rb @@ -79,7 +79,7 @@ def vote(voteable, options = {}) if options[:exclusive] self.clear_votes(voteable) end - direction = (options[:direction].to_sym == :up ? true : false) + direction = (options[:direction].to_sym == :up) Vote.create!(:vote => direction, :voteable => voteable, :voter => self) end From ce806bd41067a005e89c7ab5abf89bc5a4be96f3 Mon Sep 17 00:00:00 2001 From: Michael McQuinn Date: Tue, 9 Nov 2010 16:54:00 -0500 Subject: [PATCH 019/138] Make tally query Postgres friendly --- lib/acts_as_voteable.rb | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index 7f24724..5c3c206 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -30,21 +30,31 @@ module SingletonMethods # :at_most - Item may not have more than X votes def tally(*args) options = args.extract_options! + + # Use the explicit SQL statement throughout for Postgresql compatibility. + vote_count = "COUNT(#{Vote.table_name}.voteable_id)" + t = self.where("#{Vote.table_name}.voteable_type = '#{self.name}'") + # We join so that you can order by columns on the voteable model. t = t.joins("LEFT OUTER JOIN #{Vote.table_name} ON #{self.table_name}.#{self.primary_key} = #{Vote.table_name}.voteable_id") - t = t.having("vote_count > 0") - t = t.group("#{Vote.table_name}.voteable_id") + + t = t.having("#{vote_count} > 0") + t = t.group("#{Vote.table_name}.voteable_id, #{column_names_for_tally}") t = t.limit(options[:limit]) if options[:limit] t = t.where("#{Vote.table_name}.created_at >= ?", options[:start_at]) if options[:start_at] t = t.where("#{Vote.table_name}.created_at <= ?", options[:end_at]) if options[:end_at] t = t.where(options[:conditions]) if options[:conditions] - t = options[:order] ? t.order(options[:order]) : t.order("vote_count DESC") - t = t.having(["vote_count >= ?", options[:at_least]]) if options[:at_least] - t = t.having(["vote_count <= ?", options[:at_most]]) if options[:at_most] + t = options[:order] ? t.order(options[:order]) : t.order("#{vote_count} DESC") + t = t.having(["#{vote_count} >= ?", options[:at_least]]) if options[:at_least] + t = t.having(["#{vote_count} <= ?", options[:at_most]]) if options[:at_most] t.select("#{self.table_name}.*, COUNT(#{Vote.table_name}.voteable_id) AS vote_count") end + def column_names_for_tally + column_names.map { |column| "#{self.table_name}.#{column}" }.join(', ') + end + end module InstanceMethods @@ -82,4 +92,4 @@ def voted_by?(voter) end end -end \ No newline at end of file +end From e0082c47e5eef5a615b293dc86147b653f660eca Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Sat, 4 Dec 2010 14:54:04 -0700 Subject: [PATCH 020/138] Symbolize migration. --- lib/generators/thumbs_up/templates/migration.rb | 14 ++++++++------ lib/has_karma.rb | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/generators/thumbs_up/templates/migration.rb b/lib/generators/thumbs_up/templates/migration.rb index 26c3304..33fa336 100644 --- a/lib/generators/thumbs_up/templates/migration.rb +++ b/lib/generators/thumbs_up/templates/migration.rb @@ -1,21 +1,23 @@ class ThumbsUpMigration < ActiveRecord::Migration def self.up create_table :votes, :force => true do |t| - t.boolean :vote, :default => false + + t.boolean :vote, :default => false t.references :voteable, :polymorphic => true, :null => false t.references :voter, :polymorphic => true t.timestamps + end - add_index :votes, ["voter_id", "voter_type"], :name => "fk_voters" - add_index :votes, ["voteable_id", "voteable_type"], :name => "fk_voteables" + add_index :votes, [:voter_id, :voter_type] + add_index :votes, [:voteable_id, :voteable_type] - # If you don't want to enforce "One Person, One Vote" rules in the database, comment out the index below. - add_index :votes, ["voter_id", "voter_type", "voteable_id", "voteable_type"], :unique => true, :name => "uniq_one_vote_only" + # Comment out the line below to allow multiple votes per voter on a single entity. + add_index :votes, [:voter_id, :voter_type, :voteable_id, :voteable_type], :unique => true end def self.down drop_table :votes end -end +end \ No newline at end of file diff --git a/lib/has_karma.rb b/lib/has_karma.rb index 2464226..b593dae 100644 --- a/lib/has_karma.rb +++ b/lib/has_karma.rb @@ -13,7 +13,7 @@ def has_karma(voteable_type, options = {}) include ThumbsUp::Karma::InstanceMethods extend ThumbsUp::Karma::SingletonMethods self.karmic_objects ||= {} - self.karmic_objects[voteable_type.to_s.classify.constantize] = [ (options[:as] ? options[:as].to_s.foreign_key : self.name.foreign_key), (options[:weight] ? options[:weight] : 1).to_f ] + self.karmic_objects[voteable_type.to_s.classify.constantize] = [ (options[:as] ? options[:as].to_s.foreign_key : self.name.foreign_key), (options[:weight] || 1).to_f ] end end From 55c2257f15ae28f641348fc0366f2e1ce0ddcc85 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Sun, 5 Dec 2010 00:45:23 -0700 Subject: [PATCH 021/138] Index key too long for MySQL. --- lib/generators/thumbs_up/templates/migration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/generators/thumbs_up/templates/migration.rb b/lib/generators/thumbs_up/templates/migration.rb index 33fa336..c2887ad 100644 --- a/lib/generators/thumbs_up/templates/migration.rb +++ b/lib/generators/thumbs_up/templates/migration.rb @@ -13,7 +13,7 @@ def self.up add_index :votes, [:voteable_id, :voteable_type] # Comment out the line below to allow multiple votes per voter on a single entity. - add_index :votes, [:voter_id, :voter_type, :voteable_id, :voteable_type], :unique => true + add_index :votes, [:voter_id, :voter_type, :voteable_id, :voteable_type], :unique => true, :name => 'fk_one_vote_per_user_per_entity' end def self.down From 1f6e257bf7e418debb91f4c8c3c88470fc428447 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Thu, 16 Dec 2010 23:16:08 -0700 Subject: [PATCH 022/138] Fix for joshuap's reported issue with #tally breaking when using :at_least or :at_most conditions. https://github.com/brady8/thumbs_up/issues#issue/6 --- lib/acts_as_voteable.rb | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index 5c3c206..8a2b7a9 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -39,15 +39,24 @@ def tally(*args) # We join so that you can order by columns on the voteable model. t = t.joins("LEFT OUTER JOIN #{Vote.table_name} ON #{self.table_name}.#{self.primary_key} = #{Vote.table_name}.voteable_id") - t = t.having("#{vote_count} > 0") t = t.group("#{Vote.table_name}.voteable_id, #{column_names_for_tally}") t = t.limit(options[:limit]) if options[:limit] t = t.where("#{Vote.table_name}.created_at >= ?", options[:start_at]) if options[:start_at] t = t.where("#{Vote.table_name}.created_at <= ?", options[:end_at]) if options[:end_at] t = t.where(options[:conditions]) if options[:conditions] t = options[:order] ? t.order(options[:order]) : t.order("#{vote_count} DESC") - t = t.having(["#{vote_count} >= ?", options[:at_least]]) if options[:at_least] - t = t.having(["#{vote_count} <= ?", options[:at_most]]) if options[:at_most] + + # I haven't been able to confirm this bug yet, but Arel (2.0.7) currently blows up + # with multiple 'having' clauses. So we hack them all into one for now. + # If you have a more elegant solution, a pull request on Github would be greatly appreciated. + t = t.having([ + "#{vote_count} > 0", + (options[:at_least] ? "#{vote_count} >= #{sanitize(options[:at_least])}" : nil), + (options[:at_most] ? "#{vote_count} <= #{sanitize(options[:at_most])}" : nil) + ].compact.join(' AND ')) + # t = t.having("#{vote_count} > 0") + # t = t.having(["#{vote_count} >= ?", options[:at_least]]) if options[:at_least] + # t = t.having(["#{vote_count} <= ?", options[:at_most]]) if options[:at_most] t.select("#{self.table_name}.*, COUNT(#{Vote.table_name}.voteable_id) AS vote_count") end From 6f5d956db7d6eee8dc92a5158a794c507cdd5096 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Thu, 16 Dec 2010 23:17:08 -0700 Subject: [PATCH 023/138] Version bump to 0.3.2 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 9e11b32..9fc80f9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.1 +0.3.2 \ No newline at end of file From bc471e242b4cbc7635da244708a7b1040e34e39b Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Thu, 16 Dec 2010 23:26:58 -0700 Subject: [PATCH 024/138] Updated gemspec to take version from VERSION. --- thumbs_up.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index 09c54b1..de3d2bb 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -5,11 +5,11 @@ Gem::Specification.new do |s| s.name = %q{thumbs_up} - s.version = "0.3.1" + s.version = IO.read('./VERSION') s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Brady Bouchard", "Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnętrzak"] - s.date = %q{2010-09-06} + s.date = Date.today.to_s s.description = %q{ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com.} s.email = %q{brady@ldawn.com} s.extra_rdoc_files = [ From 605bcd62e0b9bd63a225246edde7fbd7dd3a3738 Mon Sep 17 00:00:00 2001 From: Reiner Dieterich Date: Fri, 12 Nov 2010 01:26:25 +0100 Subject: [PATCH 025/138] use new methods autoload_paths and autoload_once_paths in initializer --- rails/init.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rails/init.rb b/rails/init.rb index 87b4e50..6a72190 100644 --- a/rails/init.rb +++ b/rails/init.rb @@ -3,8 +3,8 @@ %w{ models controllers helpers }.each do |dir| path = File.join(File.dirname(__FILE__) , 'lib', dir) $LOAD_PATH << path - ActiveSupport::Dependencies.load_paths << path - ActiveSupport::Dependencies.load_once_paths.delete(path) + ActiveSupport::Dependencies.autoload_paths << path + ActiveSupport::Dependencies.autoload_once_paths.delete(path) end -require 'thumbs_up' \ No newline at end of file +require 'thumbs_up' From e91f3b22bbc565fa7a2a5c4be436a23ba25fd488 Mon Sep 17 00:00:00 2001 From: David Czarnecki Date: Tue, 11 Jan 2011 15:07:57 -0500 Subject: [PATCH 026/138] Fixing documentation for vote_exclusively_against call. --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index dc908bc..bb6ab72 100644 --- a/README.markdown +++ b/README.markdown @@ -65,7 +65,7 @@ Usage voter.vote(voteable, vote) # Adds either a +1 or -1 vote: vote => true (+1), vote => false (-1) voter.vote_exclusively_for(voteable) # Removes any previous votes by that particular voter, and votes for. - voter.vote_exclusively_for(voteable) # Removes any previous votes by that particular voter, and votes against. + voter.vote_exclusively_against(voteable) # Removes any previous votes by that particular voter, and votes against. ### Querying votes From e75edbd034206935575a8d5139e4ef17b009826a Mon Sep 17 00:00:00 2001 From: David Czarnecki Date: Tue, 11 Jan 2011 21:19:58 -0500 Subject: [PATCH 027/138] Add generator option, unique-voting, to allow you to generate migration and model without unique voting (default is true). --- README.markdown | 3 +++ lib/generators/thumbs_up/templates/migration.rb | 4 +++- lib/generators/thumbs_up/templates/vote.rb | 3 ++- lib/generators/thumbs_up/thumbs_up_generator.rb | 2 ++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/README.markdown b/README.markdown index bb6ab72..fbf3aae 100644 --- a/README.markdown +++ b/README.markdown @@ -123,6 +123,9 @@ ThumbsUp by default only allows one vote per user. This can be changed by removi add_index :votes, ["voter_id", "voter_type", "voteable_id", "voteable_type"], :unique => true, :name => "uniq_one_vote_only" +You can also use `--unique-voting false` when running the generator command: + + rails generate thumbs_up --unique-voting false Credits ======= diff --git a/lib/generators/thumbs_up/templates/migration.rb b/lib/generators/thumbs_up/templates/migration.rb index c2887ad..d08d015 100644 --- a/lib/generators/thumbs_up/templates/migration.rb +++ b/lib/generators/thumbs_up/templates/migration.rb @@ -12,8 +12,10 @@ def self.up add_index :votes, [:voter_id, :voter_type] add_index :votes, [:voteable_id, :voteable_type] - # Comment out the line below to allow multiple votes per voter on a single entity. +<% if options[:unique_voting] == true %> + # Comment out the line below to allow multiple votes per voter on a single entity. add_index :votes, [:voter_id, :voter_type, :voteable_id, :voteable_type], :unique => true, :name => 'fk_one_vote_per_user_per_entity' +<% end %> end def self.down diff --git a/lib/generators/thumbs_up/templates/vote.rb b/lib/generators/thumbs_up/templates/vote.rb index 7b6f58d..7169512 100644 --- a/lib/generators/thumbs_up/templates/vote.rb +++ b/lib/generators/thumbs_up/templates/vote.rb @@ -10,7 +10,8 @@ class Vote < ActiveRecord::Base attr_accessible :vote, :voter, :voteable +<% if options[:unique_voting] == true %> # Comment out the line below to allow multiple votes per user. validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id] - +<% end %> end \ No newline at end of file diff --git a/lib/generators/thumbs_up/thumbs_up_generator.rb b/lib/generators/thumbs_up/thumbs_up_generator.rb index ebcead6..c4b79b1 100644 --- a/lib/generators/thumbs_up/thumbs_up_generator.rb +++ b/lib/generators/thumbs_up/thumbs_up_generator.rb @@ -5,6 +5,8 @@ class ThumbsUpGenerator < Rails::Generators::Base include Rails::Generators::Migration source_root File.expand_path('../templates', __FILE__) + + class_option :unique_voting, :type => :boolean, :default => true, :desc => 'Do you want only one vote allowed per voter? (default: true)' # Implement the required interface for Rails::Generators::Migration. def self.next_migration_number(dirname) #:nodoc: From dff080bffe720107d1e3ab2672028bfc6e9aa7bb Mon Sep 17 00:00:00 2001 From: David Czarnecki Date: Wed, 12 Jan 2011 20:48:46 -0500 Subject: [PATCH 028/138] Start of the test suite for thumbs_up --- .gitignore | 1 + Gemfile | 13 ++++++++ Rakefile | 61 ++++++++++++++++++++++++++++---------- test/helper.rb | 67 ++++++++++++++++++++++++++++++++++++++++++ test/test_thumbs_up.rb | 17 +++++++++++ 5 files changed, 144 insertions(+), 15 deletions(-) create mode 100644 Gemfile create mode 100644 test/helper.rb create mode 100644 test/test_thumbs_up.rb diff --git a/.gitignore b/.gitignore index 6992551..c8993f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.gem pkg/* +Gemfile.lock diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..c474297 --- /dev/null +++ b/Gemfile @@ -0,0 +1,13 @@ +source :rubygems + +gem 'activerecord', '3.0.3' + +group :development do + gem "bundler", "~> 1.0.0" + gem "jeweler", "~> 1.5.2" + gem "rcov", ">= 0" +end + +group :test do + gem 'sqlite3-ruby', '1.3.2' +end \ No newline at end of file diff --git a/Rakefile b/Rakefile index 0befb23..844fcc4 100644 --- a/Rakefile +++ b/Rakefile @@ -1,19 +1,50 @@ # encoding: UTF-8 require 'rubygems' -require 'rake' +require 'bundler' begin - require 'jeweler' - Jeweler::Tasks.new do |gem| - gem.name = "thumbs_up" - gem.summary = "Voting for ActiveRecord with multiple vote sources and karma calculation." - gem.description = "ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com." - gem.email = "brady@ldawn.com" - gem.homepage = "http://github.com/brady8/thumbs_up" - gem.authors = ["Brady Bouchard", "Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnętrzak"] - # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings - end - Jeweler::GemcutterTasks.new -rescue LoadError - puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler" -end \ No newline at end of file + Bundler.setup(:default, :development) +rescue Bundler::BundlerError => e + $stderr.puts e.message + $stderr.puts "Run `bundle install` to install missing gems" + exit e.status_code +end +require 'rake' + +require 'jeweler' +Jeweler::Tasks.new do |gem| + gem.name = "thumbs_up" + gem.summary = "Voting for ActiveRecord with multiple vote sources and karma calculation." + gem.description = "ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com." + gem.email = "brady@ldawn.com" + gem.homepage = "http://github.com/brady8/thumbs_up" + gem.authors = ["Brady Bouchard", "Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnętrzak"] + # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings +end +Jeweler::RubygemsDotOrgTasks.new + +require 'rake/testtask' +Rake::TestTask.new(:test) do |test| + test.libs << 'lib' << 'test' + test.pattern = 'test/**/test_*.rb' + test.verbose = true +end + +require 'rcov/rcovtask' +Rcov::RcovTask.new do |test| + test.libs << 'test' + test.pattern = 'test/**/test_*.rb' + test.verbose = true +end + +require 'rake/rdoctask' +Rake::RDocTask.new do |rdoc| + version = File.exist?('VERSION') ? File.read('VERSION') : "" + + rdoc.rdoc_dir = 'rdoc' + rdoc.title = "leaderboard #{version}" + rdoc.rdoc_files.include('README*') + rdoc.rdoc_files.include('lib/**/*.rb') +end + +task :default => :test \ No newline at end of file diff --git a/test/helper.rb b/test/helper.rb new file mode 100644 index 0000000..1e18ab2 --- /dev/null +++ b/test/helper.rb @@ -0,0 +1,67 @@ +require 'test/unit' + +$LOAD_PATH.unshift(File.dirname(__FILE__)) +$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) + +require 'active_record' + +ActiveRecord::Base.establish_connection( + :adapter => "sqlite3", + :database => ":memory:" +) + +ActiveRecord::Migration.verbose = false + +ActiveRecord::Schema.define do + create_table :votes, :force => true do |t| + t.boolean :vote, :default => false + t.references :voteable, :polymorphic => true, :null => false + t.references :voter, :polymorphic => true + t.timestamps + end + + add_index :votes, [:voter_id, :voter_type] + add_index :votes, [:voteable_id, :voteable_type] + + # Comment out the line below to allow multiple votes per voter on a single entity. + add_index :votes, [:voter_id, :voter_type, :voteable_id, :voteable_type], :unique => true, :name => 'fk_one_vote_per_user_per_entity' + + create_table :users, :force => true do |t| + t.string :name + t.timestamps + end + + create_table :items, :force => true do |t| + t.string :name + t.string :description + end +end + +require 'thumbs_up' + +class Vote < ActiveRecord::Base + + scope :for_voter, lambda { |*args| where(["voter_id = ? AND voter_type = ?", args.first.id, args.first.class.name]) } + scope :for_voteable, lambda { |*args| where(["voteable_id = ? AND voteable_type = ?", args.first.id, args.first.class.name]) } + scope :recent, lambda { |*args| where(["created_at > ?", (args.first || 2.weeks.ago)]) } + scope :descending, order("created_at DESC") + + belongs_to :voteable, :polymorphic => true + belongs_to :voter, :polymorphic => true + + attr_accessible :vote, :voter, :voteable + + # Comment out the line below to allow multiple votes per user. + validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id] +end + +class User < ActiveRecord::Base + acts_as_voter +end + +class Item < ActiveRecord::Base + acts_as_voteable +end + +class Test::Unit::TestCase +end diff --git a/test/test_thumbs_up.rb b/test/test_thumbs_up.rb new file mode 100644 index 0000000..7e7ec47 --- /dev/null +++ b/test/test_thumbs_up.rb @@ -0,0 +1,17 @@ +require 'helper' + +class TestThumbsUp < Test::Unit::TestCase + def setup + Vote.delete_all + User.delete_all + Item.delete_all + end + + def test_acts_as_voter_instance_methods + user = User.create(:name => 'david') + item = Item.create(:name => 'XBOX', :description => 'XBOX console') + + assert_not_nil user.vote_for(item) + assert_equal true, user.voted_for?(item) + end +end \ No newline at end of file From c909fc17b88a40571023e6d57e7741f67eb8e386 Mon Sep 17 00:00:00 2001 From: David Czarnecki Date: Wed, 12 Jan 2011 21:07:30 -0500 Subject: [PATCH 029/138] Testing most of the acts_as_voter methods. --- test/test_thumbs_up.rb | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/test/test_thumbs_up.rb b/test/test_thumbs_up.rb index 7e7ec47..dfe1e46 100644 --- a/test/test_thumbs_up.rb +++ b/test/test_thumbs_up.rb @@ -8,10 +8,46 @@ def setup end def test_acts_as_voter_instance_methods - user = User.create(:name => 'david') + user_for = User.create(:name => 'david') + user_against = User.create(:name => 'brady') item = Item.create(:name => 'XBOX', :description => 'XBOX console') - assert_not_nil user.vote_for(item) - assert_equal true, user.voted_for?(item) + assert_not_nil user_for.vote_for(item) + assert_raises(ActiveRecord::RecordInvalid) do + user_for.vote_for(item) + end + assert_equal true, user_for.voted_for?(item) + assert_equal false, user_for.voted_against?(item) + assert_equal true, user_for.voted_on?(item) + assert_equal 1, user_for.vote_count + assert_equal 1, user_for.vote_count(:up) + assert_equal 0, user_for.vote_count(:down) + assert_equal true, user_for.voted_which_way?(item, :up) + assert_equal false, user_for.voted_which_way?(item, :down) + assert_raises(ArgumentError) do + user_for.voted_which_way?(item, :foo) + end + + assert_not_nil user_against.vote_against(item) + assert_raises(ActiveRecord::RecordInvalid) do + user_against.vote_against(item) + end + assert_equal false, user_against.voted_for?(item) + assert_equal true, user_against.voted_against?(item) + assert_equal true, user_against.voted_on?(item) + assert_equal 1, user_against.vote_count + assert_equal 0, user_against.vote_count(:up) + assert_equal 1, user_against.vote_count(:down) + assert_equal false, user_against.voted_which_way?(item, :up) + assert_equal true, user_against.voted_which_way?(item, :down) + assert_raises(ArgumentError) do + user_against.voted_which_way?(item, :foo) + end + + assert_not_nil user_against.vote_exclusively_for(item) + assert_equal true, user_against.voted_for?(item) + + assert_not_nil user_for.vote_exclusively_against(item) + assert_equal true, user_for.voted_against?(item) end end \ No newline at end of file From 1feb7ec4c66f345147c08bad8f6964a3fd4f7989 Mon Sep 17 00:00:00 2001 From: David Czarnecki Date: Thu, 13 Jan 2011 10:58:02 -0500 Subject: [PATCH 030/138] Added tests for acts_as_voteable instance methods. --- test/test_thumbs_up.rb | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/test_thumbs_up.rb b/test/test_thumbs_up.rb index dfe1e46..8d885e9 100644 --- a/test/test_thumbs_up.rb +++ b/test/test_thumbs_up.rb @@ -50,4 +50,38 @@ def test_acts_as_voter_instance_methods assert_not_nil user_for.vote_exclusively_against(item) assert_equal true, user_for.voted_against?(item) end + + def test_acts_as_voteable_instance_methods + user_for = User.create(:name => 'david') + another_user_for = User.create(:name => 'name') + user_against = User.create(:name => 'brady') + item = Item.create(:name => 'XBOX', :description => 'XBOX console') + + user_for.vote_for(item) + another_user_for.vote_for(item) + + assert_equal 2, item.votes_for + assert_equal 0, item.votes_against + assert_equal 2, item.plusminus + + user_against.vote_against(item) + + assert_equal 1, item.votes_against + assert_equal 1, item.plusminus + + assert_equal 3, item.votes_count + + voters_who_voted = item.voters_who_voted + assert_equal 3, voters_who_voted.size + assert voters_who_voted.include?(user_for) + assert voters_who_voted.include?(another_user_for) + assert voters_who_voted.include?(user_against) + + non_voting_user = User.create(:name => 'random') + + assert_equal true, item.voted_by?(user_for) + assert_equal true, item.voted_by?(another_user_for) + assert_equal true, item.voted_by?(user_against) + assert_equal false, item.voted_by?(non_voting_user) + end end \ No newline at end of file From 5b69912ce10d2ac69b65157306a05eefd2db314d Mon Sep 17 00:00:00 2001 From: David Czarnecki Date: Sat, 15 Jan 2011 09:41:12 -0500 Subject: [PATCH 031/138] Fix bug with raising ArgumentError in vote method. More acts_as_voter tests. --- lib/acts_as_voter.rb | 2 +- test/test_thumbs_up.rb | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/acts_as_voter.rb b/lib/acts_as_voter.rb index 9516a43..dbf3a49 100644 --- a/lib/acts_as_voter.rb +++ b/lib/acts_as_voter.rb @@ -75,7 +75,7 @@ def vote_exclusively_against(voteable) end def vote(voteable, options = {}) - raise ArgumentError "you must specify :up or :down in order to vote" unless options[:direction] && [:up, :down].include?(options[:direction].to_sym) + raise ArgumentError, "you must specify :up or :down in order to vote" unless options[:direction] && [:up, :down].include?(options[:direction].to_sym) if options[:exclusive] self.clear_votes(voteable) end diff --git a/test/test_thumbs_up.rb b/test/test_thumbs_up.rb index 8d885e9..d00db7a 100644 --- a/test/test_thumbs_up.rb +++ b/test/test_thumbs_up.rb @@ -49,6 +49,16 @@ def test_acts_as_voter_instance_methods assert_not_nil user_for.vote_exclusively_against(item) assert_equal true, user_for.voted_against?(item) + + user_for.clear_votes(item) + assert_equal 0, user_for.vote_count + + user_against.clear_votes(item) + assert_equal 0, user_against.vote_count + + assert_raises(ArgumentError) do + user_for.vote(item, {:direction => :foo}) + end end def test_acts_as_voteable_instance_methods From 70d55e8f35ff19194ceb0ca2f3ab54918e7a6877 Mon Sep 17 00:00:00 2001 From: David Czarnecki Date: Sun, 16 Jan 2011 11:44:28 -0500 Subject: [PATCH 032/138] Adding tally method tests. --- test/test_thumbs_up.rb | 47 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/test/test_thumbs_up.rb b/test/test_thumbs_up.rb index d00db7a..4ae9ffc 100644 --- a/test/test_thumbs_up.rb +++ b/test/test_thumbs_up.rb @@ -94,4 +94,51 @@ def test_acts_as_voteable_instance_methods assert_equal true, item.voted_by?(user_against) assert_equal false, item.voted_by?(non_voting_user) end + + def test_tally_empty + item = Item.create(:name => 'XBOX', :description => 'XBOX console') + + assert_equal 0, Item.tally.length + end + + def test_tally_starts_at + item = Item.create(:name => 'XBOX', :description => 'XBOX console') + user = User.create(:name => 'david') + + vote = user.vote_for(item) + vote.created_at = 3.days.ago + vote.save + + assert_equal 0, Item.tally(:start_at => 2.days.ago).length + assert_equal 1, Item.tally(:start_at => 4.days.ago).length + end + + def test_tally_end_at + item = Item.create(:name => 'XBOX', :description => 'XBOX console') + user = User.create(:name => 'david') + + vote = user.vote_for(item) + vote.created_at = 3.days.from_now + vote.save + + assert_equal 0, Item.tally(:end_at => 2.days.from_now).length + assert_equal 1, Item.tally(:end_at => 4.days.from_now).length + end + + def test_tally_between_start_at_end_at + item = Item.create(:name => 'XBOX', :description => 'XBOX console') + another_item = Item.create(:name => 'XBOX', :description => 'XBOX console') + user = User.create(:name => 'david') + + vote = user.vote_for(item) + vote.created_at = 2.days.ago + vote.save + + vote = user.vote_for(another_item) + vote.created_at = 3.days.from_now + vote.save + + assert_equal 1, Item.tally(:start_at => 3.days.ago, :end_at => 2.days.from_now).length + assert_equal 2, Item.tally(:start_at => 3.days.ago, :end_at => 4.days.from_now).length + end end \ No newline at end of file From 07786273f64e50d74ab6375fd5cd1c45ec27eeda Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Mon, 14 Mar 2011 08:21:45 -0400 Subject: [PATCH 033/138] Implemented rank_tally functionality modified: README.markdown modified: lib/acts_as_voteable.rb modified: test/test_thumbs_up.rb --- README.markdown | 15 ++++++++ lib/acts_as_voteable.rb | 54 +++++++++++++++++++++++++++ test/test_thumbs_up.rb | 82 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+) diff --git a/README.markdown b/README.markdown index fbf3aae..1a7272f 100644 --- a/README.markdown +++ b/README.markdown @@ -98,6 +98,21 @@ This will select the Items with between 1 and 10,000 votes, the votes having bee :at_least - Item must have at least X votes :at_most - Item may not have more than X votes +##### Tallying Rank + +Similar to tallying votes, but this will actually return voteable object collections based on a rating +system where up votes and down votes get equal rating. For Instance, a voteable with 3 upvotes and 2 +downvotes will have a rating in this instance of 1. + +##### Rank_Tally Options: + :start_at - Restrict the votes to those created after a certain time + :end_at - Restrict the votes to those created before a certain time + :conditions - A piece of SQL conditions to add to the query + :limit - The maximum number of voteables to return + :ascending - Boolean Default false. If specified true, results will be returned in ascending order (from bottom up) + :at_least - Item must have at least X votes + :at_most - Item may not have more than X votes + #### Lower level queries positiveVoteCount = voteable.votes_for diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index 8a2b7a9..25e75a6 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -15,6 +15,60 @@ def acts_as_voteable end module SingletonMethods + + # The point of this function is to return rankings based on the difference between up and down votes + # assuming equal weighting (i.e. a user with 1 up vote and 1 down vote has a Vote_Total of 0. + # First the votes table is joined twiced so that the Vote_Total can be calculated for every ID + # Then this table is joined against the specific table passed to this function to allow for + # ranking of the items within that table based on the difference between up and down votes. + # Options: + # :start_at - Restrict the votes to those created after a certain time + # :end_at - Restrict the votes to those created before a certain time + # :conditions - A piece of SQL conditions to add to the query + # :limit - The maximum number of voteables to return + # :ascending - Default false - normal order DESC (i.e. highest rank to lowest) + # :at_least - Item must have at least X votes + # :at_most - Item may not have more than X votes + def rank_tally(*args) + options = args.extract_options! + + tsub0 = Vote + tsub0 = tsub0.where("vote = ?", false) + tsub0 = tsub0.where("voteable_type = ?", self.name) + tsub0 = tsub0.group("voteable_id") + tsub0 = tsub0.select("DISTINCT voteable_id, COUNT(vote) as Votes_Against") + + tsub1 = Vote + tsub1 = tsub1.where("vote = ?", true) + tsub1 = tsub1.where("voteable_type = ?", self.name) + tsub1 = tsub1.group("voteable_id") + tsub1 = tsub1.select("DISTINCT voteable_id, COUNT(vote) as Votes_For") + + t = self.joins("LEFT OUTER JOIN (SELECT DISTINCT #{Vote.table_name}.*, + (COALESCE(vfor.Votes_For, 0)-COALESCE(against.Votes_Against, 0)) AS Vote_Total + FROM (#{Vote.table_name} LEFT JOIN + (#{tsub0.to_sql}) AS against ON #{Vote.table_name}.voteable_id = against.voteable_id) + LEFT JOIN + (#{tsub1.to_sql}) as vfor ON #{Vote.table_name}.voteable_id = vfor.voteable_id) + AS joined_#{Vote.table_name} ON #{self.table_name}.#{self.primary_key} = + joined_#{Vote.table_name}.voteable_id") + + t = t.where("joined_#{Vote.table_name}.voteable_type = '#{self.name}'") + t = t.group("joined_#{Vote.table_name}.voteable_id, joined_#{Vote.table_name}.Vote_Total, #{column_names_for_tally}") + t = t.limit(options[:limit]) if options[:limit] + t = t.where("joined_#{Vote.table_name}.created_at >= ?", options[:start_at]) if options[:start_at] + t = t.where("joined_#{Vote.table_name}.created_at <= ?", options[:end_at]) if options[:end_at] + t = t.where(options[:conditions]) if options[:conditions] + t = options[:ascending] ? t.order("joined_#{Vote.table_name}.Vote_Total") + : t.order("joined_#{Vote.table_name}.Vote_Total DESC") + + t = t.having(["COUNT(joined_#{Vote.table_name}.voteable_id) > 0", + (options[:at_least] ? "joined_votes.Vote_Total >= #{sanitize(options[:at_least])}" : nil), + (options[:at_most] ? "joined_votes.Vote_Total <= #{sanitize(options[:at_most])}" : nil) + ].compact.join(' AND ')) + + t.select("#{self.table_name}.*, joined_#{Vote.table_name}.Vote_Total") + end # Calculate the vote counts for all voteables of my type. # This method returns all voteables with at least one vote. diff --git a/test/test_thumbs_up.rb b/test/test_thumbs_up.rb index 4ae9ffc..90a64ca 100644 --- a/test/test_thumbs_up.rb +++ b/test/test_thumbs_up.rb @@ -141,4 +141,86 @@ def test_tally_between_start_at_end_at assert_equal 1, Item.tally(:start_at => 3.days.ago, :end_at => 2.days.from_now).length assert_equal 2, Item.tally(:start_at => 3.days.ago, :end_at => 4.days.from_now).length end + + def test_rank_tally_empty + item = Item.create(:name => 'XBOX', :description => 'XBOX console') + + assert_equal 0, Item.rank_tally.length + end + + def test_rank_tally_starts_at + item = Item.create(:name => 'XBOX', :description => 'XBOX console') + user = User.create(:name => 'david') + + vote = user.vote_for(item) + vote.created_at = 3.days.ago + vote.save + + assert_equal 0, Item.rank_tally(:start_at => 2.days.ago).length + assert_equal 1, Item.rank_tally(:start_at => 4.days.ago).length + end + + def test_rank_tally_end_at + item = Item.create(:name => 'XBOX', :description => 'XBOX console') + user = User.create(:name => 'david') + + vote = user.vote_for(item) + vote.created_at = 3.days.from_now + vote.save + + assert_equal 0, Item.rank_tally(:end_at => 2.days.from_now).length + assert_equal 1, Item.rank_tally(:end_at => 4.days.from_now).length + end + + def test_rank_tally_between_start_at_end_at + item = Item.create(:name => 'XBOX', :description => 'XBOX console') + another_item = Item.create(:name => 'XBOX', :description => 'XBOX console') + user = User.create(:name => 'david') + + vote = user.vote_for(item) + vote.created_at = 2.days.ago + vote.save + + vote = user.vote_for(another_item) + vote.created_at = 3.days.from_now + vote.save + + assert_equal 1, Item.rank_tally(:start_at => 3.days.ago, :end_at => 2.days.from_now).length + assert_equal 2, Item.rank_tally(:start_at => 3.days.ago, :end_at => 4.days.from_now).length + end + + def test_rank_tally_inclusion + user = User.create(:name => 'david') + item = Item.create(:name => 'XBOX', :description => 'XBOX console') + item_not_included = Item.create(:name => 'Playstation', :description => 'Playstation console') + + assert_not_nil user.vote_for(item) + + assert (Item.rank_tally.include? item) + assert (not Item.rank_tally.include? item_not_included) + end + + def test_rank_tally_default_ordering + user = User.create(:name => 'david') + item_for = Item.create(:name => 'XBOX', :description => 'XBOX console') + item_against = Item.create(:name => 'Playstation', :description => 'Playstation console') + + assert_not_nil user.vote_for(item_for) + assert_not_nil user.vote_against(item_against) + + assert_equal item_for, Item.rank_tally[0] + assert_equal item_against, Item.rank_tally[1] + end + + def test_rank_tally_ascending_ordering + user = User.create(:name => 'david') + item_for = Item.create(:name => 'XBOX', :description => 'XBOX console') + item_against = Item.create(:name => 'Playstation', :description => 'Playstation console') + + assert_not_nil user.vote_for(item_for) + assert_not_nil user.vote_against(item_against) + + assert_equal item_for, Item.rank_tally(:ascending => true)[1] + assert_equal item_against, Item.rank_tally(:ascending => true)[0] + end end \ No newline at end of file From a6cf4e661ca2977fd08457a944a7af9246631020 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Fri, 1 Apr 2011 16:33:53 +1000 Subject: [PATCH 034/138] Bumped version. --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 9fc80f9..bd73f47 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.2 \ No newline at end of file +0.4 From a615b7efdd1c92e928b2c003d00f4c8536ceb5bc Mon Sep 17 00:00:00 2001 From: oponder Date: Sun, 3 Apr 2011 13:37:49 +0200 Subject: [PATCH 035/138] Fix lib/acts_as_voteable.rb:62: syntax error, unexpected '\n' --- lib/acts_as_voteable.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index 25e75a6..e129407 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -59,8 +59,7 @@ def rank_tally(*args) t = t.where("joined_#{Vote.table_name}.created_at >= ?", options[:start_at]) if options[:start_at] t = t.where("joined_#{Vote.table_name}.created_at <= ?", options[:end_at]) if options[:end_at] t = t.where(options[:conditions]) if options[:conditions] - t = options[:ascending] ? t.order("joined_#{Vote.table_name}.Vote_Total") - : t.order("joined_#{Vote.table_name}.Vote_Total DESC") + t = options[:ascending] ? t.order("joined_#{Vote.table_name}.Vote_Total") : t.order("joined_#{Vote.table_name}.Vote_Total DESC") t = t.having(["COUNT(joined_#{Vote.table_name}.voteable_id) > 0", (options[:at_least] ? "joined_votes.Vote_Total >= #{sanitize(options[:at_least])}" : nil), From 4ad56ac502a1f3e159937fcdfa29c9db193e445e Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Mon, 4 Apr 2011 08:01:11 +1000 Subject: [PATCH 036/138] Version bump. --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index bd73f47..267577d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.4 +0.4.1 From a892cc962a63a9ac85c29c1571fcdf11db8dbcfe Mon Sep 17 00:00:00 2001 From: Timur Vafin Date: Sun, 1 May 2011 23:44:20 +0400 Subject: [PATCH 037/138] Implemented #percent_for and #percent_against methods --- lib/acts_as_voteable.rb | 42 ++++++++++-------- test/test_thumbs_up.rb | 97 +++++++++++++++++++++-------------------- 2 files changed, 75 insertions(+), 64 deletions(-) diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index e129407..bebcd74 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -15,11 +15,11 @@ def acts_as_voteable end module SingletonMethods - + # The point of this function is to return rankings based on the difference between up and down votes - # assuming equal weighting (i.e. a user with 1 up vote and 1 down vote has a Vote_Total of 0. + # assuming equal weighting (i.e. a user with 1 up vote and 1 down vote has a Vote_Total of 0. # First the votes table is joined twiced so that the Vote_Total can be calculated for every ID - # Then this table is joined against the specific table passed to this function to allow for + # Then this table is joined against the specific table passed to this function to allow for # ranking of the items within that table based on the difference between up and down votes. # Options: # :start_at - Restrict the votes to those created after a certain time @@ -31,28 +31,28 @@ module SingletonMethods # :at_most - Item may not have more than X votes def rank_tally(*args) options = args.extract_options! - + tsub0 = Vote tsub0 = tsub0.where("vote = ?", false) tsub0 = tsub0.where("voteable_type = ?", self.name) tsub0 = tsub0.group("voteable_id") tsub0 = tsub0.select("DISTINCT voteable_id, COUNT(vote) as Votes_Against") - + tsub1 = Vote tsub1 = tsub1.where("vote = ?", true) tsub1 = tsub1.where("voteable_type = ?", self.name) tsub1 = tsub1.group("voteable_id") tsub1 = tsub1.select("DISTINCT voteable_id, COUNT(vote) as Votes_For") - - t = self.joins("LEFT OUTER JOIN (SELECT DISTINCT #{Vote.table_name}.*, + + t = self.joins("LEFT OUTER JOIN (SELECT DISTINCT #{Vote.table_name}.*, (COALESCE(vfor.Votes_For, 0)-COALESCE(against.Votes_Against, 0)) AS Vote_Total FROM (#{Vote.table_name} LEFT JOIN (#{tsub0.to_sql}) AS against ON #{Vote.table_name}.voteable_id = against.voteable_id) - LEFT JOIN - (#{tsub1.to_sql}) as vfor ON #{Vote.table_name}.voteable_id = vfor.voteable_id) - AS joined_#{Vote.table_name} ON #{self.table_name}.#{self.primary_key} = + LEFT JOIN + (#{tsub1.to_sql}) as vfor ON #{Vote.table_name}.voteable_id = vfor.voteable_id) + AS joined_#{Vote.table_name} ON #{self.table_name}.#{self.primary_key} = joined_#{Vote.table_name}.voteable_id") - + t = t.where("joined_#{Vote.table_name}.voteable_type = '#{self.name}'") t = t.group("joined_#{Vote.table_name}.voteable_id, joined_#{Vote.table_name}.Vote_Total, #{column_names_for_tally}") t = t.limit(options[:limit]) if options[:limit] @@ -60,12 +60,12 @@ def rank_tally(*args) t = t.where("joined_#{Vote.table_name}.created_at <= ?", options[:end_at]) if options[:end_at] t = t.where(options[:conditions]) if options[:conditions] t = options[:ascending] ? t.order("joined_#{Vote.table_name}.Vote_Total") : t.order("joined_#{Vote.table_name}.Vote_Total DESC") - + t = t.having(["COUNT(joined_#{Vote.table_name}.voteable_id) > 0", (options[:at_least] ? "joined_votes.Vote_Total >= #{sanitize(options[:at_least])}" : nil), (options[:at_most] ? "joined_votes.Vote_Total <= #{sanitize(options[:at_most])}" : nil) ].compact.join(' AND ')) - + t.select("#{self.table_name}.*, joined_#{Vote.table_name}.Vote_Total") end @@ -83,22 +83,22 @@ def rank_tally(*args) # :at_most - Item may not have more than X votes def tally(*args) options = args.extract_options! - + # Use the explicit SQL statement throughout for Postgresql compatibility. vote_count = "COUNT(#{Vote.table_name}.voteable_id)" - + t = self.where("#{Vote.table_name}.voteable_type = '#{self.name}'") # We join so that you can order by columns on the voteable model. t = t.joins("LEFT OUTER JOIN #{Vote.table_name} ON #{self.table_name}.#{self.primary_key} = #{Vote.table_name}.voteable_id") - + t = t.group("#{Vote.table_name}.voteable_id, #{column_names_for_tally}") t = t.limit(options[:limit]) if options[:limit] t = t.where("#{Vote.table_name}.created_at >= ?", options[:start_at]) if options[:start_at] t = t.where("#{Vote.table_name}.created_at <= ?", options[:end_at]) if options[:end_at] t = t.where(options[:conditions]) if options[:conditions] t = options[:order] ? t.order(options[:order]) : t.order("#{vote_count} DESC") - + # I haven't been able to confirm this bug yet, but Arel (2.0.7) currently blows up # with multiple 'having' clauses. So we hack them all into one for now. # If you have a more elegant solution, a pull request on Github would be greatly appreciated. @@ -129,6 +129,14 @@ def votes_against Vote.where(:voteable_id => id, :voteable_type => self.class.name, :vote => false).count end + def percent_for + (votes_for.to_f * 100 / (self.votes.size + 0.0001)).round + end + + def percent_against + (votes_against.to_f * 100 / (self.votes.size + 0.0001)).round + end + # You'll probably want to use this method to display how 'good' a particular voteable # is, and/or sort based on it. def plusminus diff --git a/test/test_thumbs_up.rb b/test/test_thumbs_up.rb index 90a64ca..223dc72 100644 --- a/test/test_thumbs_up.rb +++ b/test/test_thumbs_up.rb @@ -6,12 +6,12 @@ def setup User.delete_all Item.delete_all end - + def test_acts_as_voter_instance_methods user_for = User.create(:name => 'david') user_against = User.create(:name => 'brady') item = Item.create(:name => 'XBOX', :description => 'XBOX console') - + assert_not_nil user_for.vote_for(item) assert_raises(ActiveRecord::RecordInvalid) do user_for.vote_for(item) @@ -27,7 +27,7 @@ def test_acts_as_voter_instance_methods assert_raises(ArgumentError) do user_for.voted_which_way?(item, :foo) end - + assert_not_nil user_against.vote_against(item) assert_raises(ActiveRecord::RecordInvalid) do user_against.vote_against(item) @@ -43,24 +43,24 @@ def test_acts_as_voter_instance_methods assert_raises(ArgumentError) do user_against.voted_which_way?(item, :foo) end - + assert_not_nil user_against.vote_exclusively_for(item) assert_equal true, user_against.voted_for?(item) assert_not_nil user_for.vote_exclusively_against(item) assert_equal true, user_for.voted_against?(item) - + user_for.clear_votes(item) assert_equal 0, user_for.vote_count - + user_against.clear_votes(item) assert_equal 0, user_against.vote_count - + assert_raises(ArgumentError) do user_for.vote(item, {:direction => :foo}) end end - + def test_acts_as_voteable_instance_methods user_for = User.create(:name => 'david') another_user_for = User.create(:name => 'name') @@ -69,46 +69,49 @@ def test_acts_as_voteable_instance_methods user_for.vote_for(item) another_user_for.vote_for(item) - + assert_equal 2, item.votes_for assert_equal 0, item.votes_against assert_equal 2, item.plusminus user_against.vote_against(item) - + assert_equal 1, item.votes_against assert_equal 1, item.plusminus - + assert_equal 3, item.votes_count - + + assert_equal 67, item.percent_for + assert_equal 33, item.percent_against + voters_who_voted = item.voters_who_voted - assert_equal 3, voters_who_voted.size + assert_equal 3, voters_who_voted.size assert voters_who_voted.include?(user_for) assert voters_who_voted.include?(another_user_for) assert voters_who_voted.include?(user_against) - + non_voting_user = User.create(:name => 'random') - + assert_equal true, item.voted_by?(user_for) assert_equal true, item.voted_by?(another_user_for) assert_equal true, item.voted_by?(user_against) assert_equal false, item.voted_by?(non_voting_user) end - + def test_tally_empty item = Item.create(:name => 'XBOX', :description => 'XBOX console') - + assert_equal 0, Item.tally.length end - + def test_tally_starts_at item = Item.create(:name => 'XBOX', :description => 'XBOX console') user = User.create(:name => 'david') - + vote = user.vote_for(item) vote.created_at = 3.days.ago vote.save - + assert_equal 0, Item.tally(:start_at => 2.days.ago).length assert_equal 1, Item.tally(:start_at => 4.days.ago).length end @@ -116,46 +119,46 @@ def test_tally_starts_at def test_tally_end_at item = Item.create(:name => 'XBOX', :description => 'XBOX console') user = User.create(:name => 'david') - + vote = user.vote_for(item) vote.created_at = 3.days.from_now vote.save - + assert_equal 0, Item.tally(:end_at => 2.days.from_now).length assert_equal 1, Item.tally(:end_at => 4.days.from_now).length end - + def test_tally_between_start_at_end_at item = Item.create(:name => 'XBOX', :description => 'XBOX console') another_item = Item.create(:name => 'XBOX', :description => 'XBOX console') user = User.create(:name => 'david') - + vote = user.vote_for(item) vote.created_at = 2.days.ago vote.save - + vote = user.vote_for(another_item) vote.created_at = 3.days.from_now vote.save - + assert_equal 1, Item.tally(:start_at => 3.days.ago, :end_at => 2.days.from_now).length assert_equal 2, Item.tally(:start_at => 3.days.ago, :end_at => 4.days.from_now).length end - + def test_rank_tally_empty item = Item.create(:name => 'XBOX', :description => 'XBOX console') - + assert_equal 0, Item.rank_tally.length end - + def test_rank_tally_starts_at item = Item.create(:name => 'XBOX', :description => 'XBOX console') user = User.create(:name => 'david') - + vote = user.vote_for(item) vote.created_at = 3.days.ago vote.save - + assert_equal 0, Item.rank_tally(:start_at => 2.days.ago).length assert_equal 1, Item.rank_tally(:start_at => 4.days.ago).length end @@ -163,64 +166,64 @@ def test_rank_tally_starts_at def test_rank_tally_end_at item = Item.create(:name => 'XBOX', :description => 'XBOX console') user = User.create(:name => 'david') - + vote = user.vote_for(item) vote.created_at = 3.days.from_now vote.save - + assert_equal 0, Item.rank_tally(:end_at => 2.days.from_now).length assert_equal 1, Item.rank_tally(:end_at => 4.days.from_now).length end - + def test_rank_tally_between_start_at_end_at item = Item.create(:name => 'XBOX', :description => 'XBOX console') another_item = Item.create(:name => 'XBOX', :description => 'XBOX console') user = User.create(:name => 'david') - + vote = user.vote_for(item) vote.created_at = 2.days.ago vote.save - + vote = user.vote_for(another_item) vote.created_at = 3.days.from_now vote.save - + assert_equal 1, Item.rank_tally(:start_at => 3.days.ago, :end_at => 2.days.from_now).length assert_equal 2, Item.rank_tally(:start_at => 3.days.ago, :end_at => 4.days.from_now).length end - + def test_rank_tally_inclusion user = User.create(:name => 'david') item = Item.create(:name => 'XBOX', :description => 'XBOX console') item_not_included = Item.create(:name => 'Playstation', :description => 'Playstation console') - + assert_not_nil user.vote_for(item) - + assert (Item.rank_tally.include? item) assert (not Item.rank_tally.include? item_not_included) end - + def test_rank_tally_default_ordering user = User.create(:name => 'david') item_for = Item.create(:name => 'XBOX', :description => 'XBOX console') item_against = Item.create(:name => 'Playstation', :description => 'Playstation console') - + assert_not_nil user.vote_for(item_for) assert_not_nil user.vote_against(item_against) - + assert_equal item_for, Item.rank_tally[0] assert_equal item_against, Item.rank_tally[1] end - + def test_rank_tally_ascending_ordering user = User.create(:name => 'david') item_for = Item.create(:name => 'XBOX', :description => 'XBOX console') item_against = Item.create(:name => 'Playstation', :description => 'Playstation console') - + assert_not_nil user.vote_for(item_for) assert_not_nil user.vote_against(item_against) - + assert_equal item_for, Item.rank_tally(:ascending => true)[1] assert_equal item_against, Item.rank_tally(:ascending => true)[0] end -end \ No newline at end of file +end From 785c1c4986f2e9db01bddadc5e2597ceed1a9d2a Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Thu, 1 Sep 2011 00:10:55 +1000 Subject: [PATCH 038/138] Updates to the README. Renamed rank_tally to the more intuitive plusminus_tally. --- README.markdown | 15 +++---- lib/acts_as_voteable.rb | 94 +++++++++++++++++++++-------------------- lib/has_karma.rb | 2 +- 3 files changed, 57 insertions(+), 54 deletions(-) diff --git a/README.markdown b/README.markdown index 1a7272f..74d1806 100644 --- a/README.markdown +++ b/README.markdown @@ -87,7 +87,7 @@ You can easily retrieve voteable object collections based on the properties of t :order => "items.name DESC" }) -This will select the Items with between 1 and 10,000 votes, the votes having been cast within the last two weeks (not including today), then display the 10 last items in an alphabetical list. +This will select the Items with between 1 and 10,000 votes, the votes having been cast within the last two weeks (not including today), then display the 10 last items in an alphabetical list. This tallies all votes, regardless of whether they are +1 (up) or -1 (down). ##### Tally Options: :start_at - Restrict the votes to those created after a certain time @@ -98,13 +98,12 @@ This will select the Items with between 1 and 10,000 votes, the votes having bee :at_least - Item must have at least X votes :at_most - Item may not have more than X votes -##### Tallying Rank +##### Tallying Rank ("Plusminus") -Similar to tallying votes, but this will actually return voteable object collections based on a rating -system where up votes and down votes get equal rating. For Instance, a voteable with 3 upvotes and 2 -downvotes will have a rating in this instance of 1. +This is similar to tallying votes, but this will return voteable object collections based on the sum of the differences between up and down votes (ups are +1, downs are -1). For Instance, a voteable with 3 upvotes and 2 +downvotes will have a plusminus of 1. -##### Rank_Tally Options: +##### Plusminus Tally Options: :start_at - Restrict the votes to those created after a certain time :end_at - Restrict the votes to those created before a certain time :conditions - A piece of SQL conditions to add to the query @@ -117,7 +116,7 @@ downvotes will have a rating in this instance of 1. positiveVoteCount = voteable.votes_for negativeVoteCount = voteable.votes_against - plusminus = voteable.plusminus # Votes for minus votes against. + plusminus = voteable.plusminus # Votes for, minus votes against. voter.voted_for?(voteable) # True if the voter voted for this object. voter.vote_count(:up | :down | :all) # returns the count of +1, -1, or all votes @@ -134,7 +133,7 @@ ThumbsUp by default only allows one vote per user. This can be changed by removi validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id] -#### In the migration: +#### In the migration, the unique index: add_index :votes, ["voter_id", "voter_type", "voteable_id", "voteable_type"], :unique => true, :name => "uniq_one_vote_only" diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index bebcd74..d684455 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -15,59 +15,63 @@ def acts_as_voteable end module SingletonMethods - + # The point of this function is to return rankings based on the difference between up and down votes # assuming equal weighting (i.e. a user with 1 up vote and 1 down vote has a Vote_Total of 0. # First the votes table is joined twiced so that the Vote_Total can be calculated for every ID # Then this table is joined against the specific table passed to this function to allow for # ranking of the items within that table based on the difference between up and down votes. # Options: - # :start_at - Restrict the votes to those created after a certain time - # :end_at - Restrict the votes to those created before a certain time - # :conditions - A piece of SQL conditions to add to the query - # :limit - The maximum number of voteables to return - # :ascending - Default false - normal order DESC (i.e. highest rank to lowest) - # :at_least - Item must have at least X votes - # :at_most - Item may not have more than X votes - def rank_tally(*args) - options = args.extract_options! - - tsub0 = Vote - tsub0 = tsub0.where("vote = ?", false) - tsub0 = tsub0.where("voteable_type = ?", self.name) - tsub0 = tsub0.group("voteable_id") - tsub0 = tsub0.select("DISTINCT voteable_id, COUNT(vote) as Votes_Against") - - tsub1 = Vote - tsub1 = tsub1.where("vote = ?", true) - tsub1 = tsub1.where("voteable_type = ?", self.name) - tsub1 = tsub1.group("voteable_id") - tsub1 = tsub1.select("DISTINCT voteable_id, COUNT(vote) as Votes_For") - - t = self.joins("LEFT OUTER JOIN (SELECT DISTINCT #{Vote.table_name}.*, - (COALESCE(vfor.Votes_For, 0)-COALESCE(against.Votes_Against, 0)) AS Vote_Total - FROM (#{Vote.table_name} LEFT JOIN - (#{tsub0.to_sql}) AS against ON #{Vote.table_name}.voteable_id = against.voteable_id) - LEFT JOIN - (#{tsub1.to_sql}) as vfor ON #{Vote.table_name}.voteable_id = vfor.voteable_id) - AS joined_#{Vote.table_name} ON #{self.table_name}.#{self.primary_key} = - joined_#{Vote.table_name}.voteable_id") - - t = t.where("joined_#{Vote.table_name}.voteable_type = '#{self.name}'") - t = t.group("joined_#{Vote.table_name}.voteable_id, joined_#{Vote.table_name}.Vote_Total, #{column_names_for_tally}") - t = t.limit(options[:limit]) if options[:limit] - t = t.where("joined_#{Vote.table_name}.created_at >= ?", options[:start_at]) if options[:start_at] - t = t.where("joined_#{Vote.table_name}.created_at <= ?", options[:end_at]) if options[:end_at] - t = t.where(options[:conditions]) if options[:conditions] - t = options[:ascending] ? t.order("joined_#{Vote.table_name}.Vote_Total") : t.order("joined_#{Vote.table_name}.Vote_Total DESC") - - t = t.having(["COUNT(joined_#{Vote.table_name}.voteable_id) > 0", - (options[:at_least] ? "joined_votes.Vote_Total >= #{sanitize(options[:at_least])}" : nil), - (options[:at_most] ? "joined_votes.Vote_Total <= #{sanitize(options[:at_most])}" : nil) - ].compact.join(' AND ')) + # :start_at - Restrict the votes to those created after a certain time + # :end_at - Restrict the votes to those created before a certain time + # :ascending - Default false - normal order DESC (i.e. highest rank to lowest) + # :at_least - Default 1 - Item must have at least X votes + # :at_most - Item may not have more than X votes + def plusminus_tally(*args) + options = args.extract_options! - t.select("#{self.table_name}.*, joined_#{Vote.table_name}.Vote_Total") + tsub0 = Vote + tsub0 = tsub0.where("vote = ?", false) + tsub0 = tsub0.where("voteable_type = ?", self.name) + tsub0 = tsub0.group("voteable_id") + tsub0 = tsub0.select("DISTINCT voteable_id, COUNT(vote) as votes_against") + + tsub1 = Vote + tsub1 = tsub1.where("vote = ?", true) + tsub1 = tsub1.where("voteable_type = ?", self.name) + tsub1 = tsub1.group("voteable_id") + tsub1 = tsub1.select("DISTINCT voteable_id, COUNT(vote) as votes_for") + + t = self.joins("LEFT OUTER JOIN (SELECT DISTINCT #{Vote.table_name}.*, + (COALESCE(vfor.votes_for, 0)-COALESCE(against.votes_against, 0)) AS vote_total + FROM (#{Vote.table_name} LEFT JOIN + (#{tsub0.to_sql}) AS against ON #{Vote.table_name}.voteable_id = against.voteable_id) + LEFT JOIN + (#{tsub1.to_sql}) as vfor ON #{Vote.table_name}.voteable_id = vfor.voteable_id) + AS joined_#{Vote.table_name} ON #{self.table_name}.#{self.primary_key} = + joined_#{Vote.table_name}.voteable_id") + + t = t.where("joined_#{Vote.table_name}.voteable_type = '#{self.name}'") + t = t.group("joined_#{Vote.table_name}.voteable_id, joined_#{Vote.table_name}.vote_total, #{column_names_for_tally}") + t = t.where("joined_#{Vote.table_name}.created_at >= ?", options[:start_at]) if options[:start_at] + t = t.where("joined_#{Vote.table_name}.created_at <= ?", options[:end_at]) if options[:end_at] + t = options[:ascending] ? t.order("joined_#{Vote.table_name}.vote_total") : t.order("joined_#{Vote.table_name}.vote_total DESC") + + t = t.having([ + "COUNT(joined_#{Vote.table_name}.voteable_id) > 0", + (options[:at_least] ? + "joined_#{Vote.table_name}.vote_total >= #{sanitize(options[:at_least])}" : nil + ), + (options[:at_most] ? + "joined_#{Vote.table_name}.vote_total <= #{sanitize(options[:at_most])}" : nil + ) + ].compact.join(' AND ')) + + t.select("#{self.table_name}.*, joined_#{Vote.table_name}.vote_total") end + + # #rank_tally is depreciated. + alias_method :rank_tally, :plusminus_tally # Calculate the vote counts for all voteables of my type. # This method returns all voteables with at least one vote. diff --git a/lib/has_karma.rb b/lib/has_karma.rb index b593dae..bc41460 100644 --- a/lib/has_karma.rb +++ b/lib/has_karma.rb @@ -22,7 +22,7 @@ module SingletonMethods ## Not yet implemented. Don't use it! # Find the most popular users def find_most_karmic - find(:all) + self.all end end From d148513e963110f406a850d3dba886847d49d46a Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Thu, 1 Sep 2011 23:08:01 +1000 Subject: [PATCH 039/138] Moved to simplecov instead of rcov for Ruby 1.9.x. --- .gitignore | 1 + Gemfile | 10 +++++----- Rakefile | 11 ++--------- VERSION | 2 +- test/{helper.rb => test_helper.rb} | 2 ++ test/test_thumbs_up.rb | 2 +- 6 files changed, 12 insertions(+), 16 deletions(-) rename test/{helper.rb => test_helper.rb} (98%) diff --git a/.gitignore b/.gitignore index c8993f8..4b89f1b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.gem pkg/* Gemfile.lock +coverage/ diff --git a/Gemfile b/Gemfile index c474297..6225586 100644 --- a/Gemfile +++ b/Gemfile @@ -1,13 +1,13 @@ source :rubygems -gem 'activerecord', '3.0.3' +gem 'activerecord' group :development do - gem "bundler", "~> 1.0.0" - gem "jeweler", "~> 1.5.2" - gem "rcov", ">= 0" + gem 'bundler' + gem 'jeweler' + gem 'simplecov' end group :test do - gem 'sqlite3-ruby', '1.3.2' + gem 'sqlite3' end \ No newline at end of file diff --git a/Rakefile b/Rakefile index 844fcc4..bc4649e 100644 --- a/Rakefile +++ b/Rakefile @@ -26,18 +26,11 @@ Jeweler::RubygemsDotOrgTasks.new require 'rake/testtask' Rake::TestTask.new(:test) do |test| test.libs << 'lib' << 'test' - test.pattern = 'test/**/test_*.rb' + test.test_files = Dir.glob("test/**/*_test.rb") test.verbose = true end -require 'rcov/rcovtask' -Rcov::RcovTask.new do |test| - test.libs << 'test' - test.pattern = 'test/**/test_*.rb' - test.verbose = true -end - -require 'rake/rdoctask' +require 'rdoc/task' Rake::RDocTask.new do |rdoc| version = File.exist?('VERSION') ? File.read('VERSION') : "" diff --git a/VERSION b/VERSION index 267577d..2b7c5ae 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.4.1 +0.4.2 diff --git a/test/helper.rb b/test/test_helper.rb similarity index 98% rename from test/helper.rb rename to test/test_helper.rb index 1e18ab2..1f8834b 100644 --- a/test/helper.rb +++ b/test/test_helper.rb @@ -1,3 +1,5 @@ +require 'simplecov' +SimpleCov.start require 'test/unit' $LOAD_PATH.unshift(File.dirname(__FILE__)) diff --git a/test/test_thumbs_up.rb b/test/test_thumbs_up.rb index 223dc72..26cc112 100644 --- a/test/test_thumbs_up.rb +++ b/test/test_thumbs_up.rb @@ -1,4 +1,4 @@ -require 'helper' +require File.join(File.expand_path(File.dirname(__FILE__)), 'test_helper') class TestThumbsUp < Test::Unit::TestCase def setup From d0cb558c87be2a7e9c00a42bd137c0194b3fbb40 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Thu, 15 Sep 2011 15:38:12 +1000 Subject: [PATCH 040/138] Unicode chars in gemspec were blowing up Pow.cx in development. --- Rakefile | 1 - VERSION | 2 +- thumbs_up.gemspec | 66 ++++++++++++++++++++++++++++------------------- 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/Rakefile b/Rakefile index bc4649e..13ea1ae 100644 --- a/Rakefile +++ b/Rakefile @@ -33,7 +33,6 @@ end require 'rdoc/task' Rake::RDocTask.new do |rdoc| version = File.exist?('VERSION') ? File.read('VERSION') : "" - rdoc.rdoc_dir = 'rdoc' rdoc.title = "leaderboard #{version}" rdoc.rdoc_files.include('README*') diff --git a/VERSION b/VERSION index 2b7c5ae..17b2ccd 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.4.2 +0.4.3 diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index de3d2bb..a1b5ca8 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -1,51 +1,63 @@ # Generated by jeweler # DO NOT EDIT THIS FILE DIRECTLY -# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command +# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' # -*- encoding: utf-8 -*- Gem::Specification.new do |s| - s.name = %q{thumbs_up} - s.version = IO.read('./VERSION') + s.name = "thumbs_up" + s.version = "0.4.3" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= - s.authors = ["Brady Bouchard", "Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnętrzak"] - s.date = Date.today.to_s - s.description = %q{ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com.} - s.email = %q{brady@ldawn.com} + s.authors = ["Brady Bouchard", "Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wn\u{119}trzak"] + s.date = "2011-09-15" + s.description = "ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com." + s.email = "brady@ldawn.com" s.extra_rdoc_files = [ "README.markdown" ] s.files = [ - ".gitignore", - "CHANGELOG.markdown", - "MIT-LICENSE", - "README.markdown", - "Rakefile", - "VERSION", - "lib/acts_as_voteable.rb", - "lib/acts_as_voter.rb", - "lib/generators/thumbs_up/templates/migration.rb", - "lib/generators/thumbs_up/templates/vote.rb", - "lib/generators/thumbs_up/thumbs_up_generator.rb", - "lib/has_karma.rb", - "lib/thumbs_up.rb", - "rails/init.rb", - "thumbs_up.gemspec" + "CHANGELOG.markdown", + "Gemfile", + "MIT-LICENSE", + "README.markdown", + "Rakefile", + "VERSION", + "lib/acts_as_voteable.rb", + "lib/acts_as_voter.rb", + "lib/generators/thumbs_up/templates/migration.rb", + "lib/generators/thumbs_up/templates/vote.rb", + "lib/generators/thumbs_up/thumbs_up_generator.rb", + "lib/has_karma.rb", + "lib/thumbs_up.rb", + "rails/init.rb", + "test/test_helper.rb", + "test/test_thumbs_up.rb", + "thumbs_up.gemspec" ] - s.homepage = %q{http://github.com/brady8/thumbs_up} - s.rdoc_options = ["--charset=UTF-8"] + s.homepage = "http://github.com/brady8/thumbs_up" s.require_paths = ["lib"] - s.rubygems_version = %q{1.3.7} - s.summary = %q{Voting for ActiveRecord with multiple vote sources and karma calculation.} + s.rubygems_version = "1.8.10" + s.summary = "Voting for ActiveRecord with multiple vote sources and karma calculation." if s.respond_to? :specification_version then - current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION s.specification_version = 3 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_runtime_dependency(%q, [">= 0"]) + s.add_development_dependency(%q, [">= 0"]) + s.add_development_dependency(%q, [">= 0"]) + s.add_development_dependency(%q, [">= 0"]) else + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) end else + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, [">= 0"]) end end From 12103854642b38416a6b69834ce1e6a45d8d44a2 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Tue, 8 Nov 2011 22:45:47 -0700 Subject: [PATCH 041/138] =?UTF-8?q?Apologies=20to=20Wojciech=20Wn=C4=99trz?= =?UTF-8?q?ak,=20but=20his=20last=20name=20was=20blowing=20up=20Rubygems?= =?UTF-8?q?=20on=20non-ASCII=20character.=20Removed=20for=20now.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- thumbs_up.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index a1b5ca8..6cb6136 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -5,10 +5,10 @@ Gem::Specification.new do |s| s.name = "thumbs_up" - s.version = "0.4.3" + s.version = "0.4.4" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= - s.authors = ["Brady Bouchard", "Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wn\u{119}trzak"] + s.authors = ["Brady Bouchard", "Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnetrzak"] s.date = "2011-09-15" s.description = "ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com." s.email = "brady@ldawn.com" From 04cdace4f1d156477f0c8a8c1d5a9a0713e50b13 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Sun, 27 Nov 2011 15:42:00 -0700 Subject: [PATCH 042/138] Added missing :limit option to #plusminus_tally. --- lib/acts_as_voteable.rb | 3 ++- test/test_helper.rb | 16 +++++++----- test/test_thumbs_up.rb | 57 ++++++++++++++++++++++++++--------------- 3 files changed, 49 insertions(+), 27 deletions(-) diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index d684455..0a3ef07 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -51,8 +51,9 @@ def plusminus_tally(*args) AS joined_#{Vote.table_name} ON #{self.table_name}.#{self.primary_key} = joined_#{Vote.table_name}.voteable_id") - t = t.where("joined_#{Vote.table_name}.voteable_type = '#{self.name}'") t = t.group("joined_#{Vote.table_name}.voteable_id, joined_#{Vote.table_name}.vote_total, #{column_names_for_tally}") + t = t.limit(options[:limit]) if options[:limit] + t = t.where("joined_#{Vote.table_name}.voteable_type = '#{self.name}'") t = t.where("joined_#{Vote.table_name}.created_at >= ?", options[:start_at]) if options[:start_at] t = t.where("joined_#{Vote.table_name}.created_at <= ?", options[:end_at]) if options[:end_at] t = options[:ascending] ? t.order("joined_#{Vote.table_name}.vote_total") : t.order("joined_#{Vote.table_name}.vote_total DESC") diff --git a/test/test_helper.rb b/test/test_helper.rb index 1f8834b..70c5d92 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -34,8 +34,9 @@ end create_table :items, :force => true do |t| - t.string :name - t.string :description + t.integer :user_id + t.string :name + t.string :description end end @@ -57,12 +58,15 @@ class Vote < ActiveRecord::Base validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id] end -class User < ActiveRecord::Base - acts_as_voter -end - class Item < ActiveRecord::Base acts_as_voteable + belongs_to :user +end + +class User < ActiveRecord::Base + acts_as_voter + has_many :items + has_karma(:items) end class Test::Unit::TestCase diff --git a/test/test_thumbs_up.rb b/test/test_thumbs_up.rb index 26cc112..7625e18 100644 --- a/test/test_thumbs_up.rb +++ b/test/test_thumbs_up.rb @@ -145,13 +145,13 @@ def test_tally_between_start_at_end_at assert_equal 2, Item.tally(:start_at => 3.days.ago, :end_at => 4.days.from_now).length end - def test_rank_tally_empty + def test_plusminus_tally_empty item = Item.create(:name => 'XBOX', :description => 'XBOX console') - assert_equal 0, Item.rank_tally.length + assert_equal 0, Item.plusminus_tally.length end - def test_rank_tally_starts_at + def test_plusminus_tally_starts_at item = Item.create(:name => 'XBOX', :description => 'XBOX console') user = User.create(:name => 'david') @@ -159,11 +159,11 @@ def test_rank_tally_starts_at vote.created_at = 3.days.ago vote.save - assert_equal 0, Item.rank_tally(:start_at => 2.days.ago).length - assert_equal 1, Item.rank_tally(:start_at => 4.days.ago).length + assert_equal 0, Item.plusminus_tally(:start_at => 2.days.ago).length + assert_equal 1, Item.plusminus_tally(:start_at => 4.days.ago).length end - def test_rank_tally_end_at + def test_plusminus_tally_end_at item = Item.create(:name => 'XBOX', :description => 'XBOX console') user = User.create(:name => 'david') @@ -171,11 +171,11 @@ def test_rank_tally_end_at vote.created_at = 3.days.from_now vote.save - assert_equal 0, Item.rank_tally(:end_at => 2.days.from_now).length - assert_equal 1, Item.rank_tally(:end_at => 4.days.from_now).length + assert_equal 0, Item.plusminus_tally(:end_at => 2.days.from_now).length + assert_equal 1, Item.plusminus_tally(:end_at => 4.days.from_now).length end - def test_rank_tally_between_start_at_end_at + def test_plusminus_tally_between_start_at_end_at item = Item.create(:name => 'XBOX', :description => 'XBOX console') another_item = Item.create(:name => 'XBOX', :description => 'XBOX console') user = User.create(:name => 'david') @@ -188,22 +188,22 @@ def test_rank_tally_between_start_at_end_at vote.created_at = 3.days.from_now vote.save - assert_equal 1, Item.rank_tally(:start_at => 3.days.ago, :end_at => 2.days.from_now).length - assert_equal 2, Item.rank_tally(:start_at => 3.days.ago, :end_at => 4.days.from_now).length + assert_equal 1, Item.plusminus_tally(:start_at => 3.days.ago, :end_at => 2.days.from_now).length + assert_equal 2, Item.plusminus_tally(:start_at => 3.days.ago, :end_at => 4.days.from_now).length end - def test_rank_tally_inclusion + def test_plusminus_tally_inclusion user = User.create(:name => 'david') item = Item.create(:name => 'XBOX', :description => 'XBOX console') item_not_included = Item.create(:name => 'Playstation', :description => 'Playstation console') assert_not_nil user.vote_for(item) - assert (Item.rank_tally.include? item) - assert (not Item.rank_tally.include? item_not_included) + assert (Item.plusminus_tally.include? item) + assert (not Item.plusminus_tally.include? item_not_included) end - def test_rank_tally_default_ordering + def test_plusminus_tally_default_ordering user = User.create(:name => 'david') item_for = Item.create(:name => 'XBOX', :description => 'XBOX console') item_against = Item.create(:name => 'Playstation', :description => 'Playstation console') @@ -211,11 +211,19 @@ def test_rank_tally_default_ordering assert_not_nil user.vote_for(item_for) assert_not_nil user.vote_against(item_against) - assert_equal item_for, Item.rank_tally[0] - assert_equal item_against, Item.rank_tally[1] + assert_equal item_for, Item.plusminus_tally[0] + assert_equal item_against, Item.plusminus_tally[1] + end + + def test_plusminus_tally_limit + users = (0..9).map{ |u| User.create(:name => "User #{u}") } + items = (0..9).map{ |u| Item.create(:name => "Item #{u}", :description => "Item #{u}") } + users.each{ |u| items.each { |i| u.vote_for(i) } } + assert_equal 10, Item.plusminus_tally.length + assert_equal 2, Item.plusminus_tally(:limit => 2).length end - def test_rank_tally_ascending_ordering + def test_plusminus_tally_ascending_ordering user = User.create(:name => 'david') item_for = Item.create(:name => 'XBOX', :description => 'XBOX console') item_against = Item.create(:name => 'Playstation', :description => 'Playstation console') @@ -223,7 +231,16 @@ def test_rank_tally_ascending_ordering assert_not_nil user.vote_for(item_for) assert_not_nil user.vote_against(item_against) - assert_equal item_for, Item.rank_tally(:ascending => true)[1] - assert_equal item_against, Item.rank_tally(:ascending => true)[0] + assert_equal item_for, Item.plusminus_tally(:ascending => true)[1] + assert_equal item_against, Item.plusminus_tally(:ascending => true)[0] + end + + def test_karma + users = (0..1).map{ |u| User.create(:name => "User #{u}") } + items = (0..1).map{ |u| users[0].items.create(:name => "Item #{u}", :description => "Item #{u}") } + users.each{ |u| items.each { |i| u.vote_for(i) } } + + assert_equal 4, users[0].karma + assert_equal 0, users[1].karma end end From d2e6369696a4799bce6ab58ee82d082d39ebc1a9 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Sun, 27 Nov 2011 15:46:13 -0700 Subject: [PATCH 043/138] Bumped version. --- VERSION | 2 +- thumbs_up.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 17b2ccd..0bfccb0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.4.3 +0.4.5 diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index 6cb6136..db406d5 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -5,7 +5,7 @@ Gem::Specification.new do |s| s.name = "thumbs_up" - s.version = "0.4.4" + s.version = File.read('./VERSION').strip s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Brady Bouchard", "Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnetrzak"] From 00fea110deb4e04e1c5cd203693c13c72a4935a8 Mon Sep 17 00:00:00 2001 From: zeantsoi Date: Fri, 9 Dec 2011 12:38:31 -0700 Subject: [PATCH 044/138] Updated README to document how to query the direction of a voter's vote. --- README.markdown | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 74d1806..cd81c3b 100644 --- a/README.markdown +++ b/README.markdown @@ -72,7 +72,15 @@ Usage Did the first user vote for the Car with id = 2 already? u = User.first - u.voted_on?(Car.find(2)) + u.vote_for(Car.find(2)) + u.voted_on?(Car.find(2)) #=> true + +Did the first user vote for or agains the Car with id = 2? + + u = User.first + u.vote_for(Car.find(2)) + u.voted_for?(Car.find(2)) #=> true + u.voted_against?(Car.find(2)) #=> false #### Tallying Votes From d3e6161abf0c6daeeaddb3fce28a9950aa43b7c6 Mon Sep 17 00:00:00 2001 From: zeantsoi Date: Fri, 9 Dec 2011 12:39:31 -0700 Subject: [PATCH 045/138] Updated README to document how to query the direction of a voter's vote. --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index cd81c3b..c5431a8 100644 --- a/README.markdown +++ b/README.markdown @@ -75,7 +75,7 @@ Did the first user vote for the Car with id = 2 already? u.vote_for(Car.find(2)) u.voted_on?(Car.find(2)) #=> true -Did the first user vote for or agains the Car with id = 2? +Did the first user vote for or against the Car with id = 2? u = User.first u.vote_for(Car.find(2)) From 9762762a109e3d62630c43272862e97946c737c0 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Fri, 30 Dec 2011 22:06:33 -0700 Subject: [PATCH 046/138] Added :conditions argument to #plusminus_tally. --- lib/acts_as_voteable.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index 0a3ef07..3fab614 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -27,6 +27,7 @@ module SingletonMethods # :ascending - Default false - normal order DESC (i.e. highest rank to lowest) # :at_least - Default 1 - Item must have at least X votes # :at_most - Item may not have more than X votes + # :conditions - (string) Extra conditions, if you'd like. def plusminus_tally(*args) options = args.extract_options! @@ -56,6 +57,7 @@ def plusminus_tally(*args) t = t.where("joined_#{Vote.table_name}.voteable_type = '#{self.name}'") t = t.where("joined_#{Vote.table_name}.created_at >= ?", options[:start_at]) if options[:start_at] t = t.where("joined_#{Vote.table_name}.created_at <= ?", options[:end_at]) if options[:end_at] + t = t.where(options[:conditions]) if options[:conditions] t = options[:ascending] ? t.order("joined_#{Vote.table_name}.vote_total") : t.order("joined_#{Vote.table_name}.vote_total DESC") t = t.having([ From b8f65372a6a7d18efe192543ed7aaf668e0d9f7f Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Fri, 30 Dec 2011 22:07:43 -0700 Subject: [PATCH 047/138] Version bump. --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 0bfccb0..ef52a64 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.4.5 +0.4.6 From dd989d8e26a7362e7ed95e72b26cd56d13157301 Mon Sep 17 00:00:00 2001 From: DFischer Date: Mon, 16 Jan 2012 05:29:47 -0800 Subject: [PATCH 048/138] Updated README doc --- README.markdown | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.markdown b/README.markdown index c5431a8..7638eb6 100644 --- a/README.markdown +++ b/README.markdown @@ -95,7 +95,8 @@ You can easily retrieve voteable object collections based on the properties of t :order => "items.name DESC" }) -This will select the Items with between 1 and 10,000 votes, the votes having been cast within the last two weeks (not including today), then display the 10 last items in an alphabetical list. This tallies all votes, regardless of whether they are +1 (up) or -1 (down). +This will select the Items with between 1 and 10,000 votes, the votes having been cast within the last two weeks (not including today), then display the 10 last items in an alphabetical list. *This tallies all votes, regardless of whether they are +1 (up) or -1 (down).* + ##### Tally Options: :start_at - Restrict the votes to those created after a certain time @@ -108,9 +109,20 @@ This will select the Items with between 1 and 10,000 votes, the votes having bee ##### Tallying Rank ("Plusminus") +**You most likely want to use this over the normal tally** + This is similar to tallying votes, but this will return voteable object collections based on the sum of the differences between up and down votes (ups are +1, downs are -1). For Instance, a voteable with 3 upvotes and 2 downvotes will have a plusminus of 1. + @items = Item.plusminus_tally( + { :at_least => 1, + :at_most => 10000, + :start_at => 2.weeks.ago, + :end_at => 1.day.ago, + :limit => 10, + :order => "items.name DESC" + }) + ##### Plusminus Tally Options: :start_at - Restrict the votes to those created after a certain time :end_at - Restrict the votes to those created before a certain time @@ -152,4 +164,4 @@ You can also use `--unique-voting false` when running the generator command: Credits ======= -Basic scaffold is from Peter Jackson's work on VoteFu / ActsAsVoteable. All code updated for Rails 3, cleaned up for speed and clarity, karma calculation fixed, and (hopefully) zero introduced bugs. \ No newline at end of file +Basic scaffold is from Peter Jackson's work on VoteFu / ActsAsVoteable. All code updated for Rails 3, cleaned up for speed and clarity, karma calculation fixed, and (hopefully) zero introduced bugs. From 391386225557a9ff389786c595be2b73ce659920 Mon Sep 17 00:00:00 2001 From: DFischer Date: Mon, 16 Jan 2012 05:30:23 -0800 Subject: [PATCH 049/138] Heading level --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 7638eb6..863783d 100644 --- a/README.markdown +++ b/README.markdown @@ -107,7 +107,7 @@ This will select the Items with between 1 and 10,000 votes, the votes having bee :at_least - Item must have at least X votes :at_most - Item may not have more than X votes -##### Tallying Rank ("Plusminus") +#### Tallying Rank ("Plusminus") **You most likely want to use this over the normal tally** From 20b1e7887d3d41140ce9a8cbbe6845756a47143c Mon Sep 17 00:00:00 2001 From: Joe Ellis Date: Mon, 16 Jan 2012 12:35:33 -0600 Subject: [PATCH 050/138] added method unvote_for --- lib/acts_as_voter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/acts_as_voter.rb b/lib/acts_as_voter.rb index dbf3a49..0639de6 100644 --- a/lib/acts_as_voter.rb +++ b/lib/acts_as_voter.rb @@ -77,13 +77,13 @@ def vote_exclusively_against(voteable) def vote(voteable, options = {}) raise ArgumentError, "you must specify :up or :down in order to vote" unless options[:direction] && [:up, :down].include?(options[:direction].to_sym) if options[:exclusive] - self.clear_votes(voteable) + self.unvote_for(voteable) end direction = (options[:direction].to_sym == :up) Vote.create!(:vote => direction, :voteable => voteable, :voter => self) end - def clear_votes(voteable) + def unvote_for(voteable) Vote.where( :voter_id => self.id, :voter_type => self.class.name, From 5770b7490f113df4c0a4d28d926582064279aa7e Mon Sep 17 00:00:00 2001 From: Joe Ellis Date: Mon, 16 Jan 2012 12:42:04 -0600 Subject: [PATCH 051/138] added unvote_for method to README --- README.markdown | 2 ++ test/test_thumbs_up.rb | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.markdown b/README.markdown index c5431a8..09ca846 100644 --- a/README.markdown +++ b/README.markdown @@ -67,6 +67,8 @@ Usage voter.vote_exclusively_for(voteable) # Removes any previous votes by that particular voter, and votes for. voter.vote_exclusively_against(voteable) # Removes any previous votes by that particular voter, and votes against. + vote.unvote_for(voteable) #Clears all votes for that user + ### Querying votes Did the first user vote for the Car with id = 2 already? diff --git a/test/test_thumbs_up.rb b/test/test_thumbs_up.rb index 7625e18..c418df1 100644 --- a/test/test_thumbs_up.rb +++ b/test/test_thumbs_up.rb @@ -50,10 +50,10 @@ def test_acts_as_voter_instance_methods assert_not_nil user_for.vote_exclusively_against(item) assert_equal true, user_for.voted_against?(item) - user_for.clear_votes(item) + user_for.unvote_for(item) assert_equal 0, user_for.vote_count - user_against.clear_votes(item) + user_against.unvote_for(item) assert_equal 0, user_against.vote_count assert_raises(ArgumentError) do From cce27899c94c0bb6009dc4413ea8baf4b5ef50a8 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Wed, 18 Jan 2012 08:00:05 +1000 Subject: [PATCH 052/138] Update README.markdown --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 09ca846..1e04aa1 100644 --- a/README.markdown +++ b/README.markdown @@ -67,7 +67,7 @@ Usage voter.vote_exclusively_for(voteable) # Removes any previous votes by that particular voter, and votes for. voter.vote_exclusively_against(voteable) # Removes any previous votes by that particular voter, and votes against. - vote.unvote_for(voteable) #Clears all votes for that user + vote.unvote_for(voteable) # Clears all votes for that user ### Querying votes From 2ad8cef8e2221f429a43ea8d37eee78d2dfba433 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Fri, 17 Feb 2012 18:51:48 +1000 Subject: [PATCH 053/138] Version bump to 0.5.0, with #tally speedups! --- CHANGELOG.markdown => CHANGELOG.md | 0 Gemfile | 14 +- MIT-LICENSE | 2 +- README.markdown => README.md | 59 +++------ Rakefile | 30 ++--- VERSION | 1 - lib/acts_as_voteable.rb | 122 ++++-------------- lib/thumbs_up/version.rb | 3 + test/test_helper.rb | 16 ++- test/{test_thumbs_up.rb => thumbs_up_test.rb} | 82 ++++++++---- thumbs_up.gemspec | 68 +++------- 11 files changed, 143 insertions(+), 254 deletions(-) rename CHANGELOG.markdown => CHANGELOG.md (100%) rename README.markdown => README.md (70%) delete mode 100644 VERSION create mode 100644 lib/thumbs_up/version.rb rename test/{test_thumbs_up.rb => thumbs_up_test.rb} (69%) diff --git a/CHANGELOG.markdown b/CHANGELOG.md similarity index 100% rename from CHANGELOG.markdown rename to CHANGELOG.md diff --git a/Gemfile b/Gemfile index 6225586..901f575 100644 --- a/Gemfile +++ b/Gemfile @@ -1,13 +1 @@ -source :rubygems - -gem 'activerecord' - -group :development do - gem 'bundler' - gem 'jeweler' - gem 'simplecov' -end - -group :test do - gem 'sqlite3' -end \ No newline at end of file +gemspec \ No newline at end of file diff --git a/MIT-LICENSE b/MIT-LICENSE index 1e60e98..8d89dea 100644 --- a/MIT-LICENSE +++ b/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2010 Brady Bouchard (ldawn.com) +Copyright (c) 2011 Brady Bouchard (thewellinspired.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.markdown b/README.md similarity index 70% rename from README.markdown rename to README.md index 8223c5a..d636da9 100644 --- a/README.markdown +++ b/README.md @@ -1,6 +1,8 @@ ThumbsUp ======= +**Note: Version 0.5.x is a breaking change for #plusminus_tally and #tally, with > 50% speedups.** + A ridiculously straightforward and simple package 'o' code to enable voting in your application, a la stackoverflow.com, etc. Allows an arbitrary number of entities (users, etc.) to vote on models. @@ -88,51 +90,17 @@ Did the first user vote for or against the Car with id = 2? You can easily retrieve voteable object collections based on the properties of their votes: - @items = Item.tally( - { :at_least => 1, - :at_most => 10000, - :start_at => 2.weeks.ago, - :end_at => 1.day.ago, - :limit => 10, - :order => "items.name DESC" - }) - -This will select the Items with between 1 and 10,000 votes, the votes having been cast within the last two weeks (not including today), then display the 10 last items in an alphabetical list. *This tallies all votes, regardless of whether they are +1 (up) or -1 (down).* + @items = Item.tally.limit(10).where('created_at > ?', 2.days.ago).having('vote_count < 10') - -##### Tally Options: - :start_at - Restrict the votes to those created after a certain time - :end_at - Restrict the votes to those created before a certain time - :conditions - A piece of SQL conditions to add to the query - :limit - The maximum number of voteables to return - :order - A piece of SQL to order by. Eg 'votes.count desc' or 'voteable.created_at desc' - :at_least - Item must have at least X votes - :at_most - Item may not have more than X votes +This will select the Items with less than 10 votes, the votes having been cast within the last two days, with a limit of 10 items. *This tallies all votes, regardless of whether they are +1 (up) or -1 (down).* The #tally method returns an ActiveRecord Relation, so you can chain the normal method calls on to it. #### Tallying Rank ("Plusminus") **You most likely want to use this over the normal tally** -This is similar to tallying votes, but this will return voteable object collections based on the sum of the differences between up and down votes (ups are +1, downs are -1). For Instance, a voteable with 3 upvotes and 2 -downvotes will have a plusminus of 1. - - @items = Item.plusminus_tally( - { :at_least => 1, - :at_most => 10000, - :start_at => 2.weeks.ago, - :end_at => 1.day.ago, - :limit => 10, - :order => "items.name DESC" - }) - -##### Plusminus Tally Options: - :start_at - Restrict the votes to those created after a certain time - :end_at - Restrict the votes to those created before a certain time - :conditions - A piece of SQL conditions to add to the query - :limit - The maximum number of voteables to return - :ascending - Boolean Default false. If specified true, results will be returned in ascending order (from bottom up) - :at_least - Item must have at least X votes - :at_most - Item may not have more than X votes +This is similar to tallying votes, but this will return voteable object collections based on the sum of the differences between up and down votes (ups are +1, downs are -1). For Instance, a voteable with 3 upvotes and 2 downvotes will have a plusminus of 1. + + @items = Item.plusminus_tally.limit(10).where('created_at > ?', 2.days.ago).having('plusminus > 10') #### Lower level queries @@ -163,6 +131,19 @@ You can also use `--unique-voting false` when running the generator command: rails generate thumbs_up --unique-voting false +#### Testing ThumbsUp + +Testing is a bit more than trivial now as our #tally and #plusminus_tally queries don't function properly under SQLite. To set up for testing: + +``` +$ mysql -uroot # You may have set a password locally. Change as needed. + > GRANT ALL PRIVILEGES ON 'thumbs_up_test' to 'test'@'localhost' IDENTIFIED BY 'test'; + > CREATE DATABASE 'thumbs_up_test'; + > exit; + +$ rake # Runs the test suite. +``` + Credits ======= diff --git a/Rakefile b/Rakefile index 13ea1ae..baf8214 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,9 @@ # encoding: UTF-8 require 'rubygems' -require 'bundler' +require 'bundler' unless defined?(Bundler) + +$LOAD_PATH.unshift File.expand_path("../lib", __FILE__) +require 'thumbs_up/version' begin Bundler.setup(:default, :development) @@ -11,18 +14,6 @@ rescue Bundler::BundlerError => e end require 'rake' -require 'jeweler' -Jeweler::Tasks.new do |gem| - gem.name = "thumbs_up" - gem.summary = "Voting for ActiveRecord with multiple vote sources and karma calculation." - gem.description = "ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com." - gem.email = "brady@ldawn.com" - gem.homepage = "http://github.com/brady8/thumbs_up" - gem.authors = ["Brady Bouchard", "Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnętrzak"] - # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings -end -Jeweler::RubygemsDotOrgTasks.new - require 'rake/testtask' Rake::TestTask.new(:test) do |test| test.libs << 'lib' << 'test' @@ -30,13 +21,12 @@ Rake::TestTask.new(:test) do |test| test.verbose = true end -require 'rdoc/task' -Rake::RDocTask.new do |rdoc| - version = File.exist?('VERSION') ? File.read('VERSION') : "" - rdoc.rdoc_dir = 'rdoc' - rdoc.title = "leaderboard #{version}" - rdoc.rdoc_files.include('README*') - rdoc.rdoc_files.include('lib/**/*.rb') +task :build do + system "gem build thumbs_up.gemspec" +end + +task :release => :build do + system "gem push thumbs_up-#{ThumbsUp::VERSION}" end task :default => :test \ No newline at end of file diff --git a/VERSION b/VERSION deleted file mode 100644 index ef52a64..0000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.4.6 diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index 3fab614..003d3fc 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -16,108 +16,35 @@ def acts_as_voteable module SingletonMethods - # The point of this function is to return rankings based on the difference between up and down votes - # assuming equal weighting (i.e. a user with 1 up vote and 1 down vote has a Vote_Total of 0. - # First the votes table is joined twiced so that the Vote_Total can be calculated for every ID - # Then this table is joined against the specific table passed to this function to allow for - # ranking of the items within that table based on the difference between up and down votes. - # Options: - # :start_at - Restrict the votes to those created after a certain time - # :end_at - Restrict the votes to those created before a certain time - # :ascending - Default false - normal order DESC (i.e. highest rank to lowest) - # :at_least - Default 1 - Item must have at least X votes - # :at_most - Item may not have more than X votes - # :conditions - (string) Extra conditions, if you'd like. - def plusminus_tally(*args) - options = args.extract_options! - - tsub0 = Vote - tsub0 = tsub0.where("vote = ?", false) - tsub0 = tsub0.where("voteable_type = ?", self.name) - tsub0 = tsub0.group("voteable_id") - tsub0 = tsub0.select("DISTINCT voteable_id, COUNT(vote) as votes_against") - - tsub1 = Vote - tsub1 = tsub1.where("vote = ?", true) - tsub1 = tsub1.where("voteable_type = ?", self.name) - tsub1 = tsub1.group("voteable_id") - tsub1 = tsub1.select("DISTINCT voteable_id, COUNT(vote) as votes_for") - - t = self.joins("LEFT OUTER JOIN (SELECT DISTINCT #{Vote.table_name}.*, - (COALESCE(vfor.votes_for, 0)-COALESCE(against.votes_against, 0)) AS vote_total - FROM (#{Vote.table_name} LEFT JOIN - (#{tsub0.to_sql}) AS against ON #{Vote.table_name}.voteable_id = against.voteable_id) - LEFT JOIN - (#{tsub1.to_sql}) as vfor ON #{Vote.table_name}.voteable_id = vfor.voteable_id) - AS joined_#{Vote.table_name} ON #{self.table_name}.#{self.primary_key} = - joined_#{Vote.table_name}.voteable_id") - - t = t.group("joined_#{Vote.table_name}.voteable_id, joined_#{Vote.table_name}.vote_total, #{column_names_for_tally}") - t = t.limit(options[:limit]) if options[:limit] - t = t.where("joined_#{Vote.table_name}.voteable_type = '#{self.name}'") - t = t.where("joined_#{Vote.table_name}.created_at >= ?", options[:start_at]) if options[:start_at] - t = t.where("joined_#{Vote.table_name}.created_at <= ?", options[:end_at]) if options[:end_at] - t = t.where(options[:conditions]) if options[:conditions] - t = options[:ascending] ? t.order("joined_#{Vote.table_name}.vote_total") : t.order("joined_#{Vote.table_name}.vote_total DESC") - - t = t.having([ - "COUNT(joined_#{Vote.table_name}.voteable_id) > 0", - (options[:at_least] ? - "joined_#{Vote.table_name}.vote_total >= #{sanitize(options[:at_least])}" : nil - ), - (options[:at_most] ? - "joined_#{Vote.table_name}.vote_total <= #{sanitize(options[:at_most])}" : nil - ) - ].compact.join(' AND ')) - - t.select("#{self.table_name}.*, joined_#{Vote.table_name}.vote_total") + # Calculate the plusminus for a group of voteables in one database query. + # This returns an Arel relation, so you can add conditions as you like chained on to + # this method call. + # i.e. Posts.tally.where('votes.created_at > ?', 2.days.ago) + def plusminus_tally + t = self.joins("LEFT OUTER JOIN #{Vote.table_name} ON #{self.table_name}.id = #{Vote.table_name}.voteable_id") + t = t.order("plusminus DESC") + t = t.group("#{self.table_name}.id") + t = t.select("#{self.table_name}.*") + t = t.select("SUM(CASE CAST(#{Vote.table_name}.vote AS UNSIGNED) WHEN 1 THEN 1 WHEN 0 THEN -1 ELSE 0 END) AS plusminus") + t = t.select("COUNT(#{Vote.table_name}.id) AS vote_count") end - + # #rank_tally is depreciated. alias_method :rank_tally, :plusminus_tally # Calculate the vote counts for all voteables of my type. - # This method returns all voteables with at least one vote. + # This method returns all voteables (even without any votes) by default. # The vote count for each voteable is available as #vote_count. - # - # Options: - # :start_at - Restrict the votes to those created after a certain time - # :end_at - Restrict the votes to those created before a certain time - # :conditions - A piece of SQL conditions to add to the query - # :limit - The maximum number of voteables to return - # :order - A piece of SQL to order by. Eg 'vote_count DESC' or 'voteable.created_at DESC' - # :at_least - Item must have at least X votes - # :at_most - Item may not have more than X votes + # This returns an Arel relation, so you can add conditions as you like chained on to + # this method call. + # i.e. Posts.tally.where('votes.created_at > ?', 2.days.ago) def tally(*args) - options = args.extract_options! - - # Use the explicit SQL statement throughout for Postgresql compatibility. - vote_count = "COUNT(#{Vote.table_name}.voteable_id)" - - t = self.where("#{Vote.table_name}.voteable_type = '#{self.name}'") - - # We join so that you can order by columns on the voteable model. - t = t.joins("LEFT OUTER JOIN #{Vote.table_name} ON #{self.table_name}.#{self.primary_key} = #{Vote.table_name}.voteable_id") - - t = t.group("#{Vote.table_name}.voteable_id, #{column_names_for_tally}") - t = t.limit(options[:limit]) if options[:limit] - t = t.where("#{Vote.table_name}.created_at >= ?", options[:start_at]) if options[:start_at] - t = t.where("#{Vote.table_name}.created_at <= ?", options[:end_at]) if options[:end_at] - t = t.where(options[:conditions]) if options[:conditions] - t = options[:order] ? t.order(options[:order]) : t.order("#{vote_count} DESC") - - # I haven't been able to confirm this bug yet, but Arel (2.0.7) currently blows up - # with multiple 'having' clauses. So we hack them all into one for now. - # If you have a more elegant solution, a pull request on Github would be greatly appreciated. - t = t.having([ - "#{vote_count} > 0", - (options[:at_least] ? "#{vote_count} >= #{sanitize(options[:at_least])}" : nil), - (options[:at_most] ? "#{vote_count} <= #{sanitize(options[:at_most])}" : nil) - ].compact.join(' AND ')) - # t = t.having("#{vote_count} > 0") - # t = t.having(["#{vote_count} >= ?", options[:at_least]]) if options[:at_least] - # t = t.having(["#{vote_count} <= ?", options[:at_most]]) if options[:at_most] - t.select("#{self.table_name}.*, COUNT(#{Vote.table_name}.voteable_id) AS vote_count") + t = self.joins("LEFT OUTER JOIN #{Vote.table_name} ON #{self.table_name}.id = #{Vote.table_name}.voteable_id") + t = t.order("vote_count DESC") + t = t.group("#{self.table_name}.id") + t = t.select("#{self.table_name}.*") + t = t.select("#{Vote.table_name}.*") + t = t.select("COUNT(#{Vote.table_name}.id) AS vote_count") end def column_names_for_tally @@ -129,11 +56,11 @@ def column_names_for_tally module InstanceMethods def votes_for - Vote.where(:voteable_id => id, :voteable_type => self.class.name, :vote => true).count + self.votes.where(:vote => true).count end def votes_against - Vote.where(:voteable_id => id, :voteable_type => self.class.name, :vote => false).count + self.votes.where(:vote => false).count end def percent_for @@ -162,7 +89,6 @@ def voted_by?(voter) 0 < Vote.where( :voteable_id => self.id, :voteable_type => self.class.name, - :voter_type => voter.class.name, :voter_id => voter.id ).count end diff --git a/lib/thumbs_up/version.rb b/lib/thumbs_up/version.rb new file mode 100644 index 0000000..b78886d --- /dev/null +++ b/lib/thumbs_up/version.rb @@ -0,0 +1,3 @@ +module ThumbsUp + VERSION = '0.5.0' +end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index 70c5d92..4ee0d77 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -7,10 +7,18 @@ require 'active_record' -ActiveRecord::Base.establish_connection( - :adapter => "sqlite3", - :database => ":memory:" -) +config = { + :adapter => 'mysql2', + :database => 'thumbs_up_test', + :username => 'test', + :password => 'test', + :socket => '/tmp/mysql.sock' +} + +ActiveRecord::Base.establish_connection(config) +ActiveRecord::Base.connection.drop_database config[:database] rescue nil +ActiveRecord::Base.connection.create_database config[:database] +ActiveRecord::Base.establish_connection(config) ActiveRecord::Migration.verbose = false diff --git a/test/test_thumbs_up.rb b/test/thumbs_up_test.rb similarity index 69% rename from test/test_thumbs_up.rb rename to test/thumbs_up_test.rb index c418df1..585f0a7 100644 --- a/test/test_thumbs_up.rb +++ b/test/thumbs_up_test.rb @@ -100,8 +100,7 @@ def test_acts_as_voteable_instance_methods def test_tally_empty item = Item.create(:name => 'XBOX', :description => 'XBOX console') - - assert_equal 0, Item.tally.length + assert_equal 0, Item.tally.having('vote_count > 0').length end def test_tally_starts_at @@ -112,8 +111,8 @@ def test_tally_starts_at vote.created_at = 3.days.ago vote.save - assert_equal 0, Item.tally(:start_at => 2.days.ago).length - assert_equal 1, Item.tally(:start_at => 4.days.ago).length + assert_equal 0, Item.tally.where('created_at > ?', 2.days.ago).length + assert_equal 1, Item.tally.where('created_at > ?', 4.days.ago).length end def test_tally_end_at @@ -124,8 +123,8 @@ def test_tally_end_at vote.created_at = 3.days.from_now vote.save - assert_equal 0, Item.tally(:end_at => 2.days.from_now).length - assert_equal 1, Item.tally(:end_at => 4.days.from_now).length + assert_equal 0, Item.tally.where('created_at < ?', 2.days.from_now).length + assert_equal 1, Item.tally.where('created_at < ?', 4.days.from_now).length end def test_tally_between_start_at_end_at @@ -141,14 +140,18 @@ def test_tally_between_start_at_end_at vote.created_at = 3.days.from_now vote.save - assert_equal 1, Item.tally(:start_at => 3.days.ago, :end_at => 2.days.from_now).length - assert_equal 2, Item.tally(:start_at => 3.days.ago, :end_at => 4.days.from_now).length + assert_equal 1, Item.tally.where('created_at > ?', 3.days.ago).where('created_at < ?', 2.days.from_now).length + assert_equal 2, Item.tally.where('created_at > ?', 3.days.ago).where('created_at < ?', 4.days.from_now).length end - def test_plusminus_tally_empty + def test_plusminus_tally_not_empty_without_conditions item = Item.create(:name => 'XBOX', :description => 'XBOX console') + assert_equal 1, Item.plusminus_tally.length + end - assert_equal 0, Item.plusminus_tally.length + def test_plusminus_tally_empty + item = Item.create(:name => 'XBOX', :description => 'XBOX console') + assert_equal 0, Item.plusminus_tally.having('vote_count > 0').length end def test_plusminus_tally_starts_at @@ -159,8 +162,8 @@ def test_plusminus_tally_starts_at vote.created_at = 3.days.ago vote.save - assert_equal 0, Item.plusminus_tally(:start_at => 2.days.ago).length - assert_equal 1, Item.plusminus_tally(:start_at => 4.days.ago).length + assert_equal 0, Item.plusminus_tally.where('created_at > ?', 2.days.ago).length + assert_equal 1, Item.plusminus_tally.where('created_at > ?', 4.days.ago).length end def test_plusminus_tally_end_at @@ -171,8 +174,8 @@ def test_plusminus_tally_end_at vote.created_at = 3.days.from_now vote.save - assert_equal 0, Item.plusminus_tally(:end_at => 2.days.from_now).length - assert_equal 1, Item.plusminus_tally(:end_at => 4.days.from_now).length + assert_equal 0, Item.plusminus_tally.where('created_at < ?', 2.days.from_now).length + assert_equal 1, Item.plusminus_tally.where('created_at < ?', 4.days.from_now).length end def test_plusminus_tally_between_start_at_end_at @@ -188,8 +191,8 @@ def test_plusminus_tally_between_start_at_end_at vote.created_at = 3.days.from_now vote.save - assert_equal 1, Item.plusminus_tally(:start_at => 3.days.ago, :end_at => 2.days.from_now).length - assert_equal 2, Item.plusminus_tally(:start_at => 3.days.ago, :end_at => 4.days.from_now).length + assert_equal 1, Item.plusminus_tally.where('created_at > ?', 3.days.ago).where('created_at < ?', 2.days.from_now).length + assert_equal 2, Item.plusminus_tally.where('created_at > ?', 3.days.ago).where('created_at < ?', 4.days.from_now).length end def test_plusminus_tally_inclusion @@ -199,20 +202,47 @@ def test_plusminus_tally_inclusion assert_not_nil user.vote_for(item) - assert (Item.plusminus_tally.include? item) - assert (not Item.plusminus_tally.include? item_not_included) + assert (Item.plusminus_tally.having('vote_count > 0').include? item) + assert (not Item.plusminus_tally.having('vote_count > 0').include? item_not_included) + end + + def test_plusminus_tally_voting_for + user1 = User.create(:name => 'david') + item = Item.create(:name => 'Playstation', :description => 'Playstation console') + + assert_not_nil user1.vote_for(item) + + assert_equal 1, Item.plusminus_tally[0].vote_count + assert_equal 1, Item.plusminus_tally[0].plusminus + end + + def test_plusminus_tally_voting_against + user1 = User.create(:name => 'david') + user2 = User.create(:name => 'john') + item = Item.create(:name => 'Playstation', :description => 'Playstation console') + + assert_not_nil user1.vote_against(item) + assert_not_nil user2.vote_against(item) + + assert_equal 2, Item.plusminus_tally[0].vote_count + assert_equal -2, Item.plusminus_tally[0].plusminus end def test_plusminus_tally_default_ordering - user = User.create(:name => 'david') + user1 = User.create(:name => 'david') + user2 = User.create(:name => 'john') + item_twice_for = Item.create(:name => 'XBOX2', :description => 'XBOX2 console') item_for = Item.create(:name => 'XBOX', :description => 'XBOX console') item_against = Item.create(:name => 'Playstation', :description => 'Playstation console') - assert_not_nil user.vote_for(item_for) - assert_not_nil user.vote_against(item_against) + assert_not_nil user1.vote_for(item_for) + assert_not_nil user1.vote_for(item_twice_for) + assert_not_nil user2.vote_for(item_twice_for) + assert_not_nil user1.vote_against(item_against) - assert_equal item_for, Item.plusminus_tally[0] - assert_equal item_against, Item.plusminus_tally[1] + assert_equal item_twice_for, Item.plusminus_tally[0] + assert_equal item_for, Item.plusminus_tally[1] + assert_equal item_against, Item.plusminus_tally[2] end def test_plusminus_tally_limit @@ -220,7 +250,7 @@ def test_plusminus_tally_limit items = (0..9).map{ |u| Item.create(:name => "Item #{u}", :description => "Item #{u}") } users.each{ |u| items.each { |i| u.vote_for(i) } } assert_equal 10, Item.plusminus_tally.length - assert_equal 2, Item.plusminus_tally(:limit => 2).length + assert_equal 2, Item.plusminus_tally.limit(2).length end def test_plusminus_tally_ascending_ordering @@ -231,8 +261,8 @@ def test_plusminus_tally_ascending_ordering assert_not_nil user.vote_for(item_for) assert_not_nil user.vote_against(item_against) - assert_equal item_for, Item.plusminus_tally(:ascending => true)[1] - assert_equal item_against, Item.plusminus_tally(:ascending => true)[0] + assert_equal item_for, Item.plusminus_tally.reorder('plusminus ASC')[1] + assert_equal item_against, Item.plusminus_tally.reorder('plusminus ASC')[0] end def test_karma diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index db406d5..7cbcc4f 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -1,63 +1,27 @@ -# Generated by jeweler -# DO NOT EDIT THIS FILE DIRECTLY -# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' # -*- encoding: utf-8 -*- +lib = File.expand_path('../lib', __FILE__) +$:.unshift lib unless $:.include?(lib) +require 'thumbs_up/version' + Gem::Specification.new do |s| s.name = "thumbs_up" - s.version = File.read('./VERSION').strip + s.version = ThumbsUp::VERSION - s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= - s.authors = ["Brady Bouchard", "Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnetrzak"] - s.date = "2011-09-15" - s.description = "ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com." - s.email = "brady@ldawn.com" - s.extra_rdoc_files = [ - "README.markdown" - ] - s.files = [ - "CHANGELOG.markdown", - "Gemfile", - "MIT-LICENSE", - "README.markdown", - "Rakefile", - "VERSION", - "lib/acts_as_voteable.rb", - "lib/acts_as_voter.rb", - "lib/generators/thumbs_up/templates/migration.rb", - "lib/generators/thumbs_up/templates/vote.rb", - "lib/generators/thumbs_up/thumbs_up_generator.rb", - "lib/has_karma.rb", - "lib/thumbs_up.rb", - "rails/init.rb", - "test/test_helper.rb", - "test/test_thumbs_up.rb", - "thumbs_up.gemspec" - ] + s.required_rubygems_version = '>= 1.8.10' s.homepage = "http://github.com/brady8/thumbs_up" - s.require_paths = ["lib"] - s.rubygems_version = "1.8.10" s.summary = "Voting for ActiveRecord with multiple vote sources and karma calculation." + s.description = "ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com." + s.authors = ["Brady Bouchard", "Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnetrzak"] + s.email = ["brady@thewellinspired.com"] + s.files = Dir.glob("{lib,rails,test}/**/*") + %w(CHANGELOG.md Gemfile MIT-LICENSE README.md Rakefile) + s.require_paths = ["lib"] - if s.respond_to? :specification_version then - s.specification_version = 3 + s.add_runtime_dependency('activerecord') + s.add_development_dependency('simplecov') + s.add_development_dependency('bundler') + s.add_development_dependency('mysql2') + s.add_development_dependency('rake') - if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then - s.add_runtime_dependency(%q, [">= 0"]) - s.add_development_dependency(%q, [">= 0"]) - s.add_development_dependency(%q, [">= 0"]) - s.add_development_dependency(%q, [">= 0"]) - else - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - end - else - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - s.add_dependency(%q, [">= 0"]) - end end From f3cdbf35a38aa4d467a2627dea6b9655865cb156 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Fri, 24 Feb 2012 05:56:01 +1000 Subject: [PATCH 054/138] We don't need such a high version of Rubygems. --- Rakefile | 2 +- thumbs_up.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Rakefile b/Rakefile index baf8214..a25895f 100644 --- a/Rakefile +++ b/Rakefile @@ -26,7 +26,7 @@ task :build do end task :release => :build do - system "gem push thumbs_up-#{ThumbsUp::VERSION}" + system "gem push thumbs_up-#{ThumbsUp::VERSION}.gem" end task :default => :test \ No newline at end of file diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index 7cbcc4f..40947cb 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -8,7 +8,7 @@ Gem::Specification.new do |s| s.name = "thumbs_up" s.version = ThumbsUp::VERSION - s.required_rubygems_version = '>= 1.8.10' + s.required_rubygems_version = '>= 1.7.0' s.homepage = "http://github.com/brady8/thumbs_up" s.summary = "Voting for ActiveRecord with multiple vote sources and karma calculation." s.description = "ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com." From 76bf4aabc3f8878ad38463e89312e84209a740d9 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Fri, 24 Feb 2012 05:58:57 +1000 Subject: [PATCH 055/138] Added fix and test for issue #37. --- test/thumbs_up_test.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/thumbs_up_test.rb b/test/thumbs_up_test.rb index 585f0a7..28a2b67 100644 --- a/test/thumbs_up_test.rb +++ b/test/thumbs_up_test.rb @@ -103,6 +103,16 @@ def test_tally_empty assert_equal 0, Item.tally.having('vote_count > 0').length end + def test_tally_has_id + item1 = Item.create(:name => 'XBOX', :description => 'XBOX console') + item2 = Item.create(:name => 'XBOX2', :description => 'XBOX2 console') + user = User.create(:name => 'david') + + user.vote_for(item2) + + assert_not_nil Item.tally.all.first.id + end + def test_tally_starts_at item = Item.create(:name => 'XBOX', :description => 'XBOX console') user = User.create(:name => 'david') From 64db6364a52119555b1855fc83f19ba9d6bc4413 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Fri, 24 Feb 2012 06:02:20 +1000 Subject: [PATCH 056/138] Alias #clear_votes. --- lib/acts_as_voter.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/acts_as_voter.rb b/lib/acts_as_voter.rb index 0639de6..7c43416 100644 --- a/lib/acts_as_voter.rb +++ b/lib/acts_as_voter.rb @@ -92,6 +92,8 @@ def unvote_for(voteable) ).map(&:destroy) end + alias_method :clear_votes, :unvote_for + def voted_which_way?(voteable, direction) raise ArgumentError, "expected :up or :down" unless [:up, :down].include?(direction) 0 < Vote.where( From dce5b35cc0abca73e8d62d89f2611214d4e1a39d Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Fri, 24 Feb 2012 06:04:46 +1000 Subject: [PATCH 057/138] Forgot to save a file... --- lib/acts_as_voteable.rb | 1 - lib/thumbs_up/version.rb | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index 003d3fc..9209a85 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -43,7 +43,6 @@ def tally(*args) t = t.order("vote_count DESC") t = t.group("#{self.table_name}.id") t = t.select("#{self.table_name}.*") - t = t.select("#{Vote.table_name}.*") t = t.select("COUNT(#{Vote.table_name}.id) AS vote_count") end diff --git a/lib/thumbs_up/version.rb b/lib/thumbs_up/version.rb index b78886d..b62f5df 100644 --- a/lib/thumbs_up/version.rb +++ b/lib/thumbs_up/version.rb @@ -1,3 +1,3 @@ module ThumbsUp - VERSION = '0.5.0' + VERSION = '0.5.1' end \ No newline at end of file From 405d49e7e742765f01e4e533206bd2c82ecf4553 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Fri, 24 Feb 2012 07:29:23 +1000 Subject: [PATCH 058/138] Rakefile. --- Rakefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Rakefile b/Rakefile index a25895f..42d88a9 100644 --- a/Rakefile +++ b/Rakefile @@ -27,6 +27,7 @@ end task :release => :build do system "gem push thumbs_up-#{ThumbsUp::VERSION}.gem" + system "rm thumbs_up-#{ThumbsUp::VERSION}.gem" end task :default => :test \ No newline at end of file From 49f284c0d0cce70816867cee880fe7acc3719624 Mon Sep 17 00:00:00 2001 From: Arjun Singh Date: Sun, 26 Feb 2012 15:23:19 -0800 Subject: [PATCH 059/138] Added support for STI inherited classes --- lib/acts_as_voteable.rb | 2 +- lib/acts_as_voter.rb | 16 ++++++++-------- lib/generators/thumbs_up/templates/vote.rb | 6 +++--- lib/has_karma.rb | 6 +++--- lib/thumbs_up.rb | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index 9209a85..e80230c 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -87,7 +87,7 @@ def voters_who_voted def voted_by?(voter) 0 < Vote.where( :voteable_id => self.id, - :voteable_type => self.class.name, + :voteable_type => self.class.base_class.name, :voter_id => voter.id ).count end diff --git a/lib/acts_as_voter.rb b/lib/acts_as_voter.rb index 7c43416..4e4c19f 100644 --- a/lib/acts_as_voter.rb +++ b/lib/acts_as_voter.rb @@ -32,7 +32,7 @@ module InstanceMethods # user.vote_count() # All votes def vote_count(for_or_against = :all) - v = Vote.where(:voter_id => id).where(:voter_type => self.class.name) + v = Vote.where(:voter_id => id).where(:voter_type => self.class.base_class.name) v = case for_or_against when :all then v when :up then v.where(:vote => true) @@ -52,9 +52,9 @@ def voted_against?(voteable) def voted_on?(voteable) 0 < Vote.where( :voter_id => self.id, - :voter_type => self.class.name, + :voter_type => self.class.base_class.name, :voteable_id => voteable.id, - :voteable_type => voteable.class.name + :voteable_type => voteable.class.base_class.name ).count end @@ -86,9 +86,9 @@ def vote(voteable, options = {}) def unvote_for(voteable) Vote.where( :voter_id => self.id, - :voter_type => self.class.name, + :voter_type => self.class.base_class.name, :voteable_id => voteable.id, - :voteable_type => voteable.class.name + :voteable_type => voteable.class.base_class.name ).map(&:destroy) end @@ -98,13 +98,13 @@ def voted_which_way?(voteable, direction) raise ArgumentError, "expected :up or :down" unless [:up, :down].include?(direction) 0 < Vote.where( :voter_id => self.id, - :voter_type => self.class.name, + :voter_type => self.class.base_class.name, :vote => direction == :up ? true : false, :voteable_id => voteable.id, - :voteable_type => voteable.class.name + :voteable_type => voteable.class.base_class.name ).count end end end -end \ No newline at end of file +end diff --git a/lib/generators/thumbs_up/templates/vote.rb b/lib/generators/thumbs_up/templates/vote.rb index 7169512..4866417 100644 --- a/lib/generators/thumbs_up/templates/vote.rb +++ b/lib/generators/thumbs_up/templates/vote.rb @@ -1,7 +1,7 @@ class Vote < ActiveRecord::Base - scope :for_voter, lambda { |*args| where(["voter_id = ? AND voter_type = ?", args.first.id, args.first.class.name]) } - scope :for_voteable, lambda { |*args| where(["voteable_id = ? AND voteable_type = ?", args.first.id, args.first.class.name]) } + scope :for_voter, lambda { |*args| where(["voter_id = ? AND voter_type = ?", args.first.id, args.first.class.base_class.name]) } + scope :for_voteable, lambda { |*args| where(["voteable_id = ? AND voteable_type = ?", args.first.id, args.first.class.base_class.name]) } scope :recent, lambda { |*args| where(["created_at > ?", (args.first || 2.weeks.ago)]) } scope :descending, order("created_at DESC") @@ -14,4 +14,4 @@ class Vote < ActiveRecord::Base # Comment out the line below to allow multiple votes per user. validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id] <% end %> -end \ No newline at end of file +end diff --git a/lib/has_karma.rb b/lib/has_karma.rb index bc41460..9f06228 100644 --- a/lib/has_karma.rb +++ b/lib/has_karma.rb @@ -29,10 +29,10 @@ def find_most_karmic module InstanceMethods def karma(options = {}) - self.class.karmic_objects.collect do |object, attr| - v = object.where(["#{Vote.table_name}.vote = ?", true]).where(["#{self.class.table_name}.#{self.class.primary_key} = ?", self.id]) + self.class.base_class.karmic_objects.collect do |object, attr| + v = object.where(["#{Vote.table_name}.vote = ?", true]).where(["#{self.class.base_class.table_name}.#{self.class.base_class.primary_key} = ?", self.id]) v = v.joins("INNER JOIN #{Vote.table_name} ON #{Vote.table_name}.voteable_type = '#{object.to_s}' AND #{Vote.table_name}.voteable_id = #{object.table_name}.#{object.primary_key}") - v = v.joins("INNER JOIN #{self.class.table_name} ON #{self.class.table_name}.#{self.class.primary_key} = #{object.table_name}.#{attr[0]}") + v = v.joins("INNER JOIN #{self.class.base_class.table_name} ON #{self.class.base_class.table_name}.#{self.class.base_class.primary_key} = #{object.table_name}.#{attr[0]}") (v.count.to_f * attr[1]).round end.sum end diff --git a/lib/thumbs_up.rb b/lib/thumbs_up.rb index b3d1f8a..83f7aa7 100644 --- a/lib/thumbs_up.rb +++ b/lib/thumbs_up.rb @@ -4,4 +4,4 @@ ActiveRecord::Base.send(:include, ThumbsUp::ActsAsVoteable) ActiveRecord::Base.send(:include, ThumbsUp::ActsAsVoter) -ActiveRecord::Base.send(:include, ThumbsUp::Karma) \ No newline at end of file +ActiveRecord::Base.send(:include, ThumbsUp::Karma) From 11335e4e2dcf984b5e13a58d7dd7cdbd28daf692 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Mon, 5 Mar 2012 15:56:02 +1000 Subject: [PATCH 060/138] Bump to version 0.5.2. --- lib/thumbs_up/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/thumbs_up/version.rb b/lib/thumbs_up/version.rb index b62f5df..67bff4f 100644 --- a/lib/thumbs_up/version.rb +++ b/lib/thumbs_up/version.rb @@ -1,3 +1,3 @@ module ThumbsUp - VERSION = '0.5.1' + VERSION = '0.5.2' end \ No newline at end of file From 5f74ec5342f14f2f769a751ad66aec1fc3f315dc Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Mon, 5 Mar 2012 15:58:47 +1000 Subject: [PATCH 061/138] Bump to 0.5.3. --- lib/thumbs_up/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/thumbs_up/version.rb b/lib/thumbs_up/version.rb index 67bff4f..b197e8a 100644 --- a/lib/thumbs_up/version.rb +++ b/lib/thumbs_up/version.rb @@ -1,3 +1,3 @@ module ThumbsUp - VERSION = '0.5.2' + VERSION = '0.5.3' end \ No newline at end of file From 2f1f9caa7d36e5cb242bbe7d25f704ce8cc730fd Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Wed, 7 Mar 2012 21:24:23 +1000 Subject: [PATCH 062/138] Travis-ci. --- .travis.yml | 1 + README.md | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..f819a51 --- /dev/null +++ b/.travis.yml @@ -0,0 +1 @@ +language: ruby diff --git a/README.md b/README.md index d636da9..162f633 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ ThumbsUp ======= +[![Build Status](https://secure.travis-ci.org/brady8/thumbs_up.png)](http://travis-ci.org/brady8/thumbs_up) + **Note: Version 0.5.x is a breaking change for #plusminus_tally and #tally, with > 50% speedups.** A ridiculously straightforward and simple package 'o' code to enable voting in your application, a la stackoverflow.com, etc. From a884dcbd324e6936d27ceeb18aec932cf529b357 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Wed, 7 Mar 2012 21:28:02 +1000 Subject: [PATCH 063/138] Adding Gemfile source to help Travis CI. --- Gemfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 901f575..817f62a 100644 --- a/Gemfile +++ b/Gemfile @@ -1 +1,2 @@ -gemspec \ No newline at end of file +source 'http://rubygems.org' +gemspec From 10da60b33e11d4fffe916edd928822afa07dd793 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Wed, 7 Mar 2012 21:34:43 +1000 Subject: [PATCH 064/138] Setting up MySQL in the Travis CI test enviro. --- .travis.yml | 2 ++ test/test_helper.rb | 22 +++++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index f819a51..2f3f610 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1 +1,3 @@ language: ruby +before_script: + - "mysql -e 'create database thumbs_up_test;'" diff --git a/test/test_helper.rb b/test/test_helper.rb index 4ee0d77..7949c23 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -7,13 +7,21 @@ require 'active_record' -config = { - :adapter => 'mysql2', - :database => 'thumbs_up_test', - :username => 'test', - :password => 'test', - :socket => '/tmp/mysql.sock' -} +if ENV['TRAVIS'] + config = { + :adapter => 'mysql2', + :database => 'thumbs_up_test', + :username => 'root' + } +else + config = { + :adapter => 'mysql2', + :database => 'thumbs_up_test', + :username => 'test', + :password => 'test', + :socket => '/tmp/mysql.sock' + } +end ActiveRecord::Base.establish_connection(config) ActiveRecord::Base.connection.drop_database config[:database] rescue nil From f35c1ba6b4852cc36a9bceef5b26572cd63391c4 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Wed, 7 Mar 2012 21:37:59 +1000 Subject: [PATCH 065/138] Test on more rubies. --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 2f3f610..880dcf6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,7 @@ language: ruby +rvm: + - 1.8.7 + - 1.9.2 + - 1.9.3 before_script: - "mysql -e 'create database thumbs_up_test;'" From 8600cc89d0c677dad5f2dd53e66d98cf76ba1547 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Mon, 12 Mar 2012 12:45:36 +1000 Subject: [PATCH 066/138] Add the option to tally separate up and down votes in #plusminus_tally. --- lib/acts_as_voteable.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index e80230c..b2d42f7 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -20,12 +20,18 @@ module SingletonMethods # This returns an Arel relation, so you can add conditions as you like chained on to # this method call. # i.e. Posts.tally.where('votes.created_at > ?', 2.days.ago) - def plusminus_tally + # You can also have the upvotes and downvotes returned separately in the same query: + # Post.plusminus_tally(:separate_updown => true) + def plusminus_tally(params = {}) t = self.joins("LEFT OUTER JOIN #{Vote.table_name} ON #{self.table_name}.id = #{Vote.table_name}.voteable_id") t = t.order("plusminus DESC") t = t.group("#{self.table_name}.id") t = t.select("#{self.table_name}.*") t = t.select("SUM(CASE CAST(#{Vote.table_name}.vote AS UNSIGNED) WHEN 1 THEN 1 WHEN 0 THEN -1 ELSE 0 END) AS plusminus") + if params[:separate_updown] + t = t.select("SUM(CASE CAST(#{Vote.table_name}.vote AS UNSIGNED) WHEN 1 THEN 1 WHEN 0 THEN 0 ELSE 0 END) AS up") + t = t.select("SUM(CASE CAST(#{Vote.table_name}.vote AS UNSIGNED) WHEN 1 THEN 0 WHEN 0 THEN 1 ELSE 0 END) AS down") + end t = t.select("COUNT(#{Vote.table_name}.id) AS vote_count") end @@ -72,6 +78,8 @@ def percent_against # You'll probably want to use this method to display how 'good' a particular voteable # is, and/or sort based on it. + # If you're using this for a lot of voteables, then you'd best use the #plusminus_tally + # method above. def plusminus votes_for - votes_against end From 4696df41a31f69862f0d9fcd4755848461f85d11 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Mon, 12 Mar 2012 12:46:02 +1000 Subject: [PATCH 067/138] BREAKING CHANGE: #plusminus_tally now returns 'plusminus_tally' rather than 'plusminus', as the latter was overridden by the instance method and would lead to severe performance degradation on big datasets. --- lib/acts_as_voteable.rb | 4 ++-- test/thumbs_up_test.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index b2d42f7..0c5adbd 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -24,10 +24,10 @@ module SingletonMethods # Post.plusminus_tally(:separate_updown => true) def plusminus_tally(params = {}) t = self.joins("LEFT OUTER JOIN #{Vote.table_name} ON #{self.table_name}.id = #{Vote.table_name}.voteable_id") - t = t.order("plusminus DESC") + t = t.order("plusminus_tally DESC") t = t.group("#{self.table_name}.id") t = t.select("#{self.table_name}.*") - t = t.select("SUM(CASE CAST(#{Vote.table_name}.vote AS UNSIGNED) WHEN 1 THEN 1 WHEN 0 THEN -1 ELSE 0 END) AS plusminus") + t = t.select("SUM(CASE CAST(#{Vote.table_name}.vote AS UNSIGNED) WHEN 1 THEN 1 WHEN 0 THEN -1 ELSE 0 END) AS plusminus_tally") if params[:separate_updown] t = t.select("SUM(CASE CAST(#{Vote.table_name}.vote AS UNSIGNED) WHEN 1 THEN 1 WHEN 0 THEN 0 ELSE 0 END) AS up") t = t.select("SUM(CASE CAST(#{Vote.table_name}.vote AS UNSIGNED) WHEN 1 THEN 0 WHEN 0 THEN 1 ELSE 0 END) AS down") diff --git a/test/thumbs_up_test.rb b/test/thumbs_up_test.rb index 28a2b67..3e1f4b5 100644 --- a/test/thumbs_up_test.rb +++ b/test/thumbs_up_test.rb @@ -271,8 +271,8 @@ def test_plusminus_tally_ascending_ordering assert_not_nil user.vote_for(item_for) assert_not_nil user.vote_against(item_against) - assert_equal item_for, Item.plusminus_tally.reorder('plusminus ASC')[1] - assert_equal item_against, Item.plusminus_tally.reorder('plusminus ASC')[0] + assert_equal item_for, Item.plusminus_tally.reorder('plusminus_tally ASC')[1] + assert_equal item_against, Item.plusminus_tally.reorder('plusminus_tally ASC')[0] end def test_karma From 06f968f55702a49887066d6ea5acb487f7385440 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Mon, 12 Mar 2012 20:21:00 +1000 Subject: [PATCH 068/138] Readme. --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 162f633..094790f 100644 --- a/README.md +++ b/README.md @@ -100,15 +100,16 @@ This will select the Items with less than 10 votes, the votes having been cast w **You most likely want to use this over the normal tally** -This is similar to tallying votes, but this will return voteable object collections based on the sum of the differences between up and down votes (ups are +1, downs are -1). For Instance, a voteable with 3 upvotes and 2 downvotes will have a plusminus of 1. +This is similar to tallying votes, but this will return voteable object collections based on the sum of the differences between up and down votes (ups are +1, downs are -1). For Instance, a voteable with 3 upvotes and 2 downvotes will have a plusminus_tally of 1. - @items = Item.plusminus_tally.limit(10).where('created_at > ?', 2.days.ago).having('plusminus > 10') + @items = Item.plusminus_tally.limit(10).where('created_at > ?', 2.days.ago).having('plusminus_tally > 10') #### Lower level queries positiveVoteCount = voteable.votes_for negativeVoteCount = voteable.votes_against - plusminus = voteable.plusminus # Votes for, minus votes against. + # Votes for minus votes against. If you want more than a few model instances' worth, use `plusminus_tally` instead. + plusminus = voteable.plusminus voter.voted_for?(voteable) # True if the voter voted for this object. voter.vote_count(:up | :down | :all) # returns the count of +1, -1, or all votes From 73b7f2f04950c02412db471a3133843aaddb370c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20FRERE?= Date: Tue, 20 Mar 2012 15:56:15 +0100 Subject: [PATCH 069/138] Added tests to prove count/any/empty usage is failing --- test/thumbs_up_test.rb | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/test/thumbs_up_test.rb b/test/thumbs_up_test.rb index 3e1f4b5..0869eba 100644 --- a/test/thumbs_up_test.rb +++ b/test/thumbs_up_test.rb @@ -154,6 +154,18 @@ def test_tally_between_start_at_end_at assert_equal 2, Item.tally.where('created_at > ?', 3.days.ago).where('created_at < ?', 4.days.from_now).length end + def test_tally_count + Item.tally.count + end + + def test_tally_any + Item.tally.any? + end + + def test_tally_empty + Item.tally.empty? + end + def test_plusminus_tally_not_empty_without_conditions item = Item.create(:name => 'XBOX', :description => 'XBOX console') assert_equal 1, Item.plusminus_tally.length @@ -274,7 +286,19 @@ def test_plusminus_tally_ascending_ordering assert_equal item_for, Item.plusminus_tally.reorder('plusminus_tally ASC')[1] assert_equal item_against, Item.plusminus_tally.reorder('plusminus_tally ASC')[0] end - + + def test_plusminus_tally_count + Item.plusminus_tally.count + end + + def test_plusminus_tally_any + Item.plusminus_tally.any? + end + + def test_plusminus_tally_empty + Item.plusminus_tally.empty? + end + def test_karma users = (0..1).map{ |u| User.create(:name => "User #{u}") } items = (0..1).map{ |u| users[0].items.create(:name => "Item #{u}", :description => "Item #{u}") } From 4e0337b8ad9bb2ed13ae89df7dbe953156d96aad Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Wed, 21 Mar 2012 13:35:54 +1000 Subject: [PATCH 070/138] Fixed tests for #count, #any?, etc. on #tally and #plusminus_tally. --- test/thumbs_up_test.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/thumbs_up_test.rb b/test/thumbs_up_test.rb index 0869eba..3f9e416 100644 --- a/test/thumbs_up_test.rb +++ b/test/thumbs_up_test.rb @@ -155,15 +155,15 @@ def test_tally_between_start_at_end_at end def test_tally_count - Item.tally.count + Item.tally.except(:order).count end def test_tally_any - Item.tally.any? + Item.tally.except(:order).any? end def test_tally_empty - Item.tally.empty? + Item.tally.except(:order).empty? end def test_plusminus_tally_not_empty_without_conditions @@ -288,15 +288,15 @@ def test_plusminus_tally_ascending_ordering end def test_plusminus_tally_count - Item.plusminus_tally.count + Item.plusminus_tally.except(:order).count end def test_plusminus_tally_any - Item.plusminus_tally.any? + Item.plusminus_tally.except(:order).any? end def test_plusminus_tally_empty - Item.plusminus_tally.empty? + Item.plusminus_tally.except(:order).empty? end def test_karma From 6bd8c224b4ed62aa5557031097df565c0986247e Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Thu, 5 Apr 2012 15:17:29 +1000 Subject: [PATCH 071/138] Added the lower bound of a Wilson Score as a more accurate representation of average rating. --- lib/acts_as_voteable.rb | 19 ++++++++++++++++--- test/thumbs_up_test.rb | 2 ++ thumbs_up.gemspec | 1 + 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index 0c5adbd..96dd39a 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -81,15 +81,28 @@ def percent_against # If you're using this for a lot of voteables, then you'd best use the #plusminus_tally # method above. def plusminus - votes_for - votes_against + respond_to?(:plusminus_tally) ? plusminus_tally : (votes_for - votes_against) + end + + # The lower bound of a Wilson Score with a default confidence interval of 95%. Gives a more accurate representation of average rating (plusminus) based on the number of positive ratings and total ratings. + # http://evanmiller.org/how-not-to-sort-by-average-rating.html + def ci_plusminus(confidence = 0.95) + require 'statistics2' + n = votes.size + if n == 0 + return 0 + end + z = Statistics2.pnormaldist(1 - (1 - confidence) / 2) + phat = 1.0 * votes_for / n + (phat + z * z / (2 * n) - z * Math.sqrt((phat * (1 - phat) + z * z / (4 * n)) / n)) / (1 + z * z / n) end def votes_count - self.votes.size + votes.size end def voters_who_voted - self.votes.map(&:voter).uniq + votes.map(&:voter).uniq end def voted_by?(voter) diff --git a/test/thumbs_up_test.rb b/test/thumbs_up_test.rb index 3f9e416..18b4cd9 100644 --- a/test/thumbs_up_test.rb +++ b/test/thumbs_up_test.rb @@ -73,11 +73,13 @@ def test_acts_as_voteable_instance_methods assert_equal 2, item.votes_for assert_equal 0, item.votes_against assert_equal 2, item.plusminus + assert_in_delta 0.34, item.ci_plusminus, 0.01 user_against.vote_against(item) assert_equal 1, item.votes_against assert_equal 1, item.plusminus + assert_in_delta 0.20, item.ci_plusminus, 0.01 assert_equal 3, item.votes_count diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index 40947cb..87e7f1d 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -18,6 +18,7 @@ Gem::Specification.new do |s| s.require_paths = ["lib"] s.add_runtime_dependency('activerecord') + s.add_runtime_dependency('statistics2') s.add_development_dependency('simplecov') s.add_development_dependency('bundler') s.add_development_dependency('mysql2') From 5bcd3e0d23b00e16ef06edae4431d15f536095ba Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Thu, 5 Apr 2012 15:21:26 +1000 Subject: [PATCH 072/138] Version bump. --- README.md | 2 +- lib/thumbs_up/version.rb | 2 +- thumbs_up.gemspec | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 094790f..fa36ab2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ThumbsUp ======= -[![Build Status](https://secure.travis-ci.org/brady8/thumbs_up.png)](http://travis-ci.org/brady8/thumbs_up) +[![Build Status](https://secure.travis-ci.org/bouchard/thumbs_up.png)](http://travis-ci.org/bouchard/thumbs_up) **Note: Version 0.5.x is a breaking change for #plusminus_tally and #tally, with > 50% speedups.** diff --git a/lib/thumbs_up/version.rb b/lib/thumbs_up/version.rb index b197e8a..3f5a8b9 100644 --- a/lib/thumbs_up/version.rb +++ b/lib/thumbs_up/version.rb @@ -1,3 +1,3 @@ module ThumbsUp - VERSION = '0.5.3' + VERSION = '0.5.4' end \ No newline at end of file diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index 87e7f1d..4c0acc7 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.version = ThumbsUp::VERSION s.required_rubygems_version = '>= 1.7.0' - s.homepage = "http://github.com/brady8/thumbs_up" + s.homepage = "http://github.com/bouchard/thumbs_up" s.summary = "Voting for ActiveRecord with multiple vote sources and karma calculation." s.description = "ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com." s.authors = ["Brady Bouchard", "Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnetrzak"] From 807fd78ed463007d2faf88664bda7bccf82cc579 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Tue, 10 Apr 2012 20:51:30 +1000 Subject: [PATCH 073/138] Changed required rubygems version. --- thumbs_up.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index 4c0acc7..7e9f17d 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -8,7 +8,7 @@ Gem::Specification.new do |s| s.name = "thumbs_up" s.version = ThumbsUp::VERSION - s.required_rubygems_version = '>= 1.7.0' + s.required_rubygems_version = '>= 1.6.0' s.homepage = "http://github.com/bouchard/thumbs_up" s.summary = "Voting for ActiveRecord with multiple vote sources and karma calculation." s.description = "ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com." From 1937ec24d8ca4125592f06e7efd6ac986336c6ba Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Tue, 10 Apr 2012 20:52:33 +1000 Subject: [PATCH 074/138] Bump version. --- lib/thumbs_up/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/thumbs_up/version.rb b/lib/thumbs_up/version.rb index 3f5a8b9..d600453 100644 --- a/lib/thumbs_up/version.rb +++ b/lib/thumbs_up/version.rb @@ -1,3 +1,3 @@ module ThumbsUp - VERSION = '0.5.4' + VERSION = '0.5.5' end \ No newline at end of file From ac292a00d327420e8ee88ee4dd532946fc0b0044 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Wed, 11 Apr 2012 06:03:51 +1000 Subject: [PATCH 075/138] Heroku uses *old* Rubygems, indeed. --- lib/thumbs_up/version.rb | 2 +- thumbs_up.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/thumbs_up/version.rb b/lib/thumbs_up/version.rb index d600453..28c5640 100644 --- a/lib/thumbs_up/version.rb +++ b/lib/thumbs_up/version.rb @@ -1,3 +1,3 @@ module ThumbsUp - VERSION = '0.5.5' + VERSION = '0.5.6' end \ No newline at end of file diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index 7e9f17d..fd7f31d 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -8,7 +8,7 @@ Gem::Specification.new do |s| s.name = "thumbs_up" s.version = ThumbsUp::VERSION - s.required_rubygems_version = '>= 1.6.0' + s.required_rubygems_version = '>= 1.5.0' s.homepage = "http://github.com/bouchard/thumbs_up" s.summary = "Voting for ActiveRecord with multiple vote sources and karma calculation." s.description = "ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com." From 39b4e712e8b2057965139385bfb2dfe74edecfc0 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Wed, 11 Apr 2012 11:17:07 +1000 Subject: [PATCH 076/138] Remove Rubygems required version entirely, and bump version. --- lib/thumbs_up/version.rb | 2 +- thumbs_up.gemspec | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/thumbs_up/version.rb b/lib/thumbs_up/version.rb index 28c5640..29ca25b 100644 --- a/lib/thumbs_up/version.rb +++ b/lib/thumbs_up/version.rb @@ -1,3 +1,3 @@ module ThumbsUp - VERSION = '0.5.6' + VERSION = '0.5.7' end \ No newline at end of file diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index fd7f31d..d2649b0 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -8,7 +8,6 @@ Gem::Specification.new do |s| s.name = "thumbs_up" s.version = ThumbsUp::VERSION - s.required_rubygems_version = '>= 1.5.0' s.homepage = "http://github.com/bouchard/thumbs_up" s.summary = "Voting for ActiveRecord with multiple vote sources and karma calculation." s.description = "ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com." From 2f7a0db2e3a9c3483e66c0980cb2254021026f19 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Sun, 29 Jul 2012 13:14:43 -0600 Subject: [PATCH 077/138] First class Postgres support. --- Gemfile | 2 +- README.md | 6 +- Rakefile | 12 +++- lib/acts_as_voteable.rb | 9 +-- .../thumbs_up/templates/migration.rb | 8 +-- lib/thumbs_up.rb | 9 +++ lib/thumbs_up/version.rb | 2 +- test/test_helper.rb | 63 ++++++++++++------- test/thumbs_up_test.rb | 25 +++++--- thumbs_up.gemspec | 1 + 10 files changed, 95 insertions(+), 42 deletions(-) diff --git a/Gemfile b/Gemfile index 817f62a..e45e65f 100644 --- a/Gemfile +++ b/Gemfile @@ -1,2 +1,2 @@ -source 'http://rubygems.org' +source :rubygems gemspec diff --git a/README.md b/README.md index fa36ab2..c6379c3 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ Did the first user vote for the Car with id = 2 already? u = User.first u.vote_for(Car.find(2)) u.voted_on?(Car.find(2)) #=> true - + Did the first user vote for or against the Car with id = 2? u = User.first @@ -92,6 +92,10 @@ Did the first user vote for or against the Car with id = 2? You can easily retrieve voteable object collections based on the properties of their votes: + @items = Item.tally.limit(10).where('created_at > ?', 2.days.ago).having('COUNT(votes.id) < 10') + +Or for MySQL: + @items = Item.tally.limit(10).where('created_at > ?', 2.days.ago).having('vote_count < 10') This will select the Items with less than 10 votes, the votes having been cast within the last two days, with a limit of 10 items. *This tallies all votes, regardless of whether they are +1 (up) or -1 (down).* The #tally method returns an ActiveRecord Relation, so you can chain the normal method calls on to it. diff --git a/Rakefile b/Rakefile index 42d88a9..ca6b42d 100644 --- a/Rakefile +++ b/Rakefile @@ -24,10 +24,18 @@ end task :build do system "gem build thumbs_up.gemspec" end - + task :release => :build do system "gem push thumbs_up-#{ThumbsUp::VERSION}.gem" system "rm thumbs_up-#{ThumbsUp::VERSION}.gem" end -task :default => :test \ No newline at end of file +task :test_both_databases do + # Test both MySQL and Postgres. + ENV['DB'] = 'mysql' + Rake::Task['test'].execute + ENV['DB'] = 'postgres' + Rake::Task['test'].execute +end + +task :default => :test_both_databases \ No newline at end of file diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index 96dd39a..a21d2ad 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -2,6 +2,7 @@ module ThumbsUp module ActsAsVoteable #:nodoc: def self.included(base) + base.extend ThumbsUp::Base base.extend ClassMethods end @@ -15,7 +16,7 @@ def acts_as_voteable end module SingletonMethods - + # Calculate the plusminus for a group of voteables in one database query. # This returns an Arel relation, so you can add conditions as you like chained on to # this method call. @@ -27,10 +28,10 @@ def plusminus_tally(params = {}) t = t.order("plusminus_tally DESC") t = t.group("#{self.table_name}.id") t = t.select("#{self.table_name}.*") - t = t.select("SUM(CASE CAST(#{Vote.table_name}.vote AS UNSIGNED) WHEN 1 THEN 1 WHEN 0 THEN -1 ELSE 0 END) AS plusminus_tally") + t = t.select("SUM(CASE WHEN #{Vote.table_name}.vote THEN 1 ELSE -1 END) AS plusminus_tally") if params[:separate_updown] - t = t.select("SUM(CASE CAST(#{Vote.table_name}.vote AS UNSIGNED) WHEN 1 THEN 1 WHEN 0 THEN 0 ELSE 0 END) AS up") - t = t.select("SUM(CASE CAST(#{Vote.table_name}.vote AS UNSIGNED) WHEN 1 THEN 0 WHEN 0 THEN 1 ELSE 0 END) AS down") + t = t.select("SUM(CASE WHEN #{Vote.table_name}.vote THEN 1 ELSE 0 END) AS up") + t = t.select("SUM(CASE WHEN #{Vote.table_name}.vote THEN 0 ELSE 1 END) AS down") end t = t.select("COUNT(#{Vote.table_name}.id) AS vote_count") end diff --git a/lib/generators/thumbs_up/templates/migration.rb b/lib/generators/thumbs_up/templates/migration.rb index d08d015..8be4c55 100644 --- a/lib/generators/thumbs_up/templates/migration.rb +++ b/lib/generators/thumbs_up/templates/migration.rb @@ -1,19 +1,19 @@ class ThumbsUpMigration < ActiveRecord::Migration def self.up create_table :votes, :force => true do |t| - - t.boolean :vote, :default => false + + t.boolean :vote, :default => false, :null => false t.references :voteable, :polymorphic => true, :null => false t.references :voter, :polymorphic => true t.timestamps - + end add_index :votes, [:voter_id, :voter_type] add_index :votes, [:voteable_id, :voteable_type] <% if options[:unique_voting] == true %> - # Comment out the line below to allow multiple votes per voter on a single entity. + # Comment out the line below to allow multiple votes per voter on a single entity. add_index :votes, [:voter_id, :voter_type, :voteable_id, :voteable_type], :unique => true, :name => 'fk_one_vote_per_user_per_entity' <% end %> end diff --git a/lib/thumbs_up.rb b/lib/thumbs_up.rb index 83f7aa7..26ed89d 100644 --- a/lib/thumbs_up.rb +++ b/lib/thumbs_up.rb @@ -2,6 +2,15 @@ require 'acts_as_voter' require 'has_karma' +module ThumbsUp + module Base + # Check if we're connected to a MySQL database. + def mysql? + ActiveRecord::Base.connection.adapter_name == 'MySQL' + end + end +end + ActiveRecord::Base.send(:include, ThumbsUp::ActsAsVoteable) ActiveRecord::Base.send(:include, ThumbsUp::ActsAsVoter) ActiveRecord::Base.send(:include, ThumbsUp::Karma) diff --git a/lib/thumbs_up/version.rb b/lib/thumbs_up/version.rb index 29ca25b..5cadcca 100644 --- a/lib/thumbs_up/version.rb +++ b/lib/thumbs_up/version.rb @@ -1,3 +1,3 @@ module ThumbsUp - VERSION = '0.5.7' + VERSION = '0.6.0' end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index 7949c23..72d817c 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -7,26 +7,47 @@ require 'active_record' -if ENV['TRAVIS'] - config = { - :adapter => 'mysql2', - :database => 'thumbs_up_test', - :username => 'root' - } +if ENV['DB'] == 'mysql' + if ENV['TRAVIS'] + config = { + :adapter => 'mysql2', + :database => 'thumbs_up_test', + :username => 'root' + } + else + config = { + :adapter => 'mysql2', + :database => 'thumbs_up_test', + :username => 'test', + :password => 'test', + :socket => '/tmp/mysql.sock' + } + end + + ActiveRecord::Base.establish_connection(config) + ActiveRecord::Base.connection.drop_database config[:database] rescue nil + ActiveRecord::Base.connection.create_database config[:database] + ActiveRecord::Base.establish_connection(config) else - config = { - :adapter => 'mysql2', - :database => 'thumbs_up_test', - :username => 'test', - :password => 'test', - :socket => '/tmp/mysql.sock' - } -end + if ENV['TRAVIS'] + config = { + :adapter => 'postgresql', + :database => 'thumbs_up_test', + :username => 'root' + } + else + config = { + :adapter => 'postgresql', + :database => 'thumbs_up_test', + :username => 'test' + } + end -ActiveRecord::Base.establish_connection(config) -ActiveRecord::Base.connection.drop_database config[:database] rescue nil -ActiveRecord::Base.connection.create_database config[:database] -ActiveRecord::Base.establish_connection(config) + ActiveRecord::Base.establish_connection(config.merge({ :database => 'postgres' })) + ActiveRecord::Base.connection.drop_database config[:database] + ActiveRecord::Base.connection.create_database config[:database] + ActiveRecord::Base.establish_connection(config) +end ActiveRecord::Migration.verbose = false @@ -41,14 +62,14 @@ add_index :votes, [:voter_id, :voter_type] add_index :votes, [:voteable_id, :voteable_type] - # Comment out the line below to allow multiple votes per voter on a single entity. + # Comment out the line below to allow multiple votes per voter on a single entity. add_index :votes, [:voter_id, :voter_type, :voteable_id, :voteable_type], :unique => true, :name => 'fk_one_vote_per_user_per_entity' - + create_table :users, :force => true do |t| t.string :name t.timestamps end - + create_table :items, :force => true do |t| t.integer :user_id t.string :name diff --git a/test/thumbs_up_test.rb b/test/thumbs_up_test.rb index 18b4cd9..18b2a3d 100644 --- a/test/thumbs_up_test.rb +++ b/test/thumbs_up_test.rb @@ -24,6 +24,8 @@ def test_acts_as_voter_instance_methods assert_equal 0, user_for.vote_count(:down) assert_equal true, user_for.voted_which_way?(item, :up) assert_equal false, user_for.voted_which_way?(item, :down) + assert_equal 1, user_for.votes.where(:voteable_type => 'Item').count + assert_equal 0, user_for.votes.where(:voteable_type => 'AnotherItem').count assert_raises(ArgumentError) do user_for.voted_which_way?(item, :foo) end @@ -226,8 +228,13 @@ def test_plusminus_tally_inclusion assert_not_nil user.vote_for(item) - assert (Item.plusminus_tally.having('vote_count > 0').include? item) - assert (not Item.plusminus_tally.having('vote_count > 0').include? item_not_included) + if ActiveRecord::Base.connection.adapter_name == 'MySQL' + assert (Item.plusminus_tally.having('vote_count > 0').include? item) + assert (not Item.plusminus_tally.having('vote_count > 0').include? item_not_included) + else + assert (Item.plusminus_tally.having('COUNT(votes.id) > 0').include? item) + assert (not Item.plusminus_tally.having('COUNT(votes.id) > 0').include? item_not_included) + end end def test_plusminus_tally_voting_for @@ -236,8 +243,9 @@ def test_plusminus_tally_voting_for assert_not_nil user1.vote_for(item) - assert_equal 1, Item.plusminus_tally[0].vote_count - assert_equal 1, Item.plusminus_tally[0].plusminus + # https://github.com/rails/rails/issues/1718 + assert_equal 1, Item.plusminus_tally[0].vote_count.to_i + assert_equal 1, Item.plusminus_tally[0].plusminus.to_i end def test_plusminus_tally_voting_against @@ -248,8 +256,9 @@ def test_plusminus_tally_voting_against assert_not_nil user1.vote_against(item) assert_not_nil user2.vote_against(item) - assert_equal 2, Item.plusminus_tally[0].vote_count - assert_equal -2, Item.plusminus_tally[0].plusminus + # https://github.com/rails/rails/issues/1718 + assert_equal 2, Item.plusminus_tally[0].vote_count.to_i + assert_equal -2, Item.plusminus_tally[0].plusminus.to_i end def test_plusminus_tally_default_ordering @@ -268,7 +277,7 @@ def test_plusminus_tally_default_ordering assert_equal item_for, Item.plusminus_tally[1] assert_equal item_against, Item.plusminus_tally[2] end - + def test_plusminus_tally_limit users = (0..9).map{ |u| User.create(:name => "User #{u}") } items = (0..9).map{ |u| Item.create(:name => "Item #{u}", :description => "Item #{u}") } @@ -305,7 +314,7 @@ def test_karma users = (0..1).map{ |u| User.create(:name => "User #{u}") } items = (0..1).map{ |u| users[0].items.create(:name => "Item #{u}", :description => "Item #{u}") } users.each{ |u| items.each { |i| u.vote_for(i) } } - + assert_equal 4, users[0].karma assert_equal 0, users[1].karma end diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index d2649b0..298e06f 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -21,6 +21,7 @@ Gem::Specification.new do |s| s.add_development_dependency('simplecov') s.add_development_dependency('bundler') s.add_development_dependency('mysql2') + s.add_development_dependency('pg') s.add_development_dependency('rake') end From a3e30f0875c37f1a86e23a9a72359ff863100c79 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Sun, 29 Jul 2012 13:23:51 -0600 Subject: [PATCH 078/138] Fix Travis CI. --- test/test_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index 72d817c..168c242 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -33,7 +33,7 @@ config = { :adapter => 'postgresql', :database => 'thumbs_up_test', - :username => 'root' + :username => 'postgres' } else config = { From ea3a469e7dba00f95a7823976ae0e38b9918a8c8 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Sun, 29 Jul 2012 16:13:20 -0600 Subject: [PATCH 079/138] Fixed bad cast, now with tests! --- lib/acts_as_voteable.rb | 15 ++++++++++++--- lib/thumbs_up/version.rb | 2 +- test/thumbs_up_test.rb | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index a21d2ad..31faff7 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -28,10 +28,19 @@ def plusminus_tally(params = {}) t = t.order("plusminus_tally DESC") t = t.group("#{self.table_name}.id") t = t.select("#{self.table_name}.*") - t = t.select("SUM(CASE WHEN #{Vote.table_name}.vote THEN 1 ELSE -1 END) AS plusminus_tally") + if mysql? + table = "CAST(#{Vote.table_name}.vote AS UNSIGNED)" + true_value = '1' + false_value = '0' + else + table = "#{Vote.table_name}.vote" + true_value = 'true' + false_value = 'false' + end + t = t.select("SUM(CASE #{table} WHEN #{true_value} THEN 1 WHEN #{false_value} THEN -1 ELSE 0 END) AS plusminus_tally") if params[:separate_updown] - t = t.select("SUM(CASE WHEN #{Vote.table_name}.vote THEN 1 ELSE 0 END) AS up") - t = t.select("SUM(CASE WHEN #{Vote.table_name}.vote THEN 0 ELSE 1 END) AS down") + t = t.select("SUM(CASE #{table} WHEN #{true_value} THEN 1 WHEN #{false_value} THEN 0 ELSE 0 END) AS up") + t = t.select("SUM(CASE #{table} WHEN #{true_value} THEN 0 WHEN #{false_value} THEN 1 ELSE 0 END) AS down") end t = t.select("COUNT(#{Vote.table_name}.id) AS vote_count") end diff --git a/lib/thumbs_up/version.rb b/lib/thumbs_up/version.rb index 5cadcca..c2211ca 100644 --- a/lib/thumbs_up/version.rb +++ b/lib/thumbs_up/version.rb @@ -1,3 +1,3 @@ module ThumbsUp - VERSION = '0.6.0' + VERSION = '0.6.1' end \ No newline at end of file diff --git a/test/thumbs_up_test.rb b/test/thumbs_up_test.rb index 18b2a3d..b4e9b58 100644 --- a/test/thumbs_up_test.rb +++ b/test/thumbs_up_test.rb @@ -237,6 +237,42 @@ def test_plusminus_tally_inclusion end end + def test_plusminus_tally_up + user = User.create(:name => 'david') + item1 = Item.create(:name => 'XBOX', :description => 'XBOX console') + item2 = Item.create(:name => 'Playstation', :description => 'Playstation console') + item3 = Item.create(:name => 'Wii', :description => 'Wii console') + + assert_not_nil user.vote_for(item1) + assert_not_nil user.vote_against(item2) + + assert_equal [1, 0, 0], Item.plusminus_tally(:separate_updown => true).map(&:up).map(&:to_i) + end + + def test_plusminus_tally_down + user = User.create(:name => 'david') + item1 = Item.create(:name => 'XBOX', :description => 'XBOX console') + item2 = Item.create(:name => 'Playstation', :description => 'Playstation console') + item3 = Item.create(:name => 'Wii', :description => 'Wii console') + + assert_not_nil user.vote_for(item1) + assert_not_nil user.vote_against(item2) + + assert_equal [0, 0, 1], Item.plusminus_tally(:separate_updown => true).map(&:down).map(&:to_i) + end + + def test_plusminus_tally_vote_count + user = User.create(:name => 'david') + item1 = Item.create(:name => 'XBOX', :description => 'XBOX console') + item2 = Item.create(:name => 'Playstation', :description => 'Playstation console') + item3 = Item.create(:name => 'Wii', :description => 'Wii console') + + assert_not_nil user.vote_for(item1) + assert_not_nil user.vote_against(item2) + + assert_equal [1, 0, -1], Item.plusminus_tally.map(&:plusminus_tally).map(&:to_i) + end + def test_plusminus_tally_voting_for user1 = User.create(:name => 'david') item = Item.create(:name => 'Playstation', :description => 'Playstation console') From 091281e5e2133dd333c77e08fa204fe17996d6c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20FRERE?= Date: Wed, 19 Sep 2012 10:19:18 +0200 Subject: [PATCH 080/138] Fix bug "plusminus_tally doesn't join on voteable_type" reported in https://github.com/bouchard/thumbs_up/issues/50 --- lib/acts_as_voteable.rb | 4 ++-- test/test_helper.rb | 11 +++++++++++ test/thumbs_up_test.rb | 13 +++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index 0c5adbd..9b00d3d 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -15,7 +15,7 @@ def acts_as_voteable end module SingletonMethods - + # Calculate the plusminus for a group of voteables in one database query. # This returns an Arel relation, so you can add conditions as you like chained on to # this method call. @@ -23,7 +23,7 @@ module SingletonMethods # You can also have the upvotes and downvotes returned separately in the same query: # Post.plusminus_tally(:separate_updown => true) def plusminus_tally(params = {}) - t = self.joins("LEFT OUTER JOIN #{Vote.table_name} ON #{self.table_name}.id = #{Vote.table_name}.voteable_id") + t = self.joins("LEFT OUTER JOIN #{Vote.table_name} ON #{self.table_name}.id = #{Vote.table_name}.voteable_id AND #{Vote.table_name}.voteable_type = '#{self.name}'") t = t.order("plusminus_tally DESC") t = t.group("#{self.table_name}.id") t = t.select("#{self.table_name}.*") diff --git a/test/test_helper.rb b/test/test_helper.rb index 7949c23..868e6db 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -54,6 +54,12 @@ t.string :name t.string :description end + + create_table :other_items, :force => true do |t| + t.integer :user_id + t.string :name + t.string :description + end end require 'thumbs_up' @@ -79,6 +85,11 @@ class Item < ActiveRecord::Base belongs_to :user end +class OtherItem < ActiveRecord::Base + acts_as_voteable + belongs_to :user +end + class User < ActiveRecord::Base acts_as_voter has_many :items diff --git a/test/thumbs_up_test.rb b/test/thumbs_up_test.rb index 0869eba..64033d8 100644 --- a/test/thumbs_up_test.rb +++ b/test/thumbs_up_test.rb @@ -307,4 +307,17 @@ def test_karma assert_equal 4, users[0].karma assert_equal 0, users[1].karma end + + def test_plusminus_tally_scopes_by_voteable_type + user = User.create(:name => 'david') + item = Item.create(:name => 'XBOX', :description => 'XBOX console') + another_item = OtherItem.create(:name => 'Playstation', :description => 'Playstation console') + + user.vote_for(item) + user.vote_for(another_item) + + assert_equal 1, Item.plusminus_tally.sum(&:plusminus_tally).to_i + assert_equal 1, OtherItem.plusminus_tally.sum(&:plusminus_tally).to_i + end + end From 8463f11fbb4677ac387561c52a3503211b64cdb3 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Wed, 19 Sep 2012 21:33:35 +1000 Subject: [PATCH 081/138] Version bump. --- lib/thumbs_up/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/thumbs_up/version.rb b/lib/thumbs_up/version.rb index c2211ca..cdd02cd 100644 --- a/lib/thumbs_up/version.rb +++ b/lib/thumbs_up/version.rb @@ -1,3 +1,3 @@ module ThumbsUp - VERSION = '0.6.1' + VERSION = '0.6.2' end \ No newline at end of file From 156d1cd7cc171c0b2354ab519b6c42b0c215f80b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Kalkosi=C5=84ski?= Date: Thu, 20 Sep 2012 10:15:36 +0300 Subject: [PATCH 082/138] Update SQL group by to include all columns. --- lib/acts_as_voteable.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index 91d4ed5..a66ecea 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -26,7 +26,7 @@ module SingletonMethods def plusminus_tally(params = {}) t = self.joins("LEFT OUTER JOIN #{Vote.table_name} ON #{self.table_name}.id = #{Vote.table_name}.voteable_id AND #{Vote.table_name}.voteable_type = '#{self.name}'") t = t.order("plusminus_tally DESC") - t = t.group("#{self.table_name}.id") + t = t.group(column_names_for_tally) t = t.select("#{self.table_name}.*") if mysql? table = "CAST(#{Vote.table_name}.vote AS UNSIGNED)" @@ -57,7 +57,7 @@ def plusminus_tally(params = {}) def tally(*args) t = self.joins("LEFT OUTER JOIN #{Vote.table_name} ON #{self.table_name}.id = #{Vote.table_name}.voteable_id") t = t.order("vote_count DESC") - t = t.group("#{self.table_name}.id") + t = t.group(column_names_for_tally) t = t.select("#{self.table_name}.*") t = t.select("COUNT(#{Vote.table_name}.id) AS vote_count") end From 3fdc8efdfbb1a65b98d42f8386603361c1f6bf85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Kalkosi=C5=84ski?= Date: Thu, 15 Nov 2012 13:27:34 +0100 Subject: [PATCH 083/138] Refactoring of test connection configuration. --- test/test_helper.rb | 62 +++++++++++++++++---------------------------- 1 file changed, 23 insertions(+), 39 deletions(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index ce74de5..2501414 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -7,48 +7,32 @@ require 'active_record' -if ENV['DB'] == 'mysql' - if ENV['TRAVIS'] - config = { - :adapter => 'mysql2', - :database => 'thumbs_up_test', - :username => 'root' - } +config = { + :database => 'thumbs_up_test', + :username => 'test', + :password => 'test' +} + +connect_database = config[:database] + +case ENV['DB'] + when 'mysql' + config.update({ :adapter => 'mysql2' }) + config[:username] = 'root' if ENV['TRAVIS'] + config[:socket] = '/tmp/mysql.sock' if !ENV['TRAVIS'] + when 'postgres' + config.update({:adapter => 'postgresql'}) + config[:username] = 'postgres' if ENV['TRAVIS'] + connect_database = 'postgres' else - config = { - :adapter => 'mysql2', - :database => 'thumbs_up_test', - :username => 'test', - :password => 'test', - :socket => '/tmp/mysql.sock' - } - end - - ActiveRecord::Base.establish_connection(config) - ActiveRecord::Base.connection.drop_database config[:database] rescue nil - ActiveRecord::Base.connection.create_database config[:database] - ActiveRecord::Base.establish_connection(config) -else - if ENV['TRAVIS'] - config = { - :adapter => 'postgresql', - :database => 'thumbs_up_test', - :username => 'postgres' - } - else - config = { - :adapter => 'postgresql', - :database => 'thumbs_up_test', - :username => 'test' - } - end - - ActiveRecord::Base.establish_connection(config.merge({ :database => 'postgres' })) - ActiveRecord::Base.connection.drop_database config[:database] - ActiveRecord::Base.connection.create_database config[:database] - ActiveRecord::Base.establish_connection(config) + config.update({:adapter => 'sqlite3', :database => 'db/test.sqlite3'}) end +ActiveRecord::Base.establish_connection(config.merge({:database => connect_database})) +ActiveRecord::Base.connection.drop_database config[:database] +ActiveRecord::Base.connection.create_database config[:database] +ActiveRecord::Base.establish_connection(config) + ActiveRecord::Migration.verbose = false ActiveRecord::Schema.define do From e3a0417448d6c4211015f0e75ac7b3aec4c6a3df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Kalkosi=C5=84ski?= Date: Thu, 15 Nov 2012 13:39:50 +0100 Subject: [PATCH 084/138] Allow SQLite3 testing (fails for now). --- Rakefile | 8 +++++--- test/test_helper.rb | 13 +++++++++---- thumbs_up.gemspec | 1 + 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Rakefile b/Rakefile index ca6b42d..e828fe2 100644 --- a/Rakefile +++ b/Rakefile @@ -30,12 +30,14 @@ task :release => :build do system "rm thumbs_up-#{ThumbsUp::VERSION}.gem" end -task :test_both_databases do - # Test both MySQL and Postgres. +task :test_all_databases do + # Test MySQL, Postgres and SQLite3 ENV['DB'] = 'mysql' Rake::Task['test'].execute ENV['DB'] = 'postgres' Rake::Task['test'].execute + ENV['DB'] = 'sqlite3' + Rake::Task['test'].execute end -task :default => :test_both_databases \ No newline at end of file +task :default => :test_all_databases \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index 2501414..ee18e2f 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -14,6 +14,7 @@ } connect_database = config[:database] +drop_and_create_database = true case ENV['DB'] when 'mysql' @@ -25,12 +26,16 @@ config[:username] = 'postgres' if ENV['TRAVIS'] connect_database = 'postgres' else - config.update({:adapter => 'sqlite3', :database => 'db/test.sqlite3'}) + config.update({:adapter => 'sqlite3', :database => 'test.sqlite3'}) + drop_and_create_database = false +end + +if drop_and_create_database + ActiveRecord::Base.establish_connection(config.merge({:database => connect_database})) + ActiveRecord::Base.connection.drop_database config[:database] + ActiveRecord::Base.connection.create_database config[:database] end -ActiveRecord::Base.establish_connection(config.merge({:database => connect_database})) -ActiveRecord::Base.connection.drop_database config[:database] -ActiveRecord::Base.connection.create_database config[:database] ActiveRecord::Base.establish_connection(config) ActiveRecord::Migration.verbose = false diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index 298e06f..3369bb9 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -22,6 +22,7 @@ Gem::Specification.new do |s| s.add_development_dependency('bundler') s.add_development_dependency('mysql2') s.add_development_dependency('pg') + s.add_development_dependency('sqlite3') s.add_development_dependency('rake') end From 39e37f49e9caf2b0297a0266b1defd628e23644c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Kalkosi=C5=84ski?= Date: Thu, 15 Nov 2012 13:59:54 +0100 Subject: [PATCH 085/138] Fixes #57 - deal with booleans in a general non-db specific manner. --- lib/acts_as_voteable.rb | 15 +++------------ lib/thumbs_up.rb | 9 ++++++--- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index a66ecea..d0fe230 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -28,19 +28,10 @@ def plusminus_tally(params = {}) t = t.order("plusminus_tally DESC") t = t.group(column_names_for_tally) t = t.select("#{self.table_name}.*") - if mysql? - table = "CAST(#{Vote.table_name}.vote AS UNSIGNED)" - true_value = '1' - false_value = '0' - else - table = "#{Vote.table_name}.vote" - true_value = 'true' - false_value = 'false' - end - t = t.select("SUM(CASE #{table} WHEN #{true_value} THEN 1 WHEN #{false_value} THEN -1 ELSE 0 END) AS plusminus_tally") + t = t.select("SUM(CASE #{Vote.table_name}.vote WHEN #{quoted_true} THEN 1 WHEN #{quoted_false} THEN -1 ELSE 0 END) AS plusminus_tally") if params[:separate_updown] - t = t.select("SUM(CASE #{table} WHEN #{true_value} THEN 1 WHEN #{false_value} THEN 0 ELSE 0 END) AS up") - t = t.select("SUM(CASE #{table} WHEN #{true_value} THEN 0 WHEN #{false_value} THEN 1 ELSE 0 END) AS down") + t = t.select("SUM(CASE #{Vote.table_name}.vote WHEN #{quoted_true} THEN 1 WHEN #{quoted_false} THEN 0 ELSE 0 END) AS up") + t = t.select("SUM(CASE #{Vote.table_name}.vote WHEN #{quoted_true} THEN 0 WHEN #{quoted_false} THEN 1 ELSE 0 END) AS down") end t = t.select("COUNT(#{Vote.table_name}.id) AS vote_count") end diff --git a/lib/thumbs_up.rb b/lib/thumbs_up.rb index 26ed89d..0f5e748 100644 --- a/lib/thumbs_up.rb +++ b/lib/thumbs_up.rb @@ -4,9 +4,12 @@ module ThumbsUp module Base - # Check if we're connected to a MySQL database. - def mysql? - ActiveRecord::Base.connection.adapter_name == 'MySQL' + def quoted_true + ActiveRecord::Base.connection.quoted_true + end + + def quoted_false + ActiveRecord::Base.connection.quoted_false end end end From b52b6da9e1a0172e646fb4061bcdca5ac3f30c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Kalkosi=C5=84ski?= Date: Thu, 15 Nov 2012 15:35:21 +0100 Subject: [PATCH 086/138] Fix Travis MySql username/password. --- test/test_helper.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index ee18e2f..e1e43b2 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -19,8 +19,12 @@ case ENV['DB'] when 'mysql' config.update({ :adapter => 'mysql2' }) - config[:username] = 'root' if ENV['TRAVIS'] - config[:socket] = '/tmp/mysql.sock' if !ENV['TRAVIS'] + if ENV['TRAVIS'] + config[:username] = 'root' + config.delete(:password) + else + config[:socket] = '/tmp/mysql.sock' if !ENV['TRAVIS'] + end when 'postgres' config.update({:adapter => 'postgresql'}) config[:username] = 'postgres' if ENV['TRAVIS'] From e2587061c2407d297d4a9042b6b23133ade9fc2c Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Fri, 16 Nov 2012 06:41:17 +1000 Subject: [PATCH 087/138] Fix Travis CI. --- test/test_helper.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index bed2c41..2048f85 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -25,8 +25,7 @@ config = { :adapter => 'mysql2', :database => 'thumbs_up_test', - :username => 'test', - :password => 'test' + :username => 'test' } end ActiveRecord::Base.establish_connection(config) From 550134dabbed770cd70f9b15b1830925d6d0c578 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Fri, 16 Nov 2012 06:43:42 +1000 Subject: [PATCH 088/138] Fix Travis CI. --- test/test_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index 2048f85..310f247 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -25,7 +25,7 @@ config = { :adapter => 'mysql2', :database => 'thumbs_up_test', - :username => 'test' + :username => 'root' } end ActiveRecord::Base.establish_connection(config) From e2a124c98814f10521710c1ab61e325d2e9e5e2d Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Mon, 19 Nov 2012 08:49:50 +1000 Subject: [PATCH 089/138] Ignore test database. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4b89f1b..5ba2fe6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ pkg/* Gemfile.lock coverage/ +test.sqlite3 From 578097ceff789296856ae3421754cc9647c5da65 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Mon, 19 Nov 2012 09:03:03 +1000 Subject: [PATCH 090/138] 100% test coverage. --- lib/has_karma.rb | 8 ++++---- test.sqlite3 | Bin 45056 -> 45056 bytes test/thumbs_up_test.rb | 11 ++++++++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/has_karma.rb b/lib/has_karma.rb index 9f06228..1ee49ab 100644 --- a/lib/has_karma.rb +++ b/lib/has_karma.rb @@ -19,11 +19,11 @@ def has_karma(voteable_type, options = {}) module SingletonMethods - ## Not yet implemented. Don't use it! + # Not yet implemented. Don't use it! # Find the most popular users - def find_most_karmic - self.all - end + # def find_most_karmic + # self.all + # end end diff --git a/test.sqlite3 b/test.sqlite3 index 2318d57fa9b30ec047303f045abe96d66c5f4de8..83faacf3bf510440ee18aa140382587d897fc14c 100644 GIT binary patch literal 45056 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 'XBOX', :description => 'XBOX console') + + assert_equal 0, item.ci_plusminus + user_for = User.create(:name => 'david') another_user_for = User.create(:name => 'name') user_against = User.create(:name => 'brady') - item = Item.create(:name => 'XBOX', :description => 'XBOX console') user_for.vote_for(item) another_user_for.vote_for(item) + # Use #reload to force reloading of votes from the database, + # otherwise these tests fail after "assert_equal 0, item.ci_plusminus" caches + # the votes. We hack this as caching is the correct behavious, per-request, + # in production. + item.reload + assert_equal 2, item.votes_for assert_equal 0, item.votes_against assert_equal 2, item.plusminus From ba3432028100e5cde67c5cd3064d1d2f52b95f03 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Wed, 5 Dec 2012 22:05:03 +1000 Subject: [PATCH 091/138] Add tests to document that Postgresql prevents aliases in having clauses. --- test.sqlite3 | Bin 45056 -> 45056 bytes test/thumbs_up_test.rb | 12 ++++++++++++ 2 files changed, 12 insertions(+) diff --git a/test.sqlite3 b/test.sqlite3 index 83faacf3bf510440ee18aa140382587d897fc14c..53ba7ceabdb53652b55e6ad8abe42f3ea0faff00 100644 GIT binary patch literal 45056 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 GBL54Qjvq?^ literal 45056 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 "User #{u}") } + items = (0..9).map{ |u| Item.create(:name => "Item #{u}", :description => "Item #{u}") } + users.each{ |u| items[0..8].each { |i| u.vote_for(i) } } + + # Postgresql doesn't accept aliases in HAVING clauses, so you'll need to copy and paste the whole statement from the #plusminus_tally method if you want to use HAVING('plusminus_tally > 10'), for example. + assert_equal 0, Item.plusminus_tally.limit(5).where('created_at > ?', 2.days.ago).having("SUM(CASE #{Vote.table_name}.vote WHEN #{ActiveRecord::Base.connection.quoted_true} THEN 1 WHEN #{ActiveRecord::Base.connection.quoted_false} THEN -1 ELSE 0 END) > 10").length + assert_equal 5, Item.plusminus_tally.limit(5).where('created_at > ?', 2.days.ago).having("SUM(CASE #{Vote.table_name}.vote WHEN #{ActiveRecord::Base.connection.quoted_true} THEN 1 WHEN #{ActiveRecord::Base.connection.quoted_false} THEN -1 ELSE 0 END) > 9").length + assert_equal 9, Item.plusminus_tally.limit(10).where('created_at > ?', 2.days.ago).having("SUM(CASE #{Vote.table_name}.vote WHEN #{ActiveRecord::Base.connection.quoted_true} THEN 1 WHEN #{ActiveRecord::Base.connection.quoted_false} THEN -1 ELSE 0 END) > 9").length + assert_equal 0, Item.plusminus_tally.limit(10).where('created_at > ?', 1.day.from_now).having("SUM(CASE #{Vote.table_name}.vote WHEN #{ActiveRecord::Base.connection.quoted_true} THEN 1 WHEN #{ActiveRecord::Base.connection.quoted_false} THEN -1 ELSE 0 END) > 9").length + end + def test_plusminus_tally_count Item.plusminus_tally.except(:order).count end From 348ad550372895b8c212a0bf56463495b13efcab Mon Sep 17 00:00:00 2001 From: Boska Date: Thu, 17 Jan 2013 01:26:52 +0800 Subject: [PATCH 092/138] add voters_who_voted_for/voters_who_voted_against for show typical voters vote for or against --- lib/acts_as_voteable.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index d0fe230..d2c1289 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -106,6 +106,14 @@ def voters_who_voted votes.map(&:voter).uniq end + def voters_who_voted_for + votes.where(:vote => true).map(&:voter).uniq + end + + def voters_who_voted_against + votes.where(:vote => false).map(&:voter).uniq + end + def voted_by?(voter) 0 < Vote.where( :voteable_id => self.id, From 7cc4503b88b5345f3e978be25725bd4f1f153a86 Mon Sep 17 00:00:00 2001 From: Boska Date: Sun, 20 Jan 2013 15:01:57 +0800 Subject: [PATCH 093/138] add test to 'voters_who_voted_for/against' --- test/thumbs_up_test.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/test/thumbs_up_test.rb b/test/thumbs_up_test.rb index db87e5b..61fd640 100644 --- a/test/thumbs_up_test.rb +++ b/test/thumbs_up_test.rb @@ -71,10 +71,10 @@ def test_acts_as_voteable_instance_methods user_for = User.create(:name => 'david') another_user_for = User.create(:name => 'name') user_against = User.create(:name => 'brady') + another_user_against = User.create(:name => 'name') user_for.vote_for(item) another_user_for.vote_for(item) - # Use #reload to force reloading of votes from the database, # otherwise these tests fail after "assert_equal 0, item.ci_plusminus" caches # the votes. We hack this as caching is the correct behavious, per-request, @@ -103,6 +103,18 @@ def test_acts_as_voteable_instance_methods assert voters_who_voted.include?(another_user_for) assert voters_who_voted.include?(user_against) + voters_who_voted_for = item.voters_who_voted_for + assert_equal 2, voters_who_voted_for.size + assert voters_who_voted_for.include?(user_for) + assert voters_who_voted_for.include?(another_user_for) + + another_user_against.vote_against(item) + + voters_who_voted_against = item.voters_who_voted_against + assert_equal 2, voters_who_voted_against.size + assert voters_who_voted_against.include?(user_against) + assert voters_who_voted_against.include?(another_user_against) + non_voting_user = User.create(:name => 'random') assert_equal true, item.voted_by?(user_for) From 53d3e4c6ae20f4907afe615b015902f8df590aa8 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Sun, 20 Jan 2013 17:41:28 +1030 Subject: [PATCH 094/138] Bump version. --- lib/thumbs_up/version.rb | 2 +- test.sqlite3 | Bin 45056 -> 0 bytes 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 test.sqlite3 diff --git a/lib/thumbs_up/version.rb b/lib/thumbs_up/version.rb index cdd02cd..38d8221 100644 --- a/lib/thumbs_up/version.rb +++ b/lib/thumbs_up/version.rb @@ -1,3 +1,3 @@ module ThumbsUp - VERSION = '0.6.2' + VERSION = '0.6.3' end \ No newline at end of file diff --git a/test.sqlite3 b/test.sqlite3 deleted file mode 100644 index 53ba7ceabdb53652b55e6ad8abe42f3ea0faff00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45056 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 GBL54Qjvq?^ From 23c5bfdae28bef980e36ed53089ae427ecd54629 Mon Sep 17 00:00:00 2001 From: Peter Boling Date: Thu, 14 Feb 2013 15:48:38 -0500 Subject: [PATCH 095/138] Adds ThumbsUp::Configuration Class --- lib/thumbs_up.rb | 29 +++++++++++++++---- lib/thumbs_up/base.rb | 11 ++++++++ lib/thumbs_up/configuration.rb | 51 ++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 lib/thumbs_up/base.rb create mode 100644 lib/thumbs_up/configuration.rb diff --git a/lib/thumbs_up.rb b/lib/thumbs_up.rb index 0f5e748..14e65f1 100644 --- a/lib/thumbs_up.rb +++ b/lib/thumbs_up.rb @@ -1,17 +1,36 @@ require 'acts_as_voteable' require 'acts_as_voter' require 'has_karma' +require 'thumbs_up/configuration' +require 'thumbs_up/base' +require 'thumbs_up/version' module ThumbsUp - module Base - def quoted_true - ActiveRecord::Base.connection.quoted_true + + class << self + + # An ThumbsUp::Configuration object. Must act like a hash and return sensible + # values for all ThumbsUp::Configuration::OPTIONS. See ThumbsUp::Configuration. + attr_writer :configuration + + # Call this method to modify defaults in your initializers. + # + # @example + # ThumbsUp.configure do |config| + # config.voteable_relationship_name = :votes_by + # config.voter_relationship_name = :votes_on + # end + def configure + yield(configuration) end - def quoted_false - ActiveRecord::Base.connection.quoted_false + # The configuration object. + # @see I18::Airbrake.configure + def configuration + @configuration ||= Configuration.new end end + end ActiveRecord::Base.send(:include, ThumbsUp::ActsAsVoteable) diff --git a/lib/thumbs_up/base.rb b/lib/thumbs_up/base.rb new file mode 100644 index 0000000..4d570c4 --- /dev/null +++ b/lib/thumbs_up/base.rb @@ -0,0 +1,11 @@ +module ThumbsUp + module Base + def quoted_true + ActiveRecord::Base.connection.quoted_true + end + + def quoted_false + ActiveRecord::Base.connection.quoted_false + end + end +end diff --git a/lib/thumbs_up/configuration.rb b/lib/thumbs_up/configuration.rb new file mode 100644 index 0000000..cb3331f --- /dev/null +++ b/lib/thumbs_up/configuration.rb @@ -0,0 +1,51 @@ +module ThumbsUp + class Configuration + + OPTIONS = [:voteable_relationship_name, :voter_relationship_name].freeze + + # Specify the name of the relationship from voted on things to voters. + # Default is votes + # In order to have a model that votes on itself, + # e.g. Users vote on Users, + # must change :voteable_relationship_name or :voter_relationship_name + # to a non-default value + attr_accessor :voteable_relationship_name + + # Specify the name of the relationship from voters to voted on things + # Default is votes + # In order to have a model that votes on itself, + # e.g. Users vote on Users, + # must change :voteable_relationship_name or :voter_relationship_name + # to a non-default value + attr_accessor :voter_relationship_name + + def initialize + # these defaults can be overridden in the ThumbsUp.config block + @voteable_relationship_name = :votes + @voter_relationship_name = :votes + end + + # Allows config options to be read like a hash + # + # @param [Symbol] option Key for a given attribute + def [](option) + send(option) + end + + # Returns a hash of all configurable options + def to_hash + OPTIONS.inject({}) do |hash, option| + hash[option.to_sym] = self.send(option) + hash + end + end + + # Returns a hash of all configurable options merged with +hash+ + # + # @param [Hash] hash A set of configuration options that will take precedence over the defaults + def merge(hash) + to_hash.merge(hash) + end + + end +end From db32810b0088610601f8bc9bc37ab80e244660ab Mon Sep 17 00:00:00 2001 From: Peter Boling Date: Thu, 14 Feb 2013 15:49:27 -0500 Subject: [PATCH 096/138] Utilize configuration to allow models to vote on themselves --- lib/acts_as_voteable.rb | 20 ++++++++++++++------ lib/acts_as_voter.rb | 12 ++++++++++-- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index d2c1289..ab36b67 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -8,7 +8,10 @@ def self.included(base) module ClassMethods def acts_as_voteable - has_many :votes, :as => :voteable, :dependent => :destroy + has_many ThumbsUp.configuration[:voteable_relationship_name], + :as => :voteable, + :dependent => :destroy, + :class_name => "Vote" include ThumbsUp::ActsAsVoteable::InstanceMethods extend ThumbsUp::ActsAsVoteable::SingletonMethods @@ -61,20 +64,25 @@ def column_names_for_tally module InstanceMethods + # wraps the dynamic, configured, relationship name + def _votes_by + self.send(ThumbsUp.configuration[:voteable_relationship_name]) + end + def votes_for - self.votes.where(:vote => true).count + self._votes_by.where(:vote => true).count end def votes_against - self.votes.where(:vote => false).count + self._votes_by.where(:vote => false).count end def percent_for - (votes_for.to_f * 100 / (self.votes.size + 0.0001)).round + (votes_for.to_f * 100 / (self._votes_by.size + 0.0001)).round end def percent_against - (votes_against.to_f * 100 / (self.votes.size + 0.0001)).round + (votes_against.to_f * 100 / (self._votes_by.size + 0.0001)).round end # You'll probably want to use this method to display how 'good' a particular voteable @@ -113,7 +121,7 @@ def voters_who_voted_for def voters_who_voted_against votes.where(:vote => false).map(&:voter).uniq end - + def voted_by?(voter) 0 < Vote.where( :voteable_id => self.id, diff --git a/lib/acts_as_voter.rb b/lib/acts_as_voter.rb index 4e4c19f..3da2852 100644 --- a/lib/acts_as_voter.rb +++ b/lib/acts_as_voter.rb @@ -12,8 +12,11 @@ def acts_as_voter # If you want to nullify (and keep the votes), you'll need to remove # the unique constraint on the [ voter, voteable ] index in the database. # has_many :votes, :as => :voter, :dependent => :nullify - # Destroy votes when a user is deleted. - has_many :votes, :as => :voter, :dependent => :destroy + # Destroy voter's votes when the voter is deleted. + has_many ThumbsUp.configuration[:voter_relationship_name], + :as => :voter, + :dependent => :destroy, + :class_name => "Vote" include ThumbsUp::ActsAsVoter::InstanceMethods extend ThumbsUp::ActsAsVoter::SingletonMethods @@ -27,6 +30,11 @@ module SingletonMethods # This module contains instance methods module InstanceMethods + # wraps the dynamic, configured, relationship name + def _votes_on + self.send(ThumbsUp.configuration[:voteable_relationship_name]) + end + # Usage user.vote_count(:up) # All +1 votes # user.vote_count(:down) # All -1 votes # user.vote_count() # All votes From 80602917ff07ba3ebaecaed6dd512617539a59c5 Mon Sep 17 00:00:00 2001 From: Peter Boling Date: Thu, 14 Feb 2013 15:49:55 -0500 Subject: [PATCH 097/138] Newline! --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index e828fe2..cb40a02 100644 --- a/Rakefile +++ b/Rakefile @@ -40,4 +40,4 @@ task :test_all_databases do Rake::Task['test'].execute end -task :default => :test_all_databases \ No newline at end of file +task :default => :test_all_databases From 8035f0760165638ab83ac9b870d8011a4c24522b Mon Sep 17 00:00:00 2001 From: Peter Boling Date: Thu, 14 Feb 2013 15:51:10 -0500 Subject: [PATCH 098/138] Update MySQL test instructions to current MySQL version 5.5; Adds postgres setup instructions; --- README.md | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c6379c3..2030173 100644 --- a/README.md +++ b/README.md @@ -142,14 +142,32 @@ You can also use `--unique-voting false` when running the generator command: Testing is a bit more than trivial now as our #tally and #plusminus_tally queries don't function properly under SQLite. To set up for testing: -``` -$ mysql -uroot # You may have set a password locally. Change as needed. - > GRANT ALL PRIVILEGES ON 'thumbs_up_test' to 'test'@'localhost' IDENTIFIED BY 'test'; - > CREATE DATABASE 'thumbs_up_test'; - > exit; - -$ rake # Runs the test suite. -``` +* mysql + + ``` + $ mysql -uroot # You may have set a password locally. Change as needed. + > CREATE USER 'test'@'localhost' IDENTIFIED BY 'test'; + > CREATE DATABASE thumbs_up_test; + > USE thumbs_up_test; + > GRANT ALL PRIVILEGES ON thumbs_up_test TO 'test'@'localhost' IDENTIFIED BY 'test'; + > exit; + ``` +* Postgres + + ``` + $ psql # You may have set a password locally. Change as needed. + > CREATE ROLE test; + > ALTER ROLE test WITH SUPERUSER; + > ALTER ROLE test WITH LOGIN; + > CREATE DATABASE thumbs_up_test; + > GRANT ALL PRIVILEGES ON DATABASE thumbs_up_test to test; + > \q + ``` +* Run tests + + ``` + $ rake # Runs the test suite against all adapters. + ``` Credits ======= From a9ef785fafe422786a8fe05d15929da5b63a4c06 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Sun, 20 Jan 2013 17:41:28 +1030 Subject: [PATCH 099/138] Bump version. --- lib/thumbs_up/version.rb | 2 +- test.sqlite3 | Bin 45056 -> 0 bytes 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 test.sqlite3 diff --git a/lib/thumbs_up/version.rb b/lib/thumbs_up/version.rb index cdd02cd..38d8221 100644 --- a/lib/thumbs_up/version.rb +++ b/lib/thumbs_up/version.rb @@ -1,3 +1,3 @@ module ThumbsUp - VERSION = '0.6.2' + VERSION = '0.6.3' end \ No newline at end of file diff --git a/test.sqlite3 b/test.sqlite3 deleted file mode 100644 index 53ba7ceabdb53652b55e6ad8abe42f3ea0faff00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45056 zcmeHQ3y>DomF|Du|KBf>$A}>O3?NK|vH!o{M1vrsmxzD_R;akaQO0Ey2Lhvu2|ljz zfd&`+Jw3y>g&;>8jqx@RoxE-#up`-DZ^<=Jy*buTV=sOdjgvZFk2Zn*=O z?^Hm0-Uy< zxkfUJwFDQ=m^r7r+#w-ZXb1{9FYG?HdqH{rg4xHV!#qkMMH>Qk5Zte#vA!%lE6>s@o|>b_M2 zeJlI-#ya`9N^5&NY>9E0HRg+dwe6iL`u)tt{@BfsevS_ES z?(G@qUADAmprgDDtbx8Wd-p2-s?4jwJNeM5t?f(N3;bx5IR1qSUjtf4BNN%up8jRE ze`G2`5jNGJu zmuGb!Gh@-5h2=@7O{(oqgFDcmWaWxx%Zfc}FA;zE*KiLV=MCl)OCK<^KBHfKTjd zZSMt*eqN{1+Sd9&rXOq_6iD+&Dpk9@La(*o-Rnq8hgKU(R&ci(?pF77_m9pi&K1r~ z`w#Zrc8@*6dfvLg3eC67o6Uu0vGjOpO=)uR_2Q=Dv2E|SJ>@YQewr$& z5MjJaP>rig6{lgP>Zzy*VSK8fD%YnpDIqH#8*-w!uVl=YRpGa zd6h_6t2oXO#y=&f4yjKSRg)xEW`zjjoq}p~eX20@LNyg4j87I+6EdnXI$gyHs^WXf ztfDkS7%vMdl&GF6@so-=D?}I{E2vPShv+04#Ubp0s#+1k_!vQj64g^hNffB55Mlff zL4^|4Q^EFA?>G@6jE@#nDA77p)gV>wgHf0vjE@pjC{aCCP=#fbwL*mPg9Q~zR8QrH zp05@IB8-m|R47qBRW*uyHEISBNm)E~rqVdMXbFRps$2LWJ=V zf(j)%S}Re{tEPdn?1(TvTu`AzwNzp1RZ`^^B!ux{f(j+7r;1_ZRF*wNgz=$*3ME>H z3QD4sXc%V*;|B^Vl&GExwz;QXK|~lIBB)TJda5vh{aID^5D~@?5L75pJyj5d>VZB) zgmG6;p+xmmKJ0<&vLnK{BdAcKda7#d$4ZHYh%jyoDwOCbokYV*8l<7JR){ce2`ZGR zp2|xSILasnuMiQ&O+kec)lvm8c&Tee2;(I|g%Yhp<)x9b>_M6#j28tJN>opkz+p*w z))yecc$=U?iR!81FjOCq14I~a6;vouJyjG3p>psF5MjJUP@zQiRDlm#P;J2g5yqPZ z6-rc3m7Vh`2l@aJ#+w8cO7vizM1v}vo~yG$gz-i}g%Yhp<$3BB01g*`Fy0`jP@-BY z=#^j@RjmkNydbDhqI#+%h&|=;%14B8Lr|ea^;8jzg32QUR2>j@zFY0KhQJ-u3 zh_Len6ADz%RPmBnd0d#oPb@OtBZ#V5Q*zi|LP!Ri-J~lFDglg}`y600B&bxYj4u;Z zQAX9Z7gbeI#n3JIijpT?F|2f&+U`q6llzq6{?`2$_gygZ|MCjt704@)S0JxIUV*#< zc?I$cww0$9WU(6xiu(ceXfpIyX5Ror|2c&I;#bXQ9KKBOK3};2h*wj$yxR|HgjJ-eo^+ zKMeO2++ts2ueaCPtL$a=VtbB#lpWZU?a}rDc9XTo`VZ@u)^2Nu^_X?Pwb{DP+5mSS z3|PypldSpHEGx05SY>ON)n>kLzGJ>=zGCjQ+AYf}nD3dtGk|K)mGs%8IvVV$p2gced+0RS%bCUh6WbcsdXC(V4lKo@Jej4p@80#s?{*h!q zDcRd4`w7W@T(W;C*^f!~qiB~g)+3Vr1Id0^vLBM{ZIZoJvbRX~gOdGywDFE-;{nOO zU$XC$?0Y5q9?8C2vcD(U-<9mU(8jwJjXNd#JCglv$-YCfH%s>Il6{+G-zwR+ppCbk z88=Jzw zL>zB8Fn)zL-ZAi@{=W?UJcE9H74+lRSySA%U5l(`6f{UDkpzl8l`ugSO`!KUGA7&2L zIu^_X1kmZ9WWED)4YOb_VFk<;Tw_m$-haONCd@u0FuQOv%pR<_N5gEuEb|qZkC+1U z4GUpDVV!*d%nu~yPIr?#!Py2G|H58j{mI&Bb>#j3CmC1rW%CN;704@)S0JxIUV*#< zc?I^*3gAiosO+|1>i-YU%)$O2Z!sV2|1-d#{=Wll>i_XBZG-xMyv^F6{vU6YHmLu{ zTb&K+|M6yegZh8G8{VM)pJGw}kN2Y+wf-MtQU6b|sQ;%})c;c~>i;Pg_5T!$`hSW= z{XfN`{-0t||4-{x>;Ew>_5T!$`hSW={XfN`{-0t||4*@~|EE~g|5GgL|0x#r|FmAU z{vYE~|4*@~|EE~g|5GgL|0x#r{}hY*e~Lx@KgFW{pJGw}PwQ3d|1mE0{}hY*e~Lx@ zKgFW{pJGw}PqC=~r&!egQ!MKLDHiqrv|hFTALCO0PqC=~r&!egQ!MKLDHiqr6pQ+Q zibefD#iIV7Vp0E3>s9OjF)sE06pQ+QibefD#iIV7Vp0E3v8ey2Sk(VhEb9L$7WMzM zUbX%o<5K@mv8ey2Sk(VhEb9L$7WMxWi~4_xMg2d;qW+&^QU6ctMg2d;qW+&^QU6b| zsQ;%})c;c~>i;no_5W^DaV+-y_|!qI|HsD$2K)ct8}9GjU%1b?54(3jSAD*_!d>DX z&EZMW{RZm=$e8G#;auJvhavNhalFyA&`GoLlLnYWu8&2!Du%@fU8 zX2l$1nx#FZe=EIMdZKi9>H5+o@Vx*X>mlYQjk1BQ*M;A+Nd-l&GcAiyUfxH5F1@a2y704^FKU82yOZM6!x{6Az zlWo*+r>7FS{t!=VTY9`mF4tRCmZO zO7$7pMXCNoc2TN7mR*$U(=``f38qw^l3kSQk7O66`lRflRJY46O7#iZMX5e6yC~Hk z$}URvG1*0_K3a3(g=9+g5!pql{y=t7st?O9O7$VxMX7F+U6krp*+r>tkzJJPgR+ZK z{eI1b*P$uZ2V@tedcW+VRPU2rl@~ zdZ+B7RKFv;DAjMvE=u(d*+r>tmR*$U?Xrtfy-jves<+B6O7)hS3$KDxsyE9nO7&Z^ zi&Fij?4nd}l3kSQjk1eUy+L+Ss@KadO7%L~MX6p}bKymFO7$DEi&Fi%?4ne!kzJJP z*JKx^x=D6Xs#nV{N_C^`qExSvU6ksTH5Xokr&O-Rd!LTm&-0n^)lH- zsjin@lIL?c2TNdkzJJPg|drM<+6)Xy`VPkAU&pfzU&zx z#vpph08U{1s>Xwt{%id|z5j2!;cj=idn8=Nf7JPsliGWrr$2jt7^m|b@(Sb?$SaUn zAg@4PfxH5F1@a2y704^_w_Snh^8hfGQ#c^>LA=Ar(K>1?SN?L(;3h(<8yNn=1rjHc_%9rL<;N7Oa z;w^YUfXMVQfUoTl*d$$eg<2Ly;rKSU5HUz}Nq13%DEXUO!?fI9B?^6oLB*h z|9_jk|8JLjpS#7q)xFaFGCcLa$esDOy^rPD=M~5+kXInDKwg2o0(k}U3gi{YE09+p zuRvabzhni5Ol&rck$8VtInA!Km(w8=geyT;G6ZIP27fC?S3Csfa0Xp?HNG4TfmxYB zSBSqA4uN@=L02$)D75>=(D5hs^}(O=sVn=}z{eoRPX*_AfBuS|bJnznV7E_NJagXS zD*iS2OT!}ohEL3V+0USx`u|rA_tn2-O>(Zh0(k}U3gi{YE09+puRvabyaIUz@(Sb? z$SaUn;Qy%t;|pS1V4!E9Z)HEu5Rf_8|JQOU!2eCR1Kz}Ug)`Is1H7}Z#~xul4{!Gi z&9}^(&4p&M^f)|^Ke_mNaZ~Zww)fi}XgjTKRO_zRi(99)yxp?7Wl4+E{8aPW=BZ6@ zG+o;?r>W4mt?`V;a>L6Fmp4o=yj!@d@P)!qV~26BwDyd9;yZ@;LZVSJLHLW%0B{51RG zt7-)iVf;`*g%YhpRe|qzs;M%B@ri;8C90>Y`mm{#RERJ>K~SMY^;8x3vb*v=rx+2& z#|tWysGiD8GA>oE5MjJSP@zP%R1thaIaP{*5XQ#|DwJp)s*3L^vx?FTVZ1D;P@;OO z#7{KeX-9WUMl&GEx?z;=rRERKsh@e7=>ZxG+sdtbC90on;AFhX*tICcD<0AwWN>uxu z_6WXKoP8y_Qk@Eaym0XQ!*LfOj1L!7C{Zm{n0l2|xdjPfe3+m@iR!6h7&()l*etKUPXKM1*l$P@zP%-)RpkX#n4R zS4fl)#w|gG64g_ANus{1H$;SSQ&6EqwNwENUg}yA!gxtgp+xIYd1<69dyr-b<3&M* z64g^Ba9C2FAqR*s-X^F}qI#-0gj=vxWe*Txyj4)4MDo_+*~Fy17nP@?$Ewla1VPS4d@A;Ng0phAh(q4GR+3jl`; zKp1ZjR47p`74%B5j4EG37%vDal&GF631Uxqyz&uY+z?bKQ9V@zqoDH0096NsoiCVB zp?anuNz~`sJ|gTq!Gr?UGgZ7KRvuz}MA*543H7OEswRFAD+@&kTPK)Mo_eO(_tipx z11KQb_5X>@orb&BJ>4A(@ACnd48aFkvhCelI zX*j*1T==KLhQdr^k8!Wm|Nq%0nD6ix4(jFyT9ihUwN04$g6+&_~9UA&dzoe3y})DG8&h znh6nhtYE@>T=YzFS^=(o23W`m5q6AV!gnXtVXCI8Ju+j;5Y{c2@SRI~rU=Gqbxw$| zS%PUqw(aULd0whG-at-piknv0(Sm7seI__{Q4M3jgb14{m{8;ToZ#6aRo?_mh_D%g z2{m4a$@5eXI%P~5!j2M5sBt}$563!n#SvlC1rus~s8-|9$*HDOASXoFX9N>!T+dWV zqgZif1WbsqBLx#`T*burBJo=|j^me>_SJjRRq-ofq8JAT5ylq^Djdi4RMlz}C}amz zh%o*+L51VEo~lxT8YuHcgz*J}3deCh6|`uc;+zMvLWJ=X1Qm|sS}J(X5qf-8R)jD< zUr^yduBS>Opx8};3K7Po@ew7er-BoCO;(67E)9?<(K=Kx zjiBf|AS;-)N}&mrMo5&Xp31LOGrfO!Mgpi1VO$y_QKEXPst*EHQX#^)G)AIC^;DG* z_ccXUh%hboo(g_AhNhyz(6!nibAqD1vn za6YJ>BF@efW0*8MLLB-~qIxPX_Eo2ZkQE|~e_BwXM72~&ns|ZY$N<+E5n+6qphAh_ zTO3uzO_DTJPfroTcq*t+qIIY$G00(`;}v8D*AXgk=t~3@N>oo3!x2uoJ12-R9t$dz zsGcecV%0eqWQ7Rhk)T3}>ZxFhtEY%zs0D=aP*9;n^;9tHsh*in5MewJR4CCpRGzQC z4wVEM!niM}P@;M&znZGm4#Phnj8_E}N>oo(4SiKB1!YHs@rs~AiE1yEf_S1x(Y*o{ GBL54Qjvq?^ From 19249fe1d7353c94a5f9d8a3f00c715111b8b15b Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Fri, 15 Feb 2013 21:05:37 +1030 Subject: [PATCH 100/138] Bump version. --- lib/thumbs_up/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/thumbs_up/version.rb b/lib/thumbs_up/version.rb index 38d8221..76e7549 100644 --- a/lib/thumbs_up/version.rb +++ b/lib/thumbs_up/version.rb @@ -1,3 +1,3 @@ module ThumbsUp - VERSION = '0.6.3' + VERSION = '0.6.4' end \ No newline at end of file From d2ec8dd89f1802234713c11eb62d6045a267d413 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Fri, 15 Feb 2013 21:09:22 +1030 Subject: [PATCH 101/138] Add license to gemspec. --- thumbs_up.gemspec | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index 3369bb9..cbab125 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -5,16 +5,17 @@ $:.unshift lib unless $:.include?(lib) require 'thumbs_up/version' Gem::Specification.new do |s| - s.name = "thumbs_up" + s.name = 'thumbs_up' s.version = ThumbsUp::VERSION - s.homepage = "http://github.com/bouchard/thumbs_up" - s.summary = "Voting for ActiveRecord with multiple vote sources and karma calculation." - s.description = "ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com." - s.authors = ["Brady Bouchard", "Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnetrzak"] - s.email = ["brady@thewellinspired.com"] - s.files = Dir.glob("{lib,rails,test}/**/*") + %w(CHANGELOG.md Gemfile MIT-LICENSE README.md Rakefile) - s.require_paths = ["lib"] + s.homepage = 'http://github.com/bouchard/thumbs_up' + s.license = 'MIT' + s.summary = 'Voting for ActiveRecord with multiple vote sources and karma calculation.' + s.description = 'ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com.' + s.authors = ['Brady Bouchard', 'Peter Jackson', 'Cosmin Radoi', 'Bence Nagy', 'Rob Maddox', 'Wojciech Wnetrzak'] + s.email = ['brady@thewellinspired.com'] + s.files = Dir.glob('{lib,rails,test}/**/*') + %w(CHANGELOG.md Gemfile MIT-LICENSE README.md Rakefile) + s.require_paths = ['lib'] s.add_runtime_dependency('activerecord') s.add_runtime_dependency('statistics2') From c4a9a64bfc5a44e0769d88443a95613602e067f3 Mon Sep 17 00:00:00 2001 From: Peter Boling Date: Sat, 16 Feb 2013 00:37:35 -0500 Subject: [PATCH 102/138] Fixed typo --- lib/thumbs_up.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/thumbs_up.rb b/lib/thumbs_up.rb index 14e65f1..9b35cb0 100644 --- a/lib/thumbs_up.rb +++ b/lib/thumbs_up.rb @@ -25,7 +25,7 @@ def configure end # The configuration object. - # @see I18::Airbrake.configure + # @see ThumbsUp::Configuration def configuration @configuration ||= Configuration.new end From 86c948e5b92e9e1bfd53e851e8866073aa85cb68 Mon Sep 17 00:00:00 2001 From: Peter Boling Date: Tue, 26 Feb 2013 02:54:04 -0500 Subject: [PATCH 103/138] The source :rubygems is deprecated because HTTP requests are insecure. Please change your source to 'https://rubygems.org' if possible, or 'http://rubygems.org' if not. --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index e45e65f..851fabc 100644 --- a/Gemfile +++ b/Gemfile @@ -1,2 +1,2 @@ -source :rubygems +source 'https://rubygems.org' gemspec From ec4cdc1f4cffa68b50c07e267b6801d311c31ac8 Mon Sep 17 00:00:00 2001 From: Peter Boling Date: Sat, 13 Apr 2013 17:56:30 -0400 Subject: [PATCH 104/138] create! does not return cerated object, but it can be useful in post-vote processing,\nand is more reliable than trying to find the vote just cast by querying. --- lib/acts_as_voter.rb | 5 ++++- test/thumbs_up_test.rb | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/acts_as_voter.rb b/lib/acts_as_voter.rb index b1db1b0..a707720 100644 --- a/lib/acts_as_voter.rb +++ b/lib/acts_as_voter.rb @@ -88,7 +88,10 @@ def vote(voteable, options = {}) self.unvote_for(voteable) end direction = (options[:direction].to_sym == :up) - Vote.create!(:vote => direction, :voteable => voteable, :voter => self) + # create! does not return the created object + v = Vote.new(:vote => direction, :voteable => voteable, :voter => self) + v.save! + v end def unvote_for(voteable) diff --git a/test/thumbs_up_test.rb b/test/thumbs_up_test.rb index 61fd640..c55089a 100644 --- a/test/thumbs_up_test.rb +++ b/test/thumbs_up_test.rb @@ -61,6 +61,16 @@ def test_acts_as_voter_instance_methods assert_raises(ArgumentError) do user_for.vote(item, {:direction => :foo}) end + + vote = user_against.vote(item, exclusive: true, direction: :down) + assert_equal true, user_against.voted_against?(item) + # Make sure the vote record was returned by the :vote method + assert_equal true, vote.is_a?(Vote) + + vote = user_for.vote(item, exclusive: true, direction: :up) + assert_equal true, user_for.voted_for?(item) + # Make sure the vote record was returned by the :vote method + assert_equal true, vote.is_a?(Vote) end def test_acts_as_voteable_instance_methods From 2a24198e242d624e8587108bb76644cf08498be1 Mon Sep 17 00:00:00 2001 From: Peter Boling Date: Sun, 14 Apr 2013 13:05:22 -0400 Subject: [PATCH 105/138] Fix Travis build warnings and failures (spaces before parenthesis; revert Ruby 1.9 json hash syntax) --- test/thumbs_up_test.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/thumbs_up_test.rb b/test/thumbs_up_test.rb index c55089a..a0e94f3 100644 --- a/test/thumbs_up_test.rb +++ b/test/thumbs_up_test.rb @@ -62,12 +62,12 @@ def test_acts_as_voter_instance_methods user_for.vote(item, {:direction => :foo}) end - vote = user_against.vote(item, exclusive: true, direction: :down) + vote = user_against.vote(item, :exclusive => true, :direction => :down) assert_equal true, user_against.voted_against?(item) # Make sure the vote record was returned by the :vote method assert_equal true, vote.is_a?(Vote) - vote = user_for.vote(item, exclusive: true, direction: :up) + vote = user_for.vote(item, :exclusive => true, :direction => :up) assert_equal true, user_for.voted_for?(item) # Make sure the vote record was returned by the :vote method assert_equal true, vote.is_a?(Vote) @@ -260,11 +260,11 @@ def test_plusminus_tally_inclusion assert_not_nil user.vote_for(item) if ActiveRecord::Base.connection.adapter_name == 'MySQL' - assert (Item.plusminus_tally.having('vote_count > 0').include? item) - assert (not Item.plusminus_tally.having('vote_count > 0').include? item_not_included) + assert(Item.plusminus_tally.having('vote_count > 0').include?(item)) + assert(!Item.plusminus_tally.having('vote_count > 0').include?(item_not_included)) else - assert (Item.plusminus_tally.having('COUNT(votes.id) > 0').include? item) - assert (not Item.plusminus_tally.having('COUNT(votes.id) > 0').include? item_not_included) + assert(Item.plusminus_tally.having('COUNT(votes.id) > 0').include?(item)) + assert(!Item.plusminus_tally.having('COUNT(votes.id) > 0').include?(item_not_included)) end end From ace0fc786641e81014d8f330e786d44fc45387de Mon Sep 17 00:00:00 2001 From: Peter Boling Date: Wed, 17 Apr 2013 13:00:08 -0400 Subject: [PATCH 106/138] Proper implementation of voter_relationship_name and voteable_relationship_name --- lib/acts_as_voteable.rb | 20 +++--- lib/acts_as_voter.rb | 4 +- lib/thumbs_up.rb | 4 +- test/test_helper.rb | 49 ++++++++++++++ test/thumbs_up_test.rb | 144 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 207 insertions(+), 14 deletions(-) diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index 53a9cb3..bc8f353 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -65,24 +65,24 @@ def column_names_for_tally module InstanceMethods # wraps the dynamic, configured, relationship name - def _votes_by + def _votes_on self.send(ThumbsUp.configuration[:voteable_relationship_name]) end def votes_for - self._votes_by.where(:vote => true).count + self._votes_on.where(:vote => true).count end def votes_against - self._votes_by.where(:vote => false).count + self._votes_on.where(:vote => false).count end def percent_for - (votes_for.to_f * 100 / (self._votes_by.size + 0.0001)).round + (votes_for.to_f * 100 / (self._votes_on.size + 0.0001)).round end def percent_against - (votes_against.to_f * 100 / (self._votes_by.size + 0.0001)).round + (votes_against.to_f * 100 / (self._votes_on.size + 0.0001)).round end # You'll probably want to use this method to display how 'good' a particular voteable @@ -97,7 +97,7 @@ def plusminus # http://evanmiller.org/how-not-to-sort-by-average-rating.html def ci_plusminus(confidence = 0.95) require 'statistics2' - n = votes.size + n = self._votes_on.size if n == 0 return 0 end @@ -107,19 +107,19 @@ def ci_plusminus(confidence = 0.95) end def votes_count - votes.size + _votes_on.size end def voters_who_voted - votes.map(&:voter).uniq + _votes_on.map(&:voter).uniq end def voters_who_voted_for - votes.where(:vote => true).map(&:voter).uniq + _votes_on.where(:vote => true).map(&:voter).uniq end def voters_who_voted_against - votes.where(:vote => false).map(&:voter).uniq + _votes_on.where(:vote => false).map(&:voter).uniq end def voted_by?(voter) diff --git a/lib/acts_as_voter.rb b/lib/acts_as_voter.rb index b1db1b0..892c3e3 100644 --- a/lib/acts_as_voter.rb +++ b/lib/acts_as_voter.rb @@ -31,8 +31,8 @@ module SingletonMethods module InstanceMethods # wraps the dynamic, configured, relationship name - def _votes_on - self.send(ThumbsUp.configuration[:voteable_relationship_name]) + def _votes_by + self.send(ThumbsUp.configuration[:voter_relationship_name]) end # Usage user.vote_count(:up) # All +1 votes diff --git a/lib/thumbs_up.rb b/lib/thumbs_up.rb index 9b35cb0..b1a738a 100644 --- a/lib/thumbs_up.rb +++ b/lib/thumbs_up.rb @@ -17,8 +17,8 @@ class << self # # @example # ThumbsUp.configure do |config| - # config.voteable_relationship_name = :votes_by - # config.voter_relationship_name = :votes_on + # config.voteable_relationship_name = :votes_on + # config.voter_relationship_name = :votes_by # end def configure yield(configuration) diff --git a/test/test_helper.rb b/test/test_helper.rb index 310f247..311e385 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -89,6 +89,24 @@ t.string :name t.string :description end + + create_table :user_customs, :force => true do |t| + t.string :name + t.timestamps + end + + create_table :item_customs, :force => true do |t| + t.integer :user_id + t.string :name + t.string :description + end + + create_table :other_item_customs, :force => true do |t| + t.integer :user_id + t.string :name + t.string :description + end + end require 'thumbs_up' @@ -110,16 +128,47 @@ class Vote < ActiveRecord::Base end class Item < ActiveRecord::Base + # This is default, however because of random test run ordering we need to be explicit + ThumbsUp.configuration.voteable_relationship_name = :votes + ThumbsUp.configuration.voter_relationship_name = :votes acts_as_voteable belongs_to :user end class OtherItem < ActiveRecord::Base + # This is default, however because of random test run ordering we need to be explicit + ThumbsUp.configuration.voteable_relationship_name = :votes + ThumbsUp.configuration.voter_relationship_name = :votes acts_as_voteable belongs_to :user end class User < ActiveRecord::Base + # This is default, however because of random test run ordering we need to be explicit + ThumbsUp.configuration.voteable_relationship_name = :votes + ThumbsUp.configuration.voter_relationship_name = :votes + acts_as_voter + has_many :items + has_karma(:items) +end + +class ItemCustom < ActiveRecord::Base + ThumbsUp.configuration.voteable_relationship_name = :votes_on + ThumbsUp.configuration.voter_relationship_name = :votes_by + acts_as_voteable + belongs_to :user +end + +class OtherItemCustom < ActiveRecord::Base + ThumbsUp.configuration.voteable_relationship_name = :votes_on + ThumbsUp.configuration.voter_relationship_name = :votes_by + acts_as_voteable + belongs_to :user +end + +class UserCustom < ActiveRecord::Base + ThumbsUp.configuration.voteable_relationship_name = :votes_on + ThumbsUp.configuration.voter_relationship_name = :votes_by acts_as_voter has_many :items has_karma(:items) diff --git a/test/thumbs_up_test.rb b/test/thumbs_up_test.rb index 61fd640..a1d01b1 100644 --- a/test/thumbs_up_test.rb +++ b/test/thumbs_up_test.rb @@ -8,6 +8,10 @@ def setup end def test_acts_as_voter_instance_methods + # Because these are set in several places we need to ensure the defaults are set here. + ThumbsUp.configuration.voteable_relationship_name = :votes + ThumbsUp.configuration.voter_relationship_name = :votes + user_for = User.create(:name => 'david') user_against = User.create(:name => 'brady') item = Item.create(:name => 'XBOX', :description => 'XBOX console') @@ -64,6 +68,10 @@ def test_acts_as_voter_instance_methods end def test_acts_as_voteable_instance_methods + # Because these are set in several places we need to ensure the defaults are set here. + ThumbsUp.configuration.voteable_relationship_name = :votes + ThumbsUp.configuration.voter_relationship_name = :votes + item = Item.create(:name => 'XBOX', :description => 'XBOX console') assert_equal 0, item.ci_plusminus @@ -123,6 +131,139 @@ def test_acts_as_voteable_instance_methods assert_equal false, item.voted_by?(non_voting_user) end + def test_acts_as_voter_configuration + ThumbsUp.configuration.voteable_relationship_name = :votes_on + ThumbsUp.configuration.voter_relationship_name = :votes_by + + user_for = UserCustom.create(:name => 'david') + user_against = UserCustom.create(:name => 'brady') + item = ItemCustom.create(:name => 'XBOX', :description => 'XBOX console') + + # We have changed the name of the relationship, so `votes` is not defined. + assert_raises(NoMethodError) do + user_for.votes + end + + assert_not_nil user_for.vote_for(item) + assert_raises(ActiveRecord::RecordInvalid) do + user_for.vote_for(item) + end + assert_equal true, user_for.voted_for?(item) + assert_equal false, user_for.voted_against?(item) + assert_equal true, user_for.voted_on?(item) + assert_equal 1, user_for.vote_count + assert_equal 1, user_for.vote_count(:up) + assert_equal 0, user_for.vote_count(:down) + assert_equal true, user_for.voted_which_way?(item, :up) + assert_equal false, user_for.voted_which_way?(item, :down) + assert_equal 1, user_for.votes_by.where(:voteable_type => 'ItemCustom').count + assert_equal 0, user_for.votes_by.where(:voteable_type => 'AnotherItem').count + assert_raises(ArgumentError) do + user_for.voted_which_way?(item, :foo) + end + + assert_not_nil user_against.vote_against(item) + assert_raises(ActiveRecord::RecordInvalid) do + user_against.vote_against(item) + end + assert_equal false, user_against.voted_for?(item) + assert_equal true, user_against.voted_against?(item) + assert_equal true, user_against.voted_on?(item) + assert_equal 1, user_against.vote_count + assert_equal 0, user_against.vote_count(:up) + assert_equal 1, user_against.vote_count(:down) + assert_equal false, user_against.voted_which_way?(item, :up) + assert_equal true, user_against.voted_which_way?(item, :down) + assert_raises(ArgumentError) do + user_against.voted_which_way?(item, :foo) + end + + assert_not_nil user_against.vote_exclusively_for(item) + assert_equal true, user_against.voted_for?(item) + + assert_not_nil user_for.vote_exclusively_against(item) + assert_equal true, user_for.voted_against?(item) + + user_for.unvote_for(item) + assert_equal 0, user_for.vote_count + + user_against.unvote_for(item) + assert_equal 0, user_against.vote_count + + assert_raises(ArgumentError) do + user_for.vote(item, {:direction => :foo}) + end + end + + def test_acts_as_voteable_configuration + ThumbsUp.configuration.voteable_relationship_name = :votes_on + ThumbsUp.configuration.voter_relationship_name = :votes_by + + item = ItemCustom.create(:name => 'XBOX', :description => 'XBOX console') + + assert_equal 0, item.ci_plusminus + + user_for = UserCustom.create(:name => 'david') + another_user_for = UserCustom.create(:name => 'name') + user_against = UserCustom.create(:name => 'brady') + another_user_against = UserCustom.create(:name => 'name') + + # We have changed the name of the relationship, so `votes` is not defined. + assert_raises(NoMethodError) do + item.votes + end + + user_for.vote_for(item) + another_user_for.vote_for(item) + # Use #reload to force reloading of votes from the database, + # otherwise these tests fail after "assert_equal 0, item.ci_plusminus" caches + # the votes. We hack this as caching is the correct behavious, per-request, + # in production. + item.reload + + assert_equal 2, item.votes_for + assert_equal 0, item.votes_against + assert_equal 2, item.plusminus + assert_in_delta 0.34, item.ci_plusminus, 0.01 + + user_against.vote_against(item) + + assert_equal 1, item.votes_against + assert_equal 1, item.plusminus + assert_in_delta 0.20, item.ci_plusminus, 0.01 + + assert_equal 3, item.votes_count + + assert_equal 67, item.percent_for + assert_equal 33, item.percent_against + + voters_who_voted = item.voters_who_voted + assert_equal 3, voters_who_voted.size + assert voters_who_voted.include?(user_for) + assert voters_who_voted.include?(another_user_for) + assert voters_who_voted.include?(user_against) + + voters_who_voted_for = item.voters_who_voted_for + assert_equal 2, voters_who_voted_for.size + assert voters_who_voted_for.include?(user_for) + assert voters_who_voted_for.include?(another_user_for) + + another_user_against.vote_against(item) + + voters_who_voted_against = item.voters_who_voted_against + assert_equal 2, voters_who_voted_against.size + assert voters_who_voted_against.include?(user_against) + assert voters_who_voted_against.include?(another_user_against) + + non_voting_user = UserCustom.create(:name => 'voteable_configuration') + + assert_equal true, item.voted_by?(user_for) + assert_equal true, item.voted_by?(another_user_for) + assert_equal true, item.voted_by?(user_against) + assert_equal false, item.voted_by?(non_voting_user) + end + + # Duplicated method name, why? def test_tally_empty item = Item.create(:name => 'XBOX', :description => 'XBOX console') assert_equal 0, Item.tally.having('vote_count > 0').length @@ -187,6 +328,7 @@ def test_tally_any Item.tally.except(:order).any? end + # Duplicated method name, why? def test_tally_empty Item.tally.except(:order).empty? end @@ -196,6 +338,7 @@ def test_plusminus_tally_not_empty_without_conditions assert_equal 1, Item.plusminus_tally.length end + # Duplicated method name, why? def test_plusminus_tally_empty item = Item.create(:name => 'XBOX', :description => 'XBOX console') assert_equal 0, Item.plusminus_tally.having('vote_count > 0').length @@ -375,6 +518,7 @@ def test_plusminus_tally_any Item.plusminus_tally.except(:order).any? end + # Duplicated method name, why? def test_plusminus_tally_empty Item.plusminus_tally.except(:order).empty? end From 5be5afb8e7fa762e75eb04af19993e28309dcd68 Mon Sep 17 00:00:00 2001 From: Peter Boling Date: Thu, 18 Apr 2013 16:45:04 -0400 Subject: [PATCH 107/138] comments shouldn't lie --- test/test_helper.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index 311e385..57c6329 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -128,7 +128,7 @@ class Vote < ActiveRecord::Base end class Item < ActiveRecord::Base - # This is default, however because of random test run ordering we need to be explicit + # This is default, however because the setting is app-wide, and changed elsewhere, we need to be explicit ThumbsUp.configuration.voteable_relationship_name = :votes ThumbsUp.configuration.voter_relationship_name = :votes acts_as_voteable @@ -136,7 +136,7 @@ class Item < ActiveRecord::Base end class OtherItem < ActiveRecord::Base - # This is default, however because of random test run ordering we need to be explicit + # This is default, however because the setting is app-wide, and changed elsewhere, we need to be explicit ThumbsUp.configuration.voteable_relationship_name = :votes ThumbsUp.configuration.voter_relationship_name = :votes acts_as_voteable @@ -144,7 +144,7 @@ class OtherItem < ActiveRecord::Base end class User < ActiveRecord::Base - # This is default, however because of random test run ordering we need to be explicit + # This is default, however because the setting is app-wide, and changed elsewhere, we need to be explicit ThumbsUp.configuration.voteable_relationship_name = :votes ThumbsUp.configuration.voter_relationship_name = :votes acts_as_voter From 1a9132461d00b923fb0b8e3ada4629b1cf707920 Mon Sep 17 00:00:00 2001 From: Guy Israeli Date: Fri, 19 Apr 2013 08:31:34 +0300 Subject: [PATCH 108/138] added voted_how? method --- lib/acts_as_voter.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/acts_as_voter.rb b/lib/acts_as_voter.rb index a707720..620c3ce 100644 --- a/lib/acts_as_voter.rb +++ b/lib/acts_as_voter.rb @@ -116,6 +116,16 @@ def voted_which_way?(voteable, direction) ).count end + def voted_how?(voteable) + Vote.where( + :voter_id => self.id, + :voter_type => self.class.base_class.name, + :voteable_id => voteable.id, + :voteable_type => voteable.class.base_class.name + ) + + end + end end end From b00ca748f0f1283a745208148e607f8a0f51bf77 Mon Sep 17 00:00:00 2001 From: Guy Israeli Date: Fri, 19 Apr 2013 08:45:38 +0300 Subject: [PATCH 109/138] return the vote type --- lib/acts_as_voter.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/acts_as_voter.rb b/lib/acts_as_voter.rb index 620c3ce..639c488 100644 --- a/lib/acts_as_voter.rb +++ b/lib/acts_as_voter.rb @@ -117,13 +117,17 @@ def voted_which_way?(voteable, direction) end def voted_how?(voteable) - Vote.where( + votes = Vote.where( :voter_id => self.id, :voter_type => self.class.base_class.name, :voteable_id => voteable.id, :voteable_type => voteable.class.base_class.name - ) - + ).map(&:vote) #in case votes is premitted to be duplicated + if votes.count == 1 + votes.first + else + votes + end end end From 593ab0fe18bc2212e3d5c23f4f3614d102ac003b Mon Sep 17 00:00:00 2001 From: Guy Israeli Date: Fri, 19 Apr 2013 20:15:14 +0300 Subject: [PATCH 110/138] added tests --- lib/acts_as_voter.rb | 2 ++ test/thumbs_up_test.rb | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/lib/acts_as_voter.rb b/lib/acts_as_voter.rb index 639c488..ff7e017 100644 --- a/lib/acts_as_voter.rb +++ b/lib/acts_as_voter.rb @@ -125,6 +125,8 @@ def voted_how?(voteable) ).map(&:vote) #in case votes is premitted to be duplicated if votes.count == 1 votes.first + elsif votes.count == 0 + nil else votes end diff --git a/test/thumbs_up_test.rb b/test/thumbs_up_test.rb index a0e94f3..ba4b05c 100644 --- a/test/thumbs_up_test.rb +++ b/test/thumbs_up_test.rb @@ -11,6 +11,7 @@ def test_acts_as_voter_instance_methods user_for = User.create(:name => 'david') user_against = User.create(:name => 'brady') item = Item.create(:name => 'XBOX', :description => 'XBOX console') + item2= Item.create(:name => 'PS3', :description => 'Playstation 3') assert_not_nil user_for.vote_for(item) assert_raises(ActiveRecord::RecordInvalid) do @@ -24,6 +25,7 @@ def test_acts_as_voter_instance_methods assert_equal 0, user_for.vote_count(:down) assert_equal true, user_for.voted_which_way?(item, :up) assert_equal false, user_for.voted_which_way?(item, :down) + assert_equal true, user_for.voted_how?(item) assert_equal 1, user_for.votes.where(:voteable_type => 'Item').count assert_equal 0, user_for.votes.where(:voteable_type => 'AnotherItem').count assert_raises(ArgumentError) do @@ -36,6 +38,7 @@ def test_acts_as_voter_instance_methods end assert_equal false, user_against.voted_for?(item) assert_equal true, user_against.voted_against?(item) + assert_equal false, user_against.voted_how?(item) assert_equal true, user_against.voted_on?(item) assert_equal 1, user_against.vote_count assert_equal 0, user_against.vote_count(:up) @@ -71,6 +74,8 @@ def test_acts_as_voter_instance_methods assert_equal true, user_for.voted_for?(item) # Make sure the vote record was returned by the :vote method assert_equal true, vote.is_a?(Vote) + + assert_nil user_for.voted_how?(item2) end def test_acts_as_voteable_instance_methods From 2d1fc9af3e084c166e99348bbf63efbe9eaa0d27 Mon Sep 17 00:00:00 2001 From: Guy Israeli Date: Fri, 19 Apr 2013 20:23:06 +0300 Subject: [PATCH 111/138] added to README --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 2030173..99a0766 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,11 @@ Did the first user vote for or against the Car with id = 2? u.voted_for?(Car.find(2)) #=> true u.voted_against?(Car.find(2)) #=> false +Or check directly! + + u = User.first + u.voted_how?(Car.find(2)) #=> true + #### Tallying Votes You can easily retrieve voteable object collections based on the properties of their votes: From aab6817f32ae26b58b43d6c62b2262991238f51d Mon Sep 17 00:00:00 2001 From: Guy Israeli Date: Sat, 20 Apr 2013 08:11:37 +0300 Subject: [PATCH 112/138] improved documentation --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 99a0766..33136e9 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,18 @@ Did the first user vote for or against the Car with id = 2? Or check directly! u = User.first - u.voted_how?(Car.find(2)) #=> true + u.vote_for(Car.find(2)) + u.voted_how?(Car.find(2)) #=> true, if voted_for + + u.vote_against(Car.find(3)) + u.voted_how?(Car.find(3)) #=> false, if voted_against + + u.vote_for(Car.find(4)) + u.voted_how?(Car.find(4)) #=> nil, if didn't vote for it + +in case you use `--unique-voting false` (documented below): + + u.voted_how?(Car.find(2)) #=> [false, true, true, false] #### Tallying Votes From 84aea3b0cba1354154b14b3352ad8e1fe87f2f67 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Sat, 20 Apr 2013 00:08:13 -0600 Subject: [PATCH 113/138] Added Code Climate. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 33136e9..5dc43b4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ThumbsUp ======= -[![Build Status](https://secure.travis-ci.org/bouchard/thumbs_up.png)](http://travis-ci.org/bouchard/thumbs_up) +[![Build Status](https://secure.travis-ci.org/bouchard/thumbs_up.png)](http://travis-ci.org/bouchard/thumbs_up) [![Code Climate](https://codeclimate.com/github/bouchard/thumbs_up.png)](https://codeclimate.com/github/bouchard/thumbs_up) **Note: Version 0.5.x is a breaking change for #plusminus_tally and #tally, with > 50% speedups.** From 09d4034076b2718ced7767282c9f442cdfa16619 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Sat, 20 Apr 2013 00:22:24 -0600 Subject: [PATCH 114/138] Fix tests where Postgres requires non-aliased column in HAVING clause. --- Gemfile | 2 +- test/thumbs_up_test.rb | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/Gemfile b/Gemfile index 851fabc..817f62a 100644 --- a/Gemfile +++ b/Gemfile @@ -1,2 +1,2 @@ -source 'https://rubygems.org' +source 'http://rubygems.org' gemspec diff --git a/test/thumbs_up_test.rb b/test/thumbs_up_test.rb index ba4b05c..36d284a 100644 --- a/test/thumbs_up_test.rb +++ b/test/thumbs_up_test.rb @@ -140,7 +140,9 @@ def test_acts_as_voteable_instance_methods def test_tally_empty item = Item.create(:name => 'XBOX', :description => 'XBOX console') - assert_equal 0, Item.tally.having('vote_count > 0').length + # COUNT(#{Vote.table_name}.id) is equivalent to aliased column `vote_count` - Postgres + # requires the non-aliased name in a HAVING clause. + assert_equal 0, Item.tally.having("COUNT(#{Vote.table_name}.id) > 0").length end def test_tally_has_id @@ -202,10 +204,6 @@ def test_tally_any Item.tally.except(:order).any? end - def test_tally_empty - Item.tally.except(:order).empty? - end - def test_plusminus_tally_not_empty_without_conditions item = Item.create(:name => 'XBOX', :description => 'XBOX console') assert_equal 1, Item.plusminus_tally.length @@ -213,7 +211,9 @@ def test_plusminus_tally_not_empty_without_conditions def test_plusminus_tally_empty item = Item.create(:name => 'XBOX', :description => 'XBOX console') - assert_equal 0, Item.plusminus_tally.having('vote_count > 0').length + # COUNT(#{Vote.table_name}.id) is equivalent to aliased column `vote_count` - Postgres + # requires the non-aliased name in a HAVING clause. + assert_equal 0, Item.plusminus_tally.having("COUNT(#{Vote.table_name}.id) > 0").length end def test_plusminus_tally_starts_at @@ -390,10 +390,6 @@ def test_plusminus_tally_any Item.plusminus_tally.except(:order).any? end - def test_plusminus_tally_empty - Item.plusminus_tally.except(:order).empty? - end - def test_karma users = (0..1).map{ |u| User.create(:name => "User #{u}") } items = (0..1).map{ |u| users[0].items.create(:name => "Item #{u}", :description => "Item #{u}") } From eedd5ef04883d64f87cfe311a4974689f69f1156 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Thu, 25 Apr 2013 08:01:03 -0700 Subject: [PATCH 115/138] Fixed typo in README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5dc43b4..7789f84 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ Usage voter.vote_exclusively_for(voteable) # Removes any previous votes by that particular voter, and votes for. voter.vote_exclusively_against(voteable) # Removes any previous votes by that particular voter, and votes against. - vote.unvote_for(voteable) # Clears all votes for that user + voter.unvote_for(voteable) # Clears all votes for that user ### Querying votes From d2c71c7a81c774dbfac7ed9ad5a78a59ac5e6dda Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Fri, 5 Jul 2013 08:23:24 -0600 Subject: [PATCH 116/138] Rails 4 compatibility fixes. --- lib/generators/thumbs_up/templates/vote.rb | 4 ++-- test/test_helper.rb | 4 ++-- test/thumbs_up_test.rb | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/generators/thumbs_up/templates/vote.rb b/lib/generators/thumbs_up/templates/vote.rb index 4866417..bbcc2a9 100644 --- a/lib/generators/thumbs_up/templates/vote.rb +++ b/lib/generators/thumbs_up/templates/vote.rb @@ -3,12 +3,12 @@ class Vote < ActiveRecord::Base scope :for_voter, lambda { |*args| where(["voter_id = ? AND voter_type = ?", args.first.id, args.first.class.base_class.name]) } scope :for_voteable, lambda { |*args| where(["voteable_id = ? AND voteable_type = ?", args.first.id, args.first.class.base_class.name]) } scope :recent, lambda { |*args| where(["created_at > ?", (args.first || 2.weeks.ago)]) } - scope :descending, order("created_at DESC") + scope :descending, lambda { order("created_at DESC") } belongs_to :voteable, :polymorphic => true belongs_to :voter, :polymorphic => true - attr_accessible :vote, :voter, :voteable + attr_accessible :vote, :voter, :voteable if ActiveRecord::VERSION::MAJOR < 4 <% if options[:unique_voting] == true %> # Comment out the line below to allow multiple votes per user. diff --git a/test/test_helper.rb b/test/test_helper.rb index 310f247..b882922 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -98,12 +98,12 @@ class Vote < ActiveRecord::Base scope :for_voter, lambda { |*args| where(["voter_id = ? AND voter_type = ?", args.first.id, args.first.class.name]) } scope :for_voteable, lambda { |*args| where(["voteable_id = ? AND voteable_type = ?", args.first.id, args.first.class.name]) } scope :recent, lambda { |*args| where(["created_at > ?", (args.first || 2.weeks.ago)]) } - scope :descending, order("created_at DESC") + scope :descending, lambda { order("created_at DESC") } belongs_to :voteable, :polymorphic => true belongs_to :voter, :polymorphic => true - attr_accessible :vote, :voter, :voteable + attr_accessible :vote, :voter, :voteable if ActiveRecord::VERSION::MAJOR < 4 # Comment out the line below to allow multiple votes per user. validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id] diff --git a/test/thumbs_up_test.rb b/test/thumbs_up_test.rb index 36d284a..9b79b66 100644 --- a/test/thumbs_up_test.rb +++ b/test/thumbs_up_test.rb @@ -152,7 +152,7 @@ def test_tally_has_id user.vote_for(item2) - assert_not_nil Item.tally.all.first.id + assert_not_nil Item.tally.first.id end def test_tally_starts_at @@ -407,8 +407,8 @@ def test_plusminus_tally_scopes_by_voteable_type user.vote_for(item) user.vote_for(another_item) - assert_equal 1, Item.plusminus_tally.sum(&:plusminus_tally).to_i - assert_equal 1, OtherItem.plusminus_tally.sum(&:plusminus_tally).to_i + assert_equal 1, Item.plusminus_tally.to_a.sum(&:plusminus_tally).to_i + assert_equal 1, OtherItem.plusminus_tally.to_a.sum(&:plusminus_tally).to_i end end From 8feca9c131198c8392a5daad95ee8adb926fddd0 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Fri, 5 Jul 2013 08:25:36 -0600 Subject: [PATCH 117/138] Bump version. --- lib/thumbs_up/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/thumbs_up/version.rb b/lib/thumbs_up/version.rb index 76e7549..5154d0d 100644 --- a/lib/thumbs_up/version.rb +++ b/lib/thumbs_up/version.rb @@ -1,3 +1,3 @@ module ThumbsUp - VERSION = '0.6.4' + VERSION = '0.6.5' end \ No newline at end of file From 63f466c57d2a1221ca5ef206323ca822746ed4dd Mon Sep 17 00:00:00 2001 From: Peter Boling Date: Tue, 27 Aug 2013 04:32:46 -0700 Subject: [PATCH 118/138] Adds Configuration section to readme --- README.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2030173..dd98dd6 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ Allows an arbitrary number of entities (users, etc.) to vote on models. ### Mixins This plugin introduces three mixins to your recipe book: -1. **acts\_as\_voteable** : Intended for content objects like Posts, Comments, etc. -2. **acts\_as\_voter** : Intended for voting entities, like Users. +1. **acts\_as\_voteable** : Intended for content objects like Posts, Comments, etc. * (See *Configuration* below for caveats) +2. **acts\_as\_voter** : Intended for voting entities, like Users. * (See *Configuration* below for caveats) 3. **has\_karma** : Adds some helpers to acts\_as\_voter models for calculating karma. ### Inspiration @@ -31,6 +31,22 @@ Installation rails generate thumbs_up rake db:migrate +Configuration +============= + +The relationship setup by the acts_as_voteable and acts_as_voter mixins both default to `votes`. This causes one to obscure the other if you have a single class that votes on other instances of the same class. If you have this scenario: + + class User < ActiveRecord::Base + acts_as_voter # relationship :votes will be obscured by the same named relationship from acts_as_voteable :( + acts_as_voteable + end + +Configure alternate relationship names in an initializer at `config/initializers/thumbs_up.rb`: + + ThumbsUp.configuration.voteable_relationship_name = :votes_on # defaults to :votes + ThumbsUp.configuration.voter_relationship_name = :votes_by # defaults to :votes + + Usage ===== From ea13945073c33c920368a3c78b26e575c504f92d Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Tue, 27 Aug 2013 07:35:42 -0600 Subject: [PATCH 119/138] Restrict activesupport to < 4.0.0 for Ruby < 1.9.3. --- .travis.yml | 1 + thumbs_up.gemspec | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 880dcf6..d0e4c7b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,3 +5,4 @@ rvm: - 1.9.3 before_script: - "mysql -e 'create database thumbs_up_test;'" + - "bundle update;'" diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index cbab125..36b9431 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -17,7 +17,11 @@ Gem::Specification.new do |s| s.files = Dir.glob('{lib,rails,test}/**/*') + %w(CHANGELOG.md Gemfile MIT-LICENSE README.md Rakefile) s.require_paths = ['lib'] - s.add_runtime_dependency('activerecord') + if RUBY_VERSION < '1.9.3' + s.add_runtime_dependency('activerecord', '< 4.0.0') + else + s.add_runtime_dependency('activerecord') + end s.add_runtime_dependency('statistics2') s.add_development_dependency('simplecov') s.add_development_dependency('bundler') From 6547ca488671b27dc476b87fd1c6a2c190102979 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Tue, 27 Aug 2013 07:39:11 -0600 Subject: [PATCH 120/138] Try again. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d0e4c7b..a2c05b3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,4 +5,4 @@ rvm: - 1.9.3 before_script: - "mysql -e 'create database thumbs_up_test;'" - - "bundle update;'" + - "bundle update" From 405e48c415b972bc54ac10507e1c4bd545481c59 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Sun, 1 Sep 2013 14:10:45 -0600 Subject: [PATCH 121/138] Added an upvote and downvote weight option to has_karma (closes #67). --- README.md | 4 +++- lib/has_karma.rb | 12 +++++++++--- test/test_helper.rb | 12 +++++++++++- test/thumbs_up_test.rb | 22 ++++++++++++++++++++++ 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index af14ebc..1f6bbd5 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,9 @@ Usage # Each question has a submitter_id column that tracks the user who submitted it. # The option :weight value will be multiplied to any karma from that voteable model (defaults to 1). # You can track any voteable model. - has_karma(:questions, :as => :submitter, :weight => 0.5) + has_karma :questions, :as => :submitter, :weight => 0.5 + # Karma by default is only calculated from upvotes. If you pass an array to the weight option, you can count downvotes as well (below, downvotes count for half as much karma against you): + has_karma :questions, :as => :submitter, :weight => [ 1, 0.5 ] end class Robot < ActiveRecord::Base diff --git a/lib/has_karma.rb b/lib/has_karma.rb index 1ee49ab..73f4f2e 100644 --- a/lib/has_karma.rb +++ b/lib/has_karma.rb @@ -13,7 +13,7 @@ def has_karma(voteable_type, options = {}) include ThumbsUp::Karma::InstanceMethods extend ThumbsUp::Karma::SingletonMethods self.karmic_objects ||= {} - self.karmic_objects[voteable_type.to_s.classify.constantize] = [ (options[:as] ? options[:as].to_s.foreign_key : self.name.foreign_key), (options[:weight] || 1).to_f ] + self.karmic_objects[voteable_type.to_s.classify.constantize] = [ (options[:as] ? options[:as].to_s.foreign_key : self.name.foreign_key), [ (options[:weight] || 1) ].flatten.map(&:to_f) ] end end @@ -30,10 +30,16 @@ module SingletonMethods module InstanceMethods def karma(options = {}) self.class.base_class.karmic_objects.collect do |object, attr| - v = object.where(["#{Vote.table_name}.vote = ?", true]).where(["#{self.class.base_class.table_name}.#{self.class.base_class.primary_key} = ?", self.id]) + v = object.where(["#{self.class.base_class.table_name}.#{self.class.base_class.primary_key} = ?", self.id]) v = v.joins("INNER JOIN #{Vote.table_name} ON #{Vote.table_name}.voteable_type = '#{object.to_s}' AND #{Vote.table_name}.voteable_id = #{object.table_name}.#{object.primary_key}") v = v.joins("INNER JOIN #{self.class.base_class.table_name} ON #{self.class.base_class.table_name}.#{self.class.base_class.primary_key} = #{object.table_name}.#{attr[0]}") - (v.count.to_f * attr[1]).round + upvotes = v.where(["#{Vote.table_name}.vote = ?", true]) + downvotes = v.where(["#{Vote.table_name}.vote = ?", false]) + if attr[1].length == 1 # Only count upvotes, not downvotes. + (upvotes.count.to_f * attr[1].first).round + else + (upvotes.count.to_f * attr[1].first - downvotes.count.to_f * attr[1].last).round + end end.sum end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 5e1c143..a340fa8 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -171,7 +171,17 @@ class UserCustom < ActiveRecord::Base ThumbsUp.configuration.voter_relationship_name = :votes_by acts_as_voter has_many :items - has_karma(:items) + has_karma :items + + def self.weighted_has_karma + self.karmic_objects = nil + has_karma :items, :weight => [ 10, 15 ] + end + + def self.upvote_only_has_karma + self.karmic_objects = nil + has_karma :items, :weight => 1.3 + end end class Test::Unit::TestCase diff --git a/test/thumbs_up_test.rb b/test/thumbs_up_test.rb index 53a8e7f..32aff15 100644 --- a/test/thumbs_up_test.rb +++ b/test/thumbs_up_test.rb @@ -540,6 +540,28 @@ def test_karma assert_equal 0, users[1].karma end + def test_karma_with_upvote_weights + User.upvote_only_has_karma + users = (0..1).map{ |u| User.create(:name => "User #{u}") } + items = (0..1).map{ |u| users[0].items.create(:name => "Item #{u}", :description => "Item #{u}") } + users.each{ |u| items.each { |i| u.vote_for(i) } } + + assert_equal (4 * 1.3).round, users[0].karma + assert_equal 0, users[1].karma + end + + def test_karma_with_both_upvote_and_downvote_weights + User.weighted_has_karma + for_users = (0..1).map{ |u| User.create(:name => "For User #{u}") } + against_users = (0..2).map{ |u| User.create(:name => "Against User #{u}") } + items = (0..1).map{ |u| for_users[0].items.create(:name => "Item #{u}", :description => "Item #{u}") } + for_users.each{ |u| items.each { |i| u.vote_for(i) } } + against_users.each{ |u| items.each { |i| u.vote_against(i) } } + + assert_equal 2 * (10 * 2 - 15 * 3).round, for_users[0].karma + assert_equal 0, for_users[1].karma + end + def test_plusminus_tally_scopes_by_voteable_type user = User.create(:name => 'david') item = Item.create(:name => 'XBOX', :description => 'XBOX console') From 40390b44ec6c1c392549132a3d0d95878c1a90d4 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Sun, 1 Sep 2013 14:13:21 -0600 Subject: [PATCH 122/138] Bump version. --- lib/thumbs_up/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/thumbs_up/version.rb b/lib/thumbs_up/version.rb index 5154d0d..93595b6 100644 --- a/lib/thumbs_up/version.rb +++ b/lib/thumbs_up/version.rb @@ -1,3 +1,3 @@ module ThumbsUp - VERSION = '0.6.5' + VERSION = '0.6.6' end \ No newline at end of file From e7be17e31305180e2a9400e65ecde16911f732f2 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Mon, 2 Sep 2013 13:15:57 -0600 Subject: [PATCH 123/138] Fix merge and bump version. --- lib/thumbs_up/version.rb | 2 +- test/test_helper.rb | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/thumbs_up/version.rb b/lib/thumbs_up/version.rb index 93595b6..177ffe1 100644 --- a/lib/thumbs_up/version.rb +++ b/lib/thumbs_up/version.rb @@ -1,3 +1,3 @@ module ThumbsUp - VERSION = '0.6.6' + VERSION = '0.6.7' end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index a340fa8..cb67e9f 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -150,6 +150,16 @@ class User < ActiveRecord::Base acts_as_voter has_many :items has_karma(:items) + + def self.weighted_has_karma + self.karmic_objects = nil + has_karma :items, :weight => [ 10, 15 ] + end + + def self.upvote_only_has_karma + self.karmic_objects = nil + has_karma :items, :weight => 1.3 + end end class ItemCustom < ActiveRecord::Base @@ -172,16 +182,6 @@ class UserCustom < ActiveRecord::Base acts_as_voter has_many :items has_karma :items - - def self.weighted_has_karma - self.karmic_objects = nil - has_karma :items, :weight => [ 10, 15 ] - end - - def self.upvote_only_has_karma - self.karmic_objects = nil - has_karma :items, :weight => 1.3 - end end class Test::Unit::TestCase From f25b466b3c3b540911c8d6f1b69c3d0ebd31c6d0 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Sun, 13 Apr 2014 16:03:55 -0600 Subject: [PATCH 124/138] Update and rename MIT-LICENSE to LICENSE --- LICENSE | 21 +++++++++++++++++ MIT-LICENSE | 66 ----------------------------------------------------- 2 files changed, 21 insertions(+), 66 deletions(-) create mode 100644 LICENSE delete mode 100644 MIT-LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..58fa812 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Brady Bouchard (brady@thewellinspired.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MIT-LICENSE b/MIT-LICENSE deleted file mode 100644 index 8d89dea..0000000 --- a/MIT-LICENSE +++ /dev/null @@ -1,66 +0,0 @@ -Copyright (c) 2011 Brady Bouchard (thewellinspired.com) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Major portions of this package were adapted from VoteFu, which is subject to the same license. Here is the original copyright notice for VoteFu: - -Copyright (c) 2008 Peter Jackson (peteonrails.com) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Major portions of this package were adapted from ActsAsVoteable, which is subject to the same license. Here is the original copyright notice for ActsAsVoteable: - -Copyright (c) 2006 Cosmin Radoi - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file From 6b45ed632d9f0fa3e03ae32a9ee93483e5d1f494 Mon Sep 17 00:00:00 2001 From: ldewald Date: Thu, 8 May 2014 07:26:55 -0700 Subject: [PATCH 125/138] Renaming create_migration to not override default method fixes #96 --- lib/generators/thumbs_up/thumbs_up_generator.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/generators/thumbs_up/thumbs_up_generator.rb b/lib/generators/thumbs_up/thumbs_up_generator.rb index c4b79b1..1df4e32 100644 --- a/lib/generators/thumbs_up/thumbs_up_generator.rb +++ b/lib/generators/thumbs_up/thumbs_up_generator.rb @@ -5,7 +5,7 @@ class ThumbsUpGenerator < Rails::Generators::Base include Rails::Generators::Migration source_root File.expand_path('../templates', __FILE__) - + class_option :unique_voting, :type => :boolean, :default => true, :desc => 'Do you want only one vote allowed per voter? (default: true)' # Implement the required interface for Rails::Generators::Migration. @@ -18,7 +18,7 @@ def self.next_migration_number(dirname) #:nodoc: end end - def create_migration + def create_thumbs_up_migration migration_template 'migration.rb', File.join('db', 'migrate', 'thumbs_up_migration.rb') end @@ -26,4 +26,4 @@ def move_vote_model template 'vote.rb', File.join('app', 'models', 'vote.rb') end -end \ No newline at end of file +end From 4dfa29fa002123afd2c31f3d9df35c133bafedcd Mon Sep 17 00:00:00 2001 From: Hugo Abonizio Date: Sat, 20 Jun 2015 14:56:39 -0300 Subject: [PATCH 126/138] Add Github's syntax highlighting --- README.md | 223 +++++++++++++++++++++++++++++------------------------- 1 file changed, 120 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index 1f6bbd5..a67a404 100644 --- a/README.md +++ b/README.md @@ -23,28 +23,31 @@ Installation ============ ### Require the gem: - - gem 'thumbs_up' +```shell +gem 'thumbs_up' +``` ### Create and run the ThumbsUp migration: - - rails generate thumbs_up - rake db:migrate +```shell +rails generate thumbs_up +rake db:migrate +``` Configuration ============= The relationship setup by the acts_as_voteable and acts_as_voter mixins both default to `votes`. This causes one to obscure the other if you have a single class that votes on other instances of the same class. If you have this scenario: - - class User < ActiveRecord::Base - acts_as_voter # relationship :votes will be obscured by the same named relationship from acts_as_voteable :( - acts_as_voteable - end - +```ruby +class User < ActiveRecord::Base + acts_as_voter # relationship :votes will be obscured by the same named relationship from acts_as_voteable :( + acts_as_voteable +end +``` Configure alternate relationship names in an initializer at `config/initializers/thumbs_up.rb`: - - ThumbsUp.configuration.voteable_relationship_name = :votes_on # defaults to :votes - ThumbsUp.configuration.voter_relationship_name = :votes_by # defaults to :votes +```ruby +ThumbsUp.configuration.voteable_relationship_name = :votes_on # defaults to :votes +ThumbsUp.configuration.voter_relationship_name = :votes_by # defaults to :votes +``` Usage @@ -53,84 +56,94 @@ Usage ## Getting Started ### Turn your AR models into something that can be voted upon. +```ruby +class SomeModel < ActiveRecord::Base + acts_as_voteable +end - class SomeModel < ActiveRecord::Base - acts_as_voteable - end - - class Question < ActiveRecord::Base - acts_as_voteable - end +class Question < ActiveRecord::Base + acts_as_voteable +end +``` ### Turn your Users (or any other model) into voters. - - class User < ActiveRecord::Base - acts_as_voter - # The following line is optional, and tracks karma (up votes) for questions this user has submitted. - # Each question has a submitter_id column that tracks the user who submitted it. - # The option :weight value will be multiplied to any karma from that voteable model (defaults to 1). - # You can track any voteable model. - has_karma :questions, :as => :submitter, :weight => 0.5 - # Karma by default is only calculated from upvotes. If you pass an array to the weight option, you can count downvotes as well (below, downvotes count for half as much karma against you): - has_karma :questions, :as => :submitter, :weight => [ 1, 0.5 ] - end - - class Robot < ActiveRecord::Base - acts_as_voter - end +```ruby +class User < ActiveRecord::Base + acts_as_voter + # The following line is optional, and tracks karma (up votes) for questions this user has submitted. + # Each question has a submitter_id column that tracks the user who submitted it. + # The option :weight value will be multiplied to any karma from that voteable model (defaults to 1). + # You can track any voteable model. + has_karma :questions, :as => :submitter, :weight => 0.5 + # Karma by default is only calculated from upvotes. If you pass an array to the weight option, you can count downvotes as well (below, downvotes count for half as much karma against you): + has_karma :questions, :as => :submitter, :weight => [ 1, 0.5 ] +end + +class Robot < ActiveRecord::Base + acts_as_voter +end +``` ### To cast a vote for a Model you can do the following: #### Shorthand syntax - voter.vote_for(voteable) # Adds a +1 vote - voter.vote_against(voteable) # Adds a -1 vote - voter.vote(voteable, vote) # Adds either a +1 or -1 vote: vote => true (+1), vote => false (-1) +```ruby +voter.vote_for(voteable) # Adds a +1 vote +voter.vote_against(voteable) # Adds a -1 vote +voter.vote(voteable, vote) # Adds either a +1 or -1 vote: vote => true (+1), vote => false (-1) - voter.vote_exclusively_for(voteable) # Removes any previous votes by that particular voter, and votes for. - voter.vote_exclusively_against(voteable) # Removes any previous votes by that particular voter, and votes against. +voter.vote_exclusively_for(voteable) # Removes any previous votes by that particular voter, and votes for. +voter.vote_exclusively_against(voteable) # Removes any previous votes by that particular voter, and votes against. - voter.unvote_for(voteable) # Clears all votes for that user +voter.unvote_for(voteable) # Clears all votes for that user +``` ### Querying votes Did the first user vote for the Car with id = 2 already? - - u = User.first - u.vote_for(Car.find(2)) - u.voted_on?(Car.find(2)) #=> true +```ruby +u = User.first +u.vote_for(Car.find(2)) +u.voted_on?(Car.find(2)) #=> true +``` Did the first user vote for or against the Car with id = 2? - - u = User.first - u.vote_for(Car.find(2)) - u.voted_for?(Car.find(2)) #=> true - u.voted_against?(Car.find(2)) #=> false +```ruby +u = User.first +u.vote_for(Car.find(2)) +u.voted_for?(Car.find(2)) #=> true +u.voted_against?(Car.find(2)) #=> false +``` Or check directly! +```ruby +u = User.first +u.vote_for(Car.find(2)) +u.voted_how?(Car.find(2)) #=> true, if voted_for - u = User.first - u.vote_for(Car.find(2)) - u.voted_how?(Car.find(2)) #=> true, if voted_for - - u.vote_against(Car.find(3)) - u.voted_how?(Car.find(3)) #=> false, if voted_against +u.vote_against(Car.find(3)) +u.voted_how?(Car.find(3)) #=> false, if voted_against - u.vote_for(Car.find(4)) - u.voted_how?(Car.find(4)) #=> nil, if didn't vote for it +u.vote_for(Car.find(4)) +u.voted_how?(Car.find(4)) #=> nil, if didn't vote for it +``` in case you use `--unique-voting false` (documented below): - - u.voted_how?(Car.find(2)) #=> [false, true, true, false] +```ruby +u.voted_how?(Car.find(2)) #=> [false, true, true, false] +``` #### Tallying Votes You can easily retrieve voteable object collections based on the properties of their votes: - - @items = Item.tally.limit(10).where('created_at > ?', 2.days.ago).having('COUNT(votes.id) < 10') +```ruby +@items = Item.tally.limit(10).where('created_at > ?', 2.days.ago).having('COUNT(votes.id) < 10') +``` Or for MySQL: - - @items = Item.tally.limit(10).where('created_at > ?', 2.days.ago).having('vote_count < 10') +```ruby +@items = Item.tally.limit(10).where('created_at > ?', 2.days.ago).having('vote_count < 10') +``` This will select the Items with less than 10 votes, the votes having been cast within the last two days, with a limit of 10 items. *This tallies all votes, regardless of whether they are +1 (up) or -1 (down).* The #tally method returns an ActiveRecord Relation, so you can chain the normal method calls on to it. @@ -139,21 +152,23 @@ This will select the Items with less than 10 votes, the votes having been cast w **You most likely want to use this over the normal tally** This is similar to tallying votes, but this will return voteable object collections based on the sum of the differences between up and down votes (ups are +1, downs are -1). For Instance, a voteable with 3 upvotes and 2 downvotes will have a plusminus_tally of 1. - - @items = Item.plusminus_tally.limit(10).where('created_at > ?', 2.days.ago).having('plusminus_tally > 10') +```ruby +@items = Item.plusminus_tally.limit(10).where('created_at > ?', 2.days.ago).having('plusminus_tally > 10') +``` #### Lower level queries +```ruby +positiveVoteCount = voteable.votes_for +negativeVoteCount = voteable.votes_against +# Votes for minus votes against. If you want more than a few model instances' worth, use `plusminus_tally` instead. +plusminus = voteable.plusminus - positiveVoteCount = voteable.votes_for - negativeVoteCount = voteable.votes_against - # Votes for minus votes against. If you want more than a few model instances' worth, use `plusminus_tally` instead. - plusminus = voteable.plusminus - - voter.voted_for?(voteable) # True if the voter voted for this object. - voter.vote_count(:up | :down | :all) # returns the count of +1, -1, or all votes +voter.voted_for?(voteable) # True if the voter voted for this object. +voter.vote_count(:up | :down | :all) # returns the count of +1, -1, or all votes - voteable.voted_by?(voter) # True if the voter voted for this object. - @voters = voteable.voters_who_voted +voteable.voted_by?(voter) # True if the voter voted for this object. +@voters = voteable.voters_who_voted +``` ### One vote per user! @@ -161,16 +176,18 @@ This is similar to tallying votes, but this will return voteable object collecti ThumbsUp by default only allows one vote per user. This can be changed by removing: #### In vote.rb: - - validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id] +```ruby +validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id] +``` #### In the migration, the unique index: - - add_index :votes, ["voter_id", "voter_type", "voteable_id", "voteable_type"], :unique => true, :name => "uniq_one_vote_only" - +```ruby +add_index :votes, ["voter_id", "voter_type", "voteable_id", "voteable_type"], :unique => true, :name => "uniq_one_vote_only" +``` You can also use `--unique-voting false` when running the generator command: - - rails generate thumbs_up --unique-voting false +```shell +rails generate thumbs_up --unique-voting false +``` #### Testing ThumbsUp @@ -178,30 +195,30 @@ Testing is a bit more than trivial now as our #tally and #plusminus_tally querie * mysql - ``` - $ mysql -uroot # You may have set a password locally. Change as needed. - > CREATE USER 'test'@'localhost' IDENTIFIED BY 'test'; - > CREATE DATABASE thumbs_up_test; - > USE thumbs_up_test; - > GRANT ALL PRIVILEGES ON thumbs_up_test TO 'test'@'localhost' IDENTIFIED BY 'test'; - > exit; - ``` +```sql +$ mysql -uroot # You may have set a password locally. Change as needed. + > CREATE USER 'test'@'localhost' IDENTIFIED BY 'test'; + > CREATE DATABASE thumbs_up_test; + > USE thumbs_up_test; + > GRANT ALL PRIVILEGES ON thumbs_up_test TO 'test'@'localhost' IDENTIFIED BY 'test'; + > exit; +``` * Postgres - ``` - $ psql # You may have set a password locally. Change as needed. - > CREATE ROLE test; - > ALTER ROLE test WITH SUPERUSER; - > ALTER ROLE test WITH LOGIN; - > CREATE DATABASE thumbs_up_test; - > GRANT ALL PRIVILEGES ON DATABASE thumbs_up_test to test; - > \q - ``` +```PLpgSQL +$ psql # You may have set a password locally. Change as needed. + > CREATE ROLE test; + > ALTER ROLE test WITH SUPERUSER; + > ALTER ROLE test WITH LOGIN; + > CREATE DATABASE thumbs_up_test; + > GRANT ALL PRIVILEGES ON DATABASE thumbs_up_test to test; + > \q +``` * Run tests - ``` - $ rake # Runs the test suite against all adapters. - ``` +```shell +$ rake # Runs the test suite against all adapters. +``` Credits ======= From c24940797f0b9b006e7720667f320e6eb31e5ef9 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Sat, 20 Jun 2015 12:36:48 -0600 Subject: [PATCH 127/138] Drop support for 1.8.x series Ruby. --- .travis.yml | 2 -- Rakefile | 3 +++ test/test_helper.rb | 15 +++++------ test/thumbs_up_test.rb | 56 +++++++++++++++++++++--------------------- thumbs_up.gemspec | 7 ++---- 5 files changed, 39 insertions(+), 44 deletions(-) diff --git a/.travis.yml b/.travis.yml index a2c05b3..248e1da 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,5 @@ language: ruby rvm: - - 1.8.7 - - 1.9.2 - 1.9.3 before_script: - "mysql -e 'create database thumbs_up_test;'" diff --git a/Rakefile b/Rakefile index cb40a02..9d656ae 100644 --- a/Rakefile +++ b/Rakefile @@ -33,10 +33,13 @@ end task :test_all_databases do # Test MySQL, Postgres and SQLite3 ENV['DB'] = 'mysql' + puts "Testing MySQL..." Rake::Task['test'].execute ENV['DB'] = 'postgres' + puts "Testing Postgres..." Rake::Task['test'].execute ENV['DB'] = 'sqlite3' + puts "Testing SQLite3..." Rake::Task['test'].execute end diff --git a/test/test_helper.rb b/test/test_helper.rb index cb67e9f..b5b2832 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,6 +1,6 @@ require 'simplecov' +require 'minitest/autorun' SimpleCov.start -require 'test/unit' $LOAD_PATH.unshift(File.dirname(__FILE__)) $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) @@ -64,7 +64,7 @@ t.boolean :vote, :default => false t.references :voteable, :polymorphic => true, :null => false t.references :voter, :polymorphic => true - t.timestamps + t.timestamps :null => false end add_index :votes, [:voter_id, :voter_type] @@ -75,7 +75,7 @@ create_table :users, :force => true do |t| t.string :name - t.timestamps + t.timestamps :null => false end create_table :items, :force => true do |t| @@ -92,7 +92,7 @@ create_table :user_customs, :force => true do |t| t.string :name - t.timestamps + t.timestamps :null => false end create_table :item_customs, :force => true do |t| @@ -149,7 +149,7 @@ class User < ActiveRecord::Base ThumbsUp.configuration.voter_relationship_name = :votes acts_as_voter has_many :items - has_karma(:items) + has_karma :items def self.weighted_has_karma self.karmic_objects = nil @@ -182,7 +182,4 @@ class UserCustom < ActiveRecord::Base acts_as_voter has_many :items has_karma :items -end - -class Test::Unit::TestCase -end +end \ No newline at end of file diff --git a/test/thumbs_up_test.rb b/test/thumbs_up_test.rb index 32aff15..7ab247b 100644 --- a/test/thumbs_up_test.rb +++ b/test/thumbs_up_test.rb @@ -1,6 +1,6 @@ require File.join(File.expand_path(File.dirname(__FILE__)), 'test_helper') -class TestThumbsUp < Test::Unit::TestCase +class TestThumbsUp < Minitest::Test def setup Vote.delete_all User.delete_all @@ -17,7 +17,7 @@ def test_acts_as_voter_instance_methods item = Item.create(:name => 'XBOX', :description => 'XBOX console') item2= Item.create(:name => 'PS3', :description => 'Playstation 3') - assert_not_nil user_for.vote_for(item) + refute_nil user_for.vote_for(item) assert_raises(ActiveRecord::RecordInvalid) do user_for.vote_for(item) end @@ -36,7 +36,7 @@ def test_acts_as_voter_instance_methods user_for.voted_which_way?(item, :foo) end - assert_not_nil user_against.vote_against(item) + refute_nil user_against.vote_against(item) assert_raises(ActiveRecord::RecordInvalid) do user_against.vote_against(item) end @@ -53,10 +53,10 @@ def test_acts_as_voter_instance_methods user_against.voted_which_way?(item, :foo) end - assert_not_nil user_against.vote_exclusively_for(item) + refute_nil user_against.vote_exclusively_for(item) assert_equal true, user_against.voted_for?(item) - assert_not_nil user_for.vote_exclusively_against(item) + refute_nil user_for.vote_exclusively_against(item) assert_equal true, user_for.voted_against?(item) user_for.unvote_for(item) @@ -159,7 +159,7 @@ def test_acts_as_voter_configuration user_for.votes end - assert_not_nil user_for.vote_for(item) + refute_nil user_for.vote_for(item) assert_raises(ActiveRecord::RecordInvalid) do user_for.vote_for(item) end @@ -177,7 +177,7 @@ def test_acts_as_voter_configuration user_for.voted_which_way?(item, :foo) end - assert_not_nil user_against.vote_against(item) + refute_nil user_against.vote_against(item) assert_raises(ActiveRecord::RecordInvalid) do user_against.vote_against(item) end @@ -193,10 +193,10 @@ def test_acts_as_voter_configuration user_against.voted_which_way?(item, :foo) end - assert_not_nil user_against.vote_exclusively_for(item) + refute_nil user_against.vote_exclusively_for(item) assert_equal true, user_against.voted_for?(item) - assert_not_nil user_for.vote_exclusively_against(item) + refute_nil user_for.vote_exclusively_against(item) assert_equal true, user_for.voted_against?(item) user_for.unvote_for(item) @@ -293,7 +293,7 @@ def test_tally_has_id user.vote_for(item2) - assert_not_nil Item.tally.first.id + refute_nil Item.tally.first.id end def test_tally_starts_at @@ -338,7 +338,7 @@ def test_tally_between_start_at_end_at end def test_tally_count - Item.tally.except(:order).count + Item.tally.except(:order).to_a.count end def test_tally_any @@ -403,7 +403,7 @@ def test_plusminus_tally_inclusion item = Item.create(:name => 'XBOX', :description => 'XBOX console') item_not_included = Item.create(:name => 'Playstation', :description => 'Playstation console') - assert_not_nil user.vote_for(item) + refute_nil user.vote_for(item) if ActiveRecord::Base.connection.adapter_name == 'MySQL' assert(Item.plusminus_tally.having('vote_count > 0').include?(item)) @@ -420,8 +420,8 @@ def test_plusminus_tally_up item2 = Item.create(:name => 'Playstation', :description => 'Playstation console') item3 = Item.create(:name => 'Wii', :description => 'Wii console') - assert_not_nil user.vote_for(item1) - assert_not_nil user.vote_against(item2) + refute_nil user.vote_for(item1) + refute_nil user.vote_against(item2) assert_equal [1, 0, 0], Item.plusminus_tally(:separate_updown => true).map(&:up).map(&:to_i) end @@ -432,8 +432,8 @@ def test_plusminus_tally_down item2 = Item.create(:name => 'Playstation', :description => 'Playstation console') item3 = Item.create(:name => 'Wii', :description => 'Wii console') - assert_not_nil user.vote_for(item1) - assert_not_nil user.vote_against(item2) + refute_nil user.vote_for(item1) + refute_nil user.vote_against(item2) assert_equal [0, 0, 1], Item.plusminus_tally(:separate_updown => true).map(&:down).map(&:to_i) end @@ -444,8 +444,8 @@ def test_plusminus_tally_vote_count item2 = Item.create(:name => 'Playstation', :description => 'Playstation console') item3 = Item.create(:name => 'Wii', :description => 'Wii console') - assert_not_nil user.vote_for(item1) - assert_not_nil user.vote_against(item2) + refute_nil user.vote_for(item1) + refute_nil user.vote_against(item2) assert_equal [1, 0, -1], Item.plusminus_tally.map(&:plusminus_tally).map(&:to_i) end @@ -454,7 +454,7 @@ def test_plusminus_tally_voting_for user1 = User.create(:name => 'david') item = Item.create(:name => 'Playstation', :description => 'Playstation console') - assert_not_nil user1.vote_for(item) + refute_nil user1.vote_for(item) # https://github.com/rails/rails/issues/1718 assert_equal 1, Item.plusminus_tally[0].vote_count.to_i @@ -466,8 +466,8 @@ def test_plusminus_tally_voting_against user2 = User.create(:name => 'john') item = Item.create(:name => 'Playstation', :description => 'Playstation console') - assert_not_nil user1.vote_against(item) - assert_not_nil user2.vote_against(item) + refute_nil user1.vote_against(item) + refute_nil user2.vote_against(item) # https://github.com/rails/rails/issues/1718 assert_equal 2, Item.plusminus_tally[0].vote_count.to_i @@ -481,10 +481,10 @@ def test_plusminus_tally_default_ordering item_for = Item.create(:name => 'XBOX', :description => 'XBOX console') item_against = Item.create(:name => 'Playstation', :description => 'Playstation console') - assert_not_nil user1.vote_for(item_for) - assert_not_nil user1.vote_for(item_twice_for) - assert_not_nil user2.vote_for(item_twice_for) - assert_not_nil user1.vote_against(item_against) + refute_nil user1.vote_for(item_for) + refute_nil user1.vote_for(item_twice_for) + refute_nil user2.vote_for(item_twice_for) + refute_nil user1.vote_against(item_against) assert_equal item_twice_for, Item.plusminus_tally[0] assert_equal item_for, Item.plusminus_tally[1] @@ -504,8 +504,8 @@ def test_plusminus_tally_ascending_ordering item_for = Item.create(:name => 'XBOX', :description => 'XBOX console') item_against = Item.create(:name => 'Playstation', :description => 'Playstation console') - assert_not_nil user.vote_for(item_for) - assert_not_nil user.vote_against(item_against) + refute_nil user.vote_for(item_for) + refute_nil user.vote_against(item_against) assert_equal item_for, Item.plusminus_tally.reorder('plusminus_tally ASC')[1] assert_equal item_against, Item.plusminus_tally.reorder('plusminus_tally ASC')[0] @@ -524,7 +524,7 @@ def test_plusminus_tally_limit_with_where_and_having end def test_plusminus_tally_count - Item.plusminus_tally.except(:order).count + Item.plusminus_tally.except(:order).to_a.count end def test_plusminus_tally_any diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index 36b9431..7d33bba 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -17,12 +17,9 @@ Gem::Specification.new do |s| s.files = Dir.glob('{lib,rails,test}/**/*') + %w(CHANGELOG.md Gemfile MIT-LICENSE README.md Rakefile) s.require_paths = ['lib'] - if RUBY_VERSION < '1.9.3' - s.add_runtime_dependency('activerecord', '< 4.0.0') - else - s.add_runtime_dependency('activerecord') - end + s.add_runtime_dependency('activerecord') s.add_runtime_dependency('statistics2') + s.add_development_dependency('minitest') s.add_development_dependency('simplecov') s.add_development_dependency('bundler') s.add_development_dependency('mysql2') From 204877d7ecaaf0ec91a6a3de8307af4c2d5ce465 Mon Sep 17 00:00:00 2001 From: Greg Molnar Date: Tue, 22 Sep 2015 10:06:48 +0200 Subject: [PATCH 128/138] lock AR version dependency --- thumbs_up.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index 7d33bba..7f98db2 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -17,7 +17,7 @@ Gem::Specification.new do |s| s.files = Dir.glob('{lib,rails,test}/**/*') + %w(CHANGELOG.md Gemfile MIT-LICENSE README.md Rakefile) s.require_paths = ['lib'] - s.add_runtime_dependency('activerecord') + s.add_runtime_dependency('activerecord', '~> 4.2.x') s.add_runtime_dependency('statistics2') s.add_development_dependency('minitest') s.add_development_dependency('simplecov') From 9af09e6492a00c67842655c5776ab0a5db038933 Mon Sep 17 00:00:00 2001 From: Greg Molnar Date: Sat, 3 Oct 2015 17:10:51 +0200 Subject: [PATCH 129/138] fix intermittent test failures --- test/test_helper.rb | 7 ++++++- test/thumbs_up_test.rb | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index b5b2832..be3ae18 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -151,6 +151,11 @@ class User < ActiveRecord::Base has_many :items has_karma :items + def self.default_karma + self.karmic_objects = nil + has_karma :items, :weight => 1 + end + def self.weighted_has_karma self.karmic_objects = nil has_karma :items, :weight => [ 10, 15 ] @@ -182,4 +187,4 @@ class UserCustom < ActiveRecord::Base acts_as_voter has_many :items has_karma :items -end \ No newline at end of file +end diff --git a/test/thumbs_up_test.rb b/test/thumbs_up_test.rb index 7ab247b..ca8fc31 100644 --- a/test/thumbs_up_test.rb +++ b/test/thumbs_up_test.rb @@ -532,6 +532,7 @@ def test_plusminus_tally_any end def test_karma + User.default_karma users = (0..1).map{ |u| User.create(:name => "User #{u}") } items = (0..1).map{ |u| users[0].items.create(:name => "Item #{u}", :description => "Item #{u}") } users.each{ |u| items.each { |i| u.vote_for(i) } } From 7143c78c35cc8655743b2873fcce1e4b61bde91e Mon Sep 17 00:00:00 2001 From: Greg Molnar Date: Sat, 3 Oct 2015 18:01:42 +0200 Subject: [PATCH 130/138] lock mysql2 gem version --- thumbs_up.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index 7f98db2..bd2fab1 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |s| s.add_development_dependency('minitest') s.add_development_dependency('simplecov') s.add_development_dependency('bundler') - s.add_development_dependency('mysql2') + s.add_development_dependency('mysql2', '~> 0.3.20') s.add_development_dependency('pg') s.add_development_dependency('sqlite3') s.add_development_dependency('rake') From 2d849497fb08035ea16ae391433ee51dfb5613b4 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Sat, 3 Oct 2015 18:18:21 -0700 Subject: [PATCH 131/138] Bump version. --- lib/thumbs_up/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/thumbs_up/version.rb b/lib/thumbs_up/version.rb index 177ffe1..6d45317 100644 --- a/lib/thumbs_up/version.rb +++ b/lib/thumbs_up/version.rb @@ -1,3 +1,3 @@ module ThumbsUp - VERSION = '0.6.7' + VERSION = '0.6.8' end \ No newline at end of file From 2c626024707c3a127d9f652c3cfd1febeff758e2 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Sat, 3 Oct 2015 18:19:58 -0700 Subject: [PATCH 132/138] Fix licensing. --- thumbs_up.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index bd2fab1..b3e5ca9 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -14,7 +14,7 @@ Gem::Specification.new do |s| s.description = 'ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com.' s.authors = ['Brady Bouchard', 'Peter Jackson', 'Cosmin Radoi', 'Bence Nagy', 'Rob Maddox', 'Wojciech Wnetrzak'] s.email = ['brady@thewellinspired.com'] - s.files = Dir.glob('{lib,rails,test}/**/*') + %w(CHANGELOG.md Gemfile MIT-LICENSE README.md Rakefile) + s.files = Dir.glob('{lib,rails,test}/**/*') + %w(CHANGELOG.md Gemfile LICENSE README.md Rakefile) s.require_paths = ['lib'] s.add_runtime_dependency('activerecord', '~> 4.2.x') From f97086f7483b4cec3292c634497021680f1498e0 Mon Sep 17 00:00:00 2001 From: Leo Correa Date: Tue, 26 Jan 2016 11:41:41 -0500 Subject: [PATCH 133/138] Include actual value for vote method in README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a67a404..34f48ef 100644 --- a/README.md +++ b/README.md @@ -90,11 +90,13 @@ end ```ruby voter.vote_for(voteable) # Adds a +1 vote voter.vote_against(voteable) # Adds a -1 vote -voter.vote(voteable, vote) # Adds either a +1 or -1 vote: vote => true (+1), vote => false (-1) voter.vote_exclusively_for(voteable) # Removes any previous votes by that particular voter, and votes for. voter.vote_exclusively_against(voteable) # Removes any previous votes by that particular voter, and votes against. +# Alternative method, can pass a hash that includes `:exclusive` and `:direction` options. +voter.vote(voteable, { :exclusive => false, :direction => :up }) # Votes non-exclusively, either a +1 or -1 depending on the `:direction` value + voter.unvote_for(voteable) # Clears all votes for that user ``` From c0aed5041b95b10da492199924b87ba75c1f265a Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Tue, 26 Jan 2016 11:24:16 -0800 Subject: [PATCH 134/138] Travis error. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 248e1da..b4fa8da 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,3 +4,5 @@ rvm: before_script: - "mysql -e 'create database thumbs_up_test;'" - "bundle update" +before_install: + - gem update bundler \ No newline at end of file From 7f3029f3c02b58f77490a0d7750f890c503d5e43 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Fri, 22 Apr 2016 09:29:02 -0400 Subject: [PATCH 135/138] Seems to work fine with AR 5.0.0.beta3, so allow it. --- thumbs_up.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index b3e5ca9..4dabdcb 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -17,7 +17,7 @@ Gem::Specification.new do |s| s.files = Dir.glob('{lib,rails,test}/**/*') + %w(CHANGELOG.md Gemfile LICENSE README.md Rakefile) s.require_paths = ['lib'] - s.add_runtime_dependency('activerecord', '~> 4.2.x') + s.add_runtime_dependency('activerecord', '> 4.2') s.add_runtime_dependency('statistics2') s.add_development_dependency('minitest') s.add_development_dependency('simplecov') From 4c4d1cb68cffb79632e3aff34fe1fa2821187ceb Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Fri, 22 Apr 2016 09:32:05 -0400 Subject: [PATCH 136/138] Version bump. --- lib/thumbs_up/version.rb | 2 +- thumbs_up.gemspec | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/thumbs_up/version.rb b/lib/thumbs_up/version.rb index 6d45317..87e9b6f 100644 --- a/lib/thumbs_up/version.rb +++ b/lib/thumbs_up/version.rb @@ -1,3 +1,3 @@ module ThumbsUp - VERSION = '0.6.8' + VERSION = '0.6.9' end \ No newline at end of file diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index 4dabdcb..a629ab5 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -17,12 +17,12 @@ Gem::Specification.new do |s| s.files = Dir.glob('{lib,rails,test}/**/*') + %w(CHANGELOG.md Gemfile LICENSE README.md Rakefile) s.require_paths = ['lib'] - s.add_runtime_dependency('activerecord', '> 4.2') + s.add_runtime_dependency('activerecord', '> 4.2', '< 6') s.add_runtime_dependency('statistics2') s.add_development_dependency('minitest') s.add_development_dependency('simplecov') s.add_development_dependency('bundler') - s.add_development_dependency('mysql2', '~> 0.3.20') + s.add_development_dependency('mysql2', '> 0.3.20') s.add_development_dependency('pg') s.add_development_dependency('sqlite3') s.add_development_dependency('rake') From 49636c4de412cec56b78251c00b110d4bec22e3f Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Wed, 21 Aug 2019 21:16:20 -0600 Subject: [PATCH 137/138] Update travis CI config. --- .travis.yml | 9 +++++---- lib/thumbs_up/version.rb | 2 +- thumbs_up.gemspec | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index b4fa8da..cfb8d99 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,9 @@ language: ruby rvm: - - 1.9.3 -before_script: - - "mysql -e 'create database thumbs_up_test;'" - - "bundle update" + - 2.6.3 +services: + - mysql before_install: + - "mysql -e 'create database thumbs_up_test;'" + - bundle update - gem update bundler \ No newline at end of file diff --git a/lib/thumbs_up/version.rb b/lib/thumbs_up/version.rb index 87e9b6f..bf0fd50 100644 --- a/lib/thumbs_up/version.rb +++ b/lib/thumbs_up/version.rb @@ -1,3 +1,3 @@ module ThumbsUp - VERSION = '0.6.9' + VERSION = '0.6.10' end \ No newline at end of file diff --git a/thumbs_up.gemspec b/thumbs_up.gemspec index a629ab5..7b07fbd 100644 --- a/thumbs_up.gemspec +++ b/thumbs_up.gemspec @@ -17,7 +17,7 @@ Gem::Specification.new do |s| s.files = Dir.glob('{lib,rails,test}/**/*') + %w(CHANGELOG.md Gemfile LICENSE README.md Rakefile) s.require_paths = ['lib'] - s.add_runtime_dependency('activerecord', '> 4.2', '< 6') + s.add_runtime_dependency('activerecord', '> 4.2', '< 7') s.add_runtime_dependency('statistics2') s.add_development_dependency('minitest') s.add_development_dependency('simplecov') From bd3c9037efc451f65403f1b6556e6295127dae44 Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Wed, 21 Aug 2019 21:19:57 -0600 Subject: [PATCH 138/138] Add pg service. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index cfb8d99..1ade7d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,9 @@ rvm: - 2.6.3 services: - mysql + - postgresql before_install: - "mysql -e 'create database thumbs_up_test;'" + - psql -c 'create database thumbs_up_test;' -U postgres - bundle update - gem update bundler \ No newline at end of file