objectreload-vote_fu 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ *.gemspec
2
+ pkg/
@@ -0,0 +1,35 @@
1
+ 2010-02-04
2
+ ==========
3
+ * Remove vote.rb and votes_controller.rb from gem lib
4
+
5
+ 2009-02-11
6
+ ==========
7
+ * Merge in xlash's bugfix for PostgreSQL and his has\_karma patch for multi-model support.
8
+
9
+ 2008-12-02
10
+ ==========
11
+ * Merge in maddox's README typo fix and his ActiveSupport.Dependency patch
12
+ * Merge in nagybence's updates that make the code usable as a Gem in addition to being a Rails plugin.
13
+ * Thanks for the bugfixes and proofreading, nagybence and maddox!
14
+ * Updated the gemplugin support to be compatible with maddox and nagybence's changes.
15
+ * Added details on the MyQuotable reference application.
16
+
17
+ 2008-07-20
18
+ ==========
19
+ * Protect against mass assignment misvotes using attr\_accessible
20
+ * Update acts\_as mixins to use self.class.name instead of the deprecated self.type.name
21
+
22
+ 2008-07-15
23
+ ==========
24
+ * Added examples directory
25
+ * Changed this file to markdown format for GitHub goodness
26
+ * Added a commented out unique index in the migration generator for "one person, one vote"
27
+ * Removed votes\_controller.rb from lib/ and moved to examples
28
+
29
+ 2008-07-10
30
+ ==========
31
+
32
+ * Added a generator class for the migration.
33
+ * Implemented rails/init.rb
34
+ * Implemented capability to use any model as the initiator of votes.
35
+ * Implemented acts\_as\_voter methods.
data/MIT-LICENSE ADDED
@@ -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.
data/README.markdown ADDED
@@ -0,0 +1,224 @@
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
+ ### Diference between original vote_fu and our gem
24
+
25
+ Generator creates model vote.rb in you application instead of keeping it in the gem lib.
26
+ We had bad experience with extending this class in application - unexpected things where having place.
27
+
28
+ Installation
29
+ ============
30
+ 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.
31
+
32
+ ### Via plugin
33
+ ./script/plugin install git://github.com/objectreload/vote_fu.git
34
+
35
+ ### Via gem
36
+ Add the following to your application's environment.rb:
37
+ config.gem "objectreload-vote_fu", :lib => 'vote_fu'
38
+
39
+ Install the gem:
40
+ rake gems:install
41
+
42
+ ### Create vote_fu migration and vote.rb model
43
+ ./script/generate vote_fu
44
+
45
+ Run the migration:
46
+ rake db:migrate
47
+
48
+ Usage
49
+ =====
50
+
51
+ ## Getting Started
52
+
53
+ ### Make your ActiveRecord model act as voteable.
54
+
55
+
56
+ class Model < ActiveRecord::Base
57
+ acts_as_voteable
58
+ end
59
+
60
+
61
+ ### Make your ActiveRecord model(s) that vote act as voter.
62
+
63
+ class User < ActiveRecord::Base
64
+ acts_as_voter
65
+ end
66
+
67
+ class Robot < ActiveRecord::Base
68
+ acts_as_voter
69
+ end
70
+
71
+ ### To cast a vote for a Model you can do the following:
72
+
73
+ #### Shorthand syntax
74
+ voter.vote_for(voteable) # Adds a +1 vote
75
+ voter.vote_against(voteable) # Adds a -1 vote
76
+ voter.vote(voteable, t_or_f) # Adds either +1 or -1 vote true => +1, false => -1
77
+
78
+ #### ActsAsVoteable syntax
79
+ The old acts\_as\_voteable syntax is still supported:
80
+
81
+ vote = Vote.new(:vote => true)
82
+ m = Model.find(params[:id])
83
+ m.votes << vote
84
+ user.votes << vote
85
+
86
+ ### Querying votes
87
+
88
+ #### Tallying Votes
89
+
90
+ You can easily retrieve voteable object collections based on the properties of their votes:
91
+
92
+ @items = Item.tally(
93
+ { :at_least => 1,
94
+ :at_most => 10000,
95
+ :start_at => 2.weeks.ago,
96
+ :end_at => 1.day.ago,
97
+ :limit => 10,
98
+ :order => "items.name desc"
99
+ })
100
+
101
+ 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.
102
+
103
+ ##### Tally Options:
104
+ :start_at - Restrict the votes to those created after a certain time
105
+ :end_at - Restrict the votes to those created before a certain time
106
+ :conditions - A piece of SQL conditions to add to the query
107
+ :limit - The maximum number of voteables to return
108
+ :order - A piece of SQL to order by. Eg 'votes.count desc' or 'voteable.created_at desc'
109
+ :at_least - Item must have at least X votes
110
+ :at_most - Item may not have more than X votes
111
+
112
+ #### Lower level queries
113
+ 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:
114
+
115
+ positiveVoteCount = m.votes_for
116
+ negativeVoteCount = m.votes_against
117
+ totalVoteCount = m.votes_count
118
+
119
+ 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:
120
+
121
+ allVotes = m.votes
122
+
123
+ The mixin also provides these methods:
124
+
125
+ voter.voted_for?(voteable) # True if the voter voted for this object.
126
+ voter.vote_count([true|false|"all"]) # returns the count of +1, -1, or all votes
127
+
128
+ voteable.voted_by?(voter) # True if the voter voted for this object.
129
+ @voters = voteable.voters_who_voted
130
+
131
+
132
+ #### Named Scopes
133
+
134
+ The Vote model has several named scopes you can use to find vote details:
135
+
136
+ @pete_votes = Vote.for_voter(pete)
137
+ @post_votes = Vote.for_voteable(post)
138
+ @recent_votes = Vote.recent(1.day.ago)
139
+ @descending_votes = Vote.descending
140
+
141
+ You can chain these together to make interesting queries:
142
+
143
+ # Show all of Pete's recent votes for a certain Post, in descending order (newest first)
144
+ @pete_recent_votes_on_post = Vote.for_voter(pete).for_voteable(post).recent(7.days.ago).descending
145
+
146
+ ### Experimental: Voteable Object Owner Karma
147
+ 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.
148
+
149
+ class User
150
+ has_many :posts
151
+ has_karma :posts
152
+ end
153
+
154
+ class Post
155
+ acts_as_voteable
156
+ end
157
+
158
+ # in your view, you can then do this:
159
+ Karma: <%= @user.karma %>
160
+
161
+ This feature is in alpha, but useful enough that I'm releasing it.
162
+
163
+ ### One vote per user!
164
+ If you want to limit your users to a single vote on each item, take a look in lib/vote.rb.
165
+
166
+ # Uncomment this to limit users to a single vote on each item.
167
+ # validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id]
168
+
169
+ And if you want that enforced at the database level, look in the generated migration for your voteable:
170
+
171
+ # If you want to enfore "One Person, One Vote" rules in the database, uncomment the index below
172
+ # add_index :votes, ["voter_id", "voter_type", "voteable_id", "voteable_type"], :unique => true, :name => "uniq_one_vote_only"
173
+
174
+ ### Example Application
175
+
176
+ There is now a reference application available. Due to overwhelming demand for example
177
+ code and kickstart guides, I have open-sourced MyQuotable.com in order to provide an
178
+ easy-to-follow example of how to use VoteFu with RESTful Authentication, JRails, and
179
+ other popular plugins. To get the example code:
180
+
181
+ git clone git://github.com/peteonrails/myquotable.git
182
+
183
+ There will be a screencast coming soon too. Contact me if you want to help.
184
+
185
+ Consideration
186
+ =============
187
+ If you like this software and use it, please consider recommending me on Working With Rails.
188
+
189
+ 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]
190
+
191
+ To go directly to the "Recommend Me" screen: [http://www.workingwithrails.com/recommendation/new/person/12521-peter-jackson][5]
192
+
193
+
194
+ Credits
195
+ =======
196
+
197
+ #### Contributors
198
+
199
+ * Bence Nagy, Budapest, Hungary
200
+ * Jon Maddox, Richmond, Virginia, USA
201
+
202
+ #### Other works
203
+
204
+ [Juixe - The original ActsAsVoteable plugin inspired this code.][1]
205
+
206
+ [Xelipe - This plugin is heavily influenced by Acts As Commentable.][2]
207
+
208
+ [1]: http://www.juixe.com/techknow/index.php/2006/06/24/acts-as-voteable-rails-plugin/
209
+ [2]: http://github.com/jackdempsey/acts_as_commentable/tree/master
210
+
211
+ More
212
+ ====
213
+
214
+ Support: [Use my blog for support.][6]
215
+
216
+
217
+ [Documentation from the original acts\_as\_voteable plugin][3]
218
+
219
+ [3]: http://www.juixe.com/techknow/index.php/2006/06/24/acts-as-voteable-rails-plugin/
220
+ [4]: http://www.workingwithrails.com/person/12521-peter-jackson
221
+ [5]: http://www.workingwithrails.com/recommendation/new/person/12521-peter-jackson
222
+ [6]: http://blog.peteonrails.com
223
+
224
+ Copyright (c) 2008 Peter Jackson, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,57 @@
1
+ # encoding: UTF-8
2
+ require 'rubygems'
3
+ require 'rake'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "objectreload-vote_fu"
9
+ gem.summary = "Voting for ActiveRecord with multiple vote sources and advanced features."
10
+ gem.description = "VoteFu provides the ability to have multiple voting entities on an arbitrary number of models in ActiveRecord."
11
+ gem.email = "gems@objectreload.com"
12
+ gem.homepage = "http://github.com/objectreload/vote_fu"
13
+ gem.authors = ["Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnętrzak"]
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
19
+ end
20
+
21
+ require 'rake/testtask'
22
+ Rake::TestTask.new(:test) do |test|
23
+ test.libs << 'lib' << 'test'
24
+ test.pattern = 'test/**/*_test.rb'
25
+ test.verbose = true
26
+ end
27
+
28
+ begin
29
+ require 'rcov/rcovtask'
30
+ Rcov::RcovTask.new do |test|
31
+ test.libs << 'test'
32
+ test.pattern = 'test/**/*_test.rb'
33
+ test.verbose = true
34
+ end
35
+ rescue LoadError
36
+ task :rcov do
37
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
38
+ end
39
+ end
40
+
41
+ task :test => :check_dependencies
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ if File.exist?('VERSION')
48
+ version = File.read('VERSION')
49
+ else
50
+ version = ""
51
+ end
52
+
53
+ rdoc.rdoc_dir = 'rdoc'
54
+ rdoc.title = "permissions_gem #{version}"
55
+ rdoc.rdoc_files.include('README*')
56
+ rdoc.rdoc_files.include('lib/**/*.rb')
57
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -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,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
@@ -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>
@@ -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,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
@@ -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 %>
@@ -0,0 +1 @@
1
+ page.replace_html "votes_#{@voteable.id}", :partial => "voteable_vote", :locals => {:voteable => @voteable}
@@ -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
@@ -0,0 +1,21 @@
1
+ class VoteFuMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :votes, :force => true do |t|
4
+ t.boolean :vote, :default => 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
@@ -0,0 +1,16 @@
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
+ end
@@ -0,0 +1,11 @@
1
+ class VoteFuGenerator < Rails::Generator::Base
2
+
3
+ def manifest
4
+ record do |m|
5
+ m.directory File.join('db', 'migrate')
6
+ m.migration_template 'migration.rb', 'db/migrate', :migration_file_name => 'vote_fu_migration'
7
+ m.directory File.join('app', 'models')
8
+ m.template 'vote.rb', File.join('app', 'models', 'vote.rb')
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,114 @@
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
+ def acts_as_voteable
12
+ has_many :votes, :as => :voteable, :dependent => :nullify
13
+
14
+ include Juixe::Acts::Voteable::InstanceMethods
15
+ extend Juixe::Acts::Voteable::SingletonMethods
16
+ end
17
+ end
18
+
19
+ # This module contains class methods
20
+ module SingletonMethods
21
+
22
+ # Calculate the vote counts for all voteables of my type.
23
+ def tally(options = {})
24
+ find(:all, options_for_tally(options.merge({:order =>"count DESC" })))
25
+ end
26
+
27
+ #
28
+ # Options:
29
+ # :start_at - Restrict the votes to those created after a certain time
30
+ # :end_at - Restrict the votes to those created before a certain time
31
+ # :conditions - A piece of SQL conditions to add to the query
32
+ # :limit - The maximum number of voteables to return
33
+ # :order - A piece of SQL to order by. Eg 'votes.count desc' or 'voteable.created_at desc'
34
+ # :at_least - Item must have at least X votes
35
+ # :at_most - Item may not have more than X votes
36
+ def options_for_tally (options = {})
37
+ options.assert_valid_keys :start_at, :end_at, :conditions, :at_least, :at_most, :order, :limit
38
+
39
+ scope = scope(:find)
40
+ start_at = sanitize_sql(["#{Vote.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at]
41
+ end_at = sanitize_sql(["#{Vote.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at]
42
+
43
+ type_and_context = "#{Vote.table_name}.voteable_type = #{quote_value(base_class.name)}"
44
+
45
+ conditions = [
46
+ type_and_context,
47
+ options[:conditions],
48
+ start_at,
49
+ end_at
50
+ ]
51
+
52
+ conditions = conditions.compact.join(' AND ')
53
+ conditions = merge_conditions(conditions, scope[:conditions]) if scope
54
+
55
+ joins = ["LEFT OUTER JOIN #{Vote.table_name} ON #{table_name}.#{primary_key} = #{Vote.table_name}.voteable_id"]
56
+ joins << scope[:joins] if scope && scope[:joins]
57
+ at_least = sanitize_sql(["COUNT(#{Vote.table_name}.id) >= ?", options.delete(:at_least)]) if options[:at_least]
58
+ at_most = sanitize_sql(["COUNT(#{Vote.table_name}.id) <= ?", options.delete(:at_most)]) if options[:at_most]
59
+ having = [at_least, at_most].compact.join(' AND ')
60
+ group_by = "#{Vote.table_name}.voteable_id HAVING COUNT(#{Vote.table_name}.id) > 0"
61
+ group_by << " AND #{having}" unless having.blank?
62
+
63
+ { :select => "#{table_name}.*, COUNT(#{Vote.table_name}.id) AS count",
64
+ :joins => joins.join(" "),
65
+ :conditions => conditions,
66
+ :group => group_by
67
+ }.update(options)
68
+ end
69
+ end
70
+
71
+ # This module contains instance methods
72
+ module InstanceMethods
73
+ def votes_for
74
+ Vote.count(:all, :conditions => [
75
+ "voteable_id = ? AND voteable_type = ? AND vote = ?",
76
+ id, self.class.name, true
77
+ ])
78
+ end
79
+
80
+ def votes_against
81
+ Vote.count(:all, :conditions => [
82
+ "voteable_id = ? AND voteable_type = ? AND vote = ?",
83
+ id, self.class.name, false
84
+ ])
85
+ end
86
+
87
+ # Same as voteable.votes.size
88
+ def votes_count
89
+ self.votes.size
90
+ end
91
+
92
+ def voters_who_voted
93
+ voters = []
94
+ self.votes.each { |v|
95
+ voters << v.voter
96
+ }
97
+ voters
98
+ end
99
+
100
+ def voted_by?(voter)
101
+ rtn = false
102
+ if voter
103
+ self.votes.each { |v|
104
+ rtn = true if (voter.id == v.voter_id && voter.class.name == v.voter_type)
105
+ }
106
+ end
107
+ rtn
108
+ end
109
+
110
+
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,75 @@
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
+ where = (for_or_against == "all") ?
31
+ ["voter_id = ? AND voter_type = ?", id, self.class.name ] :
32
+ ["voter_id = ? AND voter_type = ? AND vote = ?", id, self.class.name, for_or_against ]
33
+
34
+ Vote.count(:all, :conditions => where)
35
+
36
+ end
37
+
38
+ def voted_for?(voteable)
39
+ 0 < Vote.count(:all, :conditions => [
40
+ "voter_id = ? AND voter_type = ? AND vote = ? AND voteable_id = ? AND voteable_type = ?",
41
+ self.id, self.class.name, true, voteable.id, voteable.class.name
42
+ ])
43
+ end
44
+
45
+ def voted_against?(voteable)
46
+ 0 < Vote.count(:all, :conditions => [
47
+ "voter_id = ? AND voter_type = ? AND vote = ? AND voteable_id = ? AND voteable_type = ?",
48
+ self.id, self.class.name, false, voteable.id, voteable.class.name
49
+ ])
50
+ end
51
+
52
+ def voted_on?(voteable)
53
+ 0 < Vote.count(:all, :conditions => [
54
+ "voter_id = ? AND voter_type = ? AND voteable_id = ? AND voteable_type = ?",
55
+ self.id, self.class.name, voteable.id, voteable.class.name
56
+ ])
57
+ end
58
+
59
+ def vote_for(voteable)
60
+ self.vote(voteable, true)
61
+ end
62
+
63
+ def vote_against(voteable)
64
+ self.vote(voteable, false)
65
+ end
66
+
67
+ def vote(voteable, vote)
68
+ vote = Vote.new(:vote => vote, :voteable => voteable, :voter => self)
69
+ vote.save
70
+ end
71
+
72
+ end
73
+ end
74
+ end
75
+ end
data/lib/has_karma.rb ADDED
@@ -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
data/lib/vote_fu.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'acts_as_voteable'
2
+ require 'acts_as_voter'
3
+ require 'has_karma'
4
+
5
+ ActiveRecord::Base.send(:include, Juixe::Acts::Voteable)
6
+ ActiveRecord::Base.send(:include, PeteOnRails::Acts::Voter)
7
+ ActiveRecord::Base.send(:include, PeteOnRails::VoteFu::Karma)
8
+ RAILS_DEFAULT_LOGGER.info "** vote_fu: initialized properly."
data/rails/init.rb ADDED
@@ -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,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
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: objectreload-vote_fu
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Peter Jackson
8
+ - Cosmin Radoi
9
+ - Bence Nagy
10
+ - Rob Maddox
11
+ - "Wojciech Wn\xC4\x99trzak"
12
+ autorequire:
13
+ bindir: bin
14
+ cert_chain: []
15
+
16
+ date: 2010-02-04 00:00:00 +01:00
17
+ default_executable:
18
+ dependencies: []
19
+
20
+ description: VoteFu provides the ability to have multiple voting entities on an arbitrary number of models in ActiveRecord.
21
+ email: gems@objectreload.com
22
+ executables: []
23
+
24
+ extensions: []
25
+
26
+ extra_rdoc_files:
27
+ - README.markdown
28
+ files:
29
+ - .gitignore
30
+ - CHANGELOG.markdown
31
+ - MIT-LICENSE
32
+ - README.markdown
33
+ - Rakefile
34
+ - VERSION
35
+ - examples/routes.rb
36
+ - examples/users_controller.rb
37
+ - examples/voteable.html.erb
38
+ - examples/voteable.rb
39
+ - examples/voteables_controller.rb
40
+ - examples/votes/_voteable_vote.html.erb
41
+ - examples/votes/create.rjs
42
+ - examples/votes_controller.rb
43
+ - generators/vote_fu/templates/migration.rb
44
+ - generators/vote_fu/templates/vote.rb
45
+ - generators/vote_fu/vote_fu_generator.rb
46
+ - lib/acts_as_voteable.rb
47
+ - lib/acts_as_voter.rb
48
+ - lib/has_karma.rb
49
+ - lib/vote_fu.rb
50
+ - rails/init.rb
51
+ - test/vote_fu_test.rb
52
+ has_rdoc: true
53
+ homepage: http://github.com/objectreload/vote_fu
54
+ licenses: []
55
+
56
+ post_install_message:
57
+ rdoc_options:
58
+ - --charset=UTF-8
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ requirements: []
74
+
75
+ rubyforge_project:
76
+ rubygems_version: 1.3.5
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: Voting for ActiveRecord with multiple vote sources and advanced features.
80
+ test_files:
81
+ - test/vote_fu_test.rb
82
+ - examples/voteable.rb
83
+ - examples/users_controller.rb
84
+ - examples/votes_controller.rb
85
+ - examples/routes.rb
86
+ - examples/voteables_controller.rb