ballot 1.0

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 (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