ballot_box 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/README.rdoc +96 -0
- data/Rakefile +44 -0
- data/app/models/ballot_box/vote.rb +5 -0
- data/lib/ballot_box/base.rb +74 -0
- data/lib/ballot_box/callbacks.rb +49 -0
- data/lib/ballot_box/config.rb +35 -0
- data/lib/ballot_box/engine.rb +13 -0
- data/lib/ballot_box/manager.rb +55 -0
- data/lib/ballot_box/strategies/authenticated.rb +7 -0
- data/lib/ballot_box/strategies/base.rb +26 -0
- data/lib/ballot_box/version.rb +4 -0
- data/lib/ballot_box/voting.rb +133 -0
- data/lib/ballot_box.rb +26 -0
- data/lib/generators/ballot_box/USAGE +7 -0
- data/lib/generators/ballot_box/install_generator.rb +31 -0
- data/lib/generators/ballot_box/templates/migrate/create_votes.rb +34 -0
- metadata +114 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2010-2011 Aimbulance.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
= BallotBox
|
2
|
+
|
3
|
+
The BallotBox gem enables visitors to vote for and against voteable objects
|
4
|
+
|
5
|
+
== Install
|
6
|
+
|
7
|
+
gem 'ballot_box'
|
8
|
+
|
9
|
+
rails generate ballot_box:install
|
10
|
+
|
11
|
+
== Usage
|
12
|
+
|
13
|
+
Use middleware with options:
|
14
|
+
|
15
|
+
# Initialize BallotBox and set its configurations.
|
16
|
+
config.app_middleware.use BallotBox::Manager do |config|
|
17
|
+
config.routes = { "/posts/votes" => "Post" }
|
18
|
+
end
|
19
|
+
|
20
|
+
Set voteable model:
|
21
|
+
|
22
|
+
class Post < ActiveRecord::Base
|
23
|
+
ballot_box :counter_cache => true
|
24
|
+
# ballot_box :counter_cache => :rating, :strategies => [:authenticated]
|
25
|
+
end
|
26
|
+
|
27
|
+
View (just send post request to configure route):
|
28
|
+
|
29
|
+
link_to 'Vote', "/posts/votes?id=#{@post.id}", :remote => true, :method => :post
|
30
|
+
|
31
|
+
== Strategies
|
32
|
+
|
33
|
+
Strategy - is no more a simple validation at the object vote.
|
34
|
+
Authenticated - check :voter attribute is not blank.
|
35
|
+
|
36
|
+
module BallotBox
|
37
|
+
module Strategies
|
38
|
+
class Authenticated < Base
|
39
|
+
validates_presence_of :voter
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
Write your own strategies:
|
45
|
+
|
46
|
+
class MyCustomStrategy < BallotBox::Strategies::Base
|
47
|
+
validate :check_visible, :check_unique_voter
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
def check_visible
|
52
|
+
errors.add(:voteable, :invalid) unless voteable.visible?
|
53
|
+
end
|
54
|
+
|
55
|
+
def check_unique_voter
|
56
|
+
if vote.class.where(["voter_id = ? AND voter_type = ?", vote.voter_id, vote.voter_type]).exists?
|
57
|
+
errors.add(:voter, :taken)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class Post < ActiveRecord::Base
|
63
|
+
ballot_box :strategies => [:authenticated, 'MyCustomStrategy']
|
64
|
+
end
|
65
|
+
|
66
|
+
== Callbacks
|
67
|
+
|
68
|
+
Middleware callbacks:
|
69
|
+
|
70
|
+
BallotBox::Manager.before_vote do |env, vote|
|
71
|
+
vote.voter = env['warden'].user
|
72
|
+
#vote.errors.add(:voter, :empty)
|
73
|
+
end
|
74
|
+
|
75
|
+
BallotBox::Manager.after_vote do |env, vote|
|
76
|
+
Rails.logger.info(vote.to_xml)
|
77
|
+
end
|
78
|
+
|
79
|
+
ActiveRecord callbacks:
|
80
|
+
|
81
|
+
class Post < ActiveRecord::Base
|
82
|
+
ballot_box :counter_cache => true
|
83
|
+
|
84
|
+
before_vote :method_before
|
85
|
+
after_vote :method_after
|
86
|
+
|
87
|
+
def method_before
|
88
|
+
Rails.logger.info current_vote.to_xml
|
89
|
+
# To terminate register vote, just return false
|
90
|
+
# return false
|
91
|
+
end
|
92
|
+
|
93
|
+
def method_after
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require File.join(File.dirname(__FILE__), 'lib', 'ballot_box', 'version')
|
6
|
+
|
7
|
+
desc 'Default: run unit tests.'
|
8
|
+
task :default => :test
|
9
|
+
|
10
|
+
desc 'Test the ballot_box plugin.'
|
11
|
+
Rake::TestTask.new(:test) do |t|
|
12
|
+
t.libs << 'lib'
|
13
|
+
t.libs << 'test'
|
14
|
+
t.pattern = 'test/**/*_test.rb'
|
15
|
+
t.verbose = true
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'Generate documentation for the ballot_box plugin.'
|
19
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
20
|
+
rdoc.rdoc_dir = 'rdoc'
|
21
|
+
rdoc.title = 'BallotBox'
|
22
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
23
|
+
rdoc.rdoc_files.include('README')
|
24
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
25
|
+
end
|
26
|
+
|
27
|
+
begin
|
28
|
+
require 'jeweler'
|
29
|
+
Jeweler::Tasks.new do |s|
|
30
|
+
s.name = "ballot_box"
|
31
|
+
s.version = BallotBox::VERSION.dup
|
32
|
+
s.summary = "The BallotBox gem enables visitors to vote for and against voteable objects"
|
33
|
+
s.description = "The BallotBox gem enables visitors to vote for and against voteable objects"
|
34
|
+
s.email = "galeta.igor@gmail.com"
|
35
|
+
s.homepage = "https://github.com/galetahub/ballot_box"
|
36
|
+
s.authors = ["Igor Galeta", "Pavlo Galeta"]
|
37
|
+
s.files = FileList["[A-Z]*", "{app,lib}/**/*"] - ["Gemfile"]
|
38
|
+
#s.extra_rdoc_files = FileList["[A-Z]*"]
|
39
|
+
end
|
40
|
+
|
41
|
+
Jeweler::GemcutterTasks.new
|
42
|
+
rescue LoadError
|
43
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
44
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module BallotBox
|
2
|
+
module Base
|
3
|
+
def self.included(base)
|
4
|
+
base.extend SingletonMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module SingletonMethods
|
8
|
+
#
|
9
|
+
# ballot_box :counter_cache => true,
|
10
|
+
# :strategies => [:authenticated]
|
11
|
+
#
|
12
|
+
def ballot_box(options = {})
|
13
|
+
extend ClassMethods
|
14
|
+
include InstanceMethods
|
15
|
+
|
16
|
+
options = { :strategies => [:authenticated] }.merge(options)
|
17
|
+
|
18
|
+
class_attribute :ballot_box_options, :instance_writer => false
|
19
|
+
self.ballot_box_options = options
|
20
|
+
|
21
|
+
has_many :votes,
|
22
|
+
:class_name => 'BallotBox::Vote',
|
23
|
+
:as => :voteable,
|
24
|
+
:dependent => :delete_all
|
25
|
+
|
26
|
+
attr_accessor :current_vote
|
27
|
+
|
28
|
+
define_ballot_box_callbacks :vote
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
|
34
|
+
def define_ballot_box_callbacks(*callbacks)
|
35
|
+
define_callbacks *[callbacks, {:terminator => "result == false"}].flatten
|
36
|
+
callbacks.each do |callback|
|
37
|
+
eval <<-end_callbacks
|
38
|
+
def before_#{callback}(*args, &blk)
|
39
|
+
set_callback(:#{callback}, :before, *args, &blk)
|
40
|
+
end
|
41
|
+
def after_#{callback}(*args, &blk)
|
42
|
+
set_callback(:#{callback}, :after, *args, &blk)
|
43
|
+
end
|
44
|
+
end_callbacks
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def ballot_box_cached_column
|
49
|
+
if ballot_box_options[:counter_cache] == true
|
50
|
+
"votes_count"
|
51
|
+
elsif ballot_box_options[:counter_cache]
|
52
|
+
ballot_box_options[:counter_cache]
|
53
|
+
else
|
54
|
+
false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def ballot_box_strategies
|
59
|
+
@@ballot_box_strategies ||= ballot_box_options[:strategies].map { |st| BallotBox.load_strategy(st) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
module InstanceMethods
|
64
|
+
|
65
|
+
def ballot_box_cached_column
|
66
|
+
@ballot_box_cached_column ||= self.class.ballot_box_cached_column
|
67
|
+
end
|
68
|
+
|
69
|
+
def ballot_box_valid?(vote)
|
70
|
+
self.class.ballot_box_strategies.map { |st| st.new(self, vote) }.map(&:valid?).all?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module BallotBox
|
3
|
+
module Callbacks
|
4
|
+
# Hook to _run_callbacks asserting for conditions.
|
5
|
+
def _run_callbacks(kind, *args) #:nodoc:
|
6
|
+
options = args.last # Last callback arg MUST be a Hash
|
7
|
+
|
8
|
+
send("_#{kind}").each do |callback, conditions|
|
9
|
+
invalid = conditions.find do |key, value|
|
10
|
+
value.is_a?(Array) ? !value.include?(options[key]) : (value != options[key])
|
11
|
+
end
|
12
|
+
|
13
|
+
callback.call(*args) unless invalid
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# A callback that runs before create vote
|
18
|
+
# Example:
|
19
|
+
# BallotBox::Manager.before_vote do |env, opts|
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
def before_vote(options = {}, method = :push, &block)
|
23
|
+
raise BlockNotGiven unless block_given?
|
24
|
+
_before_vote.send(method, [block, options])
|
25
|
+
end
|
26
|
+
|
27
|
+
# Provides access to the callback array for before_vote
|
28
|
+
# :api: private
|
29
|
+
def _before_vote
|
30
|
+
@_before_vote ||= []
|
31
|
+
end
|
32
|
+
|
33
|
+
# A callback that runs after vote created
|
34
|
+
# Example:
|
35
|
+
# BallotBox::Manager.after_vote do |env, opts|
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
def after_vote(options = {}, method = :push, &block)
|
39
|
+
raise BlockNotGiven unless block_given?
|
40
|
+
_after_vote.send(method, [block, options])
|
41
|
+
end
|
42
|
+
|
43
|
+
# Provides access to the callback array for before_vote
|
44
|
+
# :api: private
|
45
|
+
def _after_vote
|
46
|
+
@_after_vote ||= []
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module BallotBox
|
3
|
+
class Config < Hash
|
4
|
+
# Creates an accessor that simply sets and reads a key in the hash:
|
5
|
+
#
|
6
|
+
# class Config < Hash
|
7
|
+
# hash_accessor :routes
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# config = Config.new
|
11
|
+
# config.routes = {'/posts/vote' => 'Post' }
|
12
|
+
# config[:routes] #=> {'/posts/vote' => 'Post' }
|
13
|
+
#
|
14
|
+
def self.hash_accessor(*names) #:nodoc:
|
15
|
+
names.each do |name|
|
16
|
+
class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
17
|
+
def #{name}
|
18
|
+
self[:#{name}]
|
19
|
+
end
|
20
|
+
|
21
|
+
def #{name}=(value)
|
22
|
+
self[:#{name}] = value
|
23
|
+
end
|
24
|
+
METHOD
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
hash_accessor :routes
|
29
|
+
|
30
|
+
def initialize(other={})
|
31
|
+
merge!(other)
|
32
|
+
self[:routes] ||= { "/ballot_box/vote" => 'Class' }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rails'
|
3
|
+
require 'ballot_box'
|
4
|
+
|
5
|
+
module BallotBox
|
6
|
+
class Engine < ::Rails::Engine
|
7
|
+
config.before_initialize do
|
8
|
+
ActiveSupport.on_load :active_record do
|
9
|
+
::ActiveRecord::Base.send :include, BallotBox::Base
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module BallotBox
|
3
|
+
class Manager
|
4
|
+
extend BallotBox::Callbacks
|
5
|
+
|
6
|
+
attr_accessor :config
|
7
|
+
|
8
|
+
# Initialize the middleware. If a block is given, a BallotBox::Config is yielded so you can properly
|
9
|
+
# configure the BallotBox::Manager.
|
10
|
+
def initialize(app, options={})
|
11
|
+
options.symbolize_keys!
|
12
|
+
|
13
|
+
@app, @config = app, BallotBox::Config.new(options)
|
14
|
+
yield @config if block_given?
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(env) # :nodoc:
|
19
|
+
if voting_path?(env['PATH_INFO']) && env["REQUEST_METHOD"] == "POST"
|
20
|
+
create(env)
|
21
|
+
else
|
22
|
+
@app.call(env)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# :api: private
|
27
|
+
def _run_callbacks(*args) #:nodoc:
|
28
|
+
self.class._run_callbacks(*args)
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def create(env, body = '', status = 500)
|
34
|
+
request = Rack::Request.new(env)
|
35
|
+
vote = BallotBox::Vote.new(:request => request)
|
36
|
+
vote.voteable_type = @config.routes[ request.path_info ]
|
37
|
+
|
38
|
+
_run_callbacks(:before_vote, env, vote)
|
39
|
+
|
40
|
+
body, status = vote.call
|
41
|
+
|
42
|
+
_run_callbacks(:after_vote, env, vote)
|
43
|
+
|
44
|
+
[status, {'Content-Type' => 'application/json', 'Content-Length' => body.size.to_s}, body]
|
45
|
+
end
|
46
|
+
|
47
|
+
def voting_path?(request_path)
|
48
|
+
return false if @config.routes.nil?
|
49
|
+
|
50
|
+
@config.routes.keys.any? do |route|
|
51
|
+
route == request_path
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'active_model/validations'
|
2
|
+
|
3
|
+
module BallotBox
|
4
|
+
module Strategies
|
5
|
+
class Base
|
6
|
+
include ActiveModel::Validations
|
7
|
+
|
8
|
+
attr_accessor :voteable, :vote, :request
|
9
|
+
|
10
|
+
def initialize(voteable, vote)
|
11
|
+
@voteable = voteable
|
12
|
+
@vote = vote
|
13
|
+
@request = vote.request
|
14
|
+
end
|
15
|
+
|
16
|
+
def read_attribute_for_validation(key)
|
17
|
+
@vote[key]
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the Errors object that holds all information about attribute error messages.
|
21
|
+
def errors
|
22
|
+
@vote.errors
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'ipaddr'
|
3
|
+
require "browser"
|
4
|
+
|
5
|
+
module BallotBox
|
6
|
+
module Voting
|
7
|
+
def self.included(base)
|
8
|
+
base.send :include, InstanceMethods
|
9
|
+
base.send :extend, ClassMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def self.extended(base)
|
14
|
+
base.class_eval do
|
15
|
+
# Associations
|
16
|
+
belongs_to :voteable, :polymorphic => true
|
17
|
+
belongs_to :voter, :polymorphic => true
|
18
|
+
|
19
|
+
# Validations
|
20
|
+
validates_presence_of :ip_address, :user_agent, :voteable_id, :voteable_type
|
21
|
+
validates_numericality_of :value, :only_integer => true
|
22
|
+
|
23
|
+
# Callbacks
|
24
|
+
before_save :parse_browser
|
25
|
+
after_save :update_cached_columns
|
26
|
+
after_destroy :update_cached_columns
|
27
|
+
|
28
|
+
attr_accessible :request
|
29
|
+
|
30
|
+
composed_of :ip,
|
31
|
+
:class_name => 'IPAddr',
|
32
|
+
:mapping => %w(ip_address to_i),
|
33
|
+
:constructor => Proc.new { |ip_address| IPAddr.new(ip_address, Socket::AF_INET) },
|
34
|
+
:converter => Proc.new { |value| value.is_a?(Integer) ? IPAddr.new(value, Socket::AF_INET) : IPAddr.new(value.to_s) }
|
35
|
+
|
36
|
+
scope :with_voteable, lambda { |record| where(["voteable_id = ? AND voteable_type = ?", record.id, record.class.name]) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def chart(mode)
|
41
|
+
result = case mode.to_s.downcase
|
42
|
+
when 'dates' then chart_dates
|
43
|
+
when 'browsers' then chart_browsers
|
44
|
+
when 'platforms' then chart_platforms
|
45
|
+
end
|
46
|
+
|
47
|
+
[ result ].to_json
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
def chart_dates
|
53
|
+
data = scoped.select("DATE(created_at) AS created_at, SUM(value) AS rating").group("DATE(created_at)").all
|
54
|
+
data.collect { |item| [ item.created_at, item.rating.to_i ] }
|
55
|
+
end
|
56
|
+
|
57
|
+
def chart_browsers
|
58
|
+
data = scoped.select("browser_name, SUM(value) AS rating").group("browser_name").all
|
59
|
+
data.collect { |item| [ item.browser_name, item.rating.to_i ] }
|
60
|
+
end
|
61
|
+
|
62
|
+
def chart_platforms
|
63
|
+
data = scoped.select("browser_platform, SUM(value) AS rating").group("browser_platform").all
|
64
|
+
data.collect { |item| [ item.browser_platform, item.rating.to_i ] }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
module InstanceMethods
|
69
|
+
|
70
|
+
def browser
|
71
|
+
@browser ||= Browser.new(:ua => user_agent, :accept_language => "en-us")
|
72
|
+
end
|
73
|
+
|
74
|
+
def anonymous?
|
75
|
+
voter.nil?
|
76
|
+
end
|
77
|
+
|
78
|
+
def as_json(options = nil)
|
79
|
+
options = {
|
80
|
+
:methods => [:ip],
|
81
|
+
:only => [:referrer, :value, :browser_version, :browser_name, :user_agent, :browser_platform ]
|
82
|
+
}.merge(options || {})
|
83
|
+
|
84
|
+
super
|
85
|
+
end
|
86
|
+
|
87
|
+
def request
|
88
|
+
@request
|
89
|
+
end
|
90
|
+
|
91
|
+
def request=(req)
|
92
|
+
self.ip = req.ip
|
93
|
+
self.user_agent = req.user_agent
|
94
|
+
self.referrer = req.referer
|
95
|
+
self.voteable_id = req.params["id"]
|
96
|
+
@request = req
|
97
|
+
end
|
98
|
+
|
99
|
+
def call
|
100
|
+
if register
|
101
|
+
[self.to_json, 200]
|
102
|
+
else
|
103
|
+
[self.errors.to_json, 422]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def register
|
108
|
+
if voteable && voteable.ballot_box_valid?(self)
|
109
|
+
voteable.current_vote = self
|
110
|
+
|
111
|
+
voteable.run_callbacks(:vote) do
|
112
|
+
errors.empty? && save
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
protected
|
118
|
+
|
119
|
+
def parse_browser
|
120
|
+
self.browser_name = browser.id.to_s
|
121
|
+
self.browser_version = browser.version
|
122
|
+
self.browser_platform = browser.platform.to_s
|
123
|
+
end
|
124
|
+
|
125
|
+
def update_cached_columns
|
126
|
+
if voteable && voteable.ballot_box_cached_column
|
127
|
+
count = voteable.votes.select("SUM(value)")
|
128
|
+
voteable.class.update_all("#{voteable.ballot_box_cached_column} = (#{count.to_sql})", ["id = ?", voteable.id])
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
data/lib/ballot_box.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module BallotBox
|
2
|
+
autoload :Voting, 'ballot_box/voting'
|
3
|
+
autoload :Manager, 'ballot_box/manager'
|
4
|
+
autoload :Config, 'ballot_box/config'
|
5
|
+
autoload :Callbacks, 'ballot_box/callbacks'
|
6
|
+
autoload :Base, 'ballot_box/base'
|
7
|
+
|
8
|
+
module Strategies
|
9
|
+
autoload :Base, 'ballot_box/strategies/base'
|
10
|
+
autoload :Authenticated, 'ballot_box/strategies/authenticated'
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.table_name_prefix
|
14
|
+
'ballot_box_'
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.load_strategy(name)
|
18
|
+
case name.class.name
|
19
|
+
when "Symbol" then "BallotBox::Strategies::#{name.to_s.classify}".constantize
|
20
|
+
when "String" then name.classify.constantize
|
21
|
+
else name
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'ballot_box/engine' if defined?(Rails)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
|
4
|
+
module BallotBox
|
5
|
+
module Generators
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
7
|
+
include Rails::Generators::Migration
|
8
|
+
|
9
|
+
desc "Create ballot_box migration"
|
10
|
+
source_root File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
|
11
|
+
|
12
|
+
# copy migration files
|
13
|
+
def create_migrations
|
14
|
+
migration_template "migrate/create_votes.rb", File.join('db/migrate', "ballot_box_create_votes.rb")
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.next_migration_number(dirname)
|
18
|
+
if ActiveRecord::Base.timestamped_migrations
|
19
|
+
current_time.utc.strftime("%Y%m%d%H%M%S")
|
20
|
+
else
|
21
|
+
"%.3d" % (current_migration_number(dirname) + 1)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.current_time
|
26
|
+
@current_time ||= Time.now
|
27
|
+
@current_time += 1.minute
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class BallotBoxCreateVotes < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :ballot_box_votes do |t|
|
4
|
+
# Voter
|
5
|
+
t.string :voter_type, :limit => 40
|
6
|
+
t.integer :voter_id
|
7
|
+
|
8
|
+
# Voteable
|
9
|
+
t.string :voteable_type, :limit => 40
|
10
|
+
t.integer :voteable_id
|
11
|
+
|
12
|
+
# User info
|
13
|
+
t.integer :ip_address, :limit => 8
|
14
|
+
t.string :user_agent
|
15
|
+
t.string :referrer
|
16
|
+
t.string :browser_name, :limit => 40
|
17
|
+
t.string :browser_version, :limit => 15
|
18
|
+
t.string :browser_platform, :limit => 15
|
19
|
+
|
20
|
+
# Vote value
|
21
|
+
t.integer :value, :default => 1
|
22
|
+
|
23
|
+
t.timestamps
|
24
|
+
end
|
25
|
+
|
26
|
+
add_index :ballot_box_votes, [:voter_type, :voter_id]
|
27
|
+
add_index :ballot_box_votes, [:voteable_type, :voteable_id]
|
28
|
+
add_index :ballot_box_votes, :ip_address
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.down
|
32
|
+
drop_table :ballot_box_votes
|
33
|
+
end
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ballot_box
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Igor Galeta
|
14
|
+
- Pavlo Galeta
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2011-05-18 00:00:00 +03:00
|
20
|
+
default_executable:
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
type: :runtime
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 31
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 1
|
33
|
+
- 2
|
34
|
+
version: 0.1.2
|
35
|
+
name: browser
|
36
|
+
version_requirements: *id001
|
37
|
+
prerelease: false
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
type: :runtime
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
version: "0"
|
49
|
+
name: activemodel
|
50
|
+
version_requirements: *id002
|
51
|
+
prerelease: false
|
52
|
+
description: The BallotBox gem enables visitors to vote for and against voteable objects
|
53
|
+
email: galeta.igor@gmail.com
|
54
|
+
executables: []
|
55
|
+
|
56
|
+
extensions: []
|
57
|
+
|
58
|
+
extra_rdoc_files:
|
59
|
+
- LICENSE
|
60
|
+
- README.rdoc
|
61
|
+
files:
|
62
|
+
- LICENSE
|
63
|
+
- README.rdoc
|
64
|
+
- Rakefile
|
65
|
+
- app/models/ballot_box/vote.rb
|
66
|
+
- lib/ballot_box.rb
|
67
|
+
- lib/ballot_box/base.rb
|
68
|
+
- lib/ballot_box/callbacks.rb
|
69
|
+
- lib/ballot_box/config.rb
|
70
|
+
- lib/ballot_box/engine.rb
|
71
|
+
- lib/ballot_box/manager.rb
|
72
|
+
- lib/ballot_box/strategies/authenticated.rb
|
73
|
+
- lib/ballot_box/strategies/base.rb
|
74
|
+
- lib/ballot_box/version.rb
|
75
|
+
- lib/ballot_box/voting.rb
|
76
|
+
- lib/generators/ballot_box/USAGE
|
77
|
+
- lib/generators/ballot_box/install_generator.rb
|
78
|
+
- lib/generators/ballot_box/templates/migrate/create_votes.rb
|
79
|
+
has_rdoc: true
|
80
|
+
homepage: https://github.com/galetahub/ballot_box
|
81
|
+
licenses: []
|
82
|
+
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
|
86
|
+
require_paths:
|
87
|
+
- lib
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
hash: 3
|
94
|
+
segments:
|
95
|
+
- 0
|
96
|
+
version: "0"
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
hash: 3
|
103
|
+
segments:
|
104
|
+
- 0
|
105
|
+
version: "0"
|
106
|
+
requirements: []
|
107
|
+
|
108
|
+
rubyforge_project:
|
109
|
+
rubygems_version: 1.6.2
|
110
|
+
signing_key:
|
111
|
+
specification_version: 3
|
112
|
+
summary: The BallotBox gem enables visitors to vote for and against voteable objects
|
113
|
+
test_files: []
|
114
|
+
|