ballot 1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/Contributing.md +68 -0
  3. data/History.md +5 -0
  4. data/Licence.md +27 -0
  5. data/Manifest.txt +68 -0
  6. data/README.rdoc +264 -0
  7. data/Rakefile +71 -0
  8. data/bin/ballot_generator +9 -0
  9. data/lib/ballot.rb +25 -0
  10. data/lib/ballot/action_controller.rb +32 -0
  11. data/lib/ballot/active_record.rb +152 -0
  12. data/lib/ballot/active_record/votable.rb +145 -0
  13. data/lib/ballot/active_record/vote.rb +35 -0
  14. data/lib/ballot/active_record/voter.rb +99 -0
  15. data/lib/ballot/railtie.rb +19 -0
  16. data/lib/ballot/sequel.rb +170 -0
  17. data/lib/ballot/sequel/vote.rb +99 -0
  18. data/lib/ballot/votable.rb +445 -0
  19. data/lib/ballot/vote.rb +129 -0
  20. data/lib/ballot/voter.rb +320 -0
  21. data/lib/ballot/words.rb +32 -0
  22. data/lib/generators/ballot.rb +40 -0
  23. data/lib/generators/ballot/install/install_generator.rb +27 -0
  24. data/lib/generators/ballot/install/templates/active_record/migration.rb +19 -0
  25. data/lib/generators/ballot/install/templates/sequel/migration.rb +25 -0
  26. data/lib/generators/ballot/standalone.rb +89 -0
  27. data/lib/generators/ballot/standalone/support.rb +70 -0
  28. data/lib/generators/ballot/summary/summary_generator.rb +27 -0
  29. data/lib/generators/ballot/summary/templates/active_record/migration.rb +15 -0
  30. data/lib/generators/ballot/summary/templates/sequel/migration.rb +20 -0
  31. data/lib/sequel/plugins/ballot_votable.rb +180 -0
  32. data/lib/sequel/plugins/ballot_voter.rb +125 -0
  33. data/test/active_record/ballot_votable_test.rb +16 -0
  34. data/test/active_record/ballot_voter_test.rb +13 -0
  35. data/test/active_record/rails_generator_test.rb +28 -0
  36. data/test/active_record/votable_voter_test.rb +19 -0
  37. data/test/generators/rails-activerecord/Rakefile +2 -0
  38. data/test/generators/rails-activerecord/app/.keep +0 -0
  39. data/test/generators/rails-activerecord/bin/rails +5 -0
  40. data/test/generators/rails-activerecord/config/application.rb +17 -0
  41. data/test/generators/rails-activerecord/config/boot.rb +3 -0
  42. data/test/generators/rails-activerecord/config/database.yml +12 -0
  43. data/test/generators/rails-activerecord/config/environment.rb +3 -0
  44. data/test/generators/rails-activerecord/config/routes.rb +3 -0
  45. data/test/generators/rails-activerecord/config/secrets.yml +5 -0
  46. data/test/generators/rails-activerecord/db/seeds.rb +1 -0
  47. data/test/generators/rails-activerecord/log/.keep +0 -0
  48. data/test/generators/rails-sequel/Rakefile +2 -0
  49. data/test/generators/rails-sequel/app/.keep +0 -0
  50. data/test/generators/rails-sequel/bin/rails +5 -0
  51. data/test/generators/rails-sequel/config/application.rb +14 -0
  52. data/test/generators/rails-sequel/config/boot.rb +3 -0
  53. data/test/generators/rails-sequel/config/database.yml +12 -0
  54. data/test/generators/rails-sequel/config/environment.rb +3 -0
  55. data/test/generators/rails-sequel/config/routes.rb +3 -0
  56. data/test/generators/rails-sequel/config/secrets.yml +5 -0
  57. data/test/generators/rails-sequel/db/seeds.rb +1 -0
  58. data/test/generators/rails-sequel/log/.keep +0 -0
  59. data/test/minitest_config.rb +14 -0
  60. data/test/sequel/ballot_votable_test.rb +45 -0
  61. data/test/sequel/ballot_voter_test.rb +42 -0
  62. data/test/sequel/rails_generator_test.rb +25 -0
  63. data/test/sequel/votable_voter_test.rb +19 -0
  64. data/test/sequel/vote_test.rb +105 -0
  65. data/test/support/active_record_setup.rb +145 -0
  66. data/test/support/generators_setup.rb +129 -0
  67. data/test/support/sequel_setup.rb +164 -0
  68. data/test/support/shared_examples/votable_examples.rb +630 -0
  69. data/test/support/shared_examples/voter_examples.rb +600 -0
  70. metadata +333 -0
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequel #:nodoc:
4
+ module Plugins #:nodoc:
5
+ # The BallotVoter plugin marks the model as containing objects that can
6
+ # vote. It creates a polymorphic one-to-many relationship from the
7
+ # BallotVoter model to Ballot::Sequel::Vote.
8
+ module BallotVoter
9
+ def self.apply(model) #:nodoc:
10
+ require 'ballot/sequel/vote'
11
+ require 'ballot/words'
12
+ require 'ballot/voter'
13
+
14
+ model.instance_eval do
15
+ # Create a polymorphic one-to-many relationship for voters. Based
16
+ # heavily on the one_to_many implementation from sequel_polymorphic,
17
+ # but customized to sequel-voting's needs.
18
+ one_to_many :ballots_by,
19
+ key: :voter_id,
20
+ reciprocal: :voter,
21
+ reciprocal_type: :one_to_many,
22
+ conditions: { voter_type: Ballot::Sequel.type_name(model) },
23
+ adder: ->(many_of_instance) {
24
+ many_of_instance.update(
25
+ voter_id: pk,
26
+ voter_type: Ballot::Sequel.type_name(model)
27
+ )
28
+ },
29
+ remover: ->(many_of_instance) { many_of_instance.delete },
30
+ clearer: -> { ballots_by_dataset.delete },
31
+ class: '::Ballot::Sequel::Vote'
32
+
33
+ include Ballot::Voter
34
+ extend Ballot::Voter::ClassMethods
35
+ end
36
+ end
37
+
38
+ module InstanceMethods #:nodoc:
39
+ def cast_ballot_for(votable = nil, kwargs = {}) #:nodoc:
40
+ kwargs = __ballot_voter_kwargs(votable, kwargs)
41
+ votable = Ballot::Sequel.votable_for(kwargs)
42
+ return false unless votable
43
+ votable.ballot_by(kwargs.merge(voter: self))
44
+ end
45
+ alias ballot_for cast_ballot_for
46
+
47
+ def remove_ballot_for(votable = nil, kwargs = {}) #:nodoc:
48
+ kwargs = __ballot_voter_kwargs(votable, kwargs)
49
+ votable = Ballot::Sequel.votable_for(kwargs)
50
+ return false unless votable
51
+ votable.remove_ballot_by voter: self, scope: kwargs[:scope]
52
+ end
53
+
54
+ def cast_ballot_for?(votable = nil, kwargs = {}) #:nodoc:
55
+ kwargs = __ballot_voter_kwargs(votable, kwargs)
56
+ votable_id, votable_type = Ballot::Sequel.votable_id_and_type_name_for(kwargs)
57
+ return false unless votable_id
58
+
59
+ cond = {
60
+ votable_id: votable_id,
61
+ votable_type: votable_type,
62
+ scope: kwargs[:scope]
63
+ }
64
+ cond[:vote] = Ballot::Words.truthy?(kwargs[:vote]) if kwargs.key?(:vote)
65
+
66
+ find_ballots_by(cond).any?
67
+ end
68
+ alias ballot_for? cast_ballot_for?
69
+
70
+ def ballot_as_cast_for(votable = nil, kwargs = {}) #:nodoc:
71
+ kwargs = __ballot_voter_kwargs(votable, kwargs)
72
+ votable = Ballot::Sequel.votable_for(kwargs)
73
+ return nil unless votable
74
+
75
+ cond = {
76
+ votable_id: votable.id,
77
+ votable_type: Ballot::Sequel.type_name(votable),
78
+ scope: kwargs[:scope]
79
+ }
80
+ cond[:vote] = Ballot::Words.truthy?(kwargs[:vote]) if kwargs.key?(:vote)
81
+
82
+ vote = find_ballots_by(cond).
83
+ order(Sequel.desc(:updated_at), Sequel.desc(:created_at)).
84
+ limit(1).first
85
+ vote && vote.vote
86
+ end
87
+
88
+ def ballots_for_class(klass, kwargs = {}) #:nodoc:
89
+ cond = {
90
+ votable_type: Ballot::Sequel.type_name(klass),
91
+ scope: kwargs[:scope]
92
+ }
93
+ cond[:vote] = Ballot::Words.truthy?(kwargs[:vote]) if kwargs.key?(:vote)
94
+
95
+ find_ballots_by(cond)
96
+ end
97
+
98
+ private
99
+
100
+ def find_ballots_by(*cond, &block)
101
+ ballots_by_dataset.where(*cond, &block)
102
+ end
103
+
104
+ def __eager_ballot_votables(ds)
105
+ ballots = ds.naked.select(:votable_type, :votable_id).all
106
+ partitioned = ballots.group_by { |e| e[:votable_type] }
107
+ partitioned.each_value do |value|
108
+ value.map! { |e| e[:votable_id] }
109
+ end
110
+ partitioned.each_key do |key|
111
+ votables = self.class.send(:constantize, key).
112
+ where(id: partitioned[key]).
113
+ map { |v| [ v.id, v ] }
114
+
115
+ partitioned[key] = Hash[votables]
116
+ end
117
+
118
+ ballots.map { |ballot|
119
+ partitioned[ballot[:votable_type]][ballot[:votable_id]]
120
+ }
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest_config'
4
+
5
+ describe Ballot::ActiveRecord::Votable do
6
+ describe_votable_models [
7
+ ARVotable, ARVotableVoter, ARStiVotable, ARChildOfStiVotable,
8
+ ARVotableChildOfStiNotVotable, ARVotableCache
9
+ ]
10
+ describe_non_votable_models [
11
+ ARVoter, ARNotVoter, ARNotVotable, ARStiNotVotable
12
+ ]
13
+ describe_ballot_votable
14
+ describe_ballot_votable_sti_support
15
+ describe_cached_ballot_summary ARVotableCache
16
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest_config'
4
+
5
+ describe Ballot::ActiveRecord::Voter do
6
+ describe_non_voter_models [
7
+ ARChildOfStiVotable, ARNotVotable, ARNotVoter, ARStiNotVotable,
8
+ ARStiVotable, ARVotable, ARVotableCache, ARVotableChildOfStiNotVotable
9
+ ]
10
+ describe_voter_models [ ARVoter, ARVotableVoter ]
11
+ describe_ballot_voter
12
+ describe_ballot_voter_sti_votable_support
13
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest_config'
4
+
5
+ describe 'RailsGenerator' do
6
+ describe 'for Active Record' do
7
+ let(:orm_name) { 'activerecord' }
8
+ let(:orm_version) { ActiveRecord::VERSION::STRING }
9
+ let(:rails_version) { orm_version }
10
+
11
+ test_ballot_install(
12
+ generate: %r{create\s+db/migrate/\d+_install_ballot_vote_migration},
13
+ migrate: %r{
14
+ InstallBallotVoteMigration.*
15
+ --\screate_table\(:ballot_votes(?:,\s{})?\).*
16
+ --\sadd_index\(:ballot_votes,\s\[:voter_id .*
17
+ --\sadd_index\(:ballot_votes,\s\[:votable_id
18
+ }mx,
19
+ contents: /InstallBallotVoteMigration < ActiveRecord::Migration/
20
+ )
21
+
22
+ test_ballot_summary_votable(
23
+ generate: %r{create\s+db/migrate/\d+_ballot_cache_for_votable},
24
+ migrate: /BallotCacheForVotable.*--\schange_table\(:votables(?:,\s{})?\)/mx,
25
+ contents: /BallotCacheForVotable < ActiveRecord::Migration/
26
+ )
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest_config'
4
+
5
+ describe ARVotableVoter do
6
+ describe_ballot_voter do
7
+ let(:voter) { ARVotableVoter.create(name: 'I can vote!') }
8
+ let(:voter2) { ARVotableVoter.create(name: 'I, too, can vote!') }
9
+ let(:votable) { voter2 }
10
+ let(:votable2) { voter }
11
+ end
12
+
13
+ describe_ballot_votable do
14
+ let(:voter) { ARVotableVoter.create(name: 'I can vote!') }
15
+ let(:voter2) { ARVotableVoter.create(name: 'I, too, can vote!') }
16
+ let(:votable) { voter2 }
17
+ let(:votable2) { voter }
18
+ end
19
+ end
@@ -0,0 +1,2 @@
1
+ require File.expand_path('../config/application', __FILE__)
2
+ Rails.application.load_tasks
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
4
+ require_relative '../config/boot'
5
+ require 'rails/commands'
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ require File.expand_path('../boot', __FILE__)
3
+
4
+ require 'rails'
5
+ require 'active_model/railtie'
6
+ require 'active_record/railtie'
7
+
8
+ Bundler.require(*Rails.groups)
9
+
10
+ module RailsActiverecord
11
+ class Application < Rails::Application
12
+ if Rails::VERSION::MAJOR < 5
13
+ config.active_record.raise_in_transactional_callbacks = true
14
+ end
15
+ config.eager_load = false
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3
+ require 'bundler/setup'
@@ -0,0 +1,12 @@
1
+ default: &default
2
+ adapter: sqlite3
3
+ pool: 5
4
+ timeout: 5000
5
+
6
+ development:
7
+ <<: *default
8
+ database: db/development.sqlite3
9
+
10
+ test:
11
+ <<: *default
12
+ database: db/test.sqlite3
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+ require File.expand_path('../application', __FILE__)
3
+ Rails.application.initialize!
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+ Rails.application.routes.draw do
3
+ end
@@ -0,0 +1,5 @@
1
+ development:
2
+ secret_key_base: 7dfa450af068027682b897d9cd0143c2bcc0b83001cd3b45088bfd1eb6fa999fe19aca0e535f7676977f5c168624877dcc21c41522af77ffee65ec5f59023937
3
+
4
+ test:
5
+ secret_key_base: 9f4ff20b6abd84c56c2a59805baaf0ae6db6802013cdefa07e5c73dc89ee8b126e84b34ff443da47e2c26af526dcd53692fef60d2c6ae62cfa9332282f1d7c8a
@@ -0,0 +1 @@
1
+ # frozen_string_literal: true
@@ -0,0 +1,2 @@
1
+ require File.expand_path('../config/application', __FILE__)
2
+ Rails.application.load_tasks
File without changes
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
4
+ require_relative '../config/boot'
5
+ require 'rails/commands'
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ require File.expand_path('../boot', __FILE__)
3
+
4
+ require 'rails'
5
+ require 'active_model/railtie'
6
+
7
+ Bundler.require(*Rails.groups)
8
+
9
+ module RailsSequel
10
+ class Application < Rails::Application
11
+ config.eager_load = false
12
+ config.sequel.schema_dump = true
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3
+ require 'bundler/setup'
@@ -0,0 +1,12 @@
1
+ default: &default
2
+ adapter: sqlite3
3
+ pool: 5
4
+ timeout: 5000
5
+
6
+ development:
7
+ <<: *default
8
+ database: db/development.sqlite3
9
+
10
+ test:
11
+ <<: *default
12
+ database: db/test.sqlite3
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+ require File.expand_path('../application', __FILE__)
3
+ Rails.application.initialize!
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+ Rails.application.routes.draw do
3
+ end
@@ -0,0 +1,5 @@
1
+ development:
2
+ secret_key_base: 7dfa450af068027682b897d9cd0143c2bcc0b83001cd3b45088bfd1eb6fa999fe19aca0e535f7676977f5c168624877dcc21c41522af77ffee65ec5f59023937
3
+
4
+ test:
5
+ secret_key_base: 9f4ff20b6abd84c56c2a59805baaf0ae6db6802013cdefa07e5c73dc89ee8b126e84b34ff443da47e2c26af526dcd53692fef60d2c6ae62cfa9332282f1d7c8a
@@ -0,0 +1 @@
1
+ # frozen_string_literal: true
File without changes
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ # -*- ruby encoding: utf-8 -*-
3
+
4
+ gem 'minitest'
5
+ require 'minitest/autorun'
6
+ require 'minitest/focus'
7
+ require 'minitest/moar'
8
+ require 'minitest/bisect'
9
+ require 'minitest-bonus-assertions'
10
+ require 'minitest/hooks/default'
11
+
12
+ require 'pathname'
13
+
14
+ Dir["#{__dir__}/support/**/*.rb"].sort.each do |f| require_relative f end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest_config'
4
+
5
+ describe Sequel::Plugins::BallotVotable do
6
+ describe_votable_models [
7
+ SequelVotable, SequelVotableVoter, SequelStiVotable,
8
+ SequelChildOfStiVotable, SequelVotableChildOfStiNotVotable,
9
+ SequelVotableCache
10
+ ]
11
+ describe_non_votable_models [
12
+ SequelVoter, SequelNotVoter, SequelNotVotable, SequelStiNotVotable
13
+ ]
14
+ describe_ballot_votable
15
+ describe_ballot_votable_sti_support
16
+ describe_cached_ballot_summary SequelVotableCache
17
+
18
+ describe 'association methods' do
19
+ it '#add_ballots_for adds a new instance' do
20
+ vote = Ballot::Sequel::Vote.new(voter: voter)
21
+ refute_predicate vote, :valid?
22
+ votable.add_ballots_for(vote)
23
+ assert_predicate vote, :valid?
24
+ end
25
+
26
+ it '#remove_ballots_for destroys the instance' do
27
+ votable.ballot_by voter
28
+ assert_true Ballot::Sequel::Vote.any?
29
+ votable.remove_ballots_for(votable_dataset(votable).first)
30
+ assert_true Ballot::Sequel::Vote.none?
31
+ end
32
+
33
+ it '#remove_all_ballots_for destroys all instances' do
34
+ votable.ballot_by voter
35
+ votable2.ballot_by voter
36
+ assert_true Ballot::Sequel::Vote.any?
37
+ votable.remove_all_ballots_for
38
+ assert_false Ballot::Sequel::Vote.none?
39
+ assert_true Ballot::Sequel::Vote.where(
40
+ votable_id: votable.id,
41
+ votable_type: ballot_type_name(votable)
42
+ ).none?
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest_config'
4
+
5
+ describe Sequel::Plugins::BallotVoter do
6
+ describe_non_voter_models [
7
+ SequelChildOfStiVotable, SequelNotVotable, SequelNotVoter,
8
+ SequelStiNotVotable, SequelStiVotable, SequelVotable, SequelVotableCache,
9
+ SequelVotableChildOfStiNotVotable
10
+ ]
11
+ describe_voter_models [ SequelVoter, SequelVotableVoter ]
12
+ describe_ballot_voter
13
+ describe_ballot_voter_sti_votable_support
14
+
15
+ describe 'association methods' do
16
+ it '#add_ballots_by adds a new instance' do
17
+ vote = Ballot::Sequel::Vote.new(votable: votable)
18
+ refute_predicate vote, :valid?
19
+ voter.add_ballots_by(vote)
20
+ assert_predicate vote, :valid?
21
+ end
22
+
23
+ it '#remove_ballots_by destroys the instance' do
24
+ voter.ballot_for votable
25
+ assert_true Ballot::Sequel::Vote.any?
26
+ voter.remove_ballots_by(voter_dataset(voter).first)
27
+ assert_true Ballot::Sequel::Vote.none?
28
+ end
29
+
30
+ it '#remove_all_ballots_by destroys all instances' do
31
+ voter.ballot_for votable
32
+ voter2.ballot_for votable
33
+ assert_true Ballot::Sequel::Vote.any?
34
+ voter.remove_all_ballots_by
35
+ assert_false Ballot::Sequel::Vote.none?
36
+ assert_true Ballot::Sequel::Vote.where(
37
+ voter_id: voter.id,
38
+ voter_type: ballot_type_name(voter)
39
+ ).none?
40
+ end
41
+ end
42
+ end