Skip to content

Instantly share code, notes, and snippets.

@thomasklemm
Last active March 31, 2016 12:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thomasklemm/6ebf0c8b21e8d2ba7465f05915201c45 to your computer and use it in GitHub Desktop.
Save thomasklemm/6ebf0c8b21e8d2ba7465f05915201c45 to your computer and use it in GitHub Desktop.
Planning the models and database structure for go.vote
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