Commit edbc6fc19fe4b769a30fddb66feffb9076322f06

Authored by Victor Costa
1 parent 08480dd5

Create infra to enable votes on articles and comments

app/models/article.rb
... ... @@ -76,6 +76,8 @@ class Article < ActiveRecord::Base
76 76 belongs_to :translation_of, :class_name => 'Article', :foreign_key => :translation_of_id
77 77 before_destroy :rotate_translations
78 78  
  79 + acts_as_voteable
  80 +
79 81 before_create do |article|
80 82 article.published_at ||= Time.now
81 83 if article.reference_article && !article.parent
... ...
app/models/comment.rb
... ... @@ -35,6 +35,8 @@ class Comment < ActiveRecord::Base
35 35  
36 36 xss_terminate :only => [ :body, :title, :name ], :on => 'validation'
37 37  
  38 + acts_as_voteable
  39 +
38 40 def comment_root
39 41 (reply_of && reply_of.comment_root) || self
40 42 end
... ...
app/models/person.rb
... ... @@ -81,6 +81,8 @@ class Person < Profile
81 81  
82 82 belongs_to :user, :dependent => :delete
83 83  
  84 + acts_as_voter
  85 +
84 86 def can_control_scrap?(scrap)
85 87 begin
86 88 !self.scraps(scrap).nil?
... ...
db/migrate/20140102151000_vote_fu_migration.rb 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +class VoteFuMigration < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :votes, :force => true do |t|
  4 + t.integer :vote, :null => false
  5 + t.references :voteable, :polymorphic => true, :null => false
  6 + t.references :voter, :polymorphic => true
  7 + t.timestamps
  8 + end
  9 +
  10 + add_index :votes, ["voter_id", "voter_type"], :name => "fk_voters"
  11 + add_index :votes, ["voteable_id", "voteable_type"], :name => "fk_voteables"
  12 +
  13 + # If you want to enfore "One Person, One Vote" rules in the database, uncomment the index below
  14 + # add_index :votes, ["voter_id", "voter_type", "voteable_id", "voteable_type"], :unique => true, :name => "uniq_one_vote_only"
  15 + end
  16 +
  17 + def self.down
  18 + drop_table :votes
  19 + end
  20 +
  21 +end
... ...
test/unit/article_test.rb
... ... @@ -1744,4 +1744,11 @@ class ArticleTest &lt; ActiveSupport::TestCase
1744 1744 assert_nil article.author_id
1745 1745 end
1746 1746  
  1747 + should 'vote in a article' do
  1748 + article = Article.create!(:name => 'Test', :profile => profile, :last_changed_by => nil)
  1749 + profile.vote(article, 5)
  1750 + assert_equal 1, article.voters_who_voted.length
  1751 + assert_equal 5, article.votes_total
  1752 + end
  1753 +
1747 1754 end
... ...
test/unit/comment_test.rb
... ... @@ -685,6 +685,45 @@ class CommentTest &lt; ActiveSupport::TestCase
685 685 assert_equivalent [c1,c4], Comment.without_reply
686 686 end
687 687  
  688 + should 'vote in a comment' do
  689 + comment = create_comment
  690 + person = create_user('voter').person
  691 + person.vote(comment, 5)
  692 + assert_equal 1, comment.voters_who_voted.length
  693 + assert_equal 5, comment.votes_total
  694 + end
  695 +
  696 + should 'like a comment' do
  697 + comment = create_comment
  698 + person = create_user('voter').person
  699 + assert !comment.voted_by?(person, true)
  700 + person.vote_for(comment)
  701 + assert comment.voted_by?(person, true)
  702 + assert !comment.voted_by?(person, false)
  703 + end
  704 +
  705 + should 'count voters for' do
  706 + comment = create_comment
  707 + person = create_user('voter').person
  708 + person2 = create_user('voter2').person
  709 + person3 = create_user('voter3').person
  710 + person.vote_for(comment)
  711 + person2.vote_for(comment)
  712 + person3.vote_against(comment)
  713 + assert_equal 2, comment.votes_for
  714 + end
  715 +
  716 + should 'count votes againts' do
  717 + comment = create_comment
  718 + person = create_user('voter').person
  719 + person2 = create_user('voter2').person
  720 + person3 = create_user('voter3').person
  721 + person.vote_against(comment)
  722 + person2.vote_against(comment)
  723 + person3.vote_for(comment)
  724 + assert_equal 2, comment.votes_against
  725 + end
  726 +
688 727 private
689 728  
690 729 def create_comment(args = {})
... ...
test/unit/person_test.rb
... ... @@ -1335,4 +1335,133 @@ class PersonTest &lt; ActiveSupport::TestCase
1335 1335 assert_includes non_abusers, not_abuser
1336 1336 end
1337 1337  
  1338 + should 'vote in a comment with value greater than 1' do
  1339 + comment = fast_create(Comment)
  1340 + person = fast_create(Person)
  1341 +
  1342 + person.vote(comment, 5)
  1343 + assert_equal 1, person.vote_count
  1344 + assert_equal 5, person.votes.first.vote
  1345 + assert person.voted_on?(comment)
  1346 + end
  1347 +
  1348 + should 'vote in a comment with value lesser than -1' do
  1349 + comment = fast_create(Comment)
  1350 + person = fast_create(Person)
  1351 +
  1352 + person.vote(comment, -5)
  1353 + assert_equal 1, person.vote_count
  1354 + assert_equal -5, person.votes.first.vote
  1355 + end
  1356 +
  1357 + should 'vote for a comment' do
  1358 + comment = fast_create(Comment)
  1359 + person = fast_create(Person)
  1360 +
  1361 + assert !person.voted_for?(comment)
  1362 + person.vote_for(comment)
  1363 + assert person.voted_for?(comment)
  1364 + assert !person.voted_against?(comment)
  1365 + end
  1366 +
  1367 + should 'vote against a comment' do
  1368 + comment = fast_create(Comment)
  1369 + person = fast_create(Person)
  1370 +
  1371 + assert !person.voted_against?(comment)
  1372 + person.vote_against(comment)
  1373 + assert !person.voted_for?(comment)
  1374 + assert person.voted_against?(comment)
  1375 + end
  1376 +
  1377 + should 'do not vote against a comment twice' do
  1378 + comment = fast_create(Comment)
  1379 + person = fast_create(Person)
  1380 +
  1381 + assert person.vote_against(comment)
  1382 + assert !person.vote_against(comment)
  1383 + end
  1384 +
  1385 + should 'do not vote for a comment twice' do
  1386 + comment = fast_create(Comment)
  1387 + person = fast_create(Person)
  1388 +
  1389 + assert person.vote_for(comment)
  1390 + assert !person.vote_for(comment)
  1391 + end
  1392 +
  1393 + should 'not vote against a voted for comment' do
  1394 + comment = fast_create(Comment)
  1395 + person = fast_create(Person)
  1396 +
  1397 + person.vote_for(comment)
  1398 + person.vote_against(comment)
  1399 + assert person.voted_for?(comment)
  1400 + assert !person.voted_against?(comment)
  1401 + end
  1402 +
  1403 + should 'not vote for a voted against comment' do
  1404 + comment = fast_create(Comment)
  1405 + person = fast_create(Person)
  1406 +
  1407 + person.vote_against(comment)
  1408 + person.vote_for(comment)
  1409 + assert !person.voted_for?(comment)
  1410 + assert person.voted_against?(comment)
  1411 + end
  1412 +
  1413 + should 'undo a vote for a comment' do
  1414 + comment = fast_create(Comment)
  1415 + person = fast_create(Person)
  1416 +
  1417 + person.vote_for(comment)
  1418 + assert person.voted_for?(comment)
  1419 + person.votes.for_voteable(comment).destroy_all
  1420 + assert !person.voted_for?(comment)
  1421 + end
  1422 +
  1423 + should 'count comments voted' do
  1424 + comment = fast_create(Comment)
  1425 + person = fast_create(Person)
  1426 +
  1427 + comment2 = fast_create(Comment)
  1428 + comment3 = fast_create(Comment)
  1429 + person.vote_for(comment)
  1430 + person.vote_for(comment2)
  1431 + person.vote_against(comment3)
  1432 + assert_equal 3, person.vote_count
  1433 + assert_equal 2, person.vote_count(true)
  1434 + assert_equal 1, person.vote_count(false)
  1435 + end
  1436 +
  1437 + should 'vote in a article with value greater than 1' do
  1438 + article = fast_create(Article)
  1439 + person = fast_create(Person)
  1440 +
  1441 + person.vote(article, 5)
  1442 + assert_equal 1, person.vote_count
  1443 + assert_equal 5, person.votes.first.vote
  1444 + assert person.voted_on?(article)
  1445 + end
  1446 +
  1447 + should 'vote for a article' do
  1448 + article = fast_create(Article)
  1449 + person = fast_create(Person)
  1450 +
  1451 + assert !person.voted_for?(article)
  1452 + person.vote_for(article)
  1453 + assert person.voted_for?(article)
  1454 + assert !person.voted_against?(article)
  1455 + end
  1456 +
  1457 + should 'vote against a article' do
  1458 + article = fast_create(Article)
  1459 + person = fast_create(Person)
  1460 +
  1461 + assert !person.voted_against?(article)
  1462 + person.vote_against(article)
  1463 + assert !person.voted_for?(article)
  1464 + assert person.voted_against?(article)
  1465 + end
  1466 +
1338 1467 end
... ...
vendor/plugins/vote_fu/CHANGELOG.markdown 0 → 100644
... ... @@ -0,0 +1,48 @@
  1 +2010-03-08
  2 +==========
  3 +* Vote method in `acts_as_voter` model returns true upon success.
  4 +
  5 +2010-02-21
  6 +==========
  7 +* Changed the `tally` method to add support for `at_least_total` and `at_most_total` parameters to filter by sum of votes.
  8 +* Changed the `tally` method to add support for `total` column in :order paramter.
  9 +
  10 +2010-02-20
  11 +==========
  12 +* Changed the data-type of the `vote` column in the `votes` table to integer type.
  13 +* Added support for vote count caching at the `voteable` model.
  14 +* Added new method `votes_total` on `voteable` model to return the sum of +ve and -ve votes
  15 +* Optimized several methods in `voteable` model (`voters_who_voted`, `voted_by?`)
  16 +* Cleaned the code to use associations instead of direct SQL
  17 +
  18 +2009-02-11
  19 +==========
  20 +* Merge in xlash's bugfix for PostgreSQL and his has\_karma patch for multi-model support.
  21 +
  22 +2008-12-02
  23 +==========
  24 +* Merge in maddox's README typo fix and his ActiveSupport.Dependency patch
  25 +* Merge in nagybence's updates that make the code usable as a Gem in addition to being a Rails plugin.
  26 +* Thanks for the bugfixes and proofreading, nagybence and maddox!
  27 +* Updated the gemplugin support to be compatible with maddox and nagybence's changes.
  28 +* Added details on the MyQuotable reference application.
  29 +
  30 +2008-07-20
  31 +==========
  32 +* Protect against mass assignment misvotes using attr\_accessible
  33 +* Update acts\_as mixins to use self.class.name instead of the deprecated self.type.name
  34 +
  35 +2008-07-15
  36 +==========
  37 +* Added examples directory
  38 +* Changed this file to markdown format for GitHub goodness
  39 +* Added a commented out unique index in the migration generator for "one person, one vote"
  40 +* Removed votes\_controller.rb from lib/ and moved to examples
  41 +
  42 +2008-07-10
  43 +==========
  44 +
  45 +* Added a generator class for the migration.
  46 +* Implemented rails/init.rb
  47 +* Implemented capability to use any model as the initiator of votes.
  48 +* Implemented acts\_as\_voter methods.
... ...
vendor/plugins/vote_fu/MIT-LICENSE 0 → 100644
... ... @@ -0,0 +1,43 @@
  1 +Copyright (c) 2008 Peter Jackson (peteonrails.com)
  2 +
  3 +Permission is hereby granted, free of charge, to any person obtaining
  4 +a copy of this software and associated documentation files (the
  5 +"Software"), to deal in the Software without restriction, including
  6 +without limitation the rights to use, copy, modify, merge, publish,
  7 +distribute, sublicense, and/or sell copies of the Software, and to
  8 +permit persons to whom the Software is furnished to do so, subject to
  9 +the following conditions:
  10 +
  11 +The above copyright notice and this permission notice shall be
  12 +included in all copies or substantial portions of the Software.
  13 +
  14 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  15 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  16 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  17 +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  18 +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  19 +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  20 +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  21 +
  22 +Major portions of this package were adapted from ActsAsVoteable, which is subject to the same license. Here is the original copyright notice for ActsAsVoteable:
  23 +
  24 +Copyright (c) 2006 Cosmin Radoi
  25 +
  26 +Permission is hereby granted, free of charge, to any person obtaining
  27 +a copy of this software and associated documentation files (the
  28 +"Software"), to deal in the Software without restriction, including
  29 +without limitation the rights to use, copy, modify, merge, publish,
  30 +distribute, sublicense, and/or sell copies of the Software, and to
  31 +permit persons to whom the Software is furnished to do so, subject to
  32 +the following conditions:
  33 +
  34 +The above copyright notice and this permission notice shall be
  35 +included in all copies or substantial portions of the Software.
  36 +
  37 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  38 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  39 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  40 +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  41 +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  42 +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  43 +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
