Election results in the London Borough of Sutton.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

app.rb 7.9 KiB

13 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. require 'rubygems'
  2. require 'sinatra'
  3. require 'haml'
  4. require './models'
  5. require 'rack-flash'
  6. set :root, File.dirname(__FILE__)
  7. enable :sessions
  8. use Rack::Flash
  9. class String
  10. def pluralize(num)
  11. if num == 1
  12. return self
  13. end
  14. case self[-1]
  15. when 'y'
  16. self[0..-2] + 'ies'
  17. when 's'
  18. self + "es"
  19. else
  20. self + "s"
  21. end
  22. end
  23. end
  24. helpers do
  25. # Format a number with commas for every ^3
  26. def commify(num)
  27. num.to_s.reverse.gsub(/(\d\d\d)(?=\d)(?!\d*\.)/,'\1,').reverse
  28. end
  29. # From http://snippets.dzone.com/posts/show/593
  30. def to_ordinal(num)
  31. num = num.to_i
  32. if (10...20) === num
  33. "#{num}th"
  34. else
  35. g = %w{ th st nd rd th th th th th th }
  36. a = num.to_s
  37. c = a[-1..-1].to_i
  38. a + g[c]
  39. end
  40. end
  41. def format_percent(num)
  42. sprintf("%.0f%%", num)
  43. end
  44. def short_date(d)
  45. d.strftime("%e %b %Y")
  46. end
  47. def long_date(d)
  48. d.strftime("%e %B %Y")
  49. end
  50. # Exception for Labour/Co-operative candidacies
  51. def party_name(labcoop, party_name)
  52. labcoop ? "Labour and Co-operative Party" : party_name
  53. end
  54. end
  55. get '/' do
  56. @election = Election.get(9) # FIXME magic number
  57. @election_title = "#{@election.body.name} #{@election.kind} #{long_date(@election.d)}"
  58. if params[:postcode]
  59. if @p = Postcode.get(params[:postcode].strip.upcase)
  60. # Postcode is valid and in LB Sutton
  61. if @election.body.district_name == 'constituency'
  62. @district = District.get(@p.constituency_id)
  63. else
  64. @district = District.get(@p.ward_id)
  65. end
  66. flash[:notice] = "Postcode <strong>#{@postcode}</strong> is in #{@district.name} #{@election.body.district_name}"
  67. if @p.polling_station
  68. @ps_postcode = Postcode.get(@p.polling_station.postcode)
  69. @polling_station = "Your polling station is \
  70. <a href=\"http://www.openstreetmap.org/?mlat=%s&mlon=%s&zoom=16\">%s, %s, %s</a>" \
  71. % [ @ps_postcode.lat, @ps_postcode.lng, @p.polling_station.name, \
  72. @p.polling_station.address, @p.polling_station.postcode]
  73. end
  74. redirect "/bodies/#{@election.body.slug}/elections/#{@election.d}/#{@election.body.districts_name}/#{@district.slug}"
  75. else
  76. flash.now[:error] = "<strong>#{@postcode}</strong> is not a postcode in Sutton"
  77. end
  78. end
  79. # Display a random postcode as default search term
  80. @random_pc = repository(:default).adapter.select("
  81. SELECT postcode
  82. FROM postcodes
  83. ORDER BY RANDOM()
  84. LIMIT 1
  85. ")
  86. @default_pc = @random_pc[0]
  87. @future_elections = Election.future
  88. @past_elections = Election.past
  89. haml :index
  90. end
  91. get '/bodies/:body/elections/:date' do
  92. @body = Body.first(:slug => params[:body])
  93. @election = Election.first(:body => @body, :d => params[:date])
  94. @elections_for_this_body = Election.all(:body => @body, :order => [:d])
  95. @total_seats = Candidacy.sum(:seats, :election => @election)
  96. @total_votes = Candidacy.sum(:votes, :election => @election)
  97. # There's got to be a better way to do this, either with SQL or Datamapper
  98. @total_districts = repository(:default).adapter.select("
  99. SELECT district_id
  100. FROM candidacies
  101. WHERE election_id = ?
  102. GROUP BY district_id
  103. ORDER BY district_id
  104. ", @election.id).count
  105. @results_by_party = repository(:default).adapter.select("
  106. SELECT
  107. p.colour,
  108. p.name,
  109. SUM(c.votes) AS votez,
  110. SUM(c.seats) AS seatz,
  111. COUNT(*) AS cands
  112. FROM candidacies c
  113. LEFT JOIN parties p ON p.id = c.party_id
  114. WHERE c.election_id = ?
  115. GROUP BY c.party_id, p.colour, p.name
  116. ORDER BY seatz DESC, votez DESC
  117. ", @election.id)
  118. @results_by_district = repository(:default).adapter.select("
  119. SELECT
  120. d.name,
  121. d.slug AS district_slug,
  122. SUM(c.seats) AS seats,
  123. SUM(c.votes) AS votez,
  124. COUNT(c.id) AS num_candidates
  125. FROM districts d, candidacies c
  126. WHERE
  127. c.district_id = d.id
  128. AND c.election_id = ?
  129. GROUP BY c.district_id, d.name, d.slug
  130. ORDER BY d.name
  131. ", @election.id)
  132. # For elections that haven't yet been held
  133. @districts_in_this_election = repository(:default).adapter.select("
  134. SELECT DISTINCT d.name, d.slug
  135. FROM candidacies c
  136. LEFT JOIN districts d
  137. ON c.district_id = d.id
  138. WHERE c.election_id = ?
  139. ORDER BY d.name
  140. ", @election.id)
  141. haml :electionsummary
  142. end
  143. # get '/bodies/:body/elections/:date/parties/:party' do
  144. # Not written yet. Show how this party did at this election.
  145. # end
  146. get '/bodies/?' do
  147. @bodies = Body.all
  148. haml :bodies
  149. end
  150. get '/bodies/:body/?' do
  151. @body = Body.first(:slug => params[:body])
  152. @elections = Election.all(:body => @body, :order => [:d.desc])
  153. @districts = District.all(:body => @body, :order => [:name])
  154. haml :body
  155. end
  156. # get '/wards/:slug/postcode/:postcode/?' do
  157. # @ward = Ward.first(:slug => params[:slug])
  158. # @postcode = params[:postcode]
  159. # haml :wards
  160. # end
  161. get '/candidates/:id/?' do
  162. if @deleted_candidate = DeletedCandidate.get(params[:id])
  163. redirect "/candidates/#{@deleted_candidate.candidate_id}", 301 # HTTP 301 Moved Permanently
  164. end
  165. if @candidate = Candidate.get(params[:id])
  166. @candidacies = repository(:default).adapter.select("
  167. SELECT
  168. e.d,
  169. c.*,
  170. p.name AS party_name,
  171. p.colour AS party_colour,
  172. b.name AS body_name,
  173. b.slug AS body_slug,
  174. b.districts_name AS districts_name,
  175. d.name AS district_name,
  176. d.slug AS district_slug
  177. FROM candidacies c
  178. INNER JOIN elections e
  179. ON c.election_id = e.id
  180. INNER JOIN parties p
  181. ON c.party_id = p.id
  182. INNER JOIN bodies b
  183. ON e.body_id = b.id
  184. INNER JOIN districts d
  185. ON c.district_id = d.id
  186. WHERE c.candidate_id = ?
  187. ORDER BY d
  188. ", @candidate.id)
  189. haml :candidate
  190. else
  191. 404
  192. end
  193. end
  194. get '/candidates/?' do
  195. @candidates = Candidate.all(:order => [ :surname, :forenames ])
  196. haml :candidates
  197. end
  198. get '/bodies/:body/elections/:date/:districts_name/:district' do
  199. @district = District.first(:slug => params[:district])
  200. @body = Body.first(:slug => params[:body])
  201. @election = Election.first(:body => @body, :d => params[:date])
  202. @candidacies = Candidacy.all(:district => @district, :election => @election, :order => [:votes.desc])
  203. @total_votes = Candidacy.sum(:votes, :district => @district, :election => @election)
  204. @total_candidates = Candidacy.count(:district => @district, :election => @election)
  205. @total_seats = Candidacy.sum(:seats, :district => @district, :election => @election)
  206. @districts_in_this_election = @election.candidacies.districts
  207. @poll = Poll.get(@district.id, @election.id)
  208. if @total_seats == 1
  209. @share_denominator = @total_votes
  210. elsif @poll && @poll.valid_ballot_papers
  211. @share_denominator = @poll.valid_ballot_papers
  212. else
  213. @share_denominator = @total_votes / @total_seats
  214. @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."
  215. end
  216. # Postgres: All the columns selected when using GROUP BY must either be aggregate functions or appear in the GROUP BY clause
  217. @results_by_party = repository(:default).adapter.select("
  218. SELECT
  219. p.name AS party_name,
  220. p.colour AS party_colour,
  221. COUNT(c.id) AS num_candidates,
  222. SUM(c.seats) AS num_seats,
  223. SUM(c.votes) AS total_votes
  224. FROM candidacies c
  225. LEFT JOIN parties p
  226. ON c.party_id = p.id
  227. WHERE c.district_id = ?
  228. AND c.election_id = ?
  229. GROUP BY p.name, p.colour
  230. ORDER BY total_votes DESC
  231. ", @district.id, @election.id)
  232. haml :resultsdistrict
  233. end
  234. get '/bodies/:body/:districts_name/:district' do
  235. @district = District.first(:slug => params[:district])
  236. @body = Body.first(:slug => params[:body])
  237. haml :district
  238. end
  239. get '/how-the-council-election-works' do
  240. haml :election
  241. end
  242. get '/how-the-parliament-election-works' do
  243. haml :parliament
  244. end
  245. get '/error' do
  246. haml :error
  247. end
  248. get '/about' do
  249. haml :about
  250. end
  251. not_found do
  252. haml :not_found
  253. end