acts_as_caesar 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +27 -0
- data/.rvmrc +18 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.textile +61 -0
- data/Rakefile +65 -0
- data/acts_as_caesar.gemspec +38 -0
- data/lib/acts_as_caesar/modules/act_as_candidate.rb +26 -0
- data/lib/acts_as_caesar/objects/candidate.rb +25 -0
- data/lib/acts_as_caesar/objects/key.rb +15 -0
- data/lib/acts_as_caesar/objects/notification.rb +24 -0
- data/lib/acts_as_caesar/objects/persistence.rb +45 -0
- data/lib/acts_as_caesar/objects/vote.rb +77 -0
- data/lib/acts_as_caesar/objects/voter.rb +43 -0
- data/lib/acts_as_caesar/rack.rb +63 -0
- data/lib/acts_as_caesar/rails/engine.rb +11 -0
- data/lib/acts_as_caesar/rails/view_helpers.rb +17 -0
- data/lib/acts_as_caesar/version.rb +3 -0
- data/lib/acts_as_caesar.rb +21 -0
- data/spec/assets/jasmine_helper.js.coffee +5 -0
- data/spec/assets/jquery_helper.js.coffee +2 -0
- data/spec/assets/vote_cache_spec.js.coffee +63 -0
- data/spec/assets/vote_persistence_spec.js.coffee +31 -0
- data/spec/assets/vote_spec.js.coffee +59 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +3 -0
- data/spec/dummy/app/assets/stylesheets/application.css +52 -0
- data/spec/dummy/app/controllers/albums_controller.rb +58 -0
- data/spec/dummy/app/controllers/application_controller.rb +4 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/models/album.rb +15 -0
- data/spec/dummy/app/views/albums/form.html.erb +10 -0
- data/spec/dummy/app/views/albums/index.html.erb +12 -0
- data/spec/dummy/app/views/layouts/application.html.erb +16 -0
- data/spec/dummy/config/application.rb +69 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +30 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +34 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/mongoid.rb +2 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/mongoid.yml +7 -0
- data/spec/dummy/config/routes.rb +7 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dummy/spec/controllers/albums_controller_spec.rb +33 -0
- data/spec/dummy/spec/models/album_spec.rb +8 -0
- data/spec/javascripts/support/jasmine.yml +74 -0
- data/spec/javascripts/support/jasmine_config.rb +23 -0
- data/spec/javascripts/support/jasmine_runner.rb +32 -0
- data/spec/modules/act_as_candidate_spec.rb +43 -0
- data/spec/objects/candidate_spec.rb +37 -0
- data/spec/objects/notification_spec.rb +19 -0
- data/spec/objects/persistence_spec.rb +87 -0
- data/spec/objects/vote_spec.rb +131 -0
- data/spec/objects/voter_spec.rb +80 -0
- data/spec/rack_spec.rb +97 -0
- data/spec/rails/engine_spec.rb +11 -0
- data/spec/rails/view_helpers_spec.rb +30 -0
- data/spec/rails_helper.rb +8 -0
- data/spec/spec_helper.rb +4 -0
- data/vendor/assets/javascripts/acts_as_caesar.js +1 -0
- data/vendor/assets/javascripts/initialization.js.coffee +36 -0
- data/vendor/assets/javascripts/vote.js.coffee +45 -0
- data/vendor/assets/javascripts/vote_cache.js.coffee +42 -0
- data/vendor/assets/javascripts/vote_persistence.js.coffee +51 -0
- data/vendor/assets/javascripts/vote_view.js.coffee +23 -0
- metadata +329 -0
data/.gitignore
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
*.gem
|
2
|
+
.bundle
|
3
|
+
Gemfile.lock
|
4
|
+
pkg/*
|
5
|
+
|
6
|
+
# testing
|
7
|
+
coverage
|
8
|
+
spec/javascripts/compiled
|
9
|
+
spec/javascripts/*.js
|
10
|
+
|
11
|
+
# dummy project
|
12
|
+
spec/dummy/log/*.log
|
13
|
+
spec/dummy/db/*.sqlite3
|
14
|
+
spec/dummy/.DS_Store
|
15
|
+
spec/dummy/*/.DS_Store
|
16
|
+
spec/dummy/*/*/.DS_Store
|
17
|
+
spec/dummy/*/*/*/.DS_Store
|
18
|
+
spec/dummy/*/*/*/*/.DS_Store
|
19
|
+
spec/dummy/test
|
20
|
+
spec/dummy/tmp
|
21
|
+
|
22
|
+
# .DS_Store files
|
23
|
+
.DS_Store
|
24
|
+
*/.DS_Store
|
25
|
+
*/*/.DS_Store
|
26
|
+
*/*/*/.DS_Store
|
27
|
+
*/*/*/*/.DS_Store
|
data/.rvmrc
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
if ! rvm list | grep ruby-1.9.3 ; then
|
4
|
+
rvm install 1.9.3
|
5
|
+
fi
|
6
|
+
|
7
|
+
rvm 1.9.3
|
8
|
+
|
9
|
+
if ! rvm gemset list | grep platformq_acts_as_caesar ; then
|
10
|
+
rvm gemset create platformq_acts_as_caesar
|
11
|
+
fi
|
12
|
+
|
13
|
+
rvm 1.9.3@platformq_acts_as_caesar
|
14
|
+
|
15
|
+
if ! gem list | grep -q bundler ; then
|
16
|
+
gem install --no-ri --no-rdoc bundler
|
17
|
+
bundle install --without production
|
18
|
+
fi
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright 2012 Platform Q Limited
|
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.textile
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
h1. Description
|
2
|
+
|
3
|
+
Acts As Caesar is a voting system - allowing up/down voting of objects.
|
4
|
+
|
5
|
+
h1. Installation
|
6
|
+
|
7
|
+
Simply add acts_as_caesar to your Gemfile
|
8
|
+
|
9
|
+
<pre><code>gem 'acts_as_caesar'</code></pre>
|
10
|
+
|
11
|
+
h2. Requirements
|
12
|
+
|
13
|
+
You must have Redis server installed. We will seek to make the persistence layer database agnostic over time.
|
14
|
+
|
15
|
+
You must have jQuery installed and JavaScript on the client-side is required.
|
16
|
+
|
17
|
+
h2. Configuration
|
18
|
+
|
19
|
+
Acts As Caesar will connect to Redis on localhost, port 6379 (default for Redis server), unless you override this by creating your own Redis connection using the code below (for Rails place this in @config/initializers/redis.rb@):
|
20
|
+
|
21
|
+
<pre><code>$redis = Redis.new(:host => 'localhost', :port => 6379)</code></pre>
|
22
|
+
|
23
|
+
Or for Heroku:
|
24
|
+
|
25
|
+
<pre><code>require 'uri'
|
26
|
+
uri = URI.parse(ENV["REDISTOGO_URL"])
|
27
|
+
$redis = Redis.new(host: uri.host, port: uri.port, password: uri.password)</code></pre>
|
28
|
+
|
29
|
+
h1. Usage
|
30
|
+
|
31
|
+
h2. In Rails projects
|
32
|
+
|
33
|
+
Add the JavaScript library to your app/assets/application.js file:
|
34
|
+
|
35
|
+
<pre><code>//= require acts_as_caesar</code></pre>
|
36
|
+
|
37
|
+
Mount the engine into your routes.rb file, under any path you wish:
|
38
|
+
|
39
|
+
<pre><code>mount ActsAsCaesar::Rails::Engine => "/acts_as_caesar"</code></pre>
|
40
|
+
|
41
|
+
In any ActiveModel-compatible you wish to be updated with their score include the ActAsCandidate module:
|
42
|
+
|
43
|
+
<pre><code>class Review < ActiveRecord::Base
|
44
|
+
include ActsAsCaesar::ActAsCandidate
|
45
|
+
end</code></pre>
|
46
|
+
|
47
|
+
In your views, where you wish to display the voting buttons, call the helper as so:
|
48
|
+
|
49
|
+
<pre><code><%= acts_as_caesar(candidate: review, voter: current_user, base_path: '/acts_as_caesar') %></code></pre>
|
50
|
+
|
51
|
+
*Note:* the above base_path should match the one used in the routes file.
|
52
|
+
|
53
|
+
h1. Testing
|
54
|
+
|
55
|
+
To run the Acts As Caesar tests, simply run @rake@, this will run both RSpec and Jasmine. To run just RSpec use @rake spec@ or to run just Jasmine use @rake jasmine_spec@
|
56
|
+
|
57
|
+
*Note:* the RSpec test suite expects MongoDB to be running on localhost:27017 without authentication. You can customise this in @spec/dummy/config/mongoid.yml@.
|
58
|
+
|
59
|
+
h1. License
|
60
|
+
|
61
|
+
Please see "LICENSE":https://github.com/PlatformQ/acts_as_caesar/blob/master/LICENSE
|
data/Rakefile
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'coffee-script'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
desc "Runs both Rspec and Jasmine specs"
|
7
|
+
task default: ['spec', 'jasmine_spec']
|
8
|
+
|
9
|
+
desc "Compiles CoffeeScript and runs Jasmine specs"
|
10
|
+
task :jasmine_spec do
|
11
|
+
Rake::Task['compile_coffeescript'].invoke
|
12
|
+
Rake::Task['jasmine:ci'].invoke
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "Compiles CoffeeScript files"
|
16
|
+
task :compile_coffeescript do
|
17
|
+
puts "Compiling coffeescript"
|
18
|
+
[
|
19
|
+
{
|
20
|
+
input_dir: 'vendor/assets/javascripts/*',
|
21
|
+
output_dir: 'spec/javascripts/compiled'
|
22
|
+
},
|
23
|
+
{
|
24
|
+
input_dir: 'spec/assets/*',
|
25
|
+
output_dir: 'spec/javascripts'
|
26
|
+
}
|
27
|
+
].each do |opts|
|
28
|
+
output_dir = File.join(File.dirname(__FILE__), opts[:output_dir])
|
29
|
+
|
30
|
+
Dir[File.join(output_dir, "*.js")].each { |f| FileUtils.rm f }
|
31
|
+
FileUtils.makedirs(output_dir)
|
32
|
+
|
33
|
+
Dir[File.join(File.dirname(__FILE__), opts[:input_dir])].each do |source_file|
|
34
|
+
next unless source_file =~ /#{opts[:input_dir]}\/(.*)\.coffee$/
|
35
|
+
print "."
|
36
|
+
output_file = File.join(output_dir, $1)
|
37
|
+
|
38
|
+
begin
|
39
|
+
FileUtils.makedirs(File.dirname(output_file))
|
40
|
+
|
41
|
+
File.open(output_file, 'w') do |f|
|
42
|
+
f.write CoffeeScript.compile File.read(source_file)
|
43
|
+
end
|
44
|
+
rescue Exception => e
|
45
|
+
puts "\nError in #{source_file}"
|
46
|
+
raise
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
puts ""
|
51
|
+
end
|
52
|
+
|
53
|
+
desc "Run all examples"
|
54
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
55
|
+
t.rspec_opts = %w[--color]
|
56
|
+
end
|
57
|
+
|
58
|
+
begin
|
59
|
+
require 'jasmine'
|
60
|
+
load 'jasmine/tasks/jasmine.rake'
|
61
|
+
rescue LoadError
|
62
|
+
task :jasmine do
|
63
|
+
abort "Jasmine is not available. In order to run jasmine, you must: (sudo) gem install jasmine"
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "acts_as_caesar/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "acts_as_caesar"
|
7
|
+
s.version = ActsAsCaesar::VERSION
|
8
|
+
s.authors = ["Ryan Townsend"]
|
9
|
+
s.email = ["ryan@ryantownsend.co.uk"]
|
10
|
+
s.homepage = "http://platformq.co.uk/"
|
11
|
+
s.summary = %q{Acts As Caesar is a voting system - allowing up/down voting of objects.}
|
12
|
+
s.description = %q{Acts As Caesar is a voting system - allowing up/down voting of objects.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "acts_as_caesar"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency 'redis', '3.0.0.rc1'
|
22
|
+
s.add_dependency 'json', '~> 1.6.6'
|
23
|
+
|
24
|
+
# testing only
|
25
|
+
s.add_development_dependency 'rspec', '~> 2.8'
|
26
|
+
s.add_development_dependency 'rack', '~> 1.4'
|
27
|
+
s.add_development_dependency 'rack-test', '~> 0.6'
|
28
|
+
s.add_development_dependency 'simplecov', '~> 0.6'
|
29
|
+
s.add_development_dependency 'jasmine', '~> 1.1'
|
30
|
+
s.add_development_dependency 'coffee-script', '~> 2.2'
|
31
|
+
|
32
|
+
# integration testing only
|
33
|
+
s.add_development_dependency 'rspec-rails', '~> 2.9'
|
34
|
+
s.add_development_dependency 'rails', '~> 3.2'
|
35
|
+
s.add_development_dependency 'mongoid', '~> 2.4'
|
36
|
+
s.add_development_dependency 'bson_ext', '~> 1.5'
|
37
|
+
s.add_development_dependency 'jquery-rails', '~> 2.0'
|
38
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'acts_as_caesar/objects/candidate'
|
2
|
+
require 'acts_as_caesar/objects/notification'
|
3
|
+
|
4
|
+
module ActsAsCaesar
|
5
|
+
module ActAsCandidate
|
6
|
+
def self.included(base)
|
7
|
+
Notification.subscribe(key: /^#{base.name.downcase}\_[\da-f]+$/) do |candidate|
|
8
|
+
candidate.key =~ /\_([\da-f]+)$/ &&
|
9
|
+
base.find($1).update_total_candidate_score
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def as_candidate
|
14
|
+
@as_candidate = ActsAsCaesar::Candidate.new(self)
|
15
|
+
end
|
16
|
+
|
17
|
+
def update_total_candidate_score
|
18
|
+
self.score = total_candidate_score
|
19
|
+
save!
|
20
|
+
end
|
21
|
+
|
22
|
+
def total_candidate_score
|
23
|
+
@total_candidate_score ||= as_candidate.total
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'acts_as_caesar/objects/key'
|
3
|
+
|
4
|
+
module ActsAsCaesar
|
5
|
+
class Candidate
|
6
|
+
def initialize(source)
|
7
|
+
@source = source
|
8
|
+
end
|
9
|
+
|
10
|
+
def key
|
11
|
+
Key.for_source(source)
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_json(*args)
|
15
|
+
key
|
16
|
+
end
|
17
|
+
|
18
|
+
def total
|
19
|
+
@total ||= Persistence.vote_total(self)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
attr_reader :source
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ActsAsCaesar
|
2
|
+
class Key
|
3
|
+
class << self
|
4
|
+
def for_source(source)
|
5
|
+
if source.respond_to?(:acts_as_caesar_key)
|
6
|
+
source.acts_as_caesar_key
|
7
|
+
elsif source.respond_to?(:id)
|
8
|
+
"#{source.class.name.downcase}_#{source.id}"
|
9
|
+
else
|
10
|
+
source.to_s
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module ActsAsCaesar
|
2
|
+
class Notification
|
3
|
+
class << self
|
4
|
+
# publishes a change to a candidate
|
5
|
+
def publish(object)
|
6
|
+
subscriptions.each do |subscription|
|
7
|
+
if object.key =~ subscription[:options][:key]
|
8
|
+
subscription[:process].yield(object)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# subscribes to changes on a candidate
|
14
|
+
def subscribe(options = {}, &block)
|
15
|
+
@subscriptions ||= []
|
16
|
+
@subscriptions << { options: options, process: block }
|
17
|
+
end
|
18
|
+
|
19
|
+
def subscriptions
|
20
|
+
@subscriptions || []
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'redis'
|
2
|
+
require 'acts_as_caesar/objects/notification'
|
3
|
+
|
4
|
+
module ActsAsCaesar
|
5
|
+
class Persistence
|
6
|
+
class << self
|
7
|
+
def connection
|
8
|
+
$redis ||= Redis.new(:host => 'localhost', :port => 6379)
|
9
|
+
end
|
10
|
+
|
11
|
+
def increment_total_votes_on(object)
|
12
|
+
result = connection.incr("#{object.key}_total")
|
13
|
+
Notification.publish(object)
|
14
|
+
result
|
15
|
+
end
|
16
|
+
|
17
|
+
def decrement_total_votes_on(object)
|
18
|
+
result = connection.decr("#{object.key}_total")
|
19
|
+
Notification.publish(object)
|
20
|
+
result
|
21
|
+
end
|
22
|
+
|
23
|
+
def vote_total(object)
|
24
|
+
(connection.get("#{object.key}_total") || 0).to_i
|
25
|
+
end
|
26
|
+
|
27
|
+
def vote_value(options)
|
28
|
+
key = [options[:candidate], options[:voter]].map(&:key).join("_")
|
29
|
+
(connection.get(key) || 0).to_i
|
30
|
+
end
|
31
|
+
|
32
|
+
def set_vote_value(value, options = {})
|
33
|
+
# load the keys
|
34
|
+
key = [options[:candidate], options[:voter]].map(&:key).join("_")
|
35
|
+
# if the value was zero, delete it
|
36
|
+
if value == 0
|
37
|
+
connection.del(key)
|
38
|
+
# if the value isn't zero, update it
|
39
|
+
else
|
40
|
+
connection.set(key, value)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'acts_as_caesar/objects/persistence'
|
2
|
+
|
3
|
+
module ActsAsCaesar
|
4
|
+
class Vote
|
5
|
+
class << self
|
6
|
+
def find(options)
|
7
|
+
from_value(Persistence.vote_value(options), options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def from_value(value, options = {})
|
11
|
+
case value
|
12
|
+
when 1
|
13
|
+
PositiveVote
|
14
|
+
when -1
|
15
|
+
NegativeVote
|
16
|
+
else
|
17
|
+
NoVote
|
18
|
+
end.new(options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def value
|
22
|
+
0
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def voter
|
27
|
+
@attrs[:voter]
|
28
|
+
end
|
29
|
+
|
30
|
+
def candidate
|
31
|
+
@attrs[:candidate]
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(attrs)
|
35
|
+
@attrs = attrs
|
36
|
+
end
|
37
|
+
|
38
|
+
def value
|
39
|
+
self.class.value
|
40
|
+
end
|
41
|
+
|
42
|
+
def update(new_value)
|
43
|
+
raise ArgumentError, "Only accepts Integer objects" unless new_value.kind_of?(Integer)
|
44
|
+
# don't change if the values already match
|
45
|
+
return self if new_value == value
|
46
|
+
# set the new value
|
47
|
+
Persistence.set_vote_value(new_value, @attrs)
|
48
|
+
# if the value has increased
|
49
|
+
if new_value > value
|
50
|
+
Persistence.increment_total_votes_on(candidate)
|
51
|
+
# if the value has decreased
|
52
|
+
elsif new_value < value
|
53
|
+
Persistence.decrement_total_votes_on(candidate)
|
54
|
+
end
|
55
|
+
# return the new value object
|
56
|
+
self.class.from_value(new_value, @attrs)
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_json(*args)
|
60
|
+
{
|
61
|
+
voter: voter.to_json,
|
62
|
+
candidate: candidate.to_json,
|
63
|
+
value: value
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class NoVote < Vote; end
|
69
|
+
|
70
|
+
class PositiveVote < Vote
|
71
|
+
def self.value; 1; end
|
72
|
+
end
|
73
|
+
|
74
|
+
class NegativeVote < Vote
|
75
|
+
def self.value; -1; end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'acts_as_caesar/objects/vote'
|
2
|
+
require 'acts_as_caesar/objects/key'
|
3
|
+
|
4
|
+
module ActsAsCaesar
|
5
|
+
class Voter
|
6
|
+
def initialize(source)
|
7
|
+
@source = source || NoVoter.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def has_voted_on?(candidate)
|
11
|
+
!vote_placed_on(candidate).kind_of?(NoVote)
|
12
|
+
end
|
13
|
+
|
14
|
+
def vote_value_for(candidate)
|
15
|
+
vote_placed_on(candidate).value
|
16
|
+
end
|
17
|
+
|
18
|
+
def update_vote(candidate, value)
|
19
|
+
vote_placed_on(candidate).update(value)
|
20
|
+
end
|
21
|
+
|
22
|
+
def vote_placed_on(candidate)
|
23
|
+
Vote.find(voter: self, candidate: candidate)
|
24
|
+
end
|
25
|
+
|
26
|
+
def key
|
27
|
+
Key.for_source(source)
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_json(*args)
|
31
|
+
key
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
attr_reader :source
|
36
|
+
end
|
37
|
+
|
38
|
+
class NoVoter
|
39
|
+
def acts_as_caesar_key
|
40
|
+
''
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'acts_as_caesar/objects/voter'
|
2
|
+
require 'acts_as_caesar/objects/candidate'
|
3
|
+
require 'json'
|
4
|
+
require 'rack/request'
|
5
|
+
|
6
|
+
module Rack
|
7
|
+
class ActsAsCaesar
|
8
|
+
attr_reader :request, :app
|
9
|
+
|
10
|
+
def initialize(app = nil)
|
11
|
+
@app = app
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
# pass through app if response already exists
|
16
|
+
if app
|
17
|
+
status, headers, body = app.call(env)
|
18
|
+
return [status, headers, body] unless status == 404
|
19
|
+
end
|
20
|
+
|
21
|
+
@request = Rack::Request.new(env)
|
22
|
+
if request.path =~ /\/votes\/(\w+)\/([\w\-]+).json/
|
23
|
+
if request.get?
|
24
|
+
render_votes($1, $2)
|
25
|
+
elsif request.post?
|
26
|
+
update_vote($1, $2, request.params['value'].to_i)
|
27
|
+
else
|
28
|
+
method_not_allowed
|
29
|
+
end
|
30
|
+
else
|
31
|
+
not_found
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def render_votes(voter, candidates)
|
37
|
+
voter = ::ActsAsCaesar::Voter.new(voter)
|
38
|
+
votes = candidates.split('-').map do |c|
|
39
|
+
::ActsAsCaesar::Candidate.new(c)
|
40
|
+
end.map do |c|
|
41
|
+
voter.vote_placed_on(c)
|
42
|
+
end
|
43
|
+
# return an array of vote objects
|
44
|
+
[200, {"Content-Type" => "application/json"}, [JSON.generate(votes.map(&:to_json))]]
|
45
|
+
end
|
46
|
+
|
47
|
+
def update_vote(voter, candidate, value)
|
48
|
+
voter = ::ActsAsCaesar::Voter.new(voter)
|
49
|
+
candidate = ::ActsAsCaesar::Candidate.new(candidate)
|
50
|
+
vote = voter.update_vote(candidate, value)
|
51
|
+
# return the vote object
|
52
|
+
[200, {"Content-Type" => "application/json"}, [JSON.generate(vote.to_json)]]
|
53
|
+
end
|
54
|
+
|
55
|
+
def not_found
|
56
|
+
[404, {}, ["ActsAsCaesar - Action not found"]]
|
57
|
+
end
|
58
|
+
|
59
|
+
def method_not_allowed
|
60
|
+
[405, {}, ["ActsAsCaesar - Method not allowed"]]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'acts_as_caesar/objects/candidate'
|
2
|
+
require 'acts_as_caesar/objects/voter'
|
3
|
+
|
4
|
+
module ActsAsCaesar
|
5
|
+
module Rails
|
6
|
+
module ViewHelpers
|
7
|
+
def acts_as_caesar(options = {})
|
8
|
+
content_tag(:div, options[:content] || '', (options[:html] || {}).merge({
|
9
|
+
"data-candidate" => Candidate.new(options[:candidate]).key,
|
10
|
+
"data-voter" => Voter.new(options[:voter]).key,
|
11
|
+
"data-acts-as-caesar" => 'uninit',
|
12
|
+
"data-base-path" => options[:base_path]
|
13
|
+
}.delete_if { |k,v| v.blank? }))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "acts_as_caesar/version"
|
2
|
+
require "acts_as_caesar/objects/persistence"
|
3
|
+
require "acts_as_caesar/objects/candidate"
|
4
|
+
require "acts_as_caesar/objects/voter"
|
5
|
+
require "acts_as_caesar/modules/act_as_candidate"
|
6
|
+
|
7
|
+
if defined?(::Rails)
|
8
|
+
require "acts_as_caesar/rails/engine"
|
9
|
+
if defined?(::ActiveSupport)
|
10
|
+
require "acts_as_caesar/rails/view_helpers"
|
11
|
+
::ActiveSupport.on_load(:action_view) do
|
12
|
+
include ActsAsCaesar::Rails::ViewHelpers
|
13
|
+
end
|
14
|
+
end
|
15
|
+
elsif defined?(Rack)
|
16
|
+
require "acts_as_caesar/rack"
|
17
|
+
end
|
18
|
+
|
19
|
+
module ActsAsCaesar
|
20
|
+
# Your code goes here...
|
21
|
+
end
|