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.
Files changed (84) hide show
  1. data/.gitignore +27 -0
  2. data/.rvmrc +18 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +22 -0
  5. data/README.textile +61 -0
  6. data/Rakefile +65 -0
  7. data/acts_as_caesar.gemspec +38 -0
  8. data/lib/acts_as_caesar/modules/act_as_candidate.rb +26 -0
  9. data/lib/acts_as_caesar/objects/candidate.rb +25 -0
  10. data/lib/acts_as_caesar/objects/key.rb +15 -0
  11. data/lib/acts_as_caesar/objects/notification.rb +24 -0
  12. data/lib/acts_as_caesar/objects/persistence.rb +45 -0
  13. data/lib/acts_as_caesar/objects/vote.rb +77 -0
  14. data/lib/acts_as_caesar/objects/voter.rb +43 -0
  15. data/lib/acts_as_caesar/rack.rb +63 -0
  16. data/lib/acts_as_caesar/rails/engine.rb +11 -0
  17. data/lib/acts_as_caesar/rails/view_helpers.rb +17 -0
  18. data/lib/acts_as_caesar/version.rb +3 -0
  19. data/lib/acts_as_caesar.rb +21 -0
  20. data/spec/assets/jasmine_helper.js.coffee +5 -0
  21. data/spec/assets/jquery_helper.js.coffee +2 -0
  22. data/spec/assets/vote_cache_spec.js.coffee +63 -0
  23. data/spec/assets/vote_persistence_spec.js.coffee +31 -0
  24. data/spec/assets/vote_spec.js.coffee +59 -0
  25. data/spec/dummy/README.rdoc +261 -0
  26. data/spec/dummy/Rakefile +7 -0
  27. data/spec/dummy/app/assets/javascripts/application.js +3 -0
  28. data/spec/dummy/app/assets/stylesheets/application.css +52 -0
  29. data/spec/dummy/app/controllers/albums_controller.rb +58 -0
  30. data/spec/dummy/app/controllers/application_controller.rb +4 -0
  31. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  32. data/spec/dummy/app/mailers/.gitkeep +0 -0
  33. data/spec/dummy/app/models/.gitkeep +0 -0
  34. data/spec/dummy/app/models/album.rb +15 -0
  35. data/spec/dummy/app/views/albums/form.html.erb +10 -0
  36. data/spec/dummy/app/views/albums/index.html.erb +12 -0
  37. data/spec/dummy/app/views/layouts/application.html.erb +16 -0
  38. data/spec/dummy/config/application.rb +69 -0
  39. data/spec/dummy/config/boot.rb +10 -0
  40. data/spec/dummy/config/environment.rb +5 -0
  41. data/spec/dummy/config/environments/development.rb +30 -0
  42. data/spec/dummy/config/environments/production.rb +67 -0
  43. data/spec/dummy/config/environments/test.rb +34 -0
  44. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  45. data/spec/dummy/config/initializers/inflections.rb +15 -0
  46. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  47. data/spec/dummy/config/initializers/mongoid.rb +2 -0
  48. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  49. data/spec/dummy/config/initializers/session_store.rb +8 -0
  50. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  51. data/spec/dummy/config/locales/en.yml +5 -0
  52. data/spec/dummy/config/mongoid.yml +7 -0
  53. data/spec/dummy/config/routes.rb +7 -0
  54. data/spec/dummy/config.ru +4 -0
  55. data/spec/dummy/lib/assets/.gitkeep +0 -0
  56. data/spec/dummy/log/.gitkeep +0 -0
  57. data/spec/dummy/public/404.html +26 -0
  58. data/spec/dummy/public/422.html +26 -0
  59. data/spec/dummy/public/500.html +25 -0
  60. data/spec/dummy/public/favicon.ico +0 -0
  61. data/spec/dummy/script/rails +6 -0
  62. data/spec/dummy/spec/controllers/albums_controller_spec.rb +33 -0
  63. data/spec/dummy/spec/models/album_spec.rb +8 -0
  64. data/spec/javascripts/support/jasmine.yml +74 -0
  65. data/spec/javascripts/support/jasmine_config.rb +23 -0
  66. data/spec/javascripts/support/jasmine_runner.rb +32 -0
  67. data/spec/modules/act_as_candidate_spec.rb +43 -0
  68. data/spec/objects/candidate_spec.rb +37 -0
  69. data/spec/objects/notification_spec.rb +19 -0
  70. data/spec/objects/persistence_spec.rb +87 -0
  71. data/spec/objects/vote_spec.rb +131 -0
  72. data/spec/objects/voter_spec.rb +80 -0
  73. data/spec/rack_spec.rb +97 -0
  74. data/spec/rails/engine_spec.rb +11 -0
  75. data/spec/rails/view_helpers_spec.rb +30 -0
  76. data/spec/rails_helper.rb +8 -0
  77. data/spec/spec_helper.rb +4 -0
  78. data/vendor/assets/javascripts/acts_as_caesar.js +1 -0
  79. data/vendor/assets/javascripts/initialization.js.coffee +36 -0
  80. data/vendor/assets/javascripts/vote.js.coffee +45 -0
  81. data/vendor/assets/javascripts/vote_cache.js.coffee +42 -0
  82. data/vendor/assets/javascripts/vote_persistence.js.coffee +51 -0
  83. data/vendor/assets/javascripts/vote_view.js.coffee +23 -0
  84. 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
@@ -0,0 +1,8 @@
1
+ # Configure Rails Environment
2
+ ENV["RAILS_ENV"] = "test"
3
+
4
+ require File.expand_path("../dummy/config/environment.rb", __FILE__)
5
+ require "rails/test_help"
6
+ require "rspec/rails"
7
+
8
+ Rails.backtrace_cleaner.remove_silencers!
@@ -0,0 +1,4 @@
1
+ require 'simplecov'
2
+ SimpleCov.start do
3
+ add_filter "/spec/"
4
+ end
@@ -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))