require 'rubygems'
require 'sinatra'
require 'haml'
require './models'
require 'rack-flash'
set :root, File.dirname(__FILE__)
enable :sessions
use Rack::Flash
class String
def pluralize(num)
if num == 1
return self
end
case self[-1]
when 'y'
self[0..-2] + 'ies'
when 's'
self + "es"
else
self + "s"
end
end
end
helpers do
# Format a number with commas for every ^3
def commify(num)
num.to_s.reverse.gsub(/(\d\d\d)(?=\d)(?!\d*\.)/,'\1,').reverse
end
# From http://snippets.dzone.com/posts/show/593
def to_ordinal(num)
num = num.to_i
if (10...20) === num
"#{num}th"
else
g = %w{ th st nd rd th th th th th th }
a = num.to_s
c = a[-1..-1].to_i
a + g[c]
end
end
def format_percent(num)
sprintf("%.0f%%", num)
end
def short_date(d)
d.strftime("%e %b %Y")
end
def long_date(d)
d.strftime("%e %B %Y")
end
# Exception for Labour/Co-operative candidacies
def party_name(labcoop, party_name)
labcoop ? "Labour and Co-operative Party" : party_name
end
end
get '/' do
@election = Election.get(12) # FIXME magic number
@election_title = "#{@election.body.name} #{@election.kind} #{long_date(@election.d)}"
if params[:postcode]
if @p = Postcode.get(params[:postcode].strip.upcase)
# Postcode is valid and in LB Sutton
if @election.body.district_name == 'constituency'
@district = District.get(@p.constituency_id)
else
@district = District.get(@p.ward_id)
end
flash[:notice] = "Postcode #{@postcode} is in #{@district.name} #{@election.body.district_name}"
if @p.polling_station
@ps_postcode = Postcode.get(@p.polling_station.postcode)
@polling_station = "Your polling station is \
%s, %s, %s" \
% [ @ps_postcode.lat, @ps_postcode.lng, @p.polling_station.name, \
@p.polling_station.address, @p.polling_station.postcode]
end
redirect "/bodies/#{@election.body.slug}/elections/#{@election.d}/#{@election.body.districts_name}/#{@district.slug}"
else
flash.now[:error] = "#{@postcode} is not a postcode in Sutton"
end
end
# Display a random postcode as default search term
@random_pc = repository(:default).adapter.select("
SELECT postcode
FROM postcodes
ORDER BY RANDOM()
LIMIT 1
")
@default_pc = @random_pc[0]
@future_elections = Election.future
@past_elections = Election.past
haml :index
end
get '/bodies/:body/elections/:date' do
@body = Body.first(:slug => params[:body])
@election = Election.first(:body => @body, :d => params[:date])
@elections_for_this_body = Election.all(:body => @body, :order => [:d])
@total_seats = Candidacy.sum(:seats, :election => @election)
@total_votes = Candidacy.sum(:votes, :election => @election)
# There's got to be a better way to do this, either with SQL or Datamapper
@total_districts = repository(:default).adapter.select("
SELECT district_id
FROM candidacies
WHERE election_id = ?
GROUP BY district_id
ORDER BY district_id
", @election.id).count
@results_by_party = repository(:default).adapter.select("
SELECT
p.colour,
p.name,
SUM(c.votes) AS votez,
SUM(c.seats) AS seatz,
COUNT(*) AS cands
FROM candidacies c
LEFT JOIN parties p ON p.id = c.party_id
WHERE c.election_id = ?
GROUP BY c.party_id, p.colour, p.name
ORDER BY seatz DESC, votez DESC
", @election.id)
# For elections that haven't yet been held
# @districts_in_this_election = repository(:default).adapter.select("
# SELECT DISTINCT d.name, d.slug
#
# FROM candidacies c
# LEFT JOIN districts d
# ON c.district_id = d.id
#
# WHERE c.election_id = ?
#
# ORDER BY d.name
# ", @election.id)
haml :electionsummary
end
# get '/bodies/:body/elections/:date/parties/:party' do
# Not written yet. Show how this party did at this election.
# end
get '/bodies/:body/?' do
@body = Body.first(:slug => params[:body])
@districts = District.all(:body => @body, :order => [:name])
@elections = repository(:default).adapter.select("
SELECT
e.id,
e.kind,
e.d,
SUM(p.ballot_papers_issued)::float / SUM(p.electorate) * 100 AS turnout_percent
FROM elections e
LEFT JOIN polls p
ON e.id = p.election_id
WHERE e.body_id = ?
GROUP BY p.election_id, e.id
ORDER BY e.d DESC
", @body.id)
haml :body
end
# get '/wards/:slug/postcode/:postcode/?' do
# @ward = Ward.first(:slug => params[:slug])
# @postcode = params[:postcode]
# haml :wards
# end
get '/candidates/:id/?' do
if @deleted_candidate = DeletedCandidate.get(params[:id])
redirect "/candidates/#{@deleted_candidate.candidate_id}", 302 # HTTP 302 Moved Temporarily
end
if @candidate = Candidate.get(params[:id])
@candidacies = repository(:default).adapter.select("
SELECT
e.d,
c.*,
p.name AS party_name,
p.colour AS party_colour,
b.name AS body_name,
b.slug AS body_slug,
b.districts_name AS districts_name,
d.name AS district_name,
d.slug AS district_slug
FROM candidacies c
INNER JOIN elections e
ON c.election_id = e.id
INNER JOIN parties p
ON c.party_id = p.id
INNER JOIN bodies b
ON e.body_id = b.id
INNER JOIN districts d
ON c.district_id = d.id
WHERE c.candidate_id = ?
ORDER BY d
", @candidate.id)
haml :candidate
else
404
end
end
get '/candidates/?' do
@candidates = Candidate.all(:order => [ :surname, :forenames ])
haml :candidates
end
get '/bodies/:body/elections/:date/:districts_name/:district' do
@district = District.first(:slug => params[:district])
@body = Body.first(:slug => params[:body])
@election = Election.first(:body => @body, :d => params[:date])
@candidacies = Candidacy.all(:district => @district, :election => @election, :order => [:votes.desc])
@total_votes = Candidacy.sum(:votes, :district => @district, :election => @election)
@total_candidates = Candidacy.count(:district => @district, :election => @election)
@total_seats = Candidacy.sum(:seats, :district => @district, :election => @election)
@districts_in_this_election = @election.candidacies.districts
@poll = Poll.get(@district.id, @election.id)
if @total_seats == 1
@share_denominator = @total_votes
elsif @poll && @poll.valid_ballot_papers
@share_denominator = @poll.valid_ballot_papers
else
@share_denominator = @total_votes / @total_seats
@share_message = "The vote share percentages have been estimated as we don't have data for the number of valid ballot papers in this poll."
end
# Postgres: All the columns selected when using GROUP BY must either be aggregate functions or appear in the GROUP BY clause
@results_by_party = repository(:default).adapter.select("
SELECT
p.name AS party_name,
p.colour AS party_colour,
COUNT(c.id) AS num_candidates,
SUM(c.seats) AS num_seats,
SUM(c.votes) AS total_votes
FROM candidacies c
LEFT JOIN parties p
ON c.party_id = p.id
WHERE c.district_id = ?
AND c.election_id = ?
GROUP BY p.name, p.colour
ORDER BY total_votes DESC
", @district.id, @election.id)
haml :resultsdistrict
end
get '/bodies/:body/:districts_name/:district' do
@district = District.first(:slug => params[:district])
@body = Body.first(:slug => params[:body])
haml :district
end
get '/guides/how-the-council-election-works' do
haml :election
end
get '/guides/how-the-parliament-election-works' do
haml :parliament
end
get '/error' do
haml :error
end
get '/guides' do
haml :guides
end
get '/about' do
haml :about
end
not_found do
haml :not_found
end