formal-vote 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/formal-vote.gemspec +23 -0
- data/lib/acts_as_votable.rb +135 -0
- data/lib/acts_as_voter.rb +137 -0
- data/lib/formal-vote/version.rb +3 -0
- data/lib/formal_vote.rb +34 -0
- data/lib/generators/formal_vote/formal_vote_generator.rb +29 -0
- data/lib/generators/formal_vote/templates/migration.rb +25 -0
- data/lib/generators/formal_vote/templates/vote.rb +17 -0
- data/lib/test/formal_vote_test.rb +0 -0
- data/lib/test/test_helper.rb +0 -0
- metadata +88 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 61ef60f50f00c23f217782d03bd3c03938dc0fe6
|
4
|
+
data.tar.gz: 2f1570afc9716aaf85546963b025a2332708caf0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3e9b3719432da9d66f6a2e464e46db2dcea655b80ad4c9bf96472e5df9268293107eb3ffe910efe8f50de9019513575238ef1b663e7842887bfab4a7d36af5bd
|
7
|
+
data.tar.gz: ff47bda3fa8ce8d1770263ab6133baf5d24104429e0e944aecb9544d228c26764a10e76174eb494c87428fbccf69d13f24330d9ea10a52eed3bd7d6c7a1363fd
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Ben Sharman
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# FormalVote
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'formal-vote'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install formal-vote
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/formal-vote.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'formal-vote/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "formal-vote"
|
8
|
+
spec.version = FormalVote::VERSION
|
9
|
+
spec.authors = ["Ben Sharman"]
|
10
|
+
spec.email = ["benwebdev@gmail.com"]
|
11
|
+
spec.description = "A voting library providing methods for a user to vote for, against, or abstain on a model"
|
12
|
+
spec.summary = "Formal voting library"
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module FormalVote
|
2
|
+
module ActsAsVoteable #:nodoc:
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend FormalVote::Base
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def acts_as_voteable
|
11
|
+
has_many FormalVote.configuration[:voteable_relationship_name],
|
12
|
+
:as => :voteable,
|
13
|
+
:dependent => :destroy,
|
14
|
+
:class_name => 'Vote'
|
15
|
+
|
16
|
+
include FormalVote::ActsAsVoteable::InstanceMethods
|
17
|
+
extend FormalVote::ActsAsVoteable::SingletonMethods
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module SingletonMethods
|
22
|
+
|
23
|
+
# Calculate the plusminus for a group of voteables in one database query.
|
24
|
+
# This returns an Arel relation, so you can add conditions as you like chained on to
|
25
|
+
# this method call.
|
26
|
+
# i.e. Posts.tally.where('votes.created_at > ?', 2.days.ago)
|
27
|
+
# You can also have the upvotes and downvotes returned separately in the same query:
|
28
|
+
# Post.plusminus_tally(:separate_updown => true)
|
29
|
+
def plusminus_tally(params = {})
|
30
|
+
t = self.joins("LEFT OUTER JOIN #{Vote.table_name} ON #{self.table_name}.id = #{Vote.table_name}.voteable_id AND #{Vote.table_name}.voteable_type = '#{self.name}'")
|
31
|
+
t = t.order("plusminus_tally DESC")
|
32
|
+
t = t.group(column_names_for_tally)
|
33
|
+
t = t.select("#{self.table_name}.*")
|
34
|
+
t = t.select("SUM(CASE #{Vote.table_name}.vote WHEN #{quoted_true} THEN 1 WHEN #{quoted_false} THEN -1 ELSE 0 END) AS plusminus_tally")
|
35
|
+
if params[:separate_updown]
|
36
|
+
t = t.select("SUM(CASE #{Vote.table_name}.vote WHEN #{quoted_true} THEN 1 WHEN #{quoted_false} THEN 0 ELSE 0 END) AS up")
|
37
|
+
t = t.select("SUM(CASE #{Vote.table_name}.vote WHEN #{quoted_true} THEN 0 WHEN #{quoted_false} THEN 1 ELSE 0 END) AS down")
|
38
|
+
end
|
39
|
+
t = t.select("COUNT(#{Vote.table_name}.id) AS vote_count")
|
40
|
+
end
|
41
|
+
|
42
|
+
# #rank_tally is depreciated.
|
43
|
+
alias_method :rank_tally, :plusminus_tally
|
44
|
+
|
45
|
+
# Calculate the vote counts for all voteables of my type.
|
46
|
+
# This method returns all voteables (even without any votes) by default.
|
47
|
+
# The vote count for each voteable is available as #vote_count.
|
48
|
+
# This returns an Arel relation, so you can add conditions as you like chained on to
|
49
|
+
# this method call.
|
50
|
+
# i.e. Posts.tally.where('votes.created_at > ?', 2.days.ago)
|
51
|
+
def tally(*args)
|
52
|
+
t = self.joins("LEFT OUTER JOIN #{Vote.table_name} ON #{self.table_name}.id = #{Vote.table_name}.voteable_id")
|
53
|
+
t = t.order("vote_count DESC")
|
54
|
+
t = t.group(column_names_for_tally)
|
55
|
+
t = t.select("#{self.table_name}.*")
|
56
|
+
t = t.select("COUNT(#{Vote.table_name}.id) AS vote_count")
|
57
|
+
end
|
58
|
+
|
59
|
+
def column_names_for_tally
|
60
|
+
column_names.map { |column| "#{self.table_name}.#{column}" }.join(', ')
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
module InstanceMethods
|
66
|
+
|
67
|
+
# wraps the dynamic, configured, relationship name
|
68
|
+
def _votes_on
|
69
|
+
self.send(FormalVote.configuration[:voteable_relationship_name])
|
70
|
+
end
|
71
|
+
|
72
|
+
def votes_for
|
73
|
+
self._votes_on.where(:vote => true).count
|
74
|
+
end
|
75
|
+
|
76
|
+
def votes_against
|
77
|
+
self._votes_on.where(:vote => false).count
|
78
|
+
end
|
79
|
+
|
80
|
+
def percent_for
|
81
|
+
(votes_for.to_f * 100 / (self._votes_on.size + 0.0001)).round
|
82
|
+
end
|
83
|
+
|
84
|
+
def percent_against
|
85
|
+
(votes_against.to_f * 100 / (self._votes_on.size + 0.0001)).round
|
86
|
+
end
|
87
|
+
|
88
|
+
# You'll probably want to use this method to display how 'good' a particular voteable
|
89
|
+
# is, and/or sort based on it.
|
90
|
+
# If you're using this for a lot of voteables, then you'd best use the #plusminus_tally
|
91
|
+
# method above.
|
92
|
+
def plusminus
|
93
|
+
respond_to?(:plusminus_tally) ? plusminus_tally : (votes_for - votes_against)
|
94
|
+
end
|
95
|
+
|
96
|
+
# The lower bound of a Wilson Score with a default confidence interval of 95%. Gives a more accurate representation of average rating (plusminus) based on the number of positive ratings and total ratings.
|
97
|
+
# http://evanmiller.org/how-not-to-sort-by-average-rating.html
|
98
|
+
def ci_plusminus(confidence = 0.95)
|
99
|
+
require 'statistics2'
|
100
|
+
n = self._votes_on.size
|
101
|
+
if n == 0
|
102
|
+
return 0
|
103
|
+
end
|
104
|
+
z = Statistics2.pnormaldist(1 - (1 - confidence) / 2)
|
105
|
+
phat = 1.0 * votes_for / n
|
106
|
+
(phat + z * z / (2 * n) - z * Math.sqrt((phat * (1 - phat) + z * z / (4 * n)) / n)) / (1 + z * z / n)
|
107
|
+
end
|
108
|
+
|
109
|
+
def votes_count
|
110
|
+
_votes_on.size
|
111
|
+
end
|
112
|
+
|
113
|
+
def voters_who_voted
|
114
|
+
_votes_on.map(&:voter).uniq
|
115
|
+
end
|
116
|
+
|
117
|
+
def voters_who_voted_for
|
118
|
+
_votes_on.where(:vote => true).map(&:voter).uniq
|
119
|
+
end
|
120
|
+
|
121
|
+
def voters_who_voted_against
|
122
|
+
_votes_on.where(:vote => false).map(&:voter).uniq
|
123
|
+
end
|
124
|
+
|
125
|
+
def voted_by?(voter)
|
126
|
+
0 < Vote.where(
|
127
|
+
:voteable_id => self.id,
|
128
|
+
:voteable_type => self.class.base_class.name,
|
129
|
+
:voter_id => voter.id
|
130
|
+
).count
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module FormalVote #:nodoc:
|
2
|
+
module ActsAsVoter #:nodoc:
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def acts_as_voter
|
10
|
+
|
11
|
+
# If a voting entity is deleted, keep the votes.
|
12
|
+
# If you want to nullify (and keep the votes), you'll need to remove
|
13
|
+
# the unique constraint on the [ voter, voteable ] index in the database.
|
14
|
+
# has_many :votes, :as => :voter, :dependent => :nullify
|
15
|
+
# Destroy voter's votes when the voter is deleted.
|
16
|
+
has_many FormalVote.configuration[:voter_relationship_name],
|
17
|
+
:as => :voter,
|
18
|
+
:dependent => :destroy,
|
19
|
+
:class_name => 'Vote'
|
20
|
+
|
21
|
+
include FormalVote::ActsAsVoter::InstanceMethods
|
22
|
+
extend FormalVote::ActsAsVoter::SingletonMethods
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# This module contains class methods
|
27
|
+
module SingletonMethods
|
28
|
+
end
|
29
|
+
|
30
|
+
# This module contains instance methods
|
31
|
+
module InstanceMethods
|
32
|
+
|
33
|
+
# wraps the dynamic, configured, relationship name
|
34
|
+
def _votes_by
|
35
|
+
self.send(FormalVote.configuration[:voter_relationship_name])
|
36
|
+
end
|
37
|
+
|
38
|
+
# Usage user.vote_count(:up) # All +1 votes
|
39
|
+
# user.vote_count(:down) # All -1 votes
|
40
|
+
# user.vote_count() # All votes
|
41
|
+
|
42
|
+
def vote_count(for_or_against = :all)
|
43
|
+
v = Vote.where(:voter_id => id).where(:voter_type => self.class.base_class.name)
|
44
|
+
v = case for_or_against
|
45
|
+
when :all then v
|
46
|
+
when :up then v.where(:vote => true)
|
47
|
+
when :down then v.where(:vote => false)
|
48
|
+
end
|
49
|
+
v.count
|
50
|
+
end
|
51
|
+
|
52
|
+
def voted_for?(voteable)
|
53
|
+
voted_which_way?(voteable, :up)
|
54
|
+
end
|
55
|
+
|
56
|
+
def voted_against?(voteable)
|
57
|
+
voted_which_way?(voteable, :down)
|
58
|
+
end
|
59
|
+
|
60
|
+
def voted_on?(voteable)
|
61
|
+
0 < Vote.where(
|
62
|
+
:voter_id => self.id,
|
63
|
+
:voter_type => self.class.base_class.name,
|
64
|
+
:voteable_id => voteable.id,
|
65
|
+
:voteable_type => voteable.class.base_class.name
|
66
|
+
).count
|
67
|
+
end
|
68
|
+
|
69
|
+
def vote_for(voteable)
|
70
|
+
self.vote(voteable, { :direction => :up, :exclusive => false })
|
71
|
+
end
|
72
|
+
|
73
|
+
def vote_against(voteable)
|
74
|
+
self.vote(voteable, { :direction => :down, :exclusive => false })
|
75
|
+
end
|
76
|
+
|
77
|
+
def vote_exclusively_for(voteable)
|
78
|
+
self.vote(voteable, { :direction => :up, :exclusive => true })
|
79
|
+
end
|
80
|
+
|
81
|
+
def vote_exclusively_against(voteable)
|
82
|
+
self.vote(voteable, { :direction => :down, :exclusive => true })
|
83
|
+
end
|
84
|
+
|
85
|
+
def vote(voteable, options = {})
|
86
|
+
raise ArgumentError, "you must specify :up or :down in order to vote" unless options[:direction] && [:up, :down].include?(options[:direction].to_sym)
|
87
|
+
if options[:exclusive]
|
88
|
+
self.unvote_for(voteable)
|
89
|
+
end
|
90
|
+
direction = (options[:direction].to_sym == :up)
|
91
|
+
# create! does not return the created object
|
92
|
+
v = Vote.new(:vote => direction, :voteable => voteable, :voter => self)
|
93
|
+
v.save!
|
94
|
+
v
|
95
|
+
end
|
96
|
+
|
97
|
+
def unvote_for(voteable)
|
98
|
+
Vote.where(
|
99
|
+
:voter_id => self.id,
|
100
|
+
:voter_type => self.class.base_class.name,
|
101
|
+
:voteable_id => voteable.id,
|
102
|
+
:voteable_type => voteable.class.base_class.name
|
103
|
+
).map(&:destroy)
|
104
|
+
end
|
105
|
+
|
106
|
+
alias_method :clear_votes, :unvote_for
|
107
|
+
|
108
|
+
def voted_which_way?(voteable, direction)
|
109
|
+
raise ArgumentError, "expected :up or :down" unless [:up, :down].include?(direction)
|
110
|
+
0 < Vote.where(
|
111
|
+
:voter_id => self.id,
|
112
|
+
:voter_type => self.class.base_class.name,
|
113
|
+
:vote => direction == :up ? true : false,
|
114
|
+
:voteable_id => voteable.id,
|
115
|
+
:voteable_type => voteable.class.base_class.name
|
116
|
+
).count
|
117
|
+
end
|
118
|
+
|
119
|
+
def voted_how?(voteable)
|
120
|
+
votes = Vote.where(
|
121
|
+
:voter_id => self.id,
|
122
|
+
:voter_type => self.class.base_class.name,
|
123
|
+
:voteable_id => voteable.id,
|
124
|
+
:voteable_type => voteable.class.base_class.name
|
125
|
+
).map(&:vote) #in case votes is premitted to be duplicated
|
126
|
+
if votes.count == 1
|
127
|
+
votes.first
|
128
|
+
elsif votes.count == 0
|
129
|
+
nil
|
130
|
+
else
|
131
|
+
votes
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
data/lib/formal_vote.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'acts_as_voteable'
|
2
|
+
require 'acts_as_voter'
|
3
|
+
require 'formal_vote/configuration'
|
4
|
+
require 'formal_vote/base'
|
5
|
+
require "formal_vote/version"
|
6
|
+
|
7
|
+
module FormalVote
|
8
|
+
class << self
|
9
|
+
|
10
|
+
# An ThumbsUp::Configuration object. Must act like a hash and return sensible
|
11
|
+
# values for all ThumbsUp::Configuration::OPTIONS. See ThumbsUp::Configuration.
|
12
|
+
attr_writer :configuration
|
13
|
+
|
14
|
+
# Call this method to modify defaults in your initializers.
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# ThumbsUp.configure do |config|
|
18
|
+
# config.voteable_relationship_name = :votes_on
|
19
|
+
# config.voter_relationship_name = :votes_by
|
20
|
+
# end
|
21
|
+
def configure
|
22
|
+
yield(configuration)
|
23
|
+
end
|
24
|
+
|
25
|
+
# The configuration object.
|
26
|
+
# @see ThumbsUp::Configuration
|
27
|
+
def configuration
|
28
|
+
@configuration ||= Configuration.new
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
ActiveRecord::Base.send(:include, FormalVote::ActsAsVoteable)
|
34
|
+
ActiveRecord::Base.send(:include, FormalVote::ActsAsVoter)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rails/generators/active_record'
|
2
|
+
|
3
|
+
class FormalVoteGenerator < Rails::Generators::Base
|
4
|
+
|
5
|
+
include Rails::Generators::Migration
|
6
|
+
|
7
|
+
source_root File.expand_path('../templates', __FILE__)
|
8
|
+
|
9
|
+
class_option :unique_voting, :type => :boolean, :default => true, :desc => 'Do you want only one vote allowed per voter? (default: true)'
|
10
|
+
|
11
|
+
# Implement the required interface for Rails::Generators::Migration.
|
12
|
+
def self.next_migration_number(dirname) #:nodoc:
|
13
|
+
next_migration_number = current_migration_number(dirname) + 1
|
14
|
+
if ActiveRecord::Base.timestamped_migrations
|
15
|
+
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
|
16
|
+
else
|
17
|
+
"%.3d" % next_migration_number
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_migration
|
22
|
+
migration_template 'migration.rb', File.join('db', 'migrate', 'formal_vote_migration.rb')
|
23
|
+
end
|
24
|
+
|
25
|
+
def move_vote_model
|
26
|
+
template 'vote.rb', File.join('app', 'models', 'vote.rb')
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class FormalVoteMigration < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :votes, :force => true do |t|
|
4
|
+
|
5
|
+
t.boolean :vote, :default => false, :null => false
|
6
|
+
t.references :voteable, :polymorphic => true, :null => false
|
7
|
+
t.references :voter, :polymorphic => true
|
8
|
+
t.timestamps
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
add_index :votes, [:voter_id, :voter_type]
|
13
|
+
add_index :votes, [:voteable_id, :voteable_type]
|
14
|
+
|
15
|
+
<% if options[:unique_voting] == true %>
|
16
|
+
# Comment out the line below to allow multiple votes per voter on a single entity.
|
17
|
+
add_index :votes, [:voter_id, :voter_type, :voteable_id, :voteable_type], :unique => true, :name => 'fk_one_vote_per_user_per_entity'
|
18
|
+
<% end %>
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.down
|
22
|
+
drop_table :votes
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Vote < ActiveRecord::Base
|
2
|
+
|
3
|
+
scope :for_voter, lambda { |*args| where(["voter_id = ? AND voter_type = ?", args.first.id, args.first.class.base_class.name]) }
|
4
|
+
scope :for_voteable, lambda { |*args| where(["voteable_id = ? AND voteable_type = ?", args.first.id, args.first.class.base_class.name]) }
|
5
|
+
scope :recent, lambda { |*args| where(["created_at > ?", (args.first || 2.weeks.ago)]) }
|
6
|
+
scope :descending, lambda { order("created_at DESC") }
|
7
|
+
|
8
|
+
belongs_to :voteable, :polymorphic => true
|
9
|
+
belongs_to :voter, :polymorphic => true
|
10
|
+
|
11
|
+
attr_accessible :vote, :voter, :voteable if ActiveRecord::VERSION::MAJOR < 4
|
12
|
+
|
13
|
+
<% if options[:unique_voting] == true %>
|
14
|
+
# Comment out the line below to allow multiple votes per user.
|
15
|
+
validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id]
|
16
|
+
<% end %>
|
17
|
+
end
|
File without changes
|
File without changes
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: formal-vote
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ben Sharman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-10-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: A voting library providing methods for a user to vote for, against, or
|
42
|
+
abstain on a model
|
43
|
+
email:
|
44
|
+
- benwebdev@gmail.com
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- .gitignore
|
50
|
+
- Gemfile
|
51
|
+
- LICENSE.txt
|
52
|
+
- README.md
|
53
|
+
- Rakefile
|
54
|
+
- formal-vote.gemspec
|
55
|
+
- lib/acts_as_votable.rb
|
56
|
+
- lib/acts_as_voter.rb
|
57
|
+
- lib/formal-vote/version.rb
|
58
|
+
- lib/formal_vote.rb
|
59
|
+
- lib/generators/formal_vote/formal_vote_generator.rb
|
60
|
+
- lib/generators/formal_vote/templates/migration.rb
|
61
|
+
- lib/generators/formal_vote/templates/vote.rb
|
62
|
+
- lib/test/formal_vote_test.rb
|
63
|
+
- lib/test/test_helper.rb
|
64
|
+
homepage: ''
|
65
|
+
licenses:
|
66
|
+
- MIT
|
67
|
+
metadata: {}
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
requirements: []
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 2.1.9
|
85
|
+
signing_key:
|
86
|
+
specification_version: 4
|
87
|
+
summary: Formal voting library
|
88
|
+
test_files: []
|