Skip to content

Commit e762d38

Browse files
committed
Merge pull request #375 from alphagov/add-constituency-to-api
Add constituency/country signature counts to API
2 parents 1944466 + 42ddbd9 commit e762d38

51 files changed

Lines changed: 1209 additions & 637 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/controllers/admin/petitions_controller.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,11 @@ def render_csv
4242
self.response_body = PetitionsCSVPresenter.new(@petitions).render
4343
end
4444

45-
4645
def set_file_headers
4746
headers["Content-Type"] = "text/csv"
48-
headers["Content-disposition"] = "attachment; filename=petitions.csv"
47+
headers["Content-disposition"] = "attachment; filename=#{csv_filename}"
4948
end
5049

51-
5250
def set_streaming_headers
5351
#nginx doc: Setting this to "no" will allow unbuffered responses suitable for Comet and HTTP streaming applications
5452
headers['X-Accel-Buffering'] = 'no'
@@ -57,6 +55,9 @@ def set_streaming_headers
5755
headers.delete("Content-Length")
5856
end
5957

58+
def csv_filename
59+
"#{@petitions.scope.to_s.dasherize}-petitions-#{Time.current.to_s(:number)}.csv"
60+
end
6061

6162
def filter_by_tag?
6263
params[:t].present? && !(filter_by_state? || filter_by_keyword?)

app/controllers/local_petitions_controller.rb

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@
33
class LocalPetitionsController < ApplicationController
44
respond_to :html
55

6-
before_action :sanitize_postcode
7-
before_action :find_constituency, if: :postcode?
8-
before_action :find_petitions, if: :constituency?
6+
before_action :sanitize_postcode, only: :index
7+
before_action :find_by_postcode, if: :postcode?, only: :index
8+
before_action :find_by_slug, only: :show
9+
before_action :find_petitions, if: :constituency?, only: :show
10+
before_action :redirect_to_constituency, if: :constituency?, only: :index
911

1012
def index
13+
end
14+
15+
def show
1116
respond_with(@petitions)
1217
end
1318

@@ -21,15 +26,23 @@ def postcode?
2126
@postcode.present?
2227
end
2328

24-
def find_constituency
25-
@constituency = ConstituencyApi.constituency(@postcode)
29+
def find_by_postcode
30+
@constituency = Constituency.find_by_postcode(@postcode)
31+
end
32+
33+
def find_by_slug
34+
@constituency = Constituency.find_by_slug!(params[:id])
2635
end
2736

2837
def constituency?
2938
@constituency.present?
3039
end
3140

3241
def find_petitions
33-
@petitions = Petition.popular_in_constituency(@constituency.id, 50)
42+
@petitions = Petition.popular_in_constituency(@constituency.external_id, 50)
43+
end
44+
45+
def redirect_to_constituency
46+
redirect_to local_petition_url(@constituency.slug)
3447
end
3548
end

app/helpers/page_title_helper.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ def options
4343
opts[:postcode] = formatted_postcode if postcode?
4444

4545
if postcode?
46-
opts[:count] = constituency? ? 1 : 0
46+
opts[:count] = 1
4747
else
48-
opts[:count] = -1
48+
opts[:count] = 0
4949
end
5050

5151
if petition?

app/lib/constituency_api.rb

Lines changed: 0 additions & 138 deletions
This file was deleted.