0 44 \ No newline at end of file
... ...
vendor/plugins/vote_fu/README.markdown 0 → 100644
... ... @@ -0,0 +1,255 @@
  1 +vote_fu
  2 +=======
  3 +
  4 +Allows an arbitrary number of entites (including Users) to vote on models.
  5 +
  6 +### Mixins
  7 +This plugin introduces three mixins to your recipe book:
  8 +
  9 +1. **acts\_as\_voteable** : Intended for content objects like Posts, Comments, etc.
  10 +2. **acts\_as\_voter** : Intended for voting entities, like Users.
  11 +3. **has\_karma** : Intended for voting entities, or other objects that own the things you're voting on.
  12 +
  13 +### Inspiration
  14 +
  15 +This plugin started as an adaptation / update of act\_as\_voteable. It has grown different from that plugin in several ways:
  16 +
  17 +1. You can specify the model name that initiates votes.
  18 +2. You can, with a little tuning, have more than one entity type vote on more than one model type.
  19 +3. Adds "acts\_as\_voter" behavior to the initiator of votes.
  20 +4. Introduces some newer Rails features like named\_scope and :polymorphic keywords
  21 +5. Adds "has\_karma" mixin for identifying key content contributors
  22 +
  23 +### Difference between original vote_fu
  24 +1. The data-type of the `vote` column in the `votes` table is changed to integer type.
  25 +2. Support for vote count caching at the `voteable` model.
  26 +3. New method `votes_total` on `voteable` model to return the sum of +ve and -ve votes
  27 +4. Optimized several methods in `voteable` model (`voters_who_voted`, `voted_by?`)
  28 +5. Code cleanup to use associations instead of direct SQL
  29 +6. The `tally` method supports `at_least_total` and `at_most_total` parameters to
  30 + filter by sum of votes.
  31 +7. The :order option of the `tally` method supports order by `total` (E.g: :order => "toal DESC")
  32 +Installation
  33 +============
  34 +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.
  35 +
  36 +### Via plugin
  37 + ./script/plugin install git://github.com/kandadaboggu/vote_fu.git
  38 +
  39 +### Via gem
  40 +Add the following to your application's environment.rb:
  41 + config.gem "kandadaboggu-vote_fu", :lib => 'vote_fu', :source => "http://gemcutter.org"
  42 +
  43 +Install the gem:
  44 + rake gems:install
  45 +
  46 +### Create vote_fu migration
  47 +Create a new rails migration using your new vote_fu generator (Note: "VoteableModel" is the name of the model on which you would like votes to be cast, e.g. Comment):
  48 + ./script/generate vote_fu VoteableModel
  49 +
  50 +Run the migration:
  51 + rake db:migrate
  52 +
  53 +Usage
  54 +=====
  55 +
  56 +## Getting Started
  57 +
  58 +### Make your ActiveRecord model act as voteable.
  59 +
  60 +
  61 + class Model < ActiveRecord::Base
  62 + acts_as_voteable
  63 + end
  64 +
  65 + class Post < ActiveRecord::Base
  66 + acts_as_voteable :vote_counter => true # Stores the sum of the votes in the `vote_total`
  67 + # column of the `posts` table.
  68 + end
  69 +
  70 + class Comment < ActiveRecord::Base
  71 + acts_as_voteable :vote_counter => :comments_vote_count # Stores the sum of the votes in the `comments_vote_count`
  72 + # column of the `comments` table.
  73 + end
  74 +
  75 +### Make your ActiveRecord model(s) that vote act as voter.
  76 +
  77 + class User < ActiveRecord::Base
  78 + acts_as_voter
  79 + end
  80 +
  81 + class Robot < ActiveRecord::Base
  82 + acts_as_voter
  83 + end
  84 +
  85 +### To cast a vote for a Model you can do the following:
  86 +
  87 +#### Shorthand syntax
  88 + voter.vote_for(voteable) # Adds a +1 vote
  89 + voter.vote_against(voteable) # Adds a -1 vote
  90 + voter.vote(voteable, t_or_f) # Adds either +1 or -1 vote true => +1, false => -1
  91 +
  92 +#### ActsAsVoteable syntax
  93 +The old acts\_as\_voteable syntax is still supported:
  94 +
  95 + vote = Vote.new(:vote => true)
  96 + m = Model.find(params[:id])
  97 + m.votes << vote
  98 + user.votes << vote
  99 +
  100 +### Querying votes
  101 +
  102 +#### Tallying Votes
  103 +
  104 +You can easily retrieve voteable object collections based on the properties of their votes:
  105 +
  106 + @items = Item.tally(
  107 + { :at_least => 1,
  108 + :at_most => 10000,
  109 + :start_at => 2.weeks.ago,
  110 + :end_at => 1.day.ago,
  111 + :limit => 10,
  112 + :order => "items.name desc"
  113 + })
  114 +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.
  115 +
  116 + @items = Item.tally(
  117 + { :at_least_total => 1,
  118 + :at_most_total => 10000,
  119 + :start_at => 2.weeks.ago,
  120 + :end_at => 1.day.ago,
  121 + :limit => 10,
  122 + :order => "total desc"
  123 + })
  124 +This will select the Items with between 1 and 10,000 total votes, the votes having been cast within the last two weeks (not including today), then display the 10 last items in a descending order list by total votes.
  125 +
  126 +
  127 +##### Tally Options:
  128 + :start_at - Restrict the votes to those created after a certain time
  129 + :end_at - Restrict the votes to those created before a certain time
  130 + :conditions - A piece of SQL conditions to add to the query
  131 + :limit - The maximum number of voteables to return
  132 + :order - A piece of SQL to order by. Two calculated columns `count`, and `total`
  133 + are available for sorting apart from other columns. Defaults to `total DESC`.
  134 + Eg: :order => 'count desc'
  135 + :order => 'total desc'
  136 + :order => 'post.created_at desc'
  137 + :at_least - Item must have at least X votes count
  138 + :at_most - Item may not have more than X votes count
  139 + :at_least_total - Item must have at least X votes total
  140 + :at_most_total - Item may not have more than X votes total
  141 +
  142 +#### Lower level queries
  143 +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:
  144 +
  145 + positiveVoteCount = m.votes_for
  146 + negativeVoteCount = m.votes_against
  147 + voteCount = m.votes_count
  148 + totalVote = m.votes_total
  149 +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:
  150 +
  151 + allVotes = m.votes
  152 +
  153 +The mixin also provides these methods:
  154 +
  155 + voter.voted_for?(voteable) # True if the voter voted for this object.
  156 + voter.vote_count([true|false|"all"]) # returns the count of +1, -1, or all votes
  157 +
  158 + voteable.voted_by?(voter) # True if the voter voted for this object.
  159 + @voters = voteable.voters_who_voted
  160 +
  161 +
  162 +#### Named Scopes
  163 +
  164 +The Vote model has several named scopes you can use to find vote details:
  165 +
  166 + @pete_votes = Vote.for_voter(pete)
  167 + @post_votes = Vote.for_voteable(post)
  168 + @recent_votes = Vote.recent(1.day.ago)
  169 + @descending_votes = Vote.descending
  170 +
  171 +You can chain these together to make interesting queries:
  172 +
  173 + # Show all of Pete's recent votes for a certain Post, in descending order (newest first)
  174 + @pete_recent_votes_on_post = Vote.for_voter(pete).for_voteable(post).recent(7.days.ago).descending
  175 +
  176 +### Experimental: Voteable Object Owner Karma
  177 +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.
  178 +
  179 + class User
  180 + has_many :posts
  181 + has_karma :posts
  182 + end
  183 +
  184 + class Post
  185 + acts_as_voteable
  186 + end
  187 +
  188 + # in your view, you can then do this:
  189 + Karma: <%= @user.karma %>
  190 +
  191 +This feature is in alpha, but useful enough that I'm releasing it.
  192 +
  193 +### One vote per user!
  194 +If you want to limit your users to a single vote on each item, take a look in lib/vote.rb.
  195 +
  196 + # Uncomment this to limit users to a single vote on each item.
  197 + # validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id]
  198 +
  199 +And if you want that enforced at the database level, look in the generated migration for your voteable:
  200 +
  201 + # If you want to enfore "One Person, One Vote" rules in the database, uncomment the index below
  202 + # add_index :votes, ["voter_id", "voter_type", "voteable_id", "voteable_type"], :unique => true, :name => "uniq_one_vote_only"
  203 +
  204 +### Example Application
  205 +
  206 +There is now a reference application available. Due to overwhelming demand for example
  207 +code and kickstart guides, I have open-sourced MyQuotable.com in order to provide an
  208 +easy-to-follow example of how to use VoteFu with RESTful Authentication, JRails, and
  209 +other popular plugins. To get the example code:
  210 +
  211 + git clone git://github.com/peteonrails/myquotable.git
  212 +
  213 +There will be a screencast coming soon too. Contact me if you want to help.
  214 +
  215 +Consideration
  216 +=============
  217 +If you like this software and use it, please consider recommending me on Working With Rails.
  218 +
  219 +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]
  220 +
  221 +To go directly to the "Recommend Me" screen: [http://www.workingwithrails.com/recommendation/new/person/12521-peter-jackson][5]
  222 +
  223 +
  224 +Credits
  225 +=======
  226 +
  227 +#### Contributors
  228 +
  229 +* Bence Nagy, Budapest, Hungary
  230 +* Jon Maddox, Richmond, Virginia, USA
  231 +* Kandada Boggu, Palo Alto, CA, USA
  232 +#### Other works
  233 +
  234 +[Juixe - The original ActsAsVoteable plugin inspired this code.][1]
  235 +
  236 +[Xelipe - This plugin is heavily influenced by Acts As Commentable.][2]
  237 +
  238 +[1]: http://www.juixe.com/techknow/index.php/2006/06/24/acts-as-voteable-rails-plugin/
  239 +[2]: http://github.com/jackdempsey/acts_as_commentable/tree/master
  240 +
  241 +More
  242 +====
  243 +
  244 +Support: [Use my blog for support.][6]
  245 +
  246 +
  247 +[Documentation from the original acts\_as\_voteable plugin][3]
  248 +
  249 +[3]: http://www.juixe.com/techknow/index.php/2006/06/24/acts-as-voteable-rails-plugin/
  250 +[4]: http://www.workingwithrails.com/person/12521-peter-jackson
  251 +[5]: http://www.workingwithrails.com/recommendation/new/person/12521-peter-jackson
  252 +[6]: http://blog.peteonrails.com
  253 +
  254 +Copyright (c) 2008 Peter Jackson, released under the MIT license
  255 +
... ...
vendor/plugins/vote_fu/examples/routes.rb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +
  2 +map.resources :users do |user|
  3 + user.resources :votes
  4 + user.resources :voteable do |mv|
  5 + mv.resources :votes
  6 + end
  7 +end
0 8 \ No newline at end of file
... ...
vendor/plugins/vote_fu/examples/users_controller.rb 0 → 100644
... ... @@ -0,0 +1,76 @@
  1 +# I usually use the user class from restful_authentication as my principle voter class
  2 +# There are generally no changes required to support voting in this controller.
  3 +
  4 +class UsersController < ApplicationController
  5 + # Be sure to include AuthenticationSystem in Application Controller instead
  6 + include AuthenticatedSystem
  7 +
  8 + # Protect these actions behind an admin login
  9 + before_filter :admin_required, :only => [:suspend, :unsuspend, :destroy, :purge]
  10 + before_filter :find_user, :only => [:suspend, :unsuspend, :destroy, :purge, :show]
  11 +
  12 + before_filter :login_required, :only => [:index]
  13 +
  14 + # render new.html.erb
  15 + def new
  16 + end
  17 +
  18 + # GET /users/:id
  19 + def show
  20 + end
  21 +
  22 +
  23 + def create
  24 + cookies.delete :auth_token
  25 + @user = User.new(params[:user])
  26 + @user.register! if @user.valid?
  27 + if @user.errors.empty?
  28 + self.current_user.forget_me if logged_in?
  29 + cookies.delete :auth_token
  30 + reset_session
  31 + flash[:notice] = "Thanks for signing up!"
  32 + else
  33 + render :action => 'new'
  34 + end
  35 + end
  36 +
  37 + def activate
  38 + unless params[:activation_code].blank?
  39 + self.current_user = User.find_by_activation_code(params[:activation_code])
  40 + if logged_in? && !current_user.active?
  41 + current_user.activate!
  42 + flash[:notice] = "Signup complete!"
  43 + redirect_back_or_default('/')
  44 + else
  45 + flash[:error] = "Sorry, we couldn't find that activation code. Please cut and paste your activation code into the space at left."
  46 + end
  47 + end
  48 + # render activate.html.erb
  49 + end
  50 +
  51 + def suspend
  52 + @user.suspend!
  53 + redirect_to users_path
  54 + end
  55 +
  56 + def unsuspend
  57 + @user.unsuspend!
  58 + redirect_to users_path
  59 + end
  60 +
  61 + def destroy
  62 + @user.delete!
  63 + redirect_to users_path
  64 + end
  65 +
  66 + def purge
  67 + @user.destroy
  68 + redirect_to users_path
  69 + end
  70 +
  71 +protected
  72 + def find_user
  73 + @user = User.find(params[:id])
  74 + end
  75 +
  76 +end
... ...
vendor/plugins/vote_fu/examples/voteable.html.erb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +<div id="voteable_<%= @voteable.id %>">
  2 +
  3 + ..... Show some fields .....
  4 +
  5 + <div id="votes_<%= @voteable.id %>">
  6 + <%= render :partial => "votes/voteable_vote", :locals => {:voteable => @voteable} %>
  7 + </div>
  8 +</div>
... ...
vendor/plugins/vote_fu/examples/voteable.rb 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +class Voteable < ActiveRecord::Base
  2 +
  3 + belongs_to :user
  4 +
  5 + acts_as_voteable
  6 +
  7 + named_scope :descending, :order => "created_at DESC"
  8 +
  9 +
  10 +end
0 11 \ No newline at end of file
... ...
vendor/plugins/vote_fu/examples/voteables_controller.rb 0 → 100644
... ... @@ -0,0 +1,117 @@
  1 +# This example controller assumes you are using the User class from restful_authentication
  2 +# and a nested voteable resource. See routes.rb
  3 +
  4 +
  5 +class VoteablesController < ApplicationController
  6 +
  7 + before_filter :find_user
  8 + before_filter :login_required, :only => [:new, :edit, :destroy, :create, :update]
  9 + before_filter :must_own_voteable, :only => [:edit, :destroy, :update]
  10 +
  11 + # GET /users/:id/voteables
  12 + # GET /users/:id/voteables.xml
  13 + def index
  14 + @voteable = Voteable.descending
  15 +
  16 + respond_to do |format|
  17 + format.html # index.html.erb
  18 + format.xml { render :xml => @voteables }
  19 + end
  20 + end
  21 +
  22 + # GET /users/:id/voteables/1
  23 + # GET /users/:id/voteables/1.xml
  24 + def show
  25 + @voteable = Voteable.find(params[:id])
  26 +
  27 + respond_to do |format|
  28 + format.html # show.html.erb
  29 + format.xml { render :xml => @voteable }
  30 + end
  31 + end
  32 +
  33 + # GET /users/:id/voteables/new
  34 + # GET /users/:id/voteables/new.xml
  35 + def new
  36 + @voteable = Voteable.new
  37 +
  38 + respond_to do |format|
  39 + format.html # new.html.erb
  40 + format.xml { render :xml => @voteable }
  41 + end
  42 + end
  43 +
  44 + # GET /users/:id/voteables/1/edit
  45 + def edit
  46 + @voteable ||= Voteable.find(params[:id])
  47 + end
  48 +
  49 + # POST /users/:id/voteables
  50 + # POST /users/:id/voteables.xml
  51 + def create
  52 + @voteable = Voteable.new(params[:voteable])
  53 + @voteable.user = current_user
  54 +
  55 + respond_to do |format|
  56 + if @voteable.save
  57 + flash[:notice] = 'Voteable was successfully saved.'
  58 + format.html { redirect_to([@user, @voteable]) }
  59 + format.xml { render :xml => @voteable, :status => :created, :location => @voteable }
  60 + else
  61 + format.html { render :action => "new" }
  62 + format.xml { render :xml => @voteable.errors, :status => :unprocessable_entity }
  63 + end
  64 + end
  65 + end
  66 +
  67 + # PUT /users/:id/voteable/1
  68 + # PUT /users/:id/voteable/1.xml
  69 + def update
  70 + @voteable = Voteable.find(params[:id])
  71 +
  72 + respond_to do |format|
  73 + if @quote.update_attributes(params[:voteable])
  74 + flash[:notice] = 'Voteable was successfully updated.'
  75 + format.html { redirect_to([@user, @voteable]) }
  76 + format.xml { head :ok }
  77 + else
  78 + format.html { render :action => "edit" }
  79 + format.xml { render :xml => @voteable.errors, :status => :unprocessable_entity }
  80 + end
  81 + end
  82 + end
  83 +
  84 + # DELETE /users/:id/voteable/1
  85 + # DELETE /users/:id/voteable/1.xml
  86 + def destroy
  87 + @voteable = Voteable.find(params[:id])
  88 + @voteable.destroy
  89 +
  90 + respond_to do |format|
  91 + format.html { redirect_to(user_voteables_url) }
  92 + format.xml { head :ok }
  93 + end
  94 + end
  95 +
  96 + private
  97 + def find_user
  98 + @user = User.find(params[:user_id])
  99 + end
  100 +
  101 + def must_own_voteable
  102 + @voteable ||= Voteable.find(params[:id])
  103 + @voteable.user == current_user || ownership_violation
  104 + end
  105 +
  106 + def ownership_violation
  107 + respond_to do |format|
  108 + flash[:notice] = 'You cannot edit or delete voteable that you do not own!'
  109 + format.html do
  110 + redirect_to user_path(current_user)
  111 + end
  112 + end
  113 + end
  114 +
  115 +
  116 +
  117 +end
... ...
vendor/plugins/vote_fu/examples/votes/_voteable_vote.html.erb 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +<%
  2 + # You can't vote if it is your quote,
  3 + # you are not logged in,
  4 + # or you have already voted on this item
  5 +
  6 + unless quote.user == current_user ||
  7 + !logged_in? ||
  8 + current_user.voted_on?(@voteable)
  9 +%>
  10 +
  11 + <%= link_to_remote "Up",
  12 + :url => user_voteable_votes_path(voteable.user, voteable, :vote => :true, :format => :rjs),
  13 + :method => :post
  14 + %>
  15 + /
  16 + <%= link_to_remote "Down",
  17 + :url => user_voteable_votes_path(voteable.user, voteable, :vote => :false, :format => :rjs),
  18 + :method => :post
  19 + %>
  20 +
  21 +<% end %>
  22 +
  23 +Votes: <%= voteable.votes_for - voteable.votes_against %>
... ...
vendor/plugins/vote_fu/examples/votes/create.rjs 0 → 100644
... ... @@ -0,0 +1 @@
  1 +page.replace_html "votes_#{@voteable.id}", :partial => "voteable_vote", :locals => {:voteable => @voteable}
... ...
vendor/plugins/vote_fu/examples/votes_controller.rb 0 → 100644
... ... @@ -0,0 +1,110 @@
  1 +# An example controller for "votes" that are nested resources under users. See examples/routes.rb
  2 +
  3 +class VotesController < ApplicationController
  4 +
  5 + # First, figure out our nested scope. User or Voteable?
  6 + before_filter :find_votes_for_my_scope, :only => [:index]
  7 +
  8 + before_filter :login_required, :only => [:new, :edit, :destroy, :create, :update]
  9 + before_filter :must_own_vote, :only => [:edit, :destroy, :update]
  10 + before_filter :not_allowed, :only => [:edit, :update, :new]
  11 +
  12 + # GET /users/:user_id/votes/
  13 + # GET /users/:user_id/votes.xml
  14 + # GET /users/:user_id/voteables/:voteable_id/votes/
  15 + # GET /users/:user_id/voteables/:voteable_id/votes.xml
  16 + def index
  17 + respond_to do |format|
  18 + format.html # index.html.erb
  19 + format.xml { render :xml => @votes }
  20 + end
  21 + end
  22 +
  23 + # GET /users/:user_id/votes/1
  24 + # GET /users/:user_id/votes/1.xml
  25 + # GET /users/:user_id/voteables/:voteable_id/votes/1
  26 + # GET /users/:user_id/voteables/:voteable_id/1.xml
  27 + def show
  28 + @voteable = Vote.find(params[:id])
  29 +
  30 + respond_to do |format|
  31 + format.html # show.html.erb
  32 + format.xml { render :xml => @vote }
  33 + end
  34 + end
  35 +
  36 + # GET /users/:id/votes/new
  37 + # GET /users/:id/votes/new.xml
  38 + # GET /users/:id/votes/new
  39 + # GET /users/:id/votes/new.xml
  40 + def new
  41 + # Not generally used. Most people want to vote via AJAX calls.
  42 + end
  43 +
  44 + # GET /users/:id/votes/1/edit
  45 + def edit
  46 + # Not generally used. Most people don't want to allow editing of votes.
  47 + end
  48 +
  49 + # POST /users/:user_id/voteables/:voteable_id/votes
  50 + # POST /users/:user_id/voteables/:voteable_id/votes.xml
  51 + def create
  52 + @voteable = Voteable.find(params[:quote_id])
  53 +
  54 + respond_to do |format|
  55 + if current_user.vote(@voteable, params[:vote])
  56 + format.rjs { render :action => "create", :vote => @vote }
  57 + format.html { redirect_to([@voteable.user, @voteable]) }
  58 + format.xml { render :xml => @voteable, :status => :created, :location => @voteable }
  59 + else
  60 + format.rjs { render :action => "error" }
  61 + format.html { render :action => "new" }
  62 + format.xml { render :xml => @vote.errors, :status => :unprocessable_entity }
  63 + end
  64 + end
  65 + end
  66 +
  67 + # PUT /users/:id/votes/1
  68 + # PUT /users/:id/votes/1.xml
  69 + def update
  70 + # Not generally used
  71 + end
  72 +
  73 + # DELETE /users/:id/votes/1
  74 + # DELETE /users/:id/votes/1.xml
  75 + def destroy
  76 + @vote = Vote.find(params[:id])
  77 + @vote.destroy
  78 +
  79 + respond_to do |format|
  80 + format.html { redirect_to(user_votes_url) }
  81 + format.xml { head :ok }
  82 + end
  83 + end
  84 +
  85 + private
  86 + def find_votes_for_my_scope
  87 + if params[:voteable_id]
  88 + @votes = Vote.for_voteable(Voteable.find(params[:voteable_id])).descending
  89 + elsif params[:user_id]
  90 + @votes = Vote.for_voter(User.find(params[:user_id])).descending
  91 + else
  92 + @votes = []
  93 + end
  94 + end
  95 +
  96 + def must_own_vote
  97 + @vote ||= Vote.find(params[:id])
  98 + @vote.user == current_user || ownership_violation
  99 + end
  100 +
  101 + def ownership_violation
  102 + respond_to do |format|
  103 + flash[:notice] = 'You cannot edit or delete votes that you do not own!'
  104 + format.html do
  105 + redirect_to user_path(current_user)
  106 + end
  107 + end
  108 + end
  109 +
  110 +end
... ...
vendor/plugins/vote_fu/generators/vote_fu/templates/migration.rb 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +class VoteFuMigration < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :votes, :force => true do |t|
  4 + t.integer :vote, :null => false
  5 + t.references :voteable, :polymorphic => true, :null => false
  6 + t.references :voter, :polymorphic => true
  7 + t.timestamps
  8 + end
  9 +
  10 + add_index :votes, ["voter_id", "voter_type"], :name => "fk_voters"
  11 + add_index :votes, ["voteable_id", "voteable_type"], :name => "fk_voteables"
  12 +
  13 + # If you want to enfore "One Person, One Vote" rules in the database, uncomment the index below
  14 + # add_index :votes, ["voter_id", "voter_type", "voteable_id", "voteable_type"], :unique => true, :name => "uniq_one_vote_only"
  15 + end
  16 +
  17 + def self.down
  18 + drop_table :votes
  19 + end
  20 +
  21 +end
... ...
vendor/plugins/vote_fu/generators/vote_fu/vote_fu_generator.rb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +class VoteFuGenerator < Rails::Generator::Base
  2 +
  3 + def manifest
  4 + record do |m|
  5 + m.migration_template 'migration.rb', 'db/migrate', :migration_file_name => 'vote_fu_migration'
  6 + end
  7 + end
  8 +end
... ...
vendor/plugins/vote_fu/init.rb 0 → 100644
... ... @@ -0,0 +1 @@
  1 +require 'vote_fu'
... ...
vendor/plugins/vote_fu/lib/acts_as_voteable.rb 0 → 100644
... ... @@ -0,0 +1,157 @@
  1 +# ActsAsVoteable
  2 +module Juixe
  3 + module Acts #:nodoc:
  4 + module Voteable #:nodoc:
  5 +
  6 + def self.included(base)
  7 + base.extend ClassMethods
  8 + end
  9 +
  10 + module ClassMethods
  11 + #
  12 + # Options:
  13 + # :vote_counter
  14 + # Model stores the sum of votes in the vote counter column when the value is true. This requires a column named `vote_total` in the table corresponding to `voteable` model.
  15 + # You can also specify a custom vote counter column by providing a column name instead of a true/false value to this option (e.g., :vote_counter => :my_custom_counter.)
  16 + # Note: Specifying a counter will add it to that model‘s list of readonly attributes using attr_readonly.
  17 + #
  18 + def acts_as_voteable options={}
  19 + has_many :votes, :as => :voteable, :dependent => :nullify
  20 + include Juixe::Acts::Voteable::InstanceMethods
  21 + extend Juixe::Acts::Voteable::SingletonMethods
  22 + if (options[:vote_counter])
  23 + Vote.send(:include, Juixe::Acts::Voteable::VoteCounterClassMethods) unless Vote.respond_to?(:vote_counters)
  24 + Vote.vote_counters = [self]
  25 + # define vote_counter_column instance method on voteable
  26 + counter_column_name = (options[:vote_counter] == true) ? :vote_total : options[:vote_counter]
  27 + class_eval <<-EOS
  28 + def self.vote_counter_column # def self.vote_counter_column
  29 + :"#{counter_column_name}" # :vote_total
  30 + end # end
  31 + def vote_counter_column
  32 + self.class.vote_counter_column
  33 + end
  34 + EOS
  35 +
  36 + define_method(:reload_vote_counter) {reload(:select => vote_counter_column.to_s)}
  37 + attr_readonly counter_column_name
  38 + end
  39 + end
  40 + end
  41 +
  42 + # This module contains class methods Vote class
  43 + module VoteCounterClassMethods
  44 + def self.included(base)
  45 + base.class_inheritable_array(:vote_counters)
  46 + base.after_create { |record| record.update_vote_counters(1) }
  47 + base.before_destroy { |record| record.update_vote_counters(-1) }
  48 + end
  49 +
  50 + def update_vote_counters direction
  51 + klass, vtbl = self.voteable.class, self.voteable
  52 + klass.update_counters(vtbl.id, vtbl.vote_counter_column.to_sym => (self.vote * direction) ) if self.vote_counters.any?{|c| c == klass}
  53 + end
  54 + end
  55 +
  56 + # This module contains class methods
  57 + module SingletonMethods
  58 +
  59 + # Calculate the vote counts for all voteables of my type.
  60 + # Options:
  61 + # :start_at - Restrict the votes to those created after a certain time
  62 + # :end_at - Restrict the votes to those created before a certain time
  63 + # :conditions - A piece of SQL conditions to add to the query
  64 + # :limit - The maximum number of voteables to return
  65 + # :order - A piece of SQL to order by. Two calculated columns `count`, and `total`
  66 + # are available for sorting apart from other columns. Defaults to `total DESC`.
  67 + # Eg: :order => 'count desc'
  68 + # :order => 'total desc'
  69 + # :order => 'post.created_at desc'
  70 + # :at_least - Item must have at least X votes count
  71 + # :at_most - Item may not have more than X votes count
  72 + # :at_least_total - Item must have at least X votes total
  73 + # :at_most_total - Item may not have more than X votes total
  74 + def tally(options = {})
  75 + find(:all, options_for_tally({:order =>"total DESC" }.merge(options)))
  76 + end
  77 +
  78 + def options_for_tally (options = {})
  79 + options.assert_valid_keys :start_at, :end_at, :conditions, :at_least, :at_most, :order, :limit, :at_least_total, :at_most_total
  80 +
  81 + scope = scope(:find)
  82 + start_at = sanitize_sql(["#{Vote.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
  83 + end_at = sanitize_sql(["#{Vote.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
  84 +
  85 + if respond_to?(:vote_counter_column)
  86 + # use the counter cache column if present.
  87 + total_col = "#{table_name}.#{vote_counter_column}"
  88 + at_least_total = sanitize_sql(["#{total_col} >= ?", options.delete(:at_least_total)]) if options[:at_least_total]
  89 + at_most_total = sanitize_sql(["#{total_col} <= ?", options.delete(:at_most_total)]) if options[:at_most_total]
  90 + end
  91 + conditions = [
  92 + options[:conditions],
  93 + at_least_total,
  94 + at_most_total,
  95 + start_at,
  96 + end_at
  97 + ]
  98 +
  99 + conditions = conditions.compact.join(' AND ')
  100 + conditions = merge_conditions(conditions, scope[:conditions]) if scope
  101 +
  102 + type_and_context = "#{Vote.table_name}.voteable_type = #{quote_value(base_class.name)}"
  103 + joins = ["LEFT OUTER JOIN #{Vote.table_name} ON #{table_name}.#{primary_key} = #{Vote.table_name}.voteable_id AND #{type_and_context}"]
  104 + joins << scope[:joins] if scope && scope[:joins]
  105 + at_least = sanitize_sql(["COUNT(#{Vote.table_name}.id) >= ?", options.delete(:at_least)]) if options[:at_least]
  106 + at_most = sanitize_sql(["COUNT(#{Vote.table_name}.id) <= ?", options.delete(:at_most)]) if options[:at_most]
  107 + at_least_total = at_most_total = nil # reset the values
  108 + unless respond_to?(:vote_counter_column)
  109 + # aggregate the votes when counter cache is absent.
  110 + total_col = "SUM(#{Vote.table_name}.vote)"
  111 + at_least_total = sanitize_sql(["#{total_col} >= ?", options.delete(:at_least_total)]) if options[:at_least_total]
  112 + at_most_total = sanitize_sql(["#{total_col} <= ?", options.delete(:at_most_total)]) if options[:at_most_total]
  113 + end
  114 + having = [at_least, at_most, at_least_total, at_most_total].compact.join(' AND ')
  115 + group_by = "#{Vote.table_name}.voteable_id HAVING COUNT(#{Vote.table_name}.id) > 0"
  116 + group_by << " AND #{having}" unless having.blank?
  117 +
  118 + { :select => "#{table_name}.*, COUNT(#{Vote.table_name}.id) AS count, #{total_col} AS total",
  119 + :joins => joins.join(" "),
  120 + :conditions => conditions,
  121 + :group => group_by
  122 + }.update(options)
  123 + end
  124 +
  125 + end
  126 +
  127 + # This module contains instance methods
  128 + module InstanceMethods
  129 + def votes_for
  130 + self.votes.count(:conditions => {:vote => 1})
  131 + end
  132 +
  133 + def votes_against
  134 + self.votes.count(:conditions => {:vote => -1})
  135 + end
  136 +
  137 + # Same as voteable.votes.size
  138 + def votes_count
  139 + self.votes.size
  140 + end
  141 +
  142 + def votes_total
  143 + respond_to?(:vote_counter_column) ? send(self.vote_counter_column) : self.votes.sum(:vote)
  144 + end
  145 +
  146 + def voters_who_voted
  147 + self.votes.collect(&:voter)
  148 + end
  149 +
  150 + def voted_by?(voter, for_or_against = "all")
  151 + options = (for_or_against == "all") ? {} : {:vote => (for_or_against ? 1 : -1)}
  152 + self.votes.exists?({:voter_id => voter.id, :voter_type => voter.class.name}.merge(options))
  153 + end
  154 + end
  155 + end
  156 + end
  157 +end
0 158 \ No newline at end of file
... ...
vendor/plugins/vote_fu/lib/acts_as_voter.rb 0 → 100644
... ... @@ -0,0 +1,65 @@
  1 +# ActsAsVoter
  2 +module PeteOnRails
  3 + module Acts #:nodoc:
  4 + module Voter #:nodoc:
  5 +
  6 + def self.included(base)
  7 + base.extend ClassMethods
  8 + end
  9 +
  10 + module ClassMethods
  11 + def acts_as_voter
  12 + has_many :votes, :as => :voter, :dependent => :nullify # If a voting entity is deleted, keep the votes.
  13 + include PeteOnRails::Acts::Voter::InstanceMethods
  14 + extend PeteOnRails::Acts::Voter::SingletonMethods
  15 + end
  16 + end
  17 +
  18 + # This module contains class methods
  19 + module SingletonMethods
  20 + end
  21 +
  22 + # This module contains instance methods
  23 + module InstanceMethods
  24 +
  25 + # Usage user.vote_count(true) # All +1 votes
  26 + # user.vote_count(false) # All -1 votes
  27 + # user.vote_count() # All votes
  28 + #
  29 + def vote_count(for_or_against = "all")
  30 + return self.votes.size if for_or_against == "all"
  31 + self.votes.count(:conditions => {:vote => (for_or_against ? 1 : -1)})
  32 + end
  33 +
  34 + def voted_for?(voteable)
  35 + voteable.voted_by?(self, true)
  36 + end
  37 +
  38 + def voted_against?(voteable)
  39 + voteable.voted_by?(self, false)
  40 + end
  41 +
  42 + def voted_on?(voteable)
  43 + voteable.voted_by?(self)
  44 + end
  45 +
  46 + def vote_for(voteable)
  47 + self.vote(voteable, 1)
  48 + end
  49 +
  50 + def vote_against(voteable)
  51 + self.vote(voteable, -1)
  52 + end
  53 +
  54 + def vote(voteable, vote)
  55 + Vote.create(:vote => vote, :voteable => voteable, :voter => self).tap do |v|
  56 + voteable.reload_vote_counter if !v.new_record? and voteable.respond_to?(:reload_vote_counter)
  57 + end.errors.empty?
  58 + end
  59 +
  60 + end
  61 +
  62 + end
  63 +
  64 + end
  65 +end
0 66 \ No newline at end of file
... ...
vendor/plugins/vote_fu/lib/controllers/votes_controller.rb 0 → 100644
... ... @@ -0,0 +1,96 @@
  1 +class VotesController < ApplicationController
  2 + # First, figure out our nested scope. User or vote?
  3 +# before_filter :find_my_scope
  4 +
  5 +# before_filter :find_user
  6 +
  7 +# before_filter :login_required, :only => [:new, :edit, :destroy, :create, :update]
  8 +# before_filter :must_own_vote, :only => [:edit, :destroy, :update]
  9 +
  10 +
  11 + # GET /votes/
  12 + # GET /votes.xml
  13 + def index
  14 + @votes = Vote.descending
  15 +
  16 + respond_to do |format|
  17 + format.html # index.html.erb
  18 + format.xml { render :xml => @votes }
  19 + end
  20 + end
  21 +
  22 + # GET /votes/1
  23 + # GET /votes/1.xml
  24 + def show
  25 + @vote = Vote.find(params[:id])
  26 +
  27 + respond_to do |format|
  28 + format.html # show.html.erb
  29 + format.xml { render :xml => @vote }
  30 + end
  31 + end
  32 +
  33 + # GET /votes/new
  34 + # GET /votes/new.xml
  35 + def new
  36 + @vote = Vote.new
  37 +
  38 + respond_to do |format|
  39 + format.html # new.html.erb
  40 + format.xml { render :xml => @vote }
  41 + end
  42 + end
  43 +
  44 + # GET /votes/1/edit
  45 + def edit
  46 + @vote ||= Vote.find(params[:id])
  47 + end
  48 +
  49 + # POST /votes
  50 + # POST /votes.xml
  51 + def create
  52 + @vote = Vote.new(params[:vote])
  53 + @vote.user = current_user
  54 +
  55 + respond_to do |format|
  56 + if @vote.save
  57 + flash[:notice] = 'Vote was successfully saved.'
  58 + format.html { redirect_to([@user, @vote]) }
  59 + format.xml { render :xml => @vote, :status => :created, :location => @vote }
  60 + else
  61 + format.html { render :action => "new" }
  62 + format.xml { render :xml => @vote.errors, :status => :unprocessable_entity }
  63 + end
  64 + end
  65 + end
  66 +
  67 + # PUT /votes/1
  68 + # PUT /votes/1.xml
  69 + def update
  70 + @vote = Vote.find(params[:id])
  71 +
  72 + respond_to do |format|
  73 + if @vote.update_attributes(params[:vote])
  74 + flash[:notice] = 'Vote was successfully updated.'
  75 + format.html { redirect_to([@user, @vote]) }
  76 + format.xml { head :ok }
  77 + else
  78 + format.html { render :action => "edit" }
  79 + format.xml { render :xml => @vote.errors, :status => :unprocessable_entity }
  80 + end
  81 + end
  82 + end
  83 +
  84 + # DELETE /votes/1
  85 + # DELETE /votes/1.xml
  86 + def destroy
  87 + @vote = Vote.find(params[:id])
  88 + @vote.destroy
  89 +
  90 + respond_to do |format|
  91 + format.html { redirect_to(user_votes_url) }
  92 + format.xml { head :ok }
  93 + end
  94 + end
  95 +
  96 +end
... ...
vendor/plugins/vote_fu/lib/has_karma.rb 0 → 100644
... ... @@ -0,0 +1,68 @@
  1 +# Has Karma
  2 +
  3 +module PeteOnRails
  4 + module VoteFu #:nodoc:
  5 + module Karma #:nodoc:
  6 +
  7 + def self.included(base)
  8 + base.extend ClassMethods
  9 + class << base
  10 + attr_accessor :karmatic_objects
  11 + end
  12 + end
  13 +
  14 + module ClassMethods
  15 + def has_karma(voteable_type)
  16 + self.class_eval <<-RUBY
  17 + def karma_voteable
  18 + #{voteable_type.to_s.classify}
  19 + end
  20 + RUBY
  21 + include PeteOnRails::VoteFu::Karma::InstanceMethods
  22 + extend PeteOnRails::VoteFu::Karma::SingletonMethods
  23 + if self.karmatic_objects.nil?
  24 + self.karmatic_objects = [eval(voteable_type.to_s.classify)]
  25 + else
  26 + self.karmatic_objects.push(eval(voteable_type.to_s.classify))
  27 + end
  28 + end
  29 + end
  30 +
  31 + # This module contains class methods
  32 + module SingletonMethods
  33 +
  34 + ## Not yet implemented. Don't use it!
  35 + # Find the most popular users
  36 + def find_most_karmic
  37 + find(:all)
  38 + end
  39 +
  40 + end
  41 +
  42 + # This module contains instance methods
  43 + module InstanceMethods
  44 + def karma(options = {})
  45 + #FIXME cannot have 2 models imapcting the karma simultaneously
  46 + # count the total number of votes on all of the voteable objects that are related to this object
  47 + #2009-01-30 GuillaumeNM The following line is not SQLite3 compatible, because boolean are stored as 'f' or 't', not '1', or '0'
  48 + #self.karma_voteable.sum(:vote, options_for_karma(options))
  49 + #self.karma_voteable.find(:all, options_for_karma(options)).length
  50 + karma_value = 0
  51 + self.class.karmatic_objects.each do |object|
  52 + karma_value += object.find(:all, options_for_karma(object, options)).length
  53 + end
  54 + return karma_value
  55 + end
  56 +
  57 + def options_for_karma (object, options = {})
  58 + #GuillaumeNM : 2009-01-30 Adding condition for SQLite3
  59 + conditions = ["u.id = ? AND vote = ?" , self[:id] , true]
  60 + 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}"]
  61 + { :joins => joins.join(" "), :conditions => conditions }.update(options)
  62 + end
  63 +
  64 + end
  65 +
  66 + end
  67 + end
  68 +end
... ...
vendor/plugins/vote_fu/lib/models/vote.rb 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +class Vote < ActiveRecord::Base
  2 +
  3 + named_scope :for_voter, lambda { |*args| {:conditions => ["voter_id = ? AND voter_type = ?", args.first.id, args.first.type.name]} }
  4 + named_scope :for_voteable, lambda { |*args| {:conditions => ["voteable_id = ? AND voteable_type = ?", args.first.id, args.first.type.name]} }
  5 + named_scope :recent, lambda { |*args| {:conditions => ["created_at > ?", (args.first || 2.weeks.ago).to_s(:db)]} }
  6 + named_scope :descending, :order => "created_at DESC"
  7 +
  8 + # NOTE: Votes belong to the "voteable" interface, and also to voters
  9 + belongs_to :voteable, :polymorphic => true
  10 + belongs_to :voter, :polymorphic => true
  11 +
  12 + attr_accessible :vote, :voter, :voteable
  13 +
  14 + # Uncomment this to limit users to a single vote on each item.
  15 + #validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id]
  16 +
  17 +end
... ...
vendor/plugins/vote_fu/lib/vote_fu.rb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +require 'acts_as_voteable'
  2 +require 'acts_as_voter'
  3 +require 'has_karma'
  4 +require 'models/vote.rb'
  5 +
  6 +ActiveRecord::Base.send(:include, Juixe::Acts::Voteable)
  7 +ActiveRecord::Base.send(:include, PeteOnRails::Acts::Voter)
  8 +ActiveRecord::Base.send(:include, PeteOnRails::VoteFu::Karma)
  9 +RAILS_DEFAULT_LOGGER.info "** vote_fu: initialized properly."
... ...
vendor/plugins/vote_fu/rails/init.rb 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +RAILS_DEFAULT_LOGGER.info "** vote_fu: setting up load paths"
  2 +
  3 +%w{ models controllers helpers }.each do |dir|
  4 + path = File.join(File.dirname(__FILE__) , 'lib', dir)
  5 + $LOAD_PATH << path
  6 + ActiveSupport::Dependencies.load_paths << path
  7 + ActiveSupport::Dependencies.load_once_paths.delete(path)
  8 +end
  9 +
  10 +require 'vote_fu'
0 11 \ No newline at end of file
... ...
vendor/plugins/vote_fu/test/vote_fu_test.rb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +require 'test/unit'
  2 +
  3 +class VoteFuTest < Test::Unit::TestCase
  4 + # Replace this with your real tests.
  5 + def test_this_plugin
  6 + flunk
  7 + end
  8 +end
... ...
vendor/plugins/vote_fu/vote_fu.gemspec 0 → 100644
... ... @@ -0,0 +1,36 @@
  1 +Gem::Specification.new do |s|
  2 + s.name = "kandadaboggu-vote_fu"
  3 + s.version = "0.0.15"
  4 + s.date = "2010-03-08"
  5 + s.summary = "Enhanced vote_fu with numerical voting and total vote caching."
  6 + s.email = "kandadaboggu@gmail.com"
  7 + s.homepage = "http://github.com/kandadaboggu/vote_fu"
  8 + s.description = "Enhanced vote_fu with numerical voting and total vote caching."
  9 + s.has_rdoc = false
  10 + s.authors = ["Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Kandada Boggu"]
  11 + s.files = [ "CHANGELOG.markdown",
  12 + "MIT-LICENSE",
  13 + "README.markdown",
  14 + "generators/vote_fu",
  15 + "generators/vote_fu/vote_fu_generator.rb",
  16 + "generators/vote_fu/templates",
  17 + "generators/vote_fu/templates/migration.rb",
  18 + "init.rb",
  19 + "lib/vote_fu.rb",
  20 + "lib/acts_as_voteable.rb",
  21 + "lib/acts_as_voter.rb",
  22 + "lib/has_karma.rb",
  23 + "lib/models/vote.rb",
  24 + "lib/controllers/votes_controller.rb",
  25 + "test/vote_fu_test.rb",
  26 + "examples/votes_controller.rb",
  27 + "examples/users_controller.rb",
  28 + "examples/voteables_controller.rb",
  29 + "examples/voteable.rb",
  30 + "examples/voteable.html.erb",
  31 + "examples/votes/_voteable_vote.html.erb",
  32 + "examples/votes/create.rjs",
  33 + "examples/routes.rb",
  34 + "rails/init.rb"
  35 + ]
  36 +end
... ...