acts_as_caesar 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.
- 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
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require_relative '../../lib/acts_as_caesar/objects/persistence'
|
|
3
|
+
|
|
4
|
+
describe ActsAsCaesar::Persistence do
|
|
5
|
+
context "the class" do
|
|
6
|
+
subject { ActsAsCaesar::Persistence }
|
|
7
|
+
|
|
8
|
+
it "should have a connection" do
|
|
9
|
+
subject.connection.should be_kind_of Redis
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
context "when loading a vote value" do
|
|
13
|
+
let(:keys) do
|
|
14
|
+
{ candidate: mock('candidate', key: 'candidate_1'), voter: mock('voter', key: 'voter_1') }
|
|
15
|
+
end
|
|
16
|
+
let(:key_array) { keys.values.map(&:key) }
|
|
17
|
+
|
|
18
|
+
context "and the value is positive" do
|
|
19
|
+
it "should find vote values when provided with a set of keys" do
|
|
20
|
+
subject.connection.set(key_array.join("_"), 1)
|
|
21
|
+
subject.vote_value(keys).should == 1
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
context "and the value is negative" do
|
|
26
|
+
it "should find vote values when provided with a set of keys" do
|
|
27
|
+
subject.connection.set(key_array.join("_"), -1)
|
|
28
|
+
subject.vote_value(keys).should == -1
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context "and no value is set" do
|
|
33
|
+
it "should find vote values when provided with a set of keys" do
|
|
34
|
+
subject.connection.del(key_array.join("_"))
|
|
35
|
+
subject.vote_value(keys).should == 0
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
context "when incrementing the total" do
|
|
41
|
+
let(:candidate) { mock('candidate', key: 'candidate_1') }
|
|
42
|
+
|
|
43
|
+
it "should set the total to the same value + 1" do
|
|
44
|
+
current_total = subject.vote_total(candidate)
|
|
45
|
+
subject.increment_total_votes_on(candidate)
|
|
46
|
+
subject.vote_total(candidate).should == current_total + 1
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
context "when decrementing the total" do
|
|
51
|
+
let(:candidate) { mock('candidate', key: 'candidate_1') }
|
|
52
|
+
|
|
53
|
+
it "should set the total to the same value - 1" do
|
|
54
|
+
current_total = subject.vote_total(candidate)
|
|
55
|
+
subject.decrement_total_votes_on(candidate)
|
|
56
|
+
subject.vote_total(candidate).should == current_total - 1
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
context "when setting a vote value" do
|
|
61
|
+
let(:keys) do
|
|
62
|
+
{ candidate: mock('candidate', key: 'candidate_1'), voter: mock('voter', key: 'voter_1') }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
context "and the value is positive" do
|
|
66
|
+
it "should set the value to 1" do
|
|
67
|
+
subject.set_vote_value(1, keys)
|
|
68
|
+
subject.vote_value(keys).should == 1
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
context "and the value is negative" do
|
|
73
|
+
it "should set the value to -1" do
|
|
74
|
+
subject.set_vote_value(-1, keys)
|
|
75
|
+
subject.vote_value(keys).should == -1
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
context "and no value is set" do
|
|
80
|
+
it "should set delete value" do
|
|
81
|
+
subject.set_vote_value(0, keys)
|
|
82
|
+
subject.vote_value(keys).should == 0
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require_relative '../../lib/acts_as_caesar/objects/vote'
|
|
3
|
+
|
|
4
|
+
describe ActsAsCaesar::Vote do
|
|
5
|
+
let(:voter) { ActsAsCaesar::Voter.new(mock('user')) }
|
|
6
|
+
let(:candidate) { ActsAsCaesar::Candidate.new(mock('answer')) }
|
|
7
|
+
|
|
8
|
+
subject { ActsAsCaesar::Vote }
|
|
9
|
+
|
|
10
|
+
context "when finding a vote" do
|
|
11
|
+
it "should return no vote when nil" do
|
|
12
|
+
ActsAsCaesar::Persistence.should_receive(:vote_value).and_return(nil)
|
|
13
|
+
subject.find(voter: voter, candidate: candidate).should be_kind_of ActsAsCaesar::NoVote
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "should return a positive vote when 1" do
|
|
17
|
+
ActsAsCaesar::Persistence.should_receive(:vote_value).and_return(1)
|
|
18
|
+
subject.find(voter: voter, candidate: candidate).should be_kind_of ActsAsCaesar::PositiveVote
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "should return a negative vote when -1" do
|
|
22
|
+
ActsAsCaesar::Persistence.should_receive(:vote_value).and_return(-1)
|
|
23
|
+
subject.find(voter: voter, candidate: candidate).should be_kind_of ActsAsCaesar::NegativeVote
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe ActsAsCaesar::NoVote do
|
|
29
|
+
let(:candidate) { mock('candidate') }
|
|
30
|
+
let(:params) { { candidate: candidate, voter: mock('voter') } }
|
|
31
|
+
|
|
32
|
+
subject { ActsAsCaesar::NoVote.new(params) }
|
|
33
|
+
|
|
34
|
+
context "when updating the vote to 1" do
|
|
35
|
+
before do
|
|
36
|
+
ActsAsCaesar::Persistence.should_receive(:set_vote_value).with(1, params).and_return(true)
|
|
37
|
+
ActsAsCaesar::Persistence.should_receive(:increment_total_votes_on).with(candidate).and_return(true)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "should return a positive vote" do
|
|
41
|
+
subject.update(1).should be_kind_of ActsAsCaesar::PositiveVote
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
context "when updating the vote to 0" do
|
|
46
|
+
it "should return a non-vote" do
|
|
47
|
+
subject.update(0).should be_kind_of ActsAsCaesar::NoVote
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
context "when updating the vote to -1" do
|
|
52
|
+
before do
|
|
53
|
+
ActsAsCaesar::Persistence.should_receive(:set_vote_value).with(-1, params).and_return(true)
|
|
54
|
+
ActsAsCaesar::Persistence.should_receive(:decrement_total_votes_on).with(candidate).and_return(true)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "should return a negative vote" do
|
|
58
|
+
subject.update(-1).should be_kind_of ActsAsCaesar::NegativeVote
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
describe ActsAsCaesar::PositiveVote do
|
|
64
|
+
let(:candidate) { mock('candidate') }
|
|
65
|
+
let(:params) { { candidate: candidate, voter: mock('voter') } }
|
|
66
|
+
|
|
67
|
+
subject { ActsAsCaesar::PositiveVote.new(params) }
|
|
68
|
+
|
|
69
|
+
context "when updating the vote to 1" do
|
|
70
|
+
it "should return a positive vote" do
|
|
71
|
+
subject.update(1).should be_kind_of ActsAsCaesar::PositiveVote
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
context "when updating the vote to 0" do
|
|
76
|
+
before do
|
|
77
|
+
ActsAsCaesar::Persistence.should_receive(:set_vote_value).with(0, params).and_return(true)
|
|
78
|
+
ActsAsCaesar::Persistence.should_receive(:decrement_total_votes_on).with(candidate).and_return(true)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "should return a non-vote" do
|
|
82
|
+
subject.update(0).should be_kind_of ActsAsCaesar::NoVote
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
context "when updating the vote to -1" do
|
|
87
|
+
before do
|
|
88
|
+
ActsAsCaesar::Persistence.should_receive(:set_vote_value).with(-1, params).and_return(true)
|
|
89
|
+
ActsAsCaesar::Persistence.should_receive(:decrement_total_votes_on).with(candidate).and_return(true)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it "should return a negative vote" do
|
|
93
|
+
subject.update(-1).should be_kind_of ActsAsCaesar::NegativeVote
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
describe ActsAsCaesar::NegativeVote do
|
|
99
|
+
let(:candidate) { mock('candidate') }
|
|
100
|
+
let(:params) { { candidate: candidate, voter: mock('voter') } }
|
|
101
|
+
|
|
102
|
+
subject { ActsAsCaesar::NegativeVote.new(params) }
|
|
103
|
+
|
|
104
|
+
context "when updating the vote to 1" do
|
|
105
|
+
before do
|
|
106
|
+
ActsAsCaesar::Persistence.should_receive(:set_vote_value).with(1, params).and_return(true)
|
|
107
|
+
ActsAsCaesar::Persistence.should_receive(:increment_total_votes_on).with(candidate).and_return(true)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "should return a positive vote" do
|
|
111
|
+
subject.update(1).should be_kind_of ActsAsCaesar::PositiveVote
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
context "when updating the vote to 0" do
|
|
116
|
+
before do
|
|
117
|
+
ActsAsCaesar::Persistence.should_receive(:set_vote_value).with(0, params).and_return(true)
|
|
118
|
+
ActsAsCaesar::Persistence.should_receive(:increment_total_votes_on).with(candidate).and_return(true)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "should return a non-vote" do
|
|
122
|
+
subject.update(0).should be_kind_of ActsAsCaesar::NoVote
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
context "when updating the vote to -1" do
|
|
127
|
+
it "should return a negative vote" do
|
|
128
|
+
subject.update(-1).should be_kind_of ActsAsCaesar::NegativeVote
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require_relative '../../lib/acts_as_caesar/objects/voter'
|
|
3
|
+
|
|
4
|
+
describe ActsAsCaesar::Voter do
|
|
5
|
+
let(:candidate) { mock('candidate', key: 'candidate_1') }
|
|
6
|
+
let(:source) { mock('user', key: 'user_1') }
|
|
7
|
+
let(:voter) { ActsAsCaesar::Voter.new(source) }
|
|
8
|
+
subject { voter }
|
|
9
|
+
|
|
10
|
+
context "when a voter is created with a nil source" do
|
|
11
|
+
let(:source) { nil }
|
|
12
|
+
it "should store a NoVoter object" do
|
|
13
|
+
voter.send(:source).should be_kind_of ActsAsCaesar::NoVoter
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
context "when quering vote status" do
|
|
18
|
+
context "when a positive vote has been placed for the candidate" do
|
|
19
|
+
before(:each) do
|
|
20
|
+
ActsAsCaesar::Vote.should_receive(:find).and_return(ActsAsCaesar::PositiveVote.new(voter: source, candidate: candidate))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "should return true for has_voted_on?" do
|
|
24
|
+
voter.has_voted_on?(candidate).should be_true
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "should return 1 for vote_value_for" do
|
|
28
|
+
voter.vote_value_for(candidate).should == 1
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context "when a negative vote has been placed for the candidate" do
|
|
33
|
+
before(:each) do
|
|
34
|
+
ActsAsCaesar::Vote.should_receive(:find).and_return(ActsAsCaesar::NegativeVote.new(voter: source, candidate: candidate))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "should return true for has_voted_on?" do
|
|
38
|
+
voter.has_voted_on?(candidate).should be_true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "should return -1 for vote_value_for" do
|
|
42
|
+
voter.vote_value_for(candidate).should == -1
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
context "when no vote has been placed for the candidate" do
|
|
47
|
+
before(:each) do
|
|
48
|
+
ActsAsCaesar::Vote.should_receive(:find).and_return(ActsAsCaesar::NoVote.new(voter: source, candidate: candidate))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "should return false for has_voted_on?" do
|
|
52
|
+
voter.has_voted_on?(candidate).should be_false
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "should return 0 for vote_value_for" do
|
|
56
|
+
voter.vote_value_for(candidate).should == 0
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
context "when creating a vote" do
|
|
62
|
+
before(:each) do
|
|
63
|
+
ActsAsCaesar::Vote.should_receive(:find).and_return(ActsAsCaesar::NoVote.new(voter: source, candidate: candidate))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "should return a positive vote" do
|
|
67
|
+
voter.update_vote(candidate, 1).should be_kind_of ActsAsCaesar::PositiveVote
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
context "when updating a vote to be negative" do
|
|
72
|
+
before(:each) do
|
|
73
|
+
ActsAsCaesar::Vote.should_receive(:find).and_return(ActsAsCaesar::PositiveVote.new(voter: source, candidate: candidate))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "should return a negative vote" do
|
|
77
|
+
voter.update_vote(candidate, -1).should be_kind_of ActsAsCaesar::NegativeVote
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
data/spec/rack_spec.rb
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require_relative '../lib/acts_as_caesar/rack'
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'rack/test'
|
|
5
|
+
|
|
6
|
+
ENV['RACK_ENV'] = 'test'
|
|
7
|
+
|
|
8
|
+
describe Rack::ActsAsCaesar do
|
|
9
|
+
include Rack::Test::Methods
|
|
10
|
+
|
|
11
|
+
let(:rails_response) { [404, {}, []] }
|
|
12
|
+
let(:rails_app) do
|
|
13
|
+
m = mock('rails_app')
|
|
14
|
+
m.stub(:call).and_return(rails_response)
|
|
15
|
+
m
|
|
16
|
+
end
|
|
17
|
+
let(:app) { Rack::ActsAsCaesar.new(rails_app) }
|
|
18
|
+
|
|
19
|
+
context "when Rails has already rendered a response" do
|
|
20
|
+
let(:rails_response) { [200, {}, ["Some response"]] }
|
|
21
|
+
|
|
22
|
+
it "should return the response when calling" do
|
|
23
|
+
app.call({}).should == rails_response
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
context "GET /unknown-path" do
|
|
28
|
+
before do
|
|
29
|
+
get '/unknown-path'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "should return 404" do
|
|
33
|
+
last_response.should be_not_found
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
context "GET /votes/1/1-2-3.json" do
|
|
38
|
+
before do
|
|
39
|
+
get '/votes/1/1-2-3.json'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "should return 200" do
|
|
43
|
+
last_response.should be_ok
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
context "the JSON response" do
|
|
47
|
+
subject { JSON.parse(last_response.body) }
|
|
48
|
+
|
|
49
|
+
it { should be_kind_of Array }
|
|
50
|
+
|
|
51
|
+
it "should contain 3 votes" do
|
|
52
|
+
subject.size.should == 3
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
context "each vote" do
|
|
56
|
+
it "should have a candidate" do
|
|
57
|
+
subject.each do |vote|
|
|
58
|
+
['1', '2', '3'].should include vote['candidate'].to_s
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "should have a voter" do
|
|
63
|
+
subject.each do |vote|
|
|
64
|
+
vote['voter'].should == '1'
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
context "POST /votes/voter_1/candidate_1.json" do
|
|
72
|
+
before do
|
|
73
|
+
post '/votes/voter_1/candidate_1.json', value: '-1'
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "should return a 200" do
|
|
77
|
+
last_response.should be_ok
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it "should return a JSON encoded string of the new vote" do
|
|
81
|
+
vote = JSON.parse(last_response.body)
|
|
82
|
+
vote['candidate'].should == 'candidate_1'
|
|
83
|
+
vote['voter'].should == 'voter_1'
|
|
84
|
+
vote['value'].should == -1
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
context "PUT /votes/voter_1/candidate_1.json" do
|
|
89
|
+
before do
|
|
90
|
+
put '/votes/voter_1/candidate_1.json', value: '-1'
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "should return a 405" do
|
|
94
|
+
last_response.should be_method_not_allowed
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
require_relative '../../lib/acts_as_caesar/rails/engine'
|
|
4
|
+
|
|
5
|
+
describe ActsAsCaesar::Rails::Engine do
|
|
6
|
+
subject { ActsAsCaesar::Rails::Engine }
|
|
7
|
+
|
|
8
|
+
it "should subclass Rails::Engine" do
|
|
9
|
+
subject.superclass.should == ::Rails::Engine
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'rails_helper'
|
|
2
|
+
require 'action_view'
|
|
3
|
+
|
|
4
|
+
require_relative '../../lib/acts_as_caesar/rails/view_helpers'
|
|
5
|
+
|
|
6
|
+
class TestViewHelpers < ActionView::Base
|
|
7
|
+
include ActsAsCaesar::Rails::ViewHelpers
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
describe ActsAsCaesar::Rails::ViewHelpers do
|
|
11
|
+
let(:helper) { TestViewHelpers.new }
|
|
12
|
+
|
|
13
|
+
describe "#acts_as_caesar" do
|
|
14
|
+
it "should render the candidate key" do
|
|
15
|
+
candidate = mock('candidate', acts_as_caesar_key: 'my_key')
|
|
16
|
+
tag = helper.acts_as_caesar(candidate: candidate)
|
|
17
|
+
tag.should include 'data-candidate="my_key"'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "should allow nil voters, but not render empty values" do
|
|
21
|
+
tag = helper.acts_as_caesar(candidate: "candidate_1")
|
|
22
|
+
tag.should_not include 'data-voter=""'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "should accept custom HTML attributes" do
|
|
26
|
+
tag = helper.acts_as_caesar(candidate: "candidate_1", voter: "user_1", html: { class: 'test' })
|
|
27
|
+
tag.should include 'class="test"'
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//= require_tree .
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
window.ActsAsCaesar = {}
|
|
2
|
+
|
|
3
|
+
window.ActsAsCaesar.init = (options) ->
|
|
4
|
+
jQuery(document.body).on('init', options.selector, ->
|
|
5
|
+
$aac = jQuery(this)
|
|
6
|
+
# set the base path
|
|
7
|
+
window.ActsAsCaesar.base_path = $aac.data('base-path')
|
|
8
|
+
# create buttons
|
|
9
|
+
$button_upvote = jQuery("<button></button>").addClass('upvote').text('Upvote').attr('value', 1)
|
|
10
|
+
$button_downvote = jQuery("<button></button>").addClass('downvote').text('Downvote').attr('value', -1)
|
|
11
|
+
# append buttons, update status and set value
|
|
12
|
+
$aac.append($button_upvote, $button_downvote).attr('data-acts-as-caesar', 'init')
|
|
13
|
+
# link the vote up
|
|
14
|
+
VoteView.watch($aac)
|
|
15
|
+
|
|
16
|
+
).on('click', "#{options.selector.replace(/uninit/, 'init')} button", (event) ->
|
|
17
|
+
$button = jQuery(this)
|
|
18
|
+
$aac = $button.parent()
|
|
19
|
+
# set the value to this value
|
|
20
|
+
if $aac.attr('data-current-value').toString() == $button.val()
|
|
21
|
+
$aac.data('view').update(0)
|
|
22
|
+
else
|
|
23
|
+
$aac.data('view').update($button.val())
|
|
24
|
+
# should this be nested within a form, we don't want it to submit
|
|
25
|
+
event.stopPropagation()
|
|
26
|
+
)
|
|
27
|
+
# initialize the elements
|
|
28
|
+
jQuery(options.selector).trigger('init')
|
|
29
|
+
|
|
30
|
+
if typeof jQuery == 'undefined'
|
|
31
|
+
console.log "jQuery must be installed to use acts_as_caesar"
|
|
32
|
+
else if typeof window.ActsAsCaesar != 'undefined'
|
|
33
|
+
jQuery ->
|
|
34
|
+
ActsAsCaesar.init {
|
|
35
|
+
selector: "[data-acts-as-caesar='uninit'][data-voter]"
|
|
36
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
class window.Vote
|
|
2
|
+
value: 0
|
|
3
|
+
|
|
4
|
+
@find = (voter, candidate) ->
|
|
5
|
+
VotePersistence.read(voter, candidate)
|
|
6
|
+
|
|
7
|
+
@load = (voter, candidate, callback) ->
|
|
8
|
+
VotePersistence.read(voter, candidate, callback)
|
|
9
|
+
|
|
10
|
+
# class method: creates a vote based on the passed value
|
|
11
|
+
# returns: a Positive/Negative/Pending/NoVote object
|
|
12
|
+
@from_value = (vote_value, voter, candidate) ->
|
|
13
|
+
switch vote_value
|
|
14
|
+
when 1
|
|
15
|
+
new PositiveVote(voter, candidate)
|
|
16
|
+
when -1
|
|
17
|
+
new NegativeVote(voter, candidate)
|
|
18
|
+
when null
|
|
19
|
+
new PendingVote(voter, candidate)
|
|
20
|
+
else
|
|
21
|
+
new NoVote(voter, candidate)
|
|
22
|
+
|
|
23
|
+
# instance method: initializer to store voter and candidate
|
|
24
|
+
# returns: null
|
|
25
|
+
constructor: (@voter, @candidate) ->
|
|
26
|
+
|
|
27
|
+
# instance method: changes this vote to a new value
|
|
28
|
+
# returns: a Positive/Negative/Pending/NoVote object
|
|
29
|
+
update: (new_value) ->
|
|
30
|
+
# ensure it's an integer
|
|
31
|
+
new_value = parseInt(new_value)
|
|
32
|
+
# write the value to the cache
|
|
33
|
+
VotePersistence.write(new_value, @voter, @candidate)
|
|
34
|
+
# return a new vote
|
|
35
|
+
Vote.from_value(new_value, @voter, @candidate)
|
|
36
|
+
|
|
37
|
+
class window.NoVote extends Vote
|
|
38
|
+
|
|
39
|
+
class window.PendingVote extends Vote
|
|
40
|
+
|
|
41
|
+
class window.PositiveVote extends Vote
|
|
42
|
+
value: 1
|
|
43
|
+
|
|
44
|
+
class window.NegativeVote extends Vote
|
|
45
|
+
value: -1
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
class window.VoteCache
|
|
2
|
+
# property: the object/class used to cache the values
|
|
3
|
+
# returns: see above
|
|
4
|
+
@storageEngine = window.localStorage
|
|
5
|
+
|
|
6
|
+
# property: tests whether the engine is supported
|
|
7
|
+
# returns: true or false
|
|
8
|
+
@enabled = ( (engine) ->
|
|
9
|
+
try
|
|
10
|
+
return !!engine.getItem
|
|
11
|
+
catch e
|
|
12
|
+
return false
|
|
13
|
+
)(@storageEngine)
|
|
14
|
+
|
|
15
|
+
# class method: writes a value to the cache
|
|
16
|
+
# returns: a CachedVote or NoCachedVote object
|
|
17
|
+
@write = (value, voter, candidate) ->
|
|
18
|
+
if @enabled
|
|
19
|
+
vote = new CachedVote(value)
|
|
20
|
+
@storageEngine.setItem(voter + '-' + candidate, vote.value)
|
|
21
|
+
vote
|
|
22
|
+
else
|
|
23
|
+
new NoCachedVote()
|
|
24
|
+
|
|
25
|
+
# class method: reads a value from the cache
|
|
26
|
+
# returns: a CachedVote or NoCachedVote object
|
|
27
|
+
@read = (voter, candidate) ->
|
|
28
|
+
unless @enabled
|
|
29
|
+
return new NoCachedVote()
|
|
30
|
+
|
|
31
|
+
value = @storageEngine.getItem(voter + '-' + candidate)
|
|
32
|
+
if value
|
|
33
|
+
new CachedVote(value)
|
|
34
|
+
else
|
|
35
|
+
new NoCachedVote()
|
|
36
|
+
|
|
37
|
+
class window.CachedVote
|
|
38
|
+
constructor: (value) ->
|
|
39
|
+
@value = parseInt(value)
|
|
40
|
+
|
|
41
|
+
class window.NoCachedVote
|
|
42
|
+
value: 0
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
if typeof window.ActsAsCaesar != 'undefined'
|
|
2
|
+
window.ActsAsCaesar ||= {}
|
|
3
|
+
window.ActsAsCaesar.base_path ||= ''
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class window.VotePersistence
|
|
7
|
+
# class method: generates a url to contact the server with
|
|
8
|
+
@url = (voter, candidates) ->
|
|
9
|
+
if candidates instanceof Array
|
|
10
|
+
candidates = candidates.sort().join('-')
|
|
11
|
+
[window.ActsAsCaesar.base_path, 'votes', voter, candidates].join('/') + '.json'
|
|
12
|
+
|
|
13
|
+
# class method: loads the votes asynchronously via AJAX
|
|
14
|
+
@fetch = (voter, candidates, callback) ->
|
|
15
|
+
jQuery.ajax {
|
|
16
|
+
url: this.url(voter, candidates),
|
|
17
|
+
type: 'GET'
|
|
18
|
+
dataType: 'json'
|
|
19
|
+
success: (votes) ->
|
|
20
|
+
for vote in votes
|
|
21
|
+
VotePersistence.cache(vote.value, vote.voter, vote.candidate)
|
|
22
|
+
if callback
|
|
23
|
+
callback(Vote.from_value(vote.value, vote.voter, vote.candidatee))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# class method: reads a vote either from cache or via AJAX
|
|
27
|
+
@read = (voter, candidate, callback) ->
|
|
28
|
+
cached_vote = VoteCache.read(voter, candidate)
|
|
29
|
+
if cached_vote instanceof NoCachedVote
|
|
30
|
+
@fetch(voter, candidate, callback)
|
|
31
|
+
Vote.from_value(null, voter, candidate)
|
|
32
|
+
else
|
|
33
|
+
vote = Vote.from_value(cached_vote.value, voter, candidate)
|
|
34
|
+
callback(vote) if callback
|
|
35
|
+
vote
|
|
36
|
+
|
|
37
|
+
# class method: caches a new value for a vote
|
|
38
|
+
@cache = (value, voter, candidate) ->
|
|
39
|
+
# cache the value
|
|
40
|
+
VoteCache.write(value, voter, candidate)
|
|
41
|
+
|
|
42
|
+
# class method: updates a value for a vote
|
|
43
|
+
@write = (value, voter, candidate) ->
|
|
44
|
+
VotePersistence.cache(value, voter, candidate)
|
|
45
|
+
# write the value back to the server
|
|
46
|
+
jQuery.ajax {
|
|
47
|
+
url: this.url(voter, candidate),
|
|
48
|
+
type: 'POST'
|
|
49
|
+
dataType: 'json',
|
|
50
|
+
data: { value: value }
|
|
51
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class window.VoteView
|
|
2
|
+
@watch = (element) ->
|
|
3
|
+
view = new VoteView(element)
|
|
4
|
+
element.data('view', view)
|
|
5
|
+
|
|
6
|
+
constructor: (element) ->
|
|
7
|
+
@element = element
|
|
8
|
+
@voter = @element.data('voter')
|
|
9
|
+
@candidate = @element.data('candidate')
|
|
10
|
+
@update_status()
|
|
11
|
+
|
|
12
|
+
set_vote: (vote) ->
|
|
13
|
+
@vote = vote
|
|
14
|
+
@element.attr('data-current-value', vote.value)
|
|
15
|
+
|
|
16
|
+
update_status: ->
|
|
17
|
+
view = @
|
|
18
|
+
@set_vote(Vote.load(@voter, @candidate, (vote) ->
|
|
19
|
+
view.set_vote(vote)
|
|
20
|
+
))
|
|
21
|
+
|
|
22
|
+
update: (value) ->
|
|
23
|
+
@set_vote(@vote.update(value))
|