whos_got_dirt 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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