whos_got_dirt 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +20 -0
  5. data/.yardopts +3 -0
  6. data/Gemfile +7 -0
  7. data/LICENSE +20 -0
  8. data/README.md +61 -0
  9. data/Rakefile +69 -0
  10. data/lib/whos_got_dirt/renderer.rb +66 -0
  11. data/lib/whos_got_dirt/request.rb +218 -0
  12. data/lib/whos_got_dirt/requests/entity/corp_watch.rb +123 -0
  13. data/lib/whos_got_dirt/requests/entity/little_sis.rb +52 -0
  14. data/lib/whos_got_dirt/requests/entity/open_corporates.rb +104 -0
  15. data/lib/whos_got_dirt/requests/entity/open_duka.rb +33 -0
  16. data/lib/whos_got_dirt/requests/entity/poderopedia.rb +48 -0
  17. data/lib/whos_got_dirt/requests/entity.rb +54 -0
  18. data/lib/whos_got_dirt/requests/list/little_sis.rb +37 -0
  19. data/lib/whos_got_dirt/requests/list/open_corporates.rb +36 -0
  20. data/lib/whos_got_dirt/requests/list.rb +16 -0
  21. data/lib/whos_got_dirt/requests/relation/open_corporates.rb +72 -0
  22. data/lib/whos_got_dirt/requests/relation/open_oil.rb +49 -0
  23. data/lib/whos_got_dirt/requests/relation.rb +51 -0
  24. data/lib/whos_got_dirt/response.rb +79 -0
  25. data/lib/whos_got_dirt/responses/entity/corp_watch.rb +72 -0
  26. data/lib/whos_got_dirt/responses/entity/little_sis.rb +66 -0
  27. data/lib/whos_got_dirt/responses/entity/open_corporates.rb +61 -0
  28. data/lib/whos_got_dirt/responses/entity/open_duka.rb +42 -0
  29. data/lib/whos_got_dirt/responses/entity/poderopedia.rb +44 -0
  30. data/lib/whos_got_dirt/responses/entity.rb +7 -0
  31. data/lib/whos_got_dirt/responses/helpers/little_sis.rb +34 -0
  32. data/lib/whos_got_dirt/responses/helpers/open_corporates.rb +28 -0
  33. data/lib/whos_got_dirt/responses/list/little_sis.rb +42 -0
  34. data/lib/whos_got_dirt/responses/list/open_corporates.rb +34 -0
  35. data/lib/whos_got_dirt/responses/list.rb +7 -0
  36. data/lib/whos_got_dirt/responses/relation/open_corporates.rb +66 -0
  37. data/lib/whos_got_dirt/responses/relation/open_oil.rb +70 -0
  38. data/lib/whos_got_dirt/responses/relation.rb +7 -0
  39. data/lib/whos_got_dirt/result.rb +106 -0
  40. data/lib/whos_got_dirt/validator.rb +52 -0
  41. data/lib/whos_got_dirt/version.rb +3 -0
  42. data/lib/whos_got_dirt.rb +43 -0
  43. data/schemas/popolo.json +1619 -0
  44. data/spec/cassettes/corp_watch_entity.yml +35 -0
  45. data/spec/cassettes/little_sis_entity.yml +98 -0
  46. data/spec/cassettes/little_sis_list.yml +159 -0
  47. data/spec/cassettes/open_corporates_entity.yml +137 -0
  48. data/spec/cassettes/open_corporates_list.yml +60 -0
  49. data/spec/cassettes/open_corporates_relation.yml +121 -0
  50. data/spec/cassettes/open_duka_entity.yml +1149 -0
  51. data/spec/cassettes/open_oil_relation.yml +640 -0
  52. data/spec/cassettes/poderopedia_entity.yml +43 -0
  53. data/spec/renderer_spec.rb +48 -0
  54. data/spec/request_spec.rb +205 -0
  55. data/spec/requests/entity/corp_watch_spec.rb +99 -0
  56. data/spec/requests/entity/little_sis_spec.rb +33 -0
  57. data/spec/requests/entity/open_corporates_spec.rb +81 -0
  58. data/spec/requests/entity/open_duka_spec.rb +21 -0
  59. data/spec/requests/entity/poderopedia_spec.rb +21 -0
  60. data/spec/requests/list/little_sis_spec.rb +25 -0
  61. data/spec/requests/list/open_corporates_spec.rb +33 -0
  62. data/spec/requests/relation/open_corporates_spec.rb +53 -0
  63. data/spec/requests/relation/open_oil_spec.rb +37 -0
  64. data/spec/response_spec.rb +119 -0
  65. data/spec/responses/entity/corp_watch_spec.rb +31 -0
  66. data/spec/responses/entity/little_sis_spec.rb +31 -0
  67. data/spec/responses/entity/open_corporates_spec.rb +31 -0
  68. data/spec/responses/entity/open_duka_spec.rb +25 -0
  69. data/spec/responses/entity/poderopedia_spec.rb +25 -0
  70. data/spec/responses/list/little_sis_spec.rb +31 -0
  71. data/spec/responses/list/open_corporates_spec.rb +31 -0
  72. data/spec/responses/relation/open_corporates_spec.rb +31 -0
  73. data/spec/responses/relation/open_oil_spec.rb +31 -0
  74. data/spec/result_spec.rb +90 -0
  75. data/spec/spec_helper.rb +21 -0
  76. data/spec/support/shared_examples_for_requests.rb +155 -0
  77. data/spec/validator_spec.rb +43 -0
  78. data/whos_got_dirt.gemspec +28 -0
  79. metadata +281 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 98d4adb0f91ffac1ff9ceabe8232a3861e2f900a
