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(9) # 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) @results_by_district = repository(:default).adapter.select(" SELECT d.name, d.slug AS district_slug, SUM(c.seats) AS seats, SUM(c.votes) AS votez, COUNT(c.id) AS num_candidates FROM districts d, candidacies c WHERE c.district_id = d.id AND c.election_id = ? GROUP BY c.district_id, d.name, d.slug ORDER BY d.name ", @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/?' do @bodies = Body.all haml :bodies end get '/bodies/:body/?' do @body = Body.first(:slug => params[:body]) @elections = Election.all(:body => @body, :order => [:d.desc]) @districts = District.all(:body => @body, :order => [:name]) 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}", 301 # HTTP 301 Moved Permanently 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) # 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) if params[:wiki] == 'dokuwiki' o = "^ #{@district.name} #{@district.body.district_name} results, #{@body.name} election #{short_date(@election.d)} ^^^^^^\n" o += "^ Position ^ Candidate ^ Party ^ Votes ^ % Share ^ ^\n" count = 0 @candidacies.each do |c| count += 1 o += "| %2d | [[%-30s]] | [[%-40s]] | %6s | %3s | %-7s |\n" % [ count, c.candidate.short_name, party_name(c.labcoop, c.party.name), commify(c.votes), format_percent(c.votes.to_f / @total_votes * 100), c.seats == 1 ? 'Elected' : '' ] end o else haml :resultsdistrict end end get '/bodies/:body/:districts_name/:district' do @district = District.first(:slug => params[:district]) @body = Body.first(:slug => params[:body]) haml :district end get '/how-the-council-election-works' do haml :election end get '/how-the-parliament-election-works' do haml :parliament end # get '/voting' do # haml :voting # end get '/error' do haml :error end get '/about' do haml :about end # get '/aliens' do # haml :aliens # end get '/polling-stations' do @stations = PollingStation.all haml :pollingstations end not_found do haml :not_found end