pupa 0.1.7 → 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -0
- data/PERFORMANCE.md +10 -12
- data/README.md +38 -29
- data/lib/pupa/models/area.rb +23 -0
- data/lib/pupa/models/concerns/contactable.rb +13 -1
- data/lib/pupa/models/concerns/nameable.rb +27 -3
- data/lib/pupa/models/membership.rb +3 -5
- data/lib/pupa/models/motion.rb +23 -0
- data/lib/pupa/models/organization.rb +4 -6
- data/lib/pupa/models/person.rb +4 -6
- data/lib/pupa/models/post.rb +6 -6
- data/lib/pupa/models/vote.rb +23 -0
- data/lib/pupa/models/vote_event.rb +72 -0
- data/lib/pupa/processor/client.rb +4 -4
- data/lib/pupa/version.rb +1 -1
- data/lib/pupa.rb +4 -0
- data/schemas/popolo/area.json +54 -0
- data/schemas/popolo/contact_detail.json +1 -1
- data/schemas/popolo/count.json +27 -0
- data/schemas/popolo/group_result.json +22 -0
- data/schemas/popolo/membership.json +1 -1
- data/schemas/popolo/motion.json +82 -0
- data/schemas/popolo/organization.json +1 -1
- data/schemas/popolo/person.json +1 -1
- data/schemas/popolo/vote.json +57 -0
- data/schemas/popolo/vote_event.json +93 -0
- data/spec/models/area_spec.rb +13 -0
- data/spec/models/concerns/contactable_spec.rb +4 -4
- data/spec/models/concerns/nameable_spec.rb +4 -4
- data/spec/models/motion_spec.rb +13 -0
- data/spec/models/vote_event_spec.rb +59 -0
- data/spec/models/vote_spec.rb +13 -0
- metadata +21 -4
- data/lib/pupa/processor/middleware/gzip.rb +0 -24
- data/lib/pupa/processor/middleware/raise_error.rb +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f3539ca0b77fc4b2174c42857990638713dac180
|
4
|
+
data.tar.gz: c5a18a0390ac9a3a48e0ba868ac1dd627af0db08
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c93da2b8ff26107c28e02e51b8ea2ec999b5e086cc8564e0af6062a58af7fe3b71429e20d26eefc57471313da578699218a4098cd35ca94eed831340f63beef1
|
7
|
+
data.tar.gz: 2444a4491a3c0b499f4dd40c4b188a72644fc4fddec941471816747bb2e694bd5b96c7d550fe4dd98ed6c477b358ade25b25cf17c3a329ca8733929f6041252c
|
data/Gemfile
CHANGED
data/PERFORMANCE.md
CHANGED
@@ -1,6 +1,4 @@
|
|
1
|
-
# Pupa.rb:
|
2
|
-
|
3
|
-
## Performance
|
1
|
+
# Pupa.rb: Performance
|
4
2
|
|
5
3
|
Pupa.rb offers several ways to significantly improve performance.
|
6
4
|
|
@@ -8,13 +6,13 @@ In an example case, reducing disk I/O and skipping validation as described below
|
|
8
6
|
|
9
7
|
The `import` action's performance is currently limited by the database when a dependency graph is used to determine the evaluation order. If a dependency graph cannot be used because you don't know a related object's ID, [several optimizations](https://github.com/opennorth/pupa-ruby/issues/12) can be implemented to improve performance.
|
10
8
|
|
11
|
-
|
9
|
+
## Reducing HTTP requests
|
12
10
|
|
13
11
|
HTTP requests consume the most time. To avoid repeat HTTP requests while developing a scraper, cache all HTTP responses. Pupa.rb will by default use a `web_cache` directory in the same directory as your script. You can change the directory by setting the `--cache_dir` switch on the command line, for example:
|
14
12
|
|
15
13
|
ruby cat.rb --cache_dir /tmp/my_cache_dir
|
16
14
|
|
17
|
-
|
15
|
+
## Parallelizing HTTP requests
|
18
16
|
|
19
17
|
To enable parallel requests, use the `typhoeus` gem. Unless you are using an old version of Typhoeus (< 0.5), both Faraday and Typhoeus define a Faraday adapter, but you must use the one defined by Typhoeus, like so:
|
20
18
|
|
@@ -58,11 +56,11 @@ responses.each do |response|
|
|
58
56
|
end
|
59
57
|
```
|
60
58
|
|
61
|
-
|
59
|
+
## Reducing disk I/O
|
62
60
|
|
63
61
|
After HTTP requests, disk I/O is the slowest operation. Two types of files are written to disk: HTTP responses are written to the cache directory, and JSON documents are written to the output directory. Writing to memory is much faster than writing to disk.
|
64
62
|
|
65
|
-
|
63
|
+
### RAM file systems
|
66
64
|
|
67
65
|
A simple solution is to create a file system in RAM, like `tmpfs` on Linux for example, and to use it as your `output_dir` and `cache_dir`. On OS X, you must create a RAM disk. To create a 128MB RAM disk, for example, run:
|
68
66
|
|
@@ -80,7 +78,7 @@ Once you are done with the RAM disk, release the memory:
|
|
80
78
|
|
81
79
|
Using a RAM disk will significantly improve performance; however, the data will be lost between reboots unless you move the data to a hard disk. Using Memcached (for caching) and Redis (for storage) is moderately faster than using a RAM disk, and Redis will not lose your output data between reboots.
|
82
80
|
|
83
|
-
|
81
|
+
### Memcached
|
84
82
|
|
85
83
|
You may cache HTTP responses in [Memcached](http://memcached.org/). First, require the `dalli` gem. Then:
|
86
84
|
|
@@ -88,7 +86,7 @@ You may cache HTTP responses in [Memcached](http://memcached.org/). First, requi
|
|
88
86
|
|
89
87
|
The data in Memcached will be lost between reboots.
|
90
88
|
|
91
|
-
|
89
|
+
### Redis
|
92
90
|
|
93
91
|
You may dump JSON documents in [Redis](http://redis.io/). First, require the `redis-store` gem. Then:
|
94
92
|
|
@@ -102,17 +100,17 @@ Requiring the `hiredis` gem will slightly improve performance.
|
|
102
100
|
|
103
101
|
Note that Pupa.rb flushes the Redis database before scraping. If you use Redis, **DO NOT** share a Redis database with Pupa.rb and other applications. You can select a different database than the default `0` for use with Pupa.rb by passing an argument like `redis://localhost:6379/15`, where `15` is the database number.
|
104
102
|
|
105
|
-
|
103
|
+
## Skipping validation
|
106
104
|
|
107
105
|
The `json-schema` gem is slow compared to, for example, [JSV](https://github.com/garycourt/JSV). Setting the `--no-validate` switch and running JSON Schema validations separately can further reduce a scraper's running time.
|
108
106
|
|
109
107
|
The [pupa-validate](https://npmjs.org/package/pupa-validate) npm package can be used to validate JSON documents using the faster JSV. In an example case, using JSV instead of the `json-schema` gem reduced by half the time to validate 10,000 documents.
|
110
108
|
|
111
|
-
|
109
|
+
## Ruby version
|
112
110
|
|
113
111
|
Pupa.rb requires Ruby 2.x. If you have already made all the above optimizations, you may notice a significant improvement by using Ruby 2.1, which has better garbage collection than Ruby 2.0.
|
114
112
|
|
115
|
-
|
113
|
+
## Profiling
|
116
114
|
|
117
115
|
You can profile your code using [perftools.rb](https://github.com/tmm1/perftools.rb). First, install the gem:
|
118
116
|
|
data/README.md
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# Pupa.rb: A Data Scraping Framework
|
2
2
|
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/pupa.svg)](http://badge.fury.io/rb/pupa)
|
3
4
|
[![Build Status](https://secure.travis-ci.org/opennorth/pupa-ruby.png)](http://travis-ci.org/opennorth/pupa-ruby)
|
4
5
|
[![Dependency Status](https://gemnasium.com/opennorth/pupa-ruby.png)](https://gemnasium.com/opennorth/pupa-ruby)
|
5
6
|
[![Coverage Status](https://coveralls.io/repos/opennorth/pupa-ruby/badge.png?branch=master)](https://coveralls.io/r/opennorth/pupa-ruby)
|
@@ -9,27 +10,9 @@ Pupa.rb is a Ruby 2.x fork of Sunlight Labs' [Pupa](https://github.com/opencivic
|
|
9
10
|
|
10
11
|
gem install pupa
|
11
12
|
|
12
|
-
## What it tries to solve
|
13
|
-
|
14
|
-
Pupa.rb's goal is to make scraping less painful by solving common problems:
|
15
|
-
|
16
|
-
* If you are updating a database by scraping a website, you can either delete and recreate records, or you can merge the scraped records with the saved records. Pupa.rb offers a simple way to merge records, by using an object's stable properties for identification.
|
17
|
-
* If you are scraping a source that references other sources – for example, a committee that references its members – you may want to link the source to its references with foreign keys. Pupa.rb will use whatever identifying information you scrape – for example, the members' names – to fill in the foreign keys for you.
|
18
|
-
* Data sources may use different formats in different contexts. Pupa.rb makes it easy to [select scraping methods](https://github.com/opennorth/pupa-ruby#scraping-method-selection) according to criteria, like the year of publication for example.
|
19
|
-
* By splitting the scrape (extract) and import (load) steps, it's easier for you and volunteers to start a scraper without any interaction with a database.
|
20
|
-
|
21
|
-
In short, Pupa.rb lets you spend more time on the tasks that are unique to your use case, and less time on common tasks like caching, merging and storing data. It also provides helpful features like:
|
22
|
-
|
23
|
-
* Logging, to make debugging and monitoring a scraper easier
|
24
|
-
* [Automatic response parsing](https://github.com/opennorth/pupa-ruby#automatic-response-parsing) of JSON, XML and HTML
|
25
|
-
* Option parsing, to control your scraper from the command-line
|
26
|
-
* Object validation, using [JSON Schema](http://json-schema.org/)
|
27
|
-
|
28
|
-
Pupa.rb is extensible, so that you can add your own models, parsers, helpers, actions, etc. It also offers several ways to [improve your scraper's performance](https://github.com/opennorth/pupa-ruby#performance).
|
29
|
-
|
30
13
|
## Usage
|
31
14
|
|
32
|
-
You can
|
15
|
+
You can scrape any sort of data with Pupa.rb using your own models. You can also use Pupa.rb to scrape people, organizations, memberships and posts according to the [Popolo](http://popoloproject.com/) open government data specification. This gem is up-to-date with Popolo's 2014-05-09 version, but omits the Motion, VoteEvent, Count, Vote and Area classes.
|
33
16
|
|
34
17
|
The [cat.rb](http://opennorth.github.io/pupa-ruby/docs/cat.html) example shows you how to:
|
35
18
|
|
@@ -69,21 +52,21 @@ The [organization.rb](http://opennorth.github.io/pupa-ruby/docs/organization.htm
|
|
69
52
|
|
70
53
|
JSON parsing is enabled by default. To enable automatic parsing of HTML and XML, require the `nokogiri` and `multi_xml` gems.
|
71
54
|
|
72
|
-
|
73
|
-
|
74
|
-
Both Pupa.rb and Sunlight Labs' [Pupa](https://github.com/opencivicdata/pupa) implement models for people, organizations and memberships from the [Popolo](http://popoloproject.com/) open government data specification. Pupa.rb lets you use your own classes, but Pupa only supports a fixed set of classes. A consequence of Pupa.rb's flexibility is that the value of the `_type` property for `Person`, `Organization` and `Membership` objects differs between Pupa.rb and Pupa. Pupa.rb has namespaced types like `pupa/person` – to allow Ruby to load the `Person` class in the `Pupa` module – whereas Pupa has unnamespaced types like `person`.
|
55
|
+
### Automatic response decompression
|
75
56
|
|
76
|
-
|
57
|
+
Until [Faraday Middleware](https://github.com/lostisland/faraday_middleware) releases its next version (> 0.9.1), you must use the gem from its git repository to automatically decompress responses:
|
77
58
|
|
78
59
|
```ruby
|
79
|
-
|
60
|
+
gem 'faraday_middleware', git: 'https://github.com/lostisland/faraday_middleware.git'
|
80
61
|
```
|
81
62
|
|
82
|
-
|
63
|
+
## Performance
|
64
|
+
|
65
|
+
Pupa.rb offers several ways to significantly improve performance. [Read the documentation.](https://github.com/opennorth/pupa-ruby/blob/master/PERFORMANCE.md#readme)
|
83
66
|
|
84
67
|
## Integration with ODMs
|
85
68
|
|
86
|
-
`Pupa::Model` is incompatible with `Mongoid::Document`. Don't do this
|
69
|
+
`Pupa::Model` is incompatible with `Mongoid::Document`. **Don't do this**:
|
87
70
|
|
88
71
|
```ruby
|
89
72
|
class Cat
|
@@ -92,11 +75,37 @@ class Cat
|
|
92
75
|
end
|
93
76
|
```
|
94
77
|
|
95
|
-
Instead, have a scraping model that includes `Pupa::Model` and an app model that includes `Mongoid::Document
|
78
|
+
Instead, have a simple scraping model that includes `Pupa::Model` and an app model that includes `Mongoid::Document` with your app's business logic.
|
96
79
|
|
97
|
-
##
|
80
|
+
## What it tries to solve
|
98
81
|
|
99
|
-
Pupa.rb
|
82
|
+
Pupa.rb's goal is to make scraping less painful by solving common problems:
|
83
|
+
|
84
|
+
* If you are updating a database by scraping a website, you can either delete and recreate records, or you can merge the scraped records with the saved records. Pupa.rb offers a simple way to merge records, by [using an object's stable properties for identification](http://opennorth.github.io/pupa-ruby/docs/cat.html#section-7).
|
85
|
+
* If you are scraping a source that references other sources – for example, a committee that references its members – you may want to link the source to its references with foreign keys. Pupa.rb will use whatever identifying information you scrape – for example, the members' names – to [fill in the foreign keys for you](http://opennorth.github.io/pupa-ruby/docs/bill.html#section-4).
|
86
|
+
* Data sources may use different formats in different contexts. Pupa.rb makes it easy to [select scraping methods](#scraping-method-selection) according to criteria, like the year of publication [for example](http://opennorth.github.io/pupa-ruby/docs/legislator.html#section-3).
|
87
|
+
* By splitting the scrape (extract) and import (load) steps, it's easier for you and volunteers to start a scraper without any interaction with a database.
|
88
|
+
|
89
|
+
In short, Pupa.rb lets you spend more time on the tasks that are unique to your use case, and less time on common tasks like caching, merging and storing data. It also provides helpful features like:
|
90
|
+
|
91
|
+
* Logging, to make debugging and monitoring a scraper easier
|
92
|
+
* [Automatic response parsing](#automatic-response-parsing) of JSON, XML and HTML
|
93
|
+
* [Option parsing](http://opennorth.github.io/pupa-ruby/docs/legislator.html#section-9), to control your scraper from the command-line
|
94
|
+
* [Object validation](http://opennorth.github.io/pupa-ruby/docs/cat.html#section-4), using [JSON Schema](http://json-schema.org/)
|
95
|
+
|
96
|
+
Pupa.rb is extensible, so that you can add your own models, parsers, helpers, actions, etc. It also offers several ways to [improve your scraper's performance](#performance).
|
97
|
+
|
98
|
+
## [OpenCivicData](http://opencivicdata.org/) compatibility
|
99
|
+
|
100
|
+
Both Pupa.rb and Sunlight Labs' [Pupa](https://github.com/opencivicdata/pupa) implement models for people, organizations and memberships from the [Popolo](http://popoloproject.com/) open government data specification. Pupa.rb lets you use your own classes, but Pupa only supports a fixed set of classes. A consequence of Pupa.rb's flexibility is that the value of the `_type` property for `Person`, `Organization` and `Membership` objects differs between Pupa.rb and Pupa. Pupa.rb has namespaced types like `pupa/person` – to allow Ruby to load the `Person` class in the `Pupa` module – whereas Pupa has unnamespaced types like `person`.
|
101
|
+
|
102
|
+
To save objects to MongoDB with unnamespaced types like Sunlight Labs' Pupa – in order to benefit from other tools in the [OpenCivicData](http://opencivicdata.org/) stack – add this line to the top of your script:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
require 'pupa/refinements/opencivicdata'
|
106
|
+
```
|
107
|
+
|
108
|
+
It is not currently possible to run the `scrape` action with one of Pupa.rb and Pupa, and to then run the `import` action with the other. Both actions must be run by the same library.
|
100
109
|
|
101
110
|
## Testing
|
102
111
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Pupa
|
2
|
+
# A geographic area whose geometry may change over time.
|
3
|
+
class Area
|
4
|
+
include Model
|
5
|
+
|
6
|
+
self.schema = 'popolo/area'
|
7
|
+
|
8
|
+
include Concerns::Timestamps
|
9
|
+
include Concerns::Sourceable
|
10
|
+
|
11
|
+
attr_accessor :name, :identifier, :classification, :parent_id, :geometry
|
12
|
+
dump :name, :identifier, :classification, :parent_id, :geometry
|
13
|
+
|
14
|
+
foreign_key :parent_id
|
15
|
+
|
16
|
+
# Returns the area's name.
|
17
|
+
#
|
18
|
+
# @return [String] the area's name
|
19
|
+
def to_s
|
20
|
+
name
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -26,11 +26,23 @@ module Pupa
|
|
26
26
|
# @param [String] type a type of medium, e.g. "fax" or "email"
|
27
27
|
# @param [String] value a value, e.g. a phone number or email address
|
28
28
|
# @param [String] note a note, e.g. for grouping contact details by physical location
|
29
|
-
|
29
|
+
# @param [String] label a human-readable label for the contact detail
|
30
|
+
# @param [String,Date,Time] valid_from the date from which the contact detail is valid
|
31
|
+
# @param [String,Date,Time] valid_until the date from which the contact detail is no longer valid
|
32
|
+
def add_contact_detail(type, value, note: nil, label: nil, valid_from: nil, valid_until: nil)
|
30
33
|
data = {type: type, value: value}
|
31
34
|
if note
|
32
35
|
data[:note] = note
|
33
36
|
end
|
37
|
+
if label
|
38
|
+
data[:label] = label
|
39
|
+
end
|
40
|
+
if valid_from
|
41
|
+
data[:valid_from] = valid_from
|
42
|
+
end
|
43
|
+
if valid_until
|
44
|
+
data[:valid_until] = valid_until
|
45
|
+
end
|
34
46
|
if type.present? && value.present?
|
35
47
|
@contact_details << data
|
36
48
|
end
|
@@ -24,10 +24,16 @@ module Pupa
|
|
24
24
|
# Adds an alternate or former name.
|
25
25
|
#
|
26
26
|
# @param [String] name an alternate or former name
|
27
|
-
# @param [Date,Time] start_date the date on which the name was adopted
|
28
|
-
# @param [Date,Time] end_date the date on which the name was abandoned
|
27
|
+
# @param [String,Date,Time] start_date the date on which the name was adopted
|
28
|
+
# @param [String,Date,Time] end_date the date on which the name was abandoned
|
29
29
|
# @param [String] note a note, e.g. "Birth name"
|
30
|
-
|
30
|
+
# @param [String] family_name one or more family names
|
31
|
+
# @param [String] given_name one or more primary given names
|
32
|
+
# @param [String] additional_name one or more secondary given names
|
33
|
+
# @param [String] honorific_prefix one or more honorifics preceding a person's name
|
34
|
+
# @param [String] honorific_suffix one or more honorifics following a person's name
|
35
|
+
# @param [String] patronymic_name one or more patronymic names
|
36
|
+
def add_name(name, start_date: nil, end_date: nil, note: nil, family_name: nil, given_name: nil, additional_name: nil, honorific_prefix: nil, honorific_suffix: nil, patronymic_name: nil)
|
31
37
|
data = {name: name}
|
32
38
|
if start_date
|
33
39
|
data[:start_date] = start_date
|
@@ -38,6 +44,24 @@ module Pupa
|
|
38
44
|
if note
|
39
45
|
data[:note] = note
|
40
46
|
end
|
47
|
+
if family_name
|
48
|
+
data[:family_name] = family_name
|
49
|
+
end
|
50
|
+
if given_name
|
51
|
+
data[:given_name] = given_name
|
52
|
+
end
|
53
|
+
if additional_name
|
54
|
+
data[:additional_name] = additional_name
|
55
|
+
end
|
56
|
+
if honorific_prefix
|
57
|
+
data[:honorific_prefix] = honorific_prefix
|
58
|
+
end
|
59
|
+
if honorific_suffix
|
60
|
+
data[:honorific_suffix] = honorific_suffix
|
61
|
+
end
|
62
|
+
if patronymic_name
|
63
|
+
data[:patronymic_name] = patronymic_name
|
64
|
+
end
|
41
65
|
if name.present?
|
42
66
|
@other_names << data
|
43
67
|
end
|
@@ -10,12 +10,10 @@ module Pupa
|
|
10
10
|
include Concerns::Contactable
|
11
11
|
include Concerns::Linkable
|
12
12
|
|
13
|
-
attr_accessor :label, :role, :person_id, :organization_id, :post_id,
|
14
|
-
|
15
|
-
dump :label, :role, :person_id, :organization_id, :post_id,
|
16
|
-
:start_date, :end_date
|
13
|
+
attr_accessor :label, :role, :member, :person_id, :organization_id, :post_id, :on_behalf_of_id, :area_id, :start_date, :end_date
|
14
|
+
dump :label, :role, :member, :person_id, :organization_id, :post_id, :on_behalf_of_id, :area_id, :start_date, :end_date
|
17
15
|
|
18
|
-
foreign_key :person_id, :organization_id, :post_id
|
16
|
+
foreign_key :person_id, :organization_id, :post_id, :on_behalf_of_id, :area_id
|
19
17
|
|
20
18
|
# Returns the IDs of the parties to the relationship.
|
21
19
|
#
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Pupa
|
2
|
+
# A formal step to introduce a matter for consideration by an organization.
|
3
|
+
class Motion
|
4
|
+
include Model
|
5
|
+
|
6
|
+
self.schema = 'popolo/motion'
|
7
|
+
|
8
|
+
include Concerns::Timestamps
|
9
|
+
include Concerns::Sourceable
|
10
|
+
|
11
|
+
attr_accessor :organization_id, :legislative_session_id, :creator_id, :text, :classification, :date, :requirement, :result
|
12
|
+
dump :organization_id, :legislative_session_id, :creator_id, :text, :classification, :date, :requirement, :result
|
13
|
+
|
14
|
+
foreign_key :organization_id, :legislative_session_id, :creator_id
|
15
|
+
|
16
|
+
# Returns the motion's text and organization ID.
|
17
|
+
#
|
18
|
+
# @return [String] the motion's text and organization ID
|
19
|
+
def to_s
|
20
|
+
"#{text} in #{organization_id}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -13,14 +13,12 @@ module Pupa
|
|
13
13
|
include Concerns::Contactable
|
14
14
|
include Concerns::Linkable
|
15
15
|
|
16
|
-
attr_accessor :name, :classification, :parent_id, :
|
17
|
-
|
18
|
-
dump :name, :classification, :parent_id, :parent, :founding_date,
|
19
|
-
:dissolution_date, :image
|
16
|
+
attr_accessor :name, :classification, :parent_id, :area_id, :founding_date, :dissolution_date, :image, :parent
|
17
|
+
dump :name, :classification, :parent_id, :area_id, :founding_date, :dissolution_date, :image, :parent
|
20
18
|
|
21
|
-
foreign_key :parent_id
|
19
|
+
foreign_key :parent_id, :area_id
|
22
20
|
|
23
|
-
foreign_object :parent
|
21
|
+
foreign_object :parent # for testing
|
24
22
|
|
25
23
|
# Returns the name of the organization.
|
26
24
|
#
|
data/lib/pupa/models/person.rb
CHANGED
@@ -12,12 +12,10 @@ module Pupa
|
|
12
12
|
include Concerns::Contactable
|
13
13
|
include Concerns::Linkable
|
14
14
|
|
15
|
-
attr_accessor :name, :
|
16
|
-
:honorific_prefix, :honorific_suffix, :patronymic_name, :sort_name
|
17
|
-
|
18
|
-
|
19
|
-
:honorific_prefix, :honorific_suffix, :patronymic_name, :sort_name,
|
20
|
-
:email, :gender, :birth_date, :death_date, :image, :summary, :biography
|
15
|
+
attr_accessor :name, :email, :gender, :birth_date, :death_date, :image, :summary, :biography, :national_identity,
|
16
|
+
:family_name, :given_name, :additional_name, :honorific_prefix, :honorific_suffix, :patronymic_name, :sort_name
|
17
|
+
dump :name, :email, :gender, :birth_date, :death_date, :image, :summary, :biography, :national_identity,
|
18
|
+
:family_name, :given_name, :additional_name, :honorific_prefix, :honorific_suffix, :patronymic_name, :sort_name
|
21
19
|
|
22
20
|
# Returns the person's name.
|
23
21
|
#
|
data/lib/pupa/models/post.rb
CHANGED
@@ -10,16 +10,16 @@ module Pupa
|
|
10
10
|
include Concerns::Contactable
|
11
11
|
include Concerns::Linkable
|
12
12
|
|
13
|
-
attr_accessor :label, :role, :organization_id, :start_date, :end_date
|
14
|
-
dump
|
13
|
+
attr_accessor :label, :other_label, :role, :organization_id, :area_id, :start_date, :end_date
|
14
|
+
dump :label, :other_label, :role, :organization_id, :area_id, :start_date, :end_date
|
15
15
|
|
16
|
-
foreign_key :organization_id
|
16
|
+
foreign_key :organization_id, :area_id
|
17
17
|
|
18
|
-
# Returns the post's label and organization ID.
|
18
|
+
# Returns the post's label or role and organization ID.
|
19
19
|
#
|
20
|
-
# @return [String] the post's label and organization ID
|
20
|
+
# @return [String] the post's label or role and organization ID
|
21
21
|
def to_s
|
22
|
-
"#{label} in #{organization_id}"
|
22
|
+
"#{label || role} in #{organization_id}"
|
23
23
|
end
|
24
24
|
|
25
25
|
# A post should have a unique label within an organization, through it may
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Pupa
|
2
|
+
# A voter's vote in a vote event.
|
3
|
+
class Vote
|
4
|
+
include Model
|
5
|
+
|
6
|
+
self.schema = 'popolo/vote'
|
7
|
+
|
8
|
+
include Concerns::Timestamps
|
9
|
+
include Concerns::Sourceable
|
10
|
+
|
11
|
+
attr_accessor :vote_event_id, :voter_id, :option, :group_id, :role, :weight, :pair_id
|
12
|
+
dump :vote_event_id, :voter_id, :option, :group_id, :role, :weight, :pair_id
|
13
|
+
|
14
|
+
foreign_key :vote_event_id, :voter_id, :group_id, :pair_id
|
15
|
+
|
16
|
+
# Returns the vote's option, voter ID and vote event ID.
|
17
|
+
#
|
18
|
+
# @return [String] the vote's option, voter ID and vote event ID
|
19
|
+
def to_s
|
20
|
+
"#{option} by #{voter_id} in #{vote_event_id}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Pupa
|
2
|
+
# An event at which people's votes are recorded.
|
3
|
+
class VoteEvent
|
4
|
+
include Model
|
5
|
+
|
6
|
+
self.schema = 'popolo/vote_event'
|
7
|
+
|
8
|
+
include Concerns::Timestamps
|
9
|
+
include Concerns::Sourceable
|
10
|
+
|
11
|
+
attr_accessor :identifier, :motion_id, :organization_id, :legislative_session_id, :start_date, :end_date, :result, :group_results, :counts
|
12
|
+
dump :identifier, :motion_id, :organization_id, :legislative_session_id, :start_date, :end_date, :result, :group_results, :counts
|
13
|
+
|
14
|
+
foreign_key :motion_id, :organization_id, :legislative_session_id
|
15
|
+
|
16
|
+
def initialize(*args)
|
17
|
+
@group_results = []
|
18
|
+
@counts = []
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns the vote event's identifier and organization ID.
|
23
|
+
#
|
24
|
+
# @return [String] the vote event's identifier and organization ID
|
25
|
+
def to_s
|
26
|
+
"#{identifier} in #{organization_id}"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Sets the group results.
|
30
|
+
#
|
31
|
+
# @param [Array] group_results a list of group results
|
32
|
+
def group_results=(group_results)
|
33
|
+
@group_results = symbolize_keys(group_results)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Sets the counts.
|
37
|
+
#
|
38
|
+
# @param [Array] counts a list of counts
|
39
|
+
def counts=(counts)
|
40
|
+
@counts = symbolize_keys(counts)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Adds a group result.
|
44
|
+
#
|
45
|
+
# @param [String] result the result of the vote event within a group of voters
|
46
|
+
# @param [String] group a group of voters
|
47
|
+
def add_group_result(result, group: nil)
|
48
|
+
data = {result: result}
|
49
|
+
if group
|
50
|
+
data[:group] = group
|
51
|
+
end
|
52
|
+
if result.present?
|
53
|
+
@group_results << data
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Adds a count.
|
58
|
+
#
|
59
|
+
# @param [String] option an option in a vote event
|
60
|
+
# @param [String] value the number of votes for an option
|
61
|
+
# @param [String] group a group of voters
|
62
|
+
def add_count(option, value, group: nil)
|
63
|
+
data = {option: option, value: value}
|
64
|
+
if group
|
65
|
+
data[:group] = group
|
66
|
+
end
|
67
|
+
if option.present? && value.present?
|
68
|
+
@counts << data
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -2,11 +2,9 @@ require 'active_support/cache'
|
|
2
2
|
require 'faraday_middleware'
|
3
3
|
require 'faraday_middleware/response_middleware'
|
4
4
|
|
5
|
-
require 'pupa/processor/middleware/gzip'
|
6
5
|
require 'pupa/processor/middleware/logger'
|
7
6
|
require 'pupa/processor/middleware/parse_html'
|
8
7
|
require 'pupa/processor/middleware/parse_json'
|
9
|
-
require 'pupa/processor/middleware/raise_error'
|
10
8
|
require 'pupa/refinements/faraday'
|
11
9
|
require 'pupa/refinements/faraday_middleware'
|
12
10
|
|
@@ -39,7 +37,7 @@ module Pupa
|
|
39
37
|
Faraday.new do |connection|
|
40
38
|
connection.request :url_encoded
|
41
39
|
connection.use Middleware::Logger, Logger.new('faraday', level: level)
|
42
|
-
connection.use
|
40
|
+
connection.use Faraday::Response::RaiseError
|
43
41
|
|
44
42
|
# @see http://tools.ietf.org/html/rfc4627
|
45
43
|
connection.use Middleware::ParseJson, preserve_raw: true, content_type: /\bjson$/
|
@@ -56,7 +54,9 @@ module Pupa
|
|
56
54
|
end
|
57
55
|
|
58
56
|
# Must come after the parser middlewares.
|
59
|
-
|
57
|
+
if FaradayMiddleware.const_defined?(:Gzip)
|
58
|
+
connection.use FaradayMiddleware::Gzip
|
59
|
+
end
|
60
60
|
|
61
61
|
if cache_dir
|
62
62
|
connection.response :caching do
|
data/lib/pupa/version.rb
CHANGED
data/lib/pupa.rb
CHANGED
@@ -26,10 +26,14 @@ require 'pupa/models/foreign_object'
|
|
26
26
|
require 'pupa/models/model'
|
27
27
|
require 'pupa/models/contact_detail_list'
|
28
28
|
require 'pupa/models/identifier_list'
|
29
|
+
require 'pupa/models/area'
|
29
30
|
require 'pupa/models/membership'
|
31
|
+
require 'pupa/models/motion'
|
30
32
|
require 'pupa/models/organization'
|
31
33
|
require 'pupa/models/person'
|
32
34
|
require 'pupa/models/post'
|
35
|
+
require 'pupa/models/vote'
|
36
|
+
require 'pupa/models/vote_event'
|
33
37
|
|
34
38
|
module Pupa
|
35
39
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-03/schema#",
|
3
|
+
"id": "http://popoloproject.com/schemas/area.json#",
|
4
|
+
"title": "Area",
|
5
|
+
"description": "A geographic area whose geometry may change over time",
|
6
|
+
"type": "object",
|
7
|
+
"properties": {
|
8
|
+
"id": {
|
9
|
+
"description": "The area's unique identifier",
|
10
|
+
"type": ["string", "null"]
|
11
|
+
},
|
12
|
+
"name": {
|
13
|
+
"description": "A primary name",
|
14
|
+
"type": ["string", "null"]
|
15
|
+
},
|
16
|
+
"identifier": {
|
17
|
+
"description": "An issued identifier",
|
18
|
+
"type": ["string", "null"]
|
19
|
+
},
|
20
|
+
"classification": {
|
21
|
+
"description": "An area category, e.g. city",
|
22
|
+
"type": ["string", "null"]
|
23
|
+
},
|
24
|
+
"parent_id": {
|
25
|
+
"description": "The ID of the area that contains this area",
|
26
|
+
"type": ["string", "null"]
|
27
|
+
},
|
28
|
+
"parent": {
|
29
|
+
"description": "The area that contains this area",
|
30
|
+
"$ref": "http://popoloproject.com/schemas/area.json#"
|
31
|
+
},
|
32
|
+
"geometry": {
|
33
|
+
"description": "A geometry",
|
34
|
+
"type": ["object", "null"]
|
35
|
+
},
|
36
|
+
"created_at": {
|
37
|
+
"description": "The time at which the resource was created",
|
38
|
+
"type": ["string", "null"],
|
39
|
+
"format": "date-time"
|
40
|
+
},
|
41
|
+
"updated_at": {
|
42
|
+
"description": "The time at which the resource was last modified",
|
43
|
+
"type": ["string", "null"],
|
44
|
+
"format": "date-time"
|
45
|
+
},
|
46
|
+
"sources": {
|
47
|
+
"description": "URLs to documents from which the area is derived",
|
48
|
+
"type": "array",
|
49
|
+
"items": {
|
50
|
+
"$ref": "http://popoloproject.com/schemas/link.json#"
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
@@ -28,7 +28,7 @@
|
|
28
28
|
"type": ["string", "null"],
|
29
29
|
"pattern": "^[0-9]{4}(-[0-9]{2}){0,2}$"
|
30
30
|
},
|
31
|
-
"
|
31
|
+
"valid_until": {
|
32
32
|
"description": "The date from which the contact detail is no longer valid",
|
33
33
|
"type": ["string", "null"],
|
34
34
|
"pattern": "^[0-9]{4}(-[0-9]{2}){0,2}$"
|
@@ -0,0 +1,27 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-03/schema#",
|
3
|
+
"id": "http://popoloproject.com/schemas/count.json#",
|
4
|
+
"title": "Count",
|
5
|
+
"description": "The number of votes for an option in a vote event",
|
6
|
+
"type": "object",
|
7
|
+
"properties": {
|
8
|
+
"option": {
|
9
|
+
"description": "An option in a vote event",
|
10
|
+
"type": "string",
|
11
|
+
"required": true
|
12
|
+
},
|
13
|
+
"value": {
|
14
|
+
"description": "The number of votes for an option",
|
15
|
+
"type": "integer",
|
16
|
+
"required": true
|
17
|
+
},
|
18
|
+
"group_id": {
|
19
|
+
"description": "The ID of a group of voters",
|
20
|
+
"type": ["string", "null"]
|
21
|
+
},
|
22
|
+
"group": {
|
23
|
+
"description": "A group of voters",
|
24
|
+
"type": ["object", "null"]
|
25
|
+
}
|
26
|
+
}
|
27
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-03/schema#",
|
3
|
+
"id": "http://popoloproject.com/schemas/group_result.json#",
|
4
|
+
"title": "Group result",
|
5
|
+
"description": "A result of a vote event within a group of voters",
|
6
|
+
"type": "object",
|
7
|
+
"properties": {
|
8
|
+
"group_id": {
|
9
|
+
"description": "The ID of a group of voters",
|
10
|
+
"type": ["string", "null"]
|
11
|
+
},
|
12
|
+
"group": {
|
13
|
+
"description": "A group of voters",
|
14
|
+
"type": ["object", "null"]
|
15
|
+
},
|
16
|
+
"result": {
|
17
|
+
"description": "The result of the vote event within a group of voters",
|
18
|
+
"type": "string",
|
19
|
+
"required": true
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}
|
@@ -0,0 +1,82 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-03/schema#",
|
3
|
+
"id": "http://popoloproject.com/schemas/motion.json#",
|
4
|
+
"title": "Motion",
|
5
|
+
"description": "A formal step to introduce a matter for consideration by an organization",
|
6
|
+
"type": "object",
|
7
|
+
"properties": {
|
8
|
+
"id": {
|
9
|
+
"description": "The motion's unique identifier",
|
10
|
+
"type": ["string", "null"]
|
11
|
+
},
|
12
|
+
"organization_id": {
|
13
|
+
"description": "The ID of the organization in which the motion is proposed",
|
14
|
+
"type": ["string", "null"]
|
15
|
+
},
|
16
|
+
"organization": {
|
17
|
+
"description": "The organization in which the motion is proposed",
|
18
|
+
"$ref": "http://popoloproject.com/schemas/organization.json#"
|
19
|
+
},
|
20
|
+
"legislative_session_id": {
|
21
|
+
"description": "The ID of the legislative session in which the motion is proposed",
|
22
|
+
"type": ["string", "null"]
|
23
|
+
},
|
24
|
+
"legislative_session": {
|
25
|
+
"description": "The legislative session in which the motion is proposed",
|
26
|
+
"type": ["object", "null"]
|
27
|
+
},
|
28
|
+
"creator_id": {
|
29
|
+
"description": "The ID of the person who proposed the motion",
|
30
|
+
"type": ["string", "null"]
|
31
|
+
},
|
32
|
+
"creator": {
|
33
|
+
"description": "The person who proposed the motion",
|
34
|
+
"$ref": "http://popoloproject.com/schemas/person.json#"
|
35
|
+
},
|
36
|
+
"text": {
|
37
|
+
"description": "The transcript or text of the motion",
|
38
|
+
"type": ["string", "null"]
|
39
|
+
},
|
40
|
+
"classification": {
|
41
|
+
"description": "A motion category, e.g. adjournment",
|
42
|
+
"type": ["string", "null"]
|
43
|
+
},
|
44
|
+
"date": {
|
45
|
+
"description": "The date on which the motion was proposed",
|
46
|
+
"type": ["string", "null"],
|
47
|
+
"format": "date-time"
|
48
|
+
},
|
49
|
+
"requirement": {
|
50
|
+
"description": "The requirement for the motion to be adopted",
|
51
|
+
"type": ["string", "null"]
|
52
|
+
},
|
53
|
+
"result": {
|
54
|
+
"description": "The result of the motion",
|
55
|
+
"type": ["string", "null"]
|
56
|
+
},
|
57
|
+
"vote_events": {
|
58
|
+
"description": "Events at which people vote on the motion",
|
59
|
+
"type": "array",
|
60
|
+
"items": {
|
61
|
+
"$ref": "http://popoloproject.com/schemas/vote_event.json#"
|
62
|
+
}
|
63
|
+
},
|
64
|
+
"created_at": {
|
65
|
+
"description": "The time at which the resource was created",
|
66
|
+
"type": ["string", "null"],
|
67
|
+
"format": "date-time"
|
68
|
+
},
|
69
|
+
"updated_at": {
|
70
|
+
"description": "The time at which the resource was last modified",
|
71
|
+
"type": ["string", "null"],
|
72
|
+
"format": "date-time"
|
73
|
+
},
|
74
|
+
"sources": {
|
75
|
+
"description": "URLs to documents from which the motion is derived",
|
76
|
+
"type": "array",
|
77
|
+
"items": {
|
78
|
+
"$ref": "http://popoloproject.com/schemas/link.json#"
|
79
|
+
}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
}
|
data/schemas/popolo/person.json
CHANGED
@@ -0,0 +1,57 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-03/schema#",
|
3
|
+
"id": "http://popoloproject.com/schemas/vote.json#",
|
4
|
+
"title": "Vote",
|
5
|
+
"description": "A voter's vote in a vote event",
|
6
|
+
"type": "object",
|
7
|
+
"properties": {
|
8
|
+
"id": {
|
9
|
+
"description": "The vote's unique identifier",
|
10
|
+
"type": ["string", "null"]
|
11
|
+
},
|
12
|
+
"vote_event_id": {
|
13
|
+
"description": "The ID of a vote event",
|
14
|
+
"type": ["string", "null"]
|
15
|
+
},
|
16
|
+
"vote_event": {
|
17
|
+
"description": "A vote event",
|
18
|
+
"$ref": "http://popoloproject.com/schemas/vote_event.json#"
|
19
|
+
},
|
20
|
+
"voter_id": {
|
21
|
+
"description": "The ID of the person or organization that is voting",
|
22
|
+
"type": ["string", "null"]
|
23
|
+
},
|
24
|
+
"voter": {
|
25
|
+
"description": "The person or organization that is voting",
|
26
|
+
"type": ["object", "null"]
|
27
|
+
},
|
28
|
+
"option": {
|
29
|
+
"description": "The option chosen by the voter, whether actively or passively",
|
30
|
+
"type": ["string", "null"]
|
31
|
+
},
|
32
|
+
"group_id": {
|
33
|
+
"description": "The ID of the voter's primary political group",
|
34
|
+
"type": ["string", "null"]
|
35
|
+
},
|
36
|
+
"group": {
|
37
|
+
"description": "The voter's primary political group",
|
38
|
+
"$ref": "http://popoloproject.com/schemas/organization.json#"
|
39
|
+
},
|
40
|
+
"role": {
|
41
|
+
"description": "The voter's role in the event",
|
42
|
+
"type": ["string", "null"]
|
43
|
+
},
|
44
|
+
"weight": {
|
45
|
+
"description": "The weight of the voter's vote",
|
46
|
+
"type": ["number", "null"]
|
47
|
+
},
|
48
|
+
"pair_id": {
|
49
|
+
"description": "The ID of the person with whom the voter is paired",
|
50
|
+
"type": ["string", "null"]
|
51
|
+
},
|
52
|
+
"pair": {
|
53
|
+
"description": "The person with whom the voter is paired",
|
54
|
+
"$ref": "http://popoloproject.com/schemas/person.json#"
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
@@ -0,0 +1,93 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-03/schema#",
|
3
|
+
"id": "http://popoloproject.com/schemas/vote_event.json#",
|
4
|
+
"title": "Vote event",
|
5
|
+
"description": "An event at which people's votes are recorded",
|
6
|
+
"type": "object",
|
7
|
+
"properties": {
|
8
|
+
"id": {
|
9
|
+
"description": "The vote event's unique identifier",
|
10
|
+
"type": ["string", "null"]
|
11
|
+
},
|
12
|
+
"identifier": {
|
13
|
+
"description": "An issued identifier",
|
14
|
+
"type": ["string", "null"]
|
15
|
+
},
|
16
|
+
"motion_id": {
|
17
|
+
"description": "The ID of the motion being decided",
|
18
|
+
"type": ["string", "null"]
|
19
|
+
},
|
20
|
+
"motion": {
|
21
|
+
"description": "The motion being decided",
|
22
|
+
"$ref": "http://popoloproject.com/schemas/motion.json#"
|
23
|
+
},
|
24
|
+
"organization_id": {
|
25
|
+
"description": "The ID of the organization whose members are voting",
|
26
|
+
"type": ["string", "null"]
|
27
|
+
},
|
28
|
+
"organization": {
|
29
|
+
"description": "The organization whose members are voting",
|
30
|
+
"$ref": "http://popoloproject.com/schemas/organization.json#"
|
31
|
+
},
|
32
|
+
"legislative_session_id": {
|
33
|
+
"description": "The ID of the legislative session in which the vote occurs",
|
34
|
+
"type": ["string", "null"]
|
35
|
+
},
|
36
|
+
"legislative_session": {
|
37
|
+
"description": "The legislative session in which the vote occurs",
|
38
|
+
"type": ["object", "null"]
|
39
|
+
},
|
40
|
+
"start_date": {
|
41
|
+
"description": "The time at which the event begins",
|
42
|
+
"type": ["string", "null"],
|
43
|
+
"format": "date-time"
|
44
|
+
},
|
45
|
+
"end_date": {
|
46
|
+
"description": "The time at which the event ends",
|
47
|
+
"type": ["string", "null"],
|
48
|
+
"format": "date-time"
|
49
|
+
},
|
50
|
+
"result": {
|
51
|
+
"description": "The result of the vote event",
|
52
|
+
"type": ["string", "null"]
|
53
|
+
},
|
54
|
+
"group_results": {
|
55
|
+
"description": "The result of the vote event within groups of voters",
|
56
|
+
"type": "array",
|
57
|
+
"items": {
|
58
|
+
"$ref": "http://popoloproject.com/schemas/group_result.json#"
|
59
|
+
}
|
60
|
+
},
|
61
|
+
"counts": {
|
62
|
+
"description": "The number of votes for options",
|
63
|
+
"type": "array",
|
64
|
+
"items": {
|
65
|
+
"$ref": "http://popoloproject.com/schemas/count.json#"
|
66
|
+
}
|
67
|
+
},
|
68
|
+
"votes": {
|
69
|
+
"description": "Voters' votes",
|
70
|
+
"type": "array",
|
71
|
+
"items": {
|
72
|
+
"$ref": "http://popoloproject.com/schemas/vote.json#"
|
73
|
+
}
|
74
|
+
},
|
75
|
+
"created_at": {
|
76
|
+
"description": "The time at which the resource was created",
|
77
|
+
"type": ["string", "null"],
|
78
|
+
"format": "date-time"
|
79
|
+
},
|
80
|
+
"updated_at": {
|
81
|
+
"description": "The time at which the resource was last modified",
|
82
|
+
"type": ["string", "null"],
|
83
|
+
"format": "date-time"
|
84
|
+
},
|
85
|
+
"sources": {
|
86
|
+
"description": "URLs to documents from which the vote event is derived",
|
87
|
+
"type": "array",
|
88
|
+
"items": {
|
89
|
+
"$ref": "http://popoloproject.com/schemas/link.json#"
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}
|
93
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Pupa::Area do
|
4
|
+
let :object do
|
5
|
+
Pupa::Area.new(name: 'Boston Ward 1')
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '#to_s' do
|
9
|
+
it 'should return a human-readable string' do
|
10
|
+
object.to_s.should == 'Boston Ward 1'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -31,15 +31,15 @@ describe Pupa::Concerns::Contactable do
|
|
31
31
|
end
|
32
32
|
|
33
33
|
it 'should symbolize keys' do
|
34
|
-
object.contact_details = [{'type' => 'email', 'value' => 'ceo@example.com'
|
35
|
-
object.contact_details.should == [{type: 'email', value: 'ceo@example.com'
|
34
|
+
object.contact_details = [{'type' => 'email', 'value' => 'ceo@example.com'}]
|
35
|
+
object.contact_details.should == [{type: 'email', value: 'ceo@example.com'}]
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
39
|
describe '#add_contact_detail' do
|
40
40
|
it 'should add a contact detail' do
|
41
|
-
object.add_contact_detail('email', 'ceo@example.com', note: 'work')
|
42
|
-
object.contact_details.should == [{type: 'email', value: 'ceo@example.com', note: 'work'}]
|
41
|
+
object.add_contact_detail('email', 'ceo@example.com', label: 'Email', note: 'work', valid_from: '2010-01-01', valid_until: '2010-12-31')
|
42
|
+
object.contact_details.should == [{type: 'email', value: 'ceo@example.com', label: 'Email', note: 'work', valid_from: '2010-01-01', valid_until: '2010-12-31'}]
|
43
43
|
end
|
44
44
|
|
45
45
|
it 'should not add a contact detail without a type' do
|
@@ -14,15 +14,15 @@ describe Pupa::Concerns::Nameable do
|
|
14
14
|
|
15
15
|
describe '#other_names' do
|
16
16
|
it 'should symbolize keys' do
|
17
|
-
object.other_names = [{'name' => 'Mr. Ziggy Q. Public, Esq.', '
|
18
|
-
object.other_names.should == [{name: 'Mr. Ziggy Q. Public, Esq.',
|
17
|
+
object.other_names = [{'name' => 'Mr. Ziggy Q. Public, Esq.', 'note' => 'Birth name'}]
|
18
|
+
object.other_names.should == [{name: 'Mr. Ziggy Q. Public, Esq.', note: 'Birth name'}]
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
22
|
describe '#add_name' do
|
23
23
|
it 'should add a name' do
|
24
|
-
object.add_name('Mr. Ziggy Q. Public, Esq.', start_date: '1920-01', end_date: '1949-12-31', note: 'Birth name')
|
25
|
-
object.other_names.should == [{name: 'Mr. Ziggy Q. Public, Esq.', start_date: '1920-01', end_date: '1949-12-31', note: 'Birth name'}]
|
24
|
+
object.add_name('Mr. Ziggy Q. Public, Esq.', start_date: '1920-01', end_date: '1949-12-31', note: 'Birth name', family_name: 'Public', given_name: 'John', additional_name: 'Quinlan', honorific_prefix: 'Mr.', honorific_suffix: 'Esq.')
|
25
|
+
object.other_names.should == [{name: 'Mr. Ziggy Q. Public, Esq.', start_date: '1920-01', end_date: '1949-12-31', note: 'Birth name', family_name: 'Public', given_name: 'John', additional_name: 'Quinlan', honorific_prefix: 'Mr.', honorific_suffix: 'Esq.'}]
|
26
26
|
end
|
27
27
|
|
28
28
|
it 'should not add a name without a name' do
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Pupa::Motion do
|
4
|
+
let :object do
|
5
|
+
Pupa::Motion.new(text: 'That the Bill is to be read a second time.', organization_id: 'house-of-commons')
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '#to_s' do
|
9
|
+
it 'should return a human-readable string' do
|
10
|
+
object.to_s.should == 'That the Bill is to be read a second time. in house-of-commons'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Pupa::VoteEvent do
|
4
|
+
let :object do
|
5
|
+
Pupa::VoteEvent.new(identifier: '1', organization_id: 'legislative-council-of-hong-kong')
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '#to_s' do
|
9
|
+
it 'should return a human-readable string' do
|
10
|
+
object.to_s.should == '1 in legislative-council-of-hong-kong'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#group_results' do
|
15
|
+
it 'should symbolize keys' do
|
16
|
+
object.group_results = [{'result' => 'pass', 'group' => {'name' => 'Functional constituencies'}}]
|
17
|
+
object.group_results.should == [{result: 'pass', group: {name: 'Functional constituencies'}}]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#add_group_result' do
|
22
|
+
it 'should add a group result' do
|
23
|
+
object.add_group_result('pass', group: {name: 'Functional constituencies'})
|
24
|
+
object.group_results.should == [{result: 'pass', group: {name: 'Functional constituencies'}}]
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should not add a group result without a result' do
|
28
|
+
object.add_group_result(nil)
|
29
|
+
object.add_group_result('')
|
30
|
+
object.group_results.blank?.should == true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#counts' do
|
35
|
+
it 'should symbolize keys' do
|
36
|
+
object.counts = [{'option' => 'yes', 'value' => 9, 'group' => {'name' => 'Functional constituencies'}}]
|
37
|
+
object.counts.should == [{option: 'yes', value: 9, group: {name: 'Functional constituencies'}}]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#add_count' do
|
42
|
+
it 'should add a count' do
|
43
|
+
object.add_count('yes', 9, group: {name: 'Functional constituencies'})
|
44
|
+
object.counts.should == [{option: 'yes', value: 9, group: {name: 'Functional constituencies'}}]
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should not add a contact detail without an option' do
|
48
|
+
object.add_count(nil, 9)
|
49
|
+
object.add_count('', 9)
|
50
|
+
object.counts.blank?.should == true
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should not add a contact detail without a value' do
|
54
|
+
object.add_count('yes', nil)
|
55
|
+
object.add_count('yes', '')
|
56
|
+
object.counts.blank?.should == true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Pupa::Vote do
|
4
|
+
let :object do
|
5
|
+
Pupa::Vote.new(option: 'yes', voter_id: 'john-q-public', vote_event_id: 'vote-42')
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '#to_s' do
|
9
|
+
it 'should return a human-readable string' do
|
10
|
+
object.to_s.should == 'yes by john-q-public in vote-42'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pupa
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Open North
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-10-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -295,6 +295,7 @@ files:
|
|
295
295
|
- lib/pupa.rb
|
296
296
|
- lib/pupa/errors.rb
|
297
297
|
- lib/pupa/logger.rb
|
298
|
+
- lib/pupa/models/area.rb
|
298
299
|
- lib/pupa/models/concerns/contactable.rb
|
299
300
|
- lib/pupa/models/concerns/identifiable.rb
|
300
301
|
- lib/pupa/models/concerns/indifferent_access.rb
|
@@ -307,9 +308,12 @@ files:
|
|
307
308
|
- lib/pupa/models/identifier_list.rb
|
308
309
|
- lib/pupa/models/membership.rb
|
309
310
|
- lib/pupa/models/model.rb
|
311
|
+
- lib/pupa/models/motion.rb
|
310
312
|
- lib/pupa/models/organization.rb
|
311
313
|
- lib/pupa/models/person.rb
|
312
314
|
- lib/pupa/models/post.rb
|
315
|
+
- lib/pupa/models/vote.rb
|
316
|
+
- lib/pupa/models/vote_event.rb
|
313
317
|
- lib/pupa/processor.rb
|
314
318
|
- lib/pupa/processor/client.rb
|
315
319
|
- lib/pupa/processor/connection.rb
|
@@ -320,11 +324,9 @@ files:
|
|
320
324
|
- lib/pupa/processor/document_store/file_store.rb
|
321
325
|
- lib/pupa/processor/document_store/redis_store.rb
|
322
326
|
- lib/pupa/processor/helper.rb
|
323
|
-
- lib/pupa/processor/middleware/gzip.rb
|
324
327
|
- lib/pupa/processor/middleware/logger.rb
|
325
328
|
- lib/pupa/processor/middleware/parse_html.rb
|
326
329
|
- lib/pupa/processor/middleware/parse_json.rb
|
327
|
-
- lib/pupa/processor/middleware/raise_error.rb
|
328
330
|
- lib/pupa/processor/yielder.rb
|
329
331
|
- lib/pupa/refinements/faraday.rb
|
330
332
|
- lib/pupa/refinements/faraday_middleware.rb
|
@@ -333,18 +335,25 @@ files:
|
|
333
335
|
- lib/pupa/runner.rb
|
334
336
|
- lib/pupa/version.rb
|
335
337
|
- pupa.gemspec
|
338
|
+
- schemas/popolo/area.json
|
336
339
|
- schemas/popolo/contact_detail.json
|
340
|
+
- schemas/popolo/count.json
|
341
|
+
- schemas/popolo/group_result.json
|
337
342
|
- schemas/popolo/identifier.json
|
338
343
|
- schemas/popolo/link.json
|
339
344
|
- schemas/popolo/membership.json
|
345
|
+
- schemas/popolo/motion.json
|
340
346
|
- schemas/popolo/organization.json
|
341
347
|
- schemas/popolo/other_name.json
|
342
348
|
- schemas/popolo/person.json
|
343
349
|
- schemas/popolo/post.json
|
350
|
+
- schemas/popolo/vote.json
|
351
|
+
- schemas/popolo/vote_event.json
|
344
352
|
- spec/fixtures/bar.json
|
345
353
|
- spec/fixtures/baz.json
|
346
354
|
- spec/fixtures/foo.json
|
347
355
|
- spec/logger_spec.rb
|
356
|
+
- spec/models/area_spec.rb
|
348
357
|
- spec/models/concerns/contactable_spec.rb
|
349
358
|
- spec/models/concerns/identifiable_spec.rb
|
350
359
|
- spec/models/concerns/linkable_spec.rb
|
@@ -355,9 +364,12 @@ files:
|
|
355
364
|
- spec/models/identifier_list_spec.rb
|
356
365
|
- spec/models/membership_spec.rb
|
357
366
|
- spec/models/model_spec.rb
|
367
|
+
- spec/models/motion_spec.rb
|
358
368
|
- spec/models/organization_spec.rb
|
359
369
|
- spec/models/person_spec.rb
|
360
370
|
- spec/models/post_spec.rb
|
371
|
+
- spec/models/vote_event_spec.rb
|
372
|
+
- spec/models/vote_spec.rb
|
361
373
|
- spec/processor/client_spec.rb
|
362
374
|
- spec/processor/connection_adapters/mongodb_adapter_spec.rb
|
363
375
|
- spec/processor/connection_adapters/postgresql_adapter_spec.rb
|
@@ -404,6 +416,7 @@ test_files:
|
|
404
416
|
- spec/fixtures/baz.json
|
405
417
|
- spec/fixtures/foo.json
|
406
418
|
- spec/logger_spec.rb
|
419
|
+
- spec/models/area_spec.rb
|
407
420
|
- spec/models/concerns/contactable_spec.rb
|
408
421
|
- spec/models/concerns/identifiable_spec.rb
|
409
422
|
- spec/models/concerns/linkable_spec.rb
|
@@ -414,9 +427,12 @@ test_files:
|
|
414
427
|
- spec/models/identifier_list_spec.rb
|
415
428
|
- spec/models/membership_spec.rb
|
416
429
|
- spec/models/model_spec.rb
|
430
|
+
- spec/models/motion_spec.rb
|
417
431
|
- spec/models/organization_spec.rb
|
418
432
|
- spec/models/person_spec.rb
|
419
433
|
- spec/models/post_spec.rb
|
434
|
+
- spec/models/vote_event_spec.rb
|
435
|
+
- spec/models/vote_spec.rb
|
420
436
|
- spec/processor/client_spec.rb
|
421
437
|
- spec/processor/connection_adapters/mongodb_adapter_spec.rb
|
422
438
|
- spec/processor/connection_adapters/postgresql_adapter_spec.rb
|
@@ -434,3 +450,4 @@ test_files:
|
|
434
450
|
- spec/refinements/opencivicdata_spec.rb
|
435
451
|
- spec/runner_spec.rb
|
436
452
|
- spec/spec_helper.rb
|
453
|
+
has_rdoc:
|
@@ -1,24 +0,0 @@
|
|
1
|
-
module Pupa
|
2
|
-
class Processor
|
3
|
-
module Middleware
|
4
|
-
# A Faraday response middleware for parsing gzip responses.
|
5
|
-
#
|
6
|
-
# @see https://gist.github.com/romanbsd/3892387
|
7
|
-
class Gzip < Faraday::Response::Middleware
|
8
|
-
dependency 'zlib'
|
9
|
-
|
10
|
-
def on_complete(env)
|
11
|
-
encoding = env[:response_headers]['content-encoding'].to_s.downcase
|
12
|
-
case encoding
|
13
|
-
when 'gzip'
|
14
|
-
env[:body] = Zlib::GzipReader.new(StringIO.new(env[:body]), encoding: 'ASCII-8BIT').read
|
15
|
-
env[:response_headers].delete('content-encoding')
|
16
|
-
when 'deflate'
|
17
|
-
env[:body] = Zlib::Inflate.inflate(env[:body])
|
18
|
-
env[:response_headers].delete('content-encoding')
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
@@ -1,34 +0,0 @@
|
|
1
|
-
module Pupa
|
2
|
-
class Processor
|
3
|
-
module Middleware
|
4
|
-
# A Faraday response middleware for raising an error if unsuccessful.
|
5
|
-
#
|
6
|
-
# @see Faraday::Response::RaiseError
|
7
|
-
# @note Faraday has no tests for this middleware.
|
8
|
-
class RaiseError < Faraday::Response::Middleware
|
9
|
-
def on_complete(env)
|
10
|
-
case env[:status]
|
11
|
-
when 404
|
12
|
-
raise Faraday::Error::ResourceNotFound, response_values(env)
|
13
|
-
when 407
|
14
|
-
# mimic the behavior that we get with proxy requests with HTTPS
|
15
|
-
raise Faraday::Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
|
16
|
-
when 400...600
|
17
|
-
raise Faraday::Error::ClientError, response_values(env)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def response_values(env) # XXX add more keys
|
22
|
-
{
|
23
|
-
method: env[:method],
|
24
|
-
url: env[:url].to_s,
|
25
|
-
request_headers: env[:request_headers],
|
26
|
-
status: env[:status],
|
27
|
-
response_headers: env[:response_headers],
|
28
|
-
body: env[:body].to_s,
|
29
|
-
}
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|