4
+ data.tar.gz: 627276f14cba96da225df90590eb71267ef13c1a
5
+ SHA512:
6
+ metadata.gz: d9e8cdc8e2076c071af133ac8abdcf97d0676c4edf6712c9591ab661ade771f5729abde4cef9766834fb49c282e6a6f1b134ab7daed572aaa55a9427b912184c
7
+ data.tar.gz: 1ff4a4d9c4afb312c5550afb83c48b503ecb82186fcb106240b6db7ca05187458d9fe067f1cef806e5c6f02841692150e0fb88ea21194e2560ed37f4b0010b89
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ .yardoc
4
+ Gemfile.lock
5
+ doc/*
6
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,20 @@
1
+ sudo: false
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 1.9.3
6
+ - 2.0.0
7
+ - 2.1.0
8
+ - 2.2.0
9
+ env:
10
+ global:
11
+ # CORP_WATCH_API_KEY
12
+ - secure: dLqYYE/SMgjM7t4760RyWo9JC5gyGQ4QeBFDiiznEV1th1YQjXgBZxAKzwH2msjOL93z1F9U0AGmkSwm7uX6y6l6/Oom4zyjSJv2j/GTsNCB6dKLSKLDjL01Veb9+HjLtOgp/b8cCptzs/V0k79So3ku8MPGkZR8xOTu3C+8tjQEolUFIPTAxPTYYQAz2EirUgxlvXKTPXT6cSJMFoq5LOpzMhFpRwcAL5C0dg7WLObESCqZr8ZBPmAGMFbzI9toktDPFbgp6M7MpF/9qfU3PJNnGISp3T6/YQRXOcOacKrznE8BH6LKDApA02kB2u4YeBLpkM10APwi5/4leYEB1AY+LrVrCzyWibtIDLRYJfwuiiX8rd9w1aIhFs0WEnnvcSpb/sbW6PPuc9f3p6z+AqGek5fg3UhLGJPwP9Yz6eY34bwe+pD2JwL/iksORawHhtiyP3VXsaxIQnQTV/A3qv/mTkMPsTltgtBVLZobxkgz+QTcqaZPin82EnyaD4RKAYF8pfBidGStiG/hsBkMM6vnkF0usKAR6r9A7aZdRDs9v4E1A/RY0weonJPD3caOKX5rQm5rTrd8oS8J1bMyMKDNuOo0rvpPGM0KmZ/ePRFLN4Soo0EDFmGSoZjam8QI9EN2Gu7OlcvjrT3vRp4E5KbLtRGt/kZD+Z2u23ttRg0=
13
+ # LITTLE_SIS_API_KEY
14
+ - secure: P+76fE58oTzuM+1EVJI8WhzqE8QxxgyxOhtiAudTNt6tPA7VuQvbSbe8MAkiOvSnWQ/wAiJkhGZLE93lKSjOuBTLBbCcwLtMMQ+6U/WkLSVPUxD2p+HgMynZaV1zBbGbrBpOnTj4WyeM1Jz+zJkrI5YCOO1XS39mUNn9RZh6Bepg3mzRlRmsAy47bgzhprbv21tWmR9C2LZvhzSfWu+2GCKCVrnGdBDQ1WoKhnwCEboxdjDb0sdD/GlmebLMMfiS1MRpoXw0Z4AdLm2/eBAI4t/USeGLMan23U53BZ2yBAhy5Ns42+f18zr1iOHoX05oyevlqJkKEdEhnoJ1CyxByKQVLe0oWIgW38SgWvTTl+JaSSPyiNxlK6Asr3VKcMfui+nKziHfMSZTtwm9lq6JzSaOM2L/+Qe90BHN0raWH2uvR3ACCmR/PP7tl7ZV+8nNQ4nmzt2H9DpQEnYsil/4eQ3hLrny9fTMylYJpiN1yNhaVnWMXw2hknBnUwBq42Xu3zsv1dMYGzi1C/nJWw/3imIvftiJxArOlSFcZ+dB3iBWou5sm9pJpRtUyLcwhah6zc+GEroMdv6d27r8ANabtOAMgjxR29lFpNThMBgO84DWD05CViyzCBZQf8eNWvVRrmjAC+wn8L71ZSqOgauH7Urw0qJda2uvLZ7biJBCnvs=
15
+ # OPEN_DUKA_API_KEY
16
+ - secure: CYftDtbj2dqO0YQpct+r1SKkqKGbUeQCo4O0muBLQb6l87C9kCCKfNbgfMWzpuoFQXWD0CYRQvt22S8ulQl5pA6RYV0L0pAhiijRNWs090+1aEnqbmSWz0thhrdX7YGjiaEmd8KN7iCygDGxMV/az57LhqfETKQOJdI9Q0Ozynw5LuawW1kUsrzA6wy0uWxIz9g4Whtqh3ZGFgkJjFkMff2z5ZP4XRljNjxs6h/AFGiKWKOYtGTGI4JmlbZu5ibfqZ9G+ipS3Sg5tOVWjU43hUXmrMgbXNXB2Q/mkLpxDqRYLFfyj6KWTNaeVwqbURagswZ3u57ZhntUGsqL8tB8wVwM8WbVCx9neY1RRvSIl9Rhiobe4L1qEGxG9nEVHLlAh7S6JEt/iekzkribJnwdk2IaTdJ4rjkaaYLoYW59TLsMnTAxSoEjvPRU8E0WPrlHu1xYBW0/JieuoJVPXOD+z6wAMKM7BjswtRNmCmQ53C6M8AJvgg/HbKqA+5INBQomnyHZape3aCk7DX8FnL6DuBsYNFonMFN+IjfL/eP9gx9l1rY6hmyYVCIbqZ91OTdvMHDdJsYVkBMXX9Xrp6zQQDVapo4FArWji8BRfZwyOeplmUqx4ERP99+RKgVC5mRXqygGTRzYd6rHfThPZNQNQvzrrwIohEPWC4E6u2OKi1s=
17
+ # OPEN_OIL_API_KEY
18
+ - secure: bdGjQmbZ6oYVaM/UEqyGK6fZF/LTJLqWdmzP+HpVax5c6ogsTKhcruTScmkH4IbZRD3UCRc8WAh0Y4Wcr2xoxWwcdJS0XY2QQfsENrc8k0GMBr8ijf8nrSLwon1hUdxhmvFRpsDBIU7N2qhAvcYUVvFGKp8e1FsNzxFJZRsHC2cY1UxQyiWKMkLaGgdhki56/CJefH4TRuNFkuEIYyGiOiQX+QdtX9PhIYF1ycu2ucAF1qUBh9fMLyLv8YH+Hiudy8ko8SmHskhOTHtqvD542dbSzYznrvdVo+K1+4TcBajZ7jR8smoqvIzMxUpJXjLv/UpPmz0YIor8EeVSiKslXmCcpM5b5KVT+aAkMxk/0RTOEDJ1G8MuCwypUo9CiSqtg5quaOPRK4Yu4fsqGAO7oYD1rfyZj1b1lSy91yuS55ZlIsskJhDBLk4UEcugjtqjy4d0/JDfl97WmMX9QqvLw4K9vbSp+uBAIJbulvelB5ya1VaE7k2eGxiw4T5AommhltXvn64tahMf609bc42LiV8+FsW/LO/1GPQbHIM2ojQ24Rq3SdaMQxXnJTILFyQQX672ECAf/ME9QRwY3S7T9SoKEvWeB92Flo9NzFX+wodHME+yn0av5Em8st0MaBNJlHFrc2zAYKaSIdSpvW2c+15Ip3UEBQ/YKsu8ydouVTk=
19
+ # PODEROPEDIA_API_KEY
20
+ - secure: AfDhdNDjaio3W804DXKAaL6hyThHjM5670eZSF/2kKExyI1mxGSjS7uuB8102zhh8mRyZzs1iYr2SeWUvTeOCtbIo2Vo6apctQQnVjUGJ0WGWYCJD1Y+9sWknw3PxTq1ca8x9B3/jMJeppFJpJglqI+yYWrWYS5jXJ68H6BsNXK8zTJCuGQF62dxYds36r2CLVQmvs27zqox2HpwAJQ3dzS5aM+SxOQv3TemqjhHJ+nJ3F3dISQytEfBkSXIdyEdN8tT3QkppDbMfTilAuvezdI5QuY0kxWcrQXDBCSByaKb2PAxC5JXa3MBO/EiqZ8WAv1MDfJW/0DhfJTE0+Iznm2IUBY7hLMo2up046KnkfTygBTu25F6B5eFz5geqJH4RIf0bqtxwIp1h0cVOtV3QoWILDjl88dOqMuoUNiE/36ikwkesqXGbtLKrpqCgJm2f/HDWMtLjzcsdYiyzoGsoC4yoUPQNMHis/R0uRSGTx1BEFXL0RBYOCwrztWl4OPDfQUwuLHBp+5gcGCzz1RBeuBmQHqZ2amhxSMwSzl1ehME4yF2t6Rm50ROM1QUYz1ee5fGerSpDjNFyR0aFUBpbPPrSLdIX0UKDU3W1mXVZ+wKvSZAk+5/tS89YCbgwMSl4cJwATG8uBkM/OR/G8hq3qJTP55AfdFOxIeAhXjPT7M=
data/.yardopts ADDED
@@ -0,0 +1,3 @@
1
+ --hide-void-return
2
+ --embed-mixin ClassMethods
3
+ --markup=markdown
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in the gemspec
4
+ gemspec
5
+
6
+ # Remove once vcr 2.9.4 released.
7
+ gem 'vcr', git: 'https://github.com/vcr/vcr.git'
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2015 James McKinney
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # Who's got dirt? A federated search API for entities and relations
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/whos_got_dirt.svg)](https://badge.fury.io/rb/whos_got_dirt)
4
+ [![Build Status](https://secure.travis-ci.org/influencemapping/whos_got_dirt-gem.png)](https://travis-ci.org/influencemapping/whos_got_dirt-gem)
5
+ [![Dependency Status](https://gemnasium.com/influencemapping/whos_got_dirt-gem.png)](https://gemnasium.com/influencemapping/whos_got_dirt-gem)
6
+ [![Coverage Status](https://coveralls.io/repos/influencemapping/whos_got_dirt-gem/badge.svg)](https://coveralls.io/r/influencemapping/whos_got_dirt-gem)
7
+ [![Code Climate](https://codeclimate.com/github/influencemapping/whos_got_dirt-gem.png)](https://codeclimate.com/github/influencemapping/whos_got_dirt-gem)
8
+
9
+ ## Usage
10
+
11
+ This gem provides a common API to multiple APIs. See the [server](https://github.com/influencemapping/whos_got_dirt-server) for a deployment.
12
+
13
+ To add support for new APIs, see the documentation for [Request](http://www.rubydoc.info/gems/whos_got_dirt/WhosGotDirt/Request) and [Response](http://www.rubydoc.info/gems/whos_got_dirt/WhosGotDirt/Response).
14
+
15
+ In this example, we convert generic API parameters to an OpenCorporates API URL, and request the URL with [Faraday](https://github.com/lostisland/faraday). Then, we convert the OpenCorporates API response to a generic API response.
16
+
17
+ ```ruby
18
+ require 'whos_got_dirt'
19
+ require 'faraday'
20
+
21
+ input = {
22
+ 'subject' => [{
23
+ 'name~=' => 'John Smith',
24
+ }],
25
+ 'jurisdiction_code|=' => ['gb', 'ie'],
26
+ 'role' => 'director',
27
+ 'inactive' => false,
28
+ }
29
+
30
+ url = WhosGotDirt::Requests::Relation::OpenCorporates.new(input).to_s
31
+ #=> "https://api.opencorporates.com/officers/search?q=John+Smith&jurisdiction_code=gb%7Cie&position=director&inactive=false&order=score"
32
+
33
+ response = Faraday.get(url)
34
+
35
+ results = WhosGotDirt::Responses::Relation::OpenCorporates.new(response).to_a
36
+ #=> [{"@type"=>"Relation",
37
+ # "subject"=>
38
+ # {"name"=>"JOHN SMITH",
39
+ # "contact_details"=>[],
40
+ # "occupation"=>"CONTRACTS DIRECTORS"},
41
+ # "object"=>
42
+ # {"name"=>"IMPERIAL DUCTWORK SERVICES HOLDINGS LIMITED",
43
+ # "identifiers"=>[{"identifier"=>"08484366", "scheme"=>"Company Register"}],
44
+ # "links"=>[{"url"=>"https://opencorporates.com/companies/gb/08484366", "note"=>"OpenCorporates URL"}],
45
+ # "jurisdiction_code"=>"gb"},
46
+ # "start_date"=>"2013-04-30",
47
+ # "identifiers"=>[{"identifier"=>"71863990", "scheme"=>"OpenCorporates"}],
48
+ # "links"=>[{"url"=>"https://opencorporates.com/officers/71863990", "note"=>"OpenCorporates URL"}],
49
+ # "updated_at"=>"2014-10-13T13:57:58+00:00",
50
+ # "current_status"=>"CURRENT",
51
+ # "jurisdiction_code"=>"gb",
52
+ # "role"=>"director",
53
+ # "sources"=>[{"url"=>"https://api.opencorporates.com/officers/search?inactive=false&jurisdiction_code=gb%7Cie&order=score&position=director&q=John+Smith", "note"=>"OpenCorporates"}]},
54
+ # ...]
55
+ ```
56
+
57
+ ## Acknowledgements
58
+
59
+ Most terms are from [Popolo](http://www.popoloproject.com/). The request and response formats are inspired from the [Metaweb Query Language](http://mql.freebaseapps.com/index.html) and the [OpenRefine Reconciliation Service API](https://github.com/OpenRefine/OpenRefine/wiki/Reconciliation-Service-API).
60
+
61
+ Copyright (c) 2015 James McKinney, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,69 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
8
+
9
+ begin
10
+ require 'yard'
11
+ YARD::Rake::YardocTask.new
12
+ rescue LoadError
13
+ task :yard do
14
+ abort 'YARD is not available. In order to run yard, you must: gem install yard'
15
+ end
16
+ end
17
+
18
+ desc 'Fetch schemas, rewrite references, and store locally'
19
+ task :schemas do
20
+ require 'json'
21
+ require 'open-uri'
22
+
23
+ require 'json-schema'
24
+
25
+ def process_value(value, definitions)
26
+ url = value['$ref']
27
+ if url
28
+ name = url.rpartition('/')[2].chomp('.json#')
29
+ value['$ref'] = "#/definitions/#{name}"
30
+ define(name, url, definitions)
31
+ end
32
+ end
33
+
34
+ def process_schema(url, definitions)
35
+ schema = JSON.load(open(url).read)
36
+ schema['properties'].each do |_,value|
37
+ process_value(value, definitions)
38
+ if value.key?('items')
39
+ process_value(value['items'], definitions)
40
+ end
41
+ end
42
+ schema
43
+ end
44
+
45
+ def define(name, url, definitions)
46
+ unless definitions.key?(name)
47
+ definitions[name] = {} # to avoid recursion
48
+ definitions[name] = process_schema(url, definitions)
49
+ definitions[name].delete('id')
50
+ end
51
+ end
52
+
53
+ definitions = {} # passed by reference
54
+
55
+ %w(organization person).each do |name|
56
+ define(name, "http://www.popoloproject.com/schemas/#{name}.json#", definitions)
57
+ end
58
+
59
+ schema = {
60
+ '$schema' => 'http://json-schema.org/draft-03/schema#',
61
+ 'definitions' => definitions,
62
+ }
63
+
64
+ JSON::Validator.validate!(schema, {}, validate_schema: true)
65
+
66
+ File.open(File.expand_path(File.join('..', 'schemas', 'popolo.json'), __FILE__), 'w') do |f|
67
+ f.write(JSON.pretty_generate(schema))
68
+ end
69
+ end
@@ -0,0 +1,66 @@
1
+ module WhosGotDirt
2
+ # Accepts a JSON template, which is a hash in which some values are
3
+ # [JSON Pointers](http://tools.ietf.org/html/rfc6901). The template is
4
+ # rendered by evaluating its JSON Pointers against JSON data.
5
+ #
6
+ # @example
7
+ # renderer = WhosGotDirt::Renderer.new('name' => '/fn')
8
+ # data = {'fn' => 'John Smith'}
9
+ # renderer.result(data)
10
+ # #=> {'name' => 'John Smith'}
11
+ #
12
+ # @see http://ruby-doc.org/stdlib-2.2.3/libdoc/erb/rdoc/ERB.html
13
+ class Renderer
14
+ # @!attribute [r] template
15
+ # @return [Hash] the template
16
+ attr_reader :template
17
+
18
+ # Sets the template.
19
+ #
20
+ # @param [Object] template a template
21
+ def initialize(template)
22
+ @template = template
23
+ end
24
+
25
+ # Renders the template by evaluating its JSON Pointers against JSON data.
26
+ #
27
+ # @param [Object] data the JSON data
28
+ # @return [Object] the rendered template
29
+ def result(data)
30
+ walk(template, data)
31
+ end
32
+
33
+ private
34
+
35
+ # @see https://github.com/pudo/jsonmapping/blob/master/jsonmapping/mapper.py
36
+ def walk(node, data)
37
+ case node
38
+ when Hash
39
+ hash = {}
40
+ node.each do |key,value|
41
+ if value.respond_to?(:call)
42
+ k, v = value.call(data)
43
+ else
44
+ v = walk(value, data)
45
+ end
46
+ if v
47
+ hash[k || key] = v
48
+ end
49
+ end
50
+ hash
51
+ when Array
52
+ node.map do |child|
53
+ walk(child, data)
54
+ end
55
+ when String
56
+ if node.start_with?('/')
57
+ JsonPointer.new(data, node).value
58
+ else
59
+ node
60
+ end
61
+ else
62
+ node
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,218 @@
1
+ module WhosGotDirt
2
+ # Accepts MQL parameters and return URLs to request.
3
+ #
4
+ # @example Create a new class for transforming parameters to URLs.
5
+ # class MyAPIRequest < WhosGotDirt::Request
6
+ # @base_url = 'https://api.example.com'
7
+ #
8
+ # def to_s
9
+ # "#{base_url}/endpoint?#{to_query(input)}"
10
+ # end
11
+ # end
12
+ #
13
+ # @example Use the class in requesting a URL.
14
+ # url = MyAPIRequest.new(name: 'John Smith').to_s
15
+ # response = Faraday.get(url)
16
+ # #=> "https://api.example.com/endpoint?name=John+Smith"
17
+ class Request
18
+ class << self
19
+ # @!attribute [r] base_url
20
+ # @return [String] the base URL to be used in the request
21
+ attr_reader :base_url
22
+
23
+ # Transforms a query string from a hash to a string.
24
+ #
25
+ # @param [Hash] params query string parameters
26
+ # @return [String] a query string
27
+ def to_query(params)
28
+ params.map do |key,value|
29
+ "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
30
+ end * '&'
31
+ end
32
+ end
33
+
34
+ # @!attribute input
35
+ # @return [Hash] the MQL parameters
36
+ attr_accessor :input
37
+
38
+ # @!attribute :output
39
+ # @return [Hash] the API-specific parameters
40
+ attr_reader :output
41
+
42
+ # Sets the MQL parameters.
43
+ #
44
+ # @param [Hash] input the MQL parameters
45
+ def initialize(input = {})
46
+ @input = ActiveSupport::HashWithIndifferentAccess.new(input)
47
+ @output = {}
48
+ end
49
+
50
+ # Returns the base URL to be used in the request.
51
+ #
52
+ # @return [String] the base URL to be used in the request
53
+ def base_url
54
+ self.class.base_url
55
+ end
56
+
57
+ # Helper method to map a parameter that supports the MQL equality operator.
58
+ #
59
+ # @param [String] target the API-specific parameter name
60
+ # @param [String] source the request parameter name
61
+ # @param [Hash] opts options
62
+ # @option opts [String] :input substitute MQL parameters
63
+ # @option opts [String] :transform a transformation to apply to the value
64
+ # @option opts [String] :default the default value
65
+ # @option opts [Set,Array] :valid a list of valid values
66
+ # @return [Hash] the API-specific parameters
67
+ def equal(target, source, opts = {})
68
+ params = parameters(opts)
69
+
70
+ if opts.key?(:valid)
71
+ if opts[:valid].include?(params[source])
72
+ output[target] = transform(params[source], opts)
73
+ end
74
+ else
75
+ if params[source]
76
+ output[target] = transform(params[source], opts)
77
+ elsif opts[:default]
78
+ output[target] = opts[:default]
79
+ end
80
+ end
81
+
82
+ output
83
+ end
84
+
85
+ # Helper method to map a parameter that supports the MQL `~=` operator.
86
+ #
87
+ # @param [String] target the API-specific parameter name
88
+ # @param [String] source the request parameter name
89
+ # @param [Hash] opts options
90
+ # @option opts [String] :input substitute MQL parameters
91
+ # @option opts [String] :transform a transformation to apply to the value
92
+ # @return [Hash] the API-specific parameters
93
+ def match(target, source, opts = {})
94
+ params = parameters(opts)
95
+
96
+ if params[source]
97
+ equal(target, source, opts)
98
+ elsif params["#{source}~="]
99
+ output[target] = transform(params["#{source}~="], opts)
100
+ end
101
+
102
+ output
103
+ end
104
+
105
+ # Helper method to map a parameter that supports the MQL `|=` operator.
106
+ #
107
+ # @param [String] target the API-specific parameter name
108
+ # @param [String] source the request parameter name
109
+ # @param [Hash] opts options
110
+ # @option opts [String] :input substitute MQL parameters
111
+ # @option opts [String] :transform a transformation to apply to the value
112
+ # @return [Hash] the API-specific parameters
113
+ def one_of(target, source, opts = {})
114
+ params = parameters(opts)
115
+
116
+ if params[source]
117
+ equal(target, source, opts)
118
+ elsif params["#{source}|="]
119
+ output[target] = params["#{source}|="].map{|v| transform(v, opts)}.join(or_operator)
120
+ end
121
+
122
+ output
123
+ end
124
+
125
+ # Helper method to map a parameter that supports MQL `AND`-like constraints.
126
+ #
127
+ # @param [String] target the API-specific parameter name
128
+ # @param [String] source the request parameter name
129
+ # @param [Hash] opts options
130
+ # @option opts [String] :input substitute MQL parameters
131
+ # @option opts [String] :transform a transformation to apply to the value
132
+ # @option opts [String] :transform a transformation to apply to the value
133
+ # @return [Hash] the API-specific parameters
134
+ def all_of(target, source, opts = {})
135
+ params = parameters(opts)
136
+
137
+ if params[source]
138
+ equal(target, source, opts)
139
+ else
140
+ values = []
141
+ params.each do |key,value|
142
+ if key[/:([a-z_]+)$/, 1] == source
143
+ values << value
144
+ end
145
+ end
146
+ if values.empty?
147
+ if opts.key?(:backup)
148
+ send(opts[:backup], target, source, opts)
149
+ end
150
+ else
151
+ output[target] = values.map{|v| transform(v, opts)}.join(and_operator)
152
+ end
153
+ end
154
+
155
+ output
156
+ end
157
+
158
+ # Helper method to map a date parameter that supports comparisons.
159
+ #
160
+ # @param [String] target the API-specific parameter name
161
+ # @param [String] source the request parameter name
162
+ # @param [Hash] opts options
163
+ # @option opts [String] :input substitute MQL parameters
164
+ # @return [Hash] the API-specific parameters
165
+ def date_range(target, source, opts = {})
166
+ params = parameters(opts)
167
+
168
+ # @note OpenCorporates date range format.
169
+ if params[source]
170
+ output[target] = "#{params[source]}:#{params[source]}"
171
+ elsif params["#{source}>="] || params["#{source}>"] || params["#{source}<="] || params["#{source}<"]
172
+ output[target] = "#{params["#{source}>="] || params["#{source}>"]}:#{params["#{source}<="] || params["#{source}<"]}"
173
+ end
174
+
175
+ output
176
+ end
177
+
178
+ # @abstract Subclass and override {#to_s} to return the URL from which to
179
+ # `GET` the results
180
+ def to_s
181
+ raise NotImplementedError
182
+ end
183
+
184
+ # @abstract Subclass and override {#and_operator} to return the "AND"
185
+ # operator's serialization
186
+ def and_operator
187
+ raise NotImplementedError
188
+ end
189
+
190
+ # @abstract Subclass and override {#or_operator} to return the "OR"
191
+ # operator's serialization
192
+ def or_operator
193
+ raise NotImplementedError
194
+ end
195
+
196
+ private
197
+
198
+ def to_query(params)
199
+ self.class.to_query(params)
200
+ end
201
+
202
+ def parameters(opts)
203
+ if opts.key?(:input)
204
+ opts[:input]
205
+ else
206
+ input
207
+ end
208
+ end
209
+
210
+ def transform(value, opts)
211
+ if opts.key?(:transform)
212
+ opts[:transform].call(value)
213
+ else
214
+ value
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,123 @@
1
+ module WhosGotDirt
2
+ module Requests
3
+ module Entity
4
+ # Requests for companies from the CorpWatch API.
5
+ #
6
+ # By default, name and address queries will match complete words only in
7
+ # the same order as in the query, tokens less than three characters long
8
+ # will be ignored, and MySQL's [stopwords](http://dev.mysql.com/doc/refman/5.0/en/fulltext-stopwords.html)
9
+ # will be ignored. If `substring_match` is `1`, queries will match within
10
+ # words as well as complete words, at a severe performance penalty.
11
+ #
12
+ # @example Supply an API key.
13
+ # "corp_watch_api_key": "..."
14
+ #
15
+ # @example Find entities by IRS Employer Identification Number (EIN).
16
+ # "identifiers": [{
17
+ # "identifier": "911653725",
18
+ # "scheme": "IRS Employer Identification Number"
19
+ # }]
20
+ #
21
+ # @example Find entities by SEC Central Index Key (CIK).
22
+ # "identifiers": [{
23
+ # "identifier": "37996",
24
+ # "scheme": "SEC Central Index Key"
25
+ # }]
26
+ #
27
+ # @example Find companies by SIC code.
28
+ # "industry_code": "2011"
29
+ #
30
+ # @example Find companies by SIC sector.
31
+ # "sector_code": "4100"
32
+ #
33
+ # @example Match within words on name and address queries.
34
+ # "substring_match": 1
35
+ #
36
+ # @example Find companies by country code.
37
+ # "country_code": "US"
38
+ #
39
+ # @example Find companies by country subdivision code.
40
+ # "subdiv_code": "OR"
41
+ #
42
+ # @example Find companies with SEC filings in a given year.
43
+ # "year": 2005
44
+ #
45
+ # @example Find companies with SEC filings in or before a given year.
46
+ # "year>=": 2003
47
+ #
48
+ # @example Find companies with SEC filings in or after a given year.
49
+ # "year<=": 2007
50
+ #
51
+ # @example Find companies that appear as "filers" in SEC filings or as
52
+ # subsidiaries ("relationships") only.
53
+ # "source_type": "relationships"
54
+ #
55
+ # @example Find companies with three direct descendants in a hierarchy.
56
+ # "num_children": 3
57
+ #
58
+ # @example Find companies with two direct ancestors in a hierarchy.
59
+ # "num_parents": 2
60
+ #
61
+ # @example Find companies within the hierarchy of another company.
62
+ # "top_parent_id": "cw_7324"
63
+ class CorpWatch < Request
64
+ @base_url = 'http://api.corpwatch.org/%<year>s/companies.json'
65
+
66
+ # Returns the URL to request.
67
+ #
68
+ # @return [String] the URL to request
69
+ def to_s
70
+ params = convert
71
+ if params.key?(:year)
72
+ "#{base_url % params.slice(:year).symbolize_keys}?#{to_query(params.except(:year))}"
73
+ else
74
+ "http://api.corpwatch.org/companies.json?#{to_query(params)}"
75
+ end
76
+ end
77
+
78
+ # Converts the MQL parameters to API-specific parameters.
79
+ #
80
+ # @return [Hash] API-specific parameters
81
+ # @see http://api.corpwatch.org/documentation/api_examples.html#A17
82
+ def convert
83
+ match('company_name', 'name')
84
+ equal('limit', 'limit')
85
+
86
+ input['identifiers'] && input['identifiers'].each do |identifier|
87
+ case identifier['scheme']
88
+ when 'IRS Employer Identification Number'
89
+ equal('irs_number', 'identifier', input: identifier)
90
+ when 'SEC Central Index Key'
91
+ equal('cik', 'identifier', input: identifier)
92
+ end
93
+ end
94
+
95
+ input['contact_details'] && input['contact_details'].each do |contact_detail|
96
+ if contact_detail['type'] == 'address' && (contact_detail['value'] || contact_detail['value~='])
97
+ output['raw_address'] = contact_detail['value'] || contact_detail['value~=']
98
+ end
99
+ end
100
+
101
+ # API-specific parameters.
102
+ equal('key', 'corp_watch_api_key')
103
+ # http://api.corpwatch.org/documentation/api_examples.html#A35
104
+ equal('sic_code', 'industry_code')
105
+ equal('sic_sector', 'sector_code')
106
+ equal('substring_match', 'substring_match', valid: [1])
107
+ # http://api.corpwatch.org/documentation/api_examples.html#A36
108
+ equal('country_code', 'country_code', transform: lambda{|v| v.upcase})
109
+ equal('subdiv_code', 'subdiv_code', transform: lambda{|v| v.upcase})
110
+ equal('year', 'year')
111
+ equal('min_year', 'year>=')
112
+ equal('max_year', 'year<=')
113
+ equal('source_type', 'source_type')
114
+ equal('num_children', 'num_children')
115
+ equal('num_parents', 'num_parents')
116
+ equal('top_parent_id', 'top_parent_id')
117
+
118
+ output
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,52 @@
1
+ module WhosGotDirt
2
+ module Requests
3
+ module Entity
4
+ # Requests for entities from the LittleSis API.
5
+ #
6
+ # Tokens less than two characters long will be ignored by LittleSis' `q`
7
+ # filter. The `q` parameter matches names and aliases. If `search_all` is
8
+ # `1`, it also matches descriptions and summaries.
9
+ #
10
+ # @example Supply an API key.
11
+ # "little_sis_api_key": "..."
12
+ #
13
+ # @example Match descriptions and summaries on `name~=` queries.
14
+ # "search_all": 1
15
+ class LittleSis < Request
16
+ # The JSON response has less metadata, e.g. number of results.
17
+ @base_url = 'https://api.littlesis.org/entities.xml'
18
+
19
+ # Returns the URL to request.
20
+ #
21
+ # @return [String] the URL to request
22
+ def to_s
23
+ "#{base_url}?#{to_query(convert)}"
24
+ end
25
+
26
+ # Returns the "OR" operator's serialization.
27
+ #
28
+ # @return [String] the "OR" operator's serialization
29
+ def or_operator
30
+ ','
31
+ end
32
+
33
+ # Converts the MQL parameters to API-specific parameters.
34
+ #
35
+ # @return [Hash] API-specific parameters
36
+ # @see http://api.littlesis.org/documentation#entities
37
+ # @see http://api.littlesis.org/entities/types.json
38
+ def convert
39
+ match('q', 'name')
40
+ one_of('type_ids', 'classification')
41
+ equal('num', 'limit')
42
+
43
+ # API-specific parameters.
44
+ equal('_key', 'little_sis_api_key')
45
+ equal('search_all', 'search_all', valid: [1])
46
+
47
+ output
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end