app/models/constituency.rb

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
require_dependency 'constituency/api_client'
2+
require_dependency 'constituency/api_query'
3+
4+
class Constituency < ActiveRecord::Base
5+
MP_URL = "http://www.parliament.uk/biographies/commons"
6+
7+
has_many :signatures, primary_key: :external_id
8+
has_many :petitions, through: :signatures
9+
10+
validates :name, presence: true, length: { maximum: 100 }
11+
validates :external_id, presence: true, length: { maximum: 30 }
12+
validates :ons_code, presence: true, format: %r[\A(?:E|W|S|N)\d{8}\z]
13+
validates :mp_id, length: { maximum: 30 }
14+
validates :mp_name, length: { maximum: 100 }
15+
16+
before_validation if: :name_changed? do
17+
self.slug = name.parameterize
18+
end
19+
20+
class << self
21+
def find_by_postcode(postcode)
22+
results = query.fetch(postcode)
23+
24+
if attributes = results.first
25+
find_or_initialize_by(external_id: attributes[:external_id]) do |constituency|
26+
constituency.attributes = attributes
27+
28+
if constituency.changed? || constituency.new_record?
29+
constituency.save!
30+
end
31+
end
32+
end
33+
end
34+
35+
private
36+
37+
def query
38+
ApiQuery.new
39+
end
40+
end
41+
42+
def mp_url
43+
"#{MP_URL}/#{mp_name.parameterize}/#{mp_id}"
44+
end
45+
end
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
require 'faraday'
2+
require 'postcode_sanitizer'
3+
4+
class Constituency < ActiveRecord::Base
5+
class ApiClient
6+
HOST = 'http://data.parliament.uk'
7+
ENDPOINT = '/membersdataplatform/services/mnis/Constituencies/%{postcode}/'
8+
TIMEOUT = 5
9+
10+
def call(postcode)
11+
faraday.get(path(postcode)) do |request|
12+
request.options[:timeout] = TIMEOUT
13+
request.options[:open_timeout] = TIMEOUT
14+
end
15+
end
16+
17+
private
18+
19+
def faraday
20+
@faraday ||= Faraday.new(HOST) do |f|
21+
f.response :follow_redirects
22+
f.response :raise_error
23+
f.adapter Faraday.default_adapter
24+
end
25+
end
26+
27+
def path(postcode)
28+
ENDPOINT % { postcode: escape_path(postcode) }
29+
end
30+
31+
def escape_path(value)
32+
Rack::Utils.escape_path(sanitize(value))
33+
end
34+
35+
def sanitize(value)
36+
PostcodeSanitizer.call(value)
37+
end
38+
end
39+
end
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
require 'nokogiri'
2+
3+
class Constituency < ActiveRecord::Base
4+
class ApiQuery
5+
CONSTITUENCIES = '//Constituencies/Constituency'
6+
CONSTITUENCY_ID = './Constituency_Id'
7+
CONSTITUENCY_NAME = './Name'
8+
CONSTITUENCY_CODE = './ONSCode'
9+
10+
CURRENT_MP = './RepresentingMembers/RepresentingMember[1]'
11+
MP_ID = './Member_Id'
12+
MP_NAME = './Member'
13+
MP_DATE = './StartDate'
14+
15+
def fetch(postcode)
16+
response = client.call(postcode)
17+
18+
if response.success?
19+
parse(response.body)
20+
else
21+
[]
22+
end
23+
rescue Faraday::ResourceNotFound, Faraday::ClientError => e
24+
return []
25+
rescue Faraday::Error => e
26+
Appsignal.send_exception(e) if defined?(Appsignal)
27+
return []
28+
end
29+
30+
private
31+
32+
def client
33+
@client ||= ApiClient.new
34+
end
35+
36+
def parse(body)
37+
xml = Nokogiri::XML(body)
38+
39+
xml.xpath(CONSTITUENCIES).map do |node|
40+
{}.tap do |attrs|
41+
attrs[:name] = node.xpath(CONSTITUENCY_NAME).text
42+
attrs[:external_id] = node.xpath(CONSTITUENCY_ID).text
43+
attrs[:ons_code] = node.xpath(CONSTITUENCY_CODE).text
44+
45+
if mp = node.at_xpath(CURRENT_MP)
46+
attrs[:mp_id] = mp.xpath(MP_ID).text
47+
attrs[:mp_name] = mp.xpath(MP_NAME).text
48+
attrs[:mp_date] = mp.xpath(MP_DATE).text
49+
end
50+
end
51+
end
52+
end
53+
end
54+
end

app/models/constituency_petition_journal.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
class ConstituencyPetitionJournal < ActiveRecord::Base
22
belongs_to :petition
3+
belongs_to :constituency, primary_key: :external_id
34

45
validates :petition, presence: true
56
validates :constituency_id, presence: true, length: { maximum: 255 }
67
validates :constituency_id, uniqueness: { scope: [:petition_id] }
78
validates :signature_count, presence: true
89

10+
delegate :name, :ons_code, :mp_name, to: :constituency
11+
912
scope :ordered, -> {
1013
order("#{table_name}.signature_count DESC")
1114
}

0 commit comments

Comments
 (0)