acts_as_meritocracy 0.2
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.
- data/Gemfile +2 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +64 -0
- data/Rakefile +35 -0
- data/lib/acts_as_meritocracy.rb +6 -0
- data/lib/acts_as_meritocracy/model.rb +157 -0
- data/lib/acts_as_meritocracy/version.rb +3 -0
- data/lib/generators/acts_as_meritocracy/migration/migration_generator.rb +38 -0
- data/lib/generators/acts_as_meritocracy/migration/templates/active_record/migration.rb +20 -0
- data/lib/generators/acts_as_meritocracy/migration/templates/active_record/vote.rb +21 -0
- data/rails/init.rb +10 -0
- data/test/meritocracy_test.rb +55 -0
- data/test/test_helper.rb +61 -0
- metadata +128 -0
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Nicolas Maisonneuve (n.maisonneuve@gmail.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.
|
data/README.rdoc
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
= meritocracy
|
2
|
+
This plugin introduces a mixin to get a weigted voting system for qualitative (categorical) items.
|
3
|
+
|
4
|
+
As a measure of the quality of the consensus/inter-rater agreement, you can choose between a weighted variant of fleiss kappa (http://en.wikipedia.org/wiki/Fleiss%27_kappa) (by default) or the entropy (http://en.wikipedia.org/wiki/Entropy_(information_theory)) of the vote distribution
|
5
|
+
but you can change it. The consensus score is in the interval [0,1]. very high consensus=1 , very low consensus=0
|
6
|
+
|
7
|
+
NOTE: this voting system has been developed to take (collective) decision about items, not rank items (e.g. by popularity).
|
8
|
+
i.e. generally once a certain level of consensus is reached on a given item, a decision is taken and the vote is close.
|
9
|
+
|
10
|
+
== scenario example
|
11
|
+
You have a set of items and would like to classify them according to 4 predefined categories. For that you ask the opinion to the public.
|
12
|
+
As some people are better/more reliable than other, their votes can be weighted so they have more power in the collective decision
|
13
|
+
|
14
|
+
== installation
|
15
|
+
To use it, add it to your Gemfile:
|
16
|
+
|
17
|
+
gem 'acts_as_meritocracy'
|
18
|
+
|
19
|
+
== Usage
|
20
|
+
|
21
|
+
class Item < ActiveRecord::Base
|
22
|
+
|
23
|
+
act_as_meritocracy
|
24
|
+
end
|
25
|
+
|
26
|
+
item= Item.create()
|
27
|
+
item.submit_vote(user1, 4) #user1 votes for the decision 4, by default the vote_weight=1
|
28
|
+
item.submit_vote(user2, 1, 2) #user2 votes for the decision 1 with a vote_weight=2. A vote_weight=2 means that the voter has a vote equal to 2 normal voters (vote_weight=1)
|
29
|
+
item.submit_vote(user1, 1) #user1 can change/update her vote
|
30
|
+
item.submit_vote(user3, 2) #user3 vote for decision 2
|
31
|
+
item.submit_vote(user4, 3,5) #user3 vote for decision 2
|
32
|
+
|
33
|
+
item.best_decision #get the most (weighted) voted decision
|
34
|
+
=>3
|
35
|
+
|
36
|
+
|
37
|
+
item.nb_votes(true) # get the number of votes - with the weight
|
38
|
+
=> 1+2+1+5= 9
|
39
|
+
|
40
|
+
item.nb_votes(false) # get the number of votes - without taking into account the weight
|
41
|
+
=> 1+1+1+1= 4
|
42
|
+
|
43
|
+
item.vote_distribution # get the frequency distribution of the decisions [{:decision1=>freq1},{:decision1=>freq2}, ..,{:decision1=>freq3}]. the frequency takes into account the vote weight.
|
44
|
+
=> [{1=>3},{2=1},{3=5}]
|
45
|
+
|
46
|
+
# compute the consensus score based on the vote distribution
|
47
|
+
# by default I used a variante of the Fleiss Kappa metrics
|
48
|
+
item.consensus
|
49
|
+
=> 0.361
|
50
|
+
# An entropy-based computation of the consensus is also proposed
|
51
|
+
# (a perfect disagreement between 2 categories give a higher score than for 10 categories, less predictable)
|
52
|
+
item.consensus("entropy")
|
53
|
+
=>0.063
|
54
|
+
|
55
|
+
item.votes # retrieve the list of votes {decision, vote_weight) , so item.votes.where(:vote=>1) get the list of votes having decision=1
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
|
61
|
+
== Copyright
|
62
|
+
|
63
|
+
Copyright (c) 2012 nm. See MIT-LICENSE.txt for further details.
|
64
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'bundler' unless defined?(Bundler)
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
6
|
+
require 'acts_as_meritocracy/version'
|
7
|
+
|
8
|
+
begin
|
9
|
+
Bundler.setup(:default, :development)
|
10
|
+
rescue Bundler::BundlerError => e
|
11
|
+
$stderr.puts e.message
|
12
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
13
|
+
exit e.status_code
|
14
|
+
end
|
15
|
+
require 'rake'
|
16
|
+
|
17
|
+
|
18
|
+
require 'rake/testtask'
|
19
|
+
Rake::TestTask.new(:test) do |test|
|
20
|
+
test.libs << 'lib' << 'test'
|
21
|
+
test.test_files = Dir.glob("test/**/*_test.rb")
|
22
|
+
test.verbose = true
|
23
|
+
end
|
24
|
+
|
25
|
+
task :build do
|
26
|
+
system "gem build acts_as_meritocracy.gemspec"
|
27
|
+
end
|
28
|
+
|
29
|
+
task :release => :build do
|
30
|
+
|
31
|
+
system "gem push acts_as_meritocracy-#{ActsAsMeritocracy::VERSION}.gem"
|
32
|
+
system "rm acts_as_meritocracy-#{ActsAsMeritocracy::VERSION}.gem"
|
33
|
+
end
|
34
|
+
|
35
|
+
task :default => :test
|
@@ -0,0 +1,157 @@
|
|
1
|
+
module ActsAsMeritocracy #:nodoc:
|
2
|
+
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
# Example:
|
10
|
+
# class User < ActiveRecord::Base
|
11
|
+
# acts_as_meritocracy {:min_votes=>, :max_votes=>}}
|
12
|
+
# end
|
13
|
+
def acts_as_meritocracy(options={})
|
14
|
+
|
15
|
+
# todo trigger action when decision is reliable
|
16
|
+
# acts_as_meritocracy {:quorum=>, :min_consensus=>}}
|
17
|
+
#adding class attribute
|
18
|
+
#class_attribute :_min_votes, :_max_votes, :_consensus, :_status_update_fct
|
19
|
+
#self._min_votes=options[:min_votes] || 3
|
20
|
+
#self._max_votes=options[:max_votes] || 6
|
21
|
+
#self._consensus=options[:consensus] || 0.7
|
22
|
+
#self._status_update_fct=options[:on_status_change] || :decision_updated
|
23
|
+
|
24
|
+
has_many :votes, :as => :voteable, :dependent => :destroy
|
25
|
+
has_many :voters, :through => :votes
|
26
|
+
attr_accessible :voters, :score, :num_votes, :entropy # entropy
|
27
|
+
|
28
|
+
|
29
|
+
scope :voted_by, lambda { |user|
|
30
|
+
joins(:votes).where("votes.voteable_type = ?", self.name).where("votes.voter_id=?", user.id)
|
31
|
+
}
|
32
|
+
|
33
|
+
include ActsAsMeritocracy::InstanceMethods
|
34
|
+
extend ActsAsMeritocracy::SingletonMethods
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module SingletonMethods
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
module InstanceMethods
|
43
|
+
|
44
|
+
|
45
|
+
def voted_by?(user)
|
46
|
+
self.votes.exists?(:voter_id=>user.id)
|
47
|
+
end
|
48
|
+
|
49
|
+
# number of votes taking into account (or not) the weight of the vote
|
50
|
+
# 1 vote with vote_weight=2 ==> 2 votes
|
51
|
+
def nb_votes(weighted=true)
|
52
|
+
if (weighted)
|
53
|
+
self.votes.sum('vote_weight')
|
54
|
+
else
|
55
|
+
self.votes.count
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# retrieve the vote distribution
|
60
|
+
def vote_distribution
|
61
|
+
Vote.select("vote, sum(vote_weight) as freq").where(:voteable_id=>self.id).group(:vote).order("freq DESC")
|
62
|
+
end
|
63
|
+
|
64
|
+
#majority vote (get the decision with the higher number of votes)
|
65
|
+
def best_decision(tie_method="random")
|
66
|
+
best=nil
|
67
|
+
tie=[]
|
68
|
+
nb_categories=0
|
69
|
+
self.vote_distribution.each { |category|
|
70
|
+
if ((best.nil?) || (category.freq>best.freq))
|
71
|
+
best=category
|
72
|
+
tie=[]
|
73
|
+
tie<<category
|
74
|
+
# managing tie
|
75
|
+
elsif (category.freq==best.freq)
|
76
|
+
tie<<category
|
77
|
+
end
|
78
|
+
nb_categories=nb_categories+1
|
79
|
+
}
|
80
|
+
|
81
|
+
# tie detected
|
82
|
+
if (tie.size>1)
|
83
|
+
case (tie_method)
|
84
|
+
when "random" then
|
85
|
+
tie[rand(tie.size)].vote
|
86
|
+
when "nodecision" then
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
# no vote
|
90
|
+
elsif (best.nil?)
|
91
|
+
nil
|
92
|
+
# get the most chosen category
|
93
|
+
else
|
94
|
+
best.vote
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Consensus score using the vote distribubtion
|
99
|
+
# very high consensus=1 , very low consensus=0
|
100
|
+
# could be used as an indicator to measure the difficulty of users to take collectively a decision
|
101
|
+
# 2 methods are proposed
|
102
|
+
# - Entropy
|
103
|
+
# - Fleiss Kappa taking into account the weight (NOTE: I removed the pb of chance in the computation)
|
104
|
+
def consensus(method="kappa")
|
105
|
+
consensus= case method
|
106
|
+
when "entropy" then
|
107
|
+
dist=self.vote_distribution
|
108
|
+
nb_votes=dist.inject(0) { |sum, category| sum+category.freq }.to_f
|
109
|
+
h=self.vote_distribution.inject(0) { |h, category|
|
110
|
+
p_j=category.freq.to_f/nb_votes
|
111
|
+
h + p_j*Math.log(p_j)
|
112
|
+
}
|
113
|
+
h=[h, -1].max
|
114
|
+
1+h
|
115
|
+
|
116
|
+
when "kappa" then
|
117
|
+
dist=self.vote_distribution
|
118
|
+
nb_votes=dist.inject(0) { |sum, category| sum+category.freq }.to_f
|
119
|
+
fact=dist.inject(0) { |fact, category|
|
120
|
+
fact+category.freq**2
|
121
|
+
}
|
122
|
+
(fact-nb_votes)/(nb_votes*(nb_votes-1))
|
123
|
+
else
|
124
|
+
0
|
125
|
+
end
|
126
|
+
consensus
|
127
|
+
end
|
128
|
+
|
129
|
+
#submit or update the vote of someone
|
130
|
+
def submit_vote(user, vote, vote_weight=1)
|
131
|
+
|
132
|
+
#check existence
|
133
|
+
v=Vote.where(:voter_id=>user.id, :voteable_id=>self.id, :voteable_type => self.class.name).first
|
134
|
+
|
135
|
+
#if not create new vote
|
136
|
+
if (v.nil?)
|
137
|
+
v=Vote.create(:voter=>user,
|
138
|
+
:voteable=>self,
|
139
|
+
:vote=>vote,
|
140
|
+
:vote_weight=>vote_weight)
|
141
|
+
#modify existing vote
|
142
|
+
else
|
143
|
+
v.update_attributes(:vote=>vote, :vote_weight=>vote_weight)
|
144
|
+
end
|
145
|
+
v
|
146
|
+
end
|
147
|
+
|
148
|
+
protected
|
149
|
+
|
150
|
+
def trigger_decision
|
151
|
+
# if we are enough sure of the decision we trigger an action
|
152
|
+
if (nb_votes>=self.class._min_votes && consensus>self.class._consensus_ratio)
|
153
|
+
#we trigger an action
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
|
4
|
+
module Model
|
5
|
+
class MigrationGenerator < Rails::Generators::Base
|
6
|
+
include Rails::Generators::Migration
|
7
|
+
|
8
|
+
desc "Generates migration for Votes models"
|
9
|
+
|
10
|
+
def self.orm
|
11
|
+
Rails::Generators.options[:rails][:orm]
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.source_root
|
15
|
+
File.join(File.dirname(__FILE__), 'templates', (orm.to_s unless orm.class.eql?(String)) )
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.orm_has_migration?
|
19
|
+
[:active_record].include? orm
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.next_migration_number(dirname)
|
23
|
+
if ActiveRecord::Base.timestamped_migrations
|
24
|
+
migration_number = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
|
25
|
+
migration_number += 1
|
26
|
+
migration_number.to_s
|
27
|
+
else
|
28
|
+
"%.3d" % (current_migration_number(dirname) + 1)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_migration_file
|
33
|
+
if self.class.orm_has_migration?
|
34
|
+
migration_template 'migration.rb', 'db/migrate/acts_as_taggable_on_migration'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class ActsAsTaggableOnMigration < ActiveRecord::Migration
|
2
|
+
|
3
|
+
def self.up
|
4
|
+
create_table :votes, :force => true do |t|
|
5
|
+
t.integer :voter_id, :limit => 11
|
6
|
+
t.references :voteable, :polymorphic=>true
|
7
|
+
t.integer :vote_weight, :default=>1
|
8
|
+
t.integer :vote, :default=>1
|
9
|
+
t.datetime :updated_at
|
10
|
+
t.datetime :created_at
|
11
|
+
end
|
12
|
+
add_index :votes, :tag_id
|
13
|
+
add_index :votes, [:voteable_id, :voteable_type]
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.down
|
17
|
+
drop_table :votes
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
# inspired by thumb_up gem
|
3
|
+
# https://raw.github.com/brady8/thumbs_up/
|
4
|
+
|
5
|
+
class Vote < ActiveRecord::Base
|
6
|
+
|
7
|
+
scope :for_voter, lambda { |*args| where(["voter_id = ?", args.first.id]) }
|
8
|
+
scope :for_voteable, lambda { |*args| where(["voteable_id = ? AND voteable_type = ?", args.first.id, args.first.class.base_class.name]) }
|
9
|
+
scope :recent, lambda { |*args| where(["created_at > ?", (args.first || 2.weeks.ago)]) }
|
10
|
+
scope :descending, order("created_at DESC")
|
11
|
+
|
12
|
+
belongs_to :voteable, :polymorphic => true
|
13
|
+
|
14
|
+
belongs_to :voter, :class_name=>"User"
|
15
|
+
|
16
|
+
attr_accessible :vote, :voter, :voteable, :vote_weight
|
17
|
+
|
18
|
+
# Comment out the line below to allow multiple votes per user.
|
19
|
+
validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_id]
|
20
|
+
|
21
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
RAILS_DEFAULT_LOGGER.info "** acts_as_meritocracy: 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.autoload_paths << path
|
7
|
+
ActiveSupport::Dependencies.autoload_once_paths.delete(path)
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'acts_as_meritocracy'
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), 'test_helper')
|
2
|
+
|
3
|
+
class MeritocracyTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
Vote.delete_all
|
6
|
+
User.delete_all
|
7
|
+
Item.delete_all
|
8
|
+
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_acts_as_meritocracy_methods
|
13
|
+
user1 = User.create!()
|
14
|
+
user2 = User.create!()
|
15
|
+
user3 = User.create!()
|
16
|
+
user4=User.create!()
|
17
|
+
|
18
|
+
item = Item.create!()
|
19
|
+
|
20
|
+
item.submit_vote(user1, 4) #user1 votes for the decision 4, by default the vote_weight=1
|
21
|
+
item.submit_vote(user2, 1, 2) #user2 votes for the decision 1 with a vote_weight=2. A vote_weight=2 means that the voter has a vote equal to 2 normal voters (vote_weight=1)
|
22
|
+
item.submit_vote(user1, 1) #user1 can change/update her vote
|
23
|
+
item.submit_vote(user3, 2) #user3 vote for decision 2
|
24
|
+
item.submit_vote(user4, 3, 5) #user3 vote for decision 2
|
25
|
+
|
26
|
+
assert_equal 9, item.nb_votes
|
27
|
+
|
28
|
+
dist=item.vote_distribution
|
29
|
+
|
30
|
+
assert_equal 3, dist[0].vote
|
31
|
+
assert_equal 5, dist[0].freq
|
32
|
+
assert_equal 1, dist[1].vote
|
33
|
+
assert_equal 3, dist[1].freq
|
34
|
+
|
35
|
+
assert_equal 0.361, item.consensus.round(3) #fleiss kappa taking into account the weight
|
36
|
+
assert_equal 0.063, item.consensus("entropy").round(3)
|
37
|
+
|
38
|
+
|
39
|
+
#everyone agrees
|
40
|
+
item2 = Item.create!()
|
41
|
+
item2.submit_vote(user1, 1)
|
42
|
+
item2.submit_vote(user2, 1, 3)
|
43
|
+
item2.submit_vote(user3, 1, 2)
|
44
|
+
assert_equal 1, item2.consensus
|
45
|
+
|
46
|
+
#everyone disagrees
|
47
|
+
item3 = Item.create!()
|
48
|
+
item3.submit_vote(user1, 1)
|
49
|
+
item3.submit_vote(user2, 2)
|
50
|
+
|
51
|
+
|
52
|
+
assert_equal 0.0, item3.consensus #entropy
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
5
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
6
|
+
|
7
|
+
|
8
|
+
#require 'simplecov'
|
9
|
+
#SimpleCov.start
|
10
|
+
|
11
|
+
require 'active_record'
|
12
|
+
require 'logger'
|
13
|
+
|
14
|
+
|
15
|
+
TEST_DATABASE_FILE = File.join(File.dirname(__FILE__), '..', 'test.sqlite3')
|
16
|
+
File.unlink(TEST_DATABASE_FILE) if File.exist?(TEST_DATABASE_FILE)
|
17
|
+
ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => TEST_DATABASE_FILE
|
18
|
+
|
19
|
+
ActiveRecord::Migration.verbose = true
|
20
|
+
#ActiveRecord::Base.logger = Logger.new(STDOUT)
|
21
|
+
ActiveRecord::Schema.define do
|
22
|
+
|
23
|
+
create_table :votes, :force => true do |t|
|
24
|
+
t.integer :voter_id, :default => false
|
25
|
+
t.integer :vote, :default => false
|
26
|
+
t.references :voteable, :polymorphic => true, :null => false
|
27
|
+
t.integer :vote_weight, :default=>1
|
28
|
+
t.timestamps
|
29
|
+
end
|
30
|
+
|
31
|
+
create_table :users, :force => true do |t|
|
32
|
+
t.timestamps
|
33
|
+
end
|
34
|
+
|
35
|
+
create_table :items, :force => true do |t|
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
require 'acts_as_meritocracy'
|
41
|
+
|
42
|
+
class User < ActiveRecord::Base
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
class Vote < ActiveRecord::Base
|
47
|
+
belongs_to :voteable, :polymorphic => true
|
48
|
+
belongs_to :voter, :class_name=>"User"
|
49
|
+
attr_accessible :vote, :voter, :voteable, :vote_weight
|
50
|
+
|
51
|
+
# Comment out the line below to allow multiple votes per user.
|
52
|
+
validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_id]
|
53
|
+
end
|
54
|
+
|
55
|
+
class Item < ActiveRecord::Base
|
56
|
+
acts_as_meritocracy
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
class Test::Unit::TestCase
|
61
|
+
end
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: acts_as_meritocracy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.2'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nicolas Maisonneuve
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activerecord
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: bundler
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: sqlite3
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rake
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: Weighted majority voting system for qualitative (categorical) items (i.e.assuming
|
79
|
+
the set of decision has no natural ordering). As a measure of the quality of the
|
80
|
+
consensus/inter-agreement, you can choose between a weighted variant of fleiss Kappa (by
|
81
|
+
default) or the entropy of the vote distribution
|
82
|
+
email:
|
83
|
+
- n.maisonneuve.com
|
84
|
+
executables: []
|
85
|
+
extensions: []
|
86
|
+
extra_rdoc_files: []
|
87
|
+
files:
|
88
|
+
- lib/acts_as_meritocracy/model.rb
|
89
|
+
- lib/acts_as_meritocracy/version.rb
|
90
|
+
- lib/acts_as_meritocracy.rb
|
91
|
+
- lib/generators/acts_as_meritocracy/migration/migration_generator.rb
|
92
|
+
- lib/generators/acts_as_meritocracy/migration/templates/active_record/migration.rb
|
93
|
+
- lib/generators/acts_as_meritocracy/migration/templates/active_record/vote.rb
|
94
|
+
- rails/init.rb
|
95
|
+
- test/meritocracy_test.rb
|
96
|
+
- test/test_helper.rb
|
97
|
+
- Gemfile
|
98
|
+
- MIT-LICENSE
|
99
|
+
- README.rdoc
|
100
|
+
- Rakefile
|
101
|
+
homepage: https://github.com/nmaisonneuve/acts_as_meritocracy
|
102
|
+
licenses: []
|
103
|
+
post_install_message:
|
104
|
+
rdoc_options: []
|
105
|
+
require_paths:
|
106
|
+
- lib
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ! '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
segments:
|
114
|
+
- 0
|
115
|
+
hash: -127910143
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ! '>='
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: 1.9.0
|
122
|
+
requirements: []
|
123
|
+
rubyforge_project:
|
124
|
+
rubygems_version: 1.8.21
|
125
|
+
signing_key:
|
126
|
+
specification_version: 3
|
127
|
+
summary: Weighted voting system for deliberation.
|
128
|
+
test_files: []
|