Last active
March 31, 2016 12:45
-
-
Save thomasklemm/6ebf0c8b21e8d2ba7465f05915201c45 to your computer and use it in GitHub Desktop.
Planning the models and database structure for go.vote
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Planning the database structure for go.vote | |
# Model structure | |
The main models are: | |
- Election | |
- Candidate | |
- Voter | |
- Vote | |
The models are associated as follows: | |
- Election | |
The election is the main model around with the application revolves. | |
Each election is considered to stand alone. It has it's own set of candidates, | |
voters and votes. | |
- Candidate | |
A candidate belongs to an election. He can be voted for by voters added for this election. | |
- Voter | |
A voter is an entity that can vote for a candidate in an election. By doing so he casts a vote. | |
The voter is not shared across multiple elections, but needs to be added for each election seperately. | |
Parties might e.g. import a spreadsheet listing all registered members. | |
- Vote | |
A vote is cast when a voter decides for a candidate. | |
# Migrations | |
create_table :elections do |t| | |
t.timestamps null: false | |
t.text :name | |
end | |
create_table :candidates do |t| | |
t.timestamps null: false | |
t.integer :election_id, null: false | |
t.text :name | |
end | |
add_index :candidates, :election_id | |
create_table :voters do |t| | |
t.timestamps null: false | |
t.integer :election_id, null: false | |
t.text :voter_token | |
t.text :name | |
t.text :email | |
end | |
add_index :voters, :election_id | |
add_index :voters, :voter_token, unique: true | |
create_table :votes do |t| | |
t.timestamps null: false | |
t.integer :election_id, null: false | |
t.integer :voter_id, null: false | |
t.integer :candidate_id, null: false | |
end | |
add_index :votes, :election_id | |
add_index :votes, :voter_id, unique: true | |
add_index :votes, :candidate_id | |
# app/models/election.rb | |
class Election | |
has_many :candidates | |
has_many :votes | |
end | |
# app/models/candidate.rb | |
class Candidate | |
belongs_to :election | |
has_many :votes | |
end | |
# app/models/voter.rb | |
class Voter | |
include GeneratesVoterToken | |
belongs_to :election | |
has_one :vote | |
validates :election_id, presence: true | |
validates :name, presence: true | |
end | |
# app/models/voter/generates_voter_token.rb | |
module Voter::GeneratesVoterToken | |
extend ActiveSupport::Concern | |
VOTER_TOKEN_LENGTH = 8 # 1.28e14 possibilities | |
included do | |
before_validation :generate_voter_token | |
validates :voter_token, uniqueness: true | |
end | |
private | |
def generate_voter_token | |
self.voter_token ||= loop do | |
random_token = SecureRandom.base58(VOTER_TOKEN_LENGTH) | |
break random_token unless self.class.exists?(voter_token: random_token) | |
end | |
end | |
end | |
# app/models/vote.rb | |
class Vote | |
include SetsElectionId | |
belongs_to :election | |
belongs_to :voter | |
belongs_to :candidate | |
validates :voter_id, :candidate_id, presence: true | |
end | |
# app/models/vote/sets_election_id.rb | |
module Vote::SetsElectionId | |
extend ActiveSupport::Concern | |
included do | |
before_validation :set_election_id | |
validates :election_id, presence: true | |
validate :validate_election_id_is_matching_voter_and_candidate | |
end | |
private | |
def set_election_id | |
self.election_id = voter.election_id | |
end | |
def validate_election_id_is_matching_voter_and_candidate | |
unless election_id_is_a_match? | |
errors.add(:election_id, 'is a mismatch') | |
end | |
end | |
def election_id_is_a_match? | |
election_id.present? && | |
(election_id == voter.election_id) && | |
(election_id == candidate.election_id) | |
end | |
end | |
# config/initializer/securerandom_base58.rb | |
# This is a backport from Rails 5. | |
# Source: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/securerandom.rb | |
require 'securerandom' | |
module SecureRandom | |
BASE58_ALPHABET = ('0'..'9').to_a + ('A'..'Z').to_a + ('a'..'z').to_a - ['0', 'O', 'I', 'l'] | |
# SecureRandom.base58 generates a random base58 string. | |
# | |
# The argument _n_ specifies the length, of the random string to be generated. | |
# | |
# If _n_ is not specified or is nil, 16 is assumed. It may be larger in the future. | |
# | |
# The result may contain alphanumeric characters except 0, O, I and l | |
# | |
# p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE" | |
# p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7" | |
# | |
def self.base58(n = 16) | |
SecureRandom.random_bytes(n).unpack("C*").map do |byte| | |
idx = byte % 64 | |
idx = SecureRandom.random_number(58) if idx >= 58 | |
BASE58_ALPHABET[idx] | |
end.join | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment