parliament-ruby 0.5.19 → 0.6.0

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -3
  3. data/.travis.yml +0 -1
  4. data/Gemfile +2 -0
  5. data/Makefile +9 -4
  6. data/README.md +185 -11
  7. data/Rakefile +1 -1
  8. data/lib/parliament/client_error.rb +21 -0
  9. data/lib/parliament/decorator/constituency_area.rb +27 -0
  10. data/lib/parliament/{decorators → decorator}/constituency_group.rb +29 -1
  11. data/lib/parliament/decorator/contact_point.rb +48 -0
  12. data/lib/parliament/decorator/gender.rb +13 -0
  13. data/lib/parliament/decorator/gender_identity.rb +13 -0
  14. data/lib/parliament/decorator/house.rb +41 -0
  15. data/lib/parliament/decorator/house_incumbency.rb +50 -0
  16. data/lib/parliament/decorator/house_seat.rb +27 -0
  17. data/lib/parliament/decorator/incumbency.rb +57 -0
  18. data/lib/parliament/decorator/party.rb +27 -0
  19. data/lib/parliament/decorator/party_membership.rb +36 -0
  20. data/lib/parliament/decorator/person.rb +224 -0
  21. data/lib/parliament/{decorators → decorator}/postal_address.rb +5 -1
  22. data/lib/parliament/decorator/seat_incumbency.rb +64 -0
  23. data/lib/parliament/decorator.rb +7 -0
  24. data/lib/parliament/network_error.rb +13 -0
  25. data/lib/parliament/no_content_response_error.rb +19 -0
  26. data/lib/parliament/request.rb +112 -33
  27. data/lib/parliament/response.rb +76 -9
  28. data/lib/parliament/server_error.rb +21 -0
  29. data/lib/parliament/utils.rb +113 -13
  30. data/lib/parliament/version.rb +1 -1
  31. data/lib/parliament.rb +8 -4
  32. data/parliament-ruby.gemspec +6 -6
  33. metadata +32 -28
  34. data/lib/parliament/decorators/constituency_area.rb +0 -17
  35. data/lib/parliament/decorators/contact_point.rb +0 -29
  36. data/lib/parliament/decorators/gender.rb +0 -9
  37. data/lib/parliament/decorators/gender_identity.rb +0 -9
  38. data/lib/parliament/decorators/house.rb +0 -28
  39. data/lib/parliament/decorators/house_incumbency.rb +0 -31
  40. data/lib/parliament/decorators/house_seat.rb +0 -17
  41. data/lib/parliament/decorators/incumbency.rb +0 -35
  42. data/lib/parliament/decorators/party.rb +0 -17
  43. data/lib/parliament/decorators/party_membership.rb +0 -23
  44. data/lib/parliament/decorators/person.rb +0 -152
  45. data/lib/parliament/decorators/seat_incumbency.rb +0 -39
  46. data/lib/parliament/no_content_error.rb +0 -9
@@ -1,6 +1,10 @@
1
1
  module Parliament
2
- module Decorators
2
+ module Decorator
3
+ # Decorator namespace for Grom::Node instances with type: http://id.ukpds.org/schema/PostalAddress
3
4
  module PostalAddress
5
+ # Builds a full address using the lines of the address and the postcode.
6
+ #
7
+ # @return [String, String] the full address of the Grom::Node or an empty string.
4
8
  def full_address
5
9
  address_array.join(', ')
6
10
  end
@@ -0,0 +1,64 @@
1
+ module Parliament
2
+ module Decorator
3
+ # Decorator namespace for Grom::Node instances with type: http://id.ukpds.org/schema/SeatIncumbency
4
+ module SeatIncumbency
5
+ # Alias incumbencyStartDate with fallback.
6
+ #
7
+ # @return [DateTime, nil] the start date of the Grom::Node or nil.
8
+ def start_date
9
+ respond_to?(:incumbencyStartDate) ? DateTime.parse(incumbencyStartDate) : nil
10
+ end
11
+
12
+ # Alias incumbencyEndDate with fallback.
13
+ #
14
+ # @return [DateTime, nil] the end date of the Grom::Node or nil.
15
+ def end_date
16
+ respond_to?(:incumbencyEndDate) ? DateTime.parse(incumbencyEndDate) : nil
17
+ end
18
+
19
+ # Alias seatIncumbencyHasHouseSeat with fallback.
20
+ #
21
+ # @return [Grom::Node, nil] the seat of the Grom::Node or nil.
22
+ def seat
23
+ respond_to?(:seatIncumbencyHasHouseSeat) ? seatIncumbencyHasHouseSeat.first : nil
24
+ end
25
+
26
+ # Checks if Grom::Node has an end date.
27
+ #
28
+ # @return [Boolean] a boolean depending on whether or not the Grom::Node has an end date.
29
+ def current?
30
+ has_end_date = respond_to?(:incumbencyEndDate)
31
+
32
+ !has_end_date
33
+ end
34
+
35
+ # Alias houseSeatHasHouse with fallback.
36
+ #
37
+ # @return [Grom::Node, nil] the house of the Grom::Node or nil.
38
+ def house
39
+ seat.nil? ? nil : seat.house
40
+ end
41
+
42
+ # Alias houseSeatHasConstituencyGroup with fallback.
43
+ #
44
+ # @return [Grom::Node, nil] the constituency of the Grom::Node or nil.
45
+ def constituency
46
+ seat.nil? ? nil : seat.constituency
47
+ end
48
+
49
+ # Alias incumbencyHasContactPoint with fallback.
50
+ #
51
+ # @return [Array, Array] the contact points of the Grom::Node or an empty array.
52
+ def contact_points
53
+ respond_to?(:incumbencyHasContactPoint) ? incumbencyHasContactPoint : []
54
+ end
55
+
56
+ # Alias incumbencyHasMember with fallback.
57
+ #
58
+ # @return [Grom::Node, nil] the member connected to the Grom::Node or nil.
59
+ def member
60
+ respond_to?(:incumbencyHasMember) ? incumbencyHasMember.first : nil
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,7 @@
1
+ module Parliament
2
+ # Decorator namespace
3
+ module Decorator
4
+ # require all the decorators
5
+ Dir[File.join(File.dirname(__FILE__), '../parliament/decorator', '*.rb')].each { |decorator| require decorator }
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ module Parliament
2
+ # A parent class that standardises the error message generated for network errors.
3
+ #
4
+ # @see Parliament::ClientError
5
+ # @see Parliament::ServerError
6
+ #
7
+ # @since 0.6.0
8
+ class NetworkError < StandardError
9
+ def initialize(url, response)
10
+ super("#{response.code} HTTP status code received from: #{url} - #{response.message}")
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ module Parliament
2
+ # An error raised when a 204 status code is returned by Net::HTTP inside of Parliament::Request.
3
+ #
4
+ # @since 0.6.0
5
+ class NoContentResponseError < Parliament::NetworkError
6
+ # @param [String] url the url that caused the Parliament::ClientError
7
+ # @param [Net::HTTPResponse] response the Net:HTTPResponse that caused the Parliament::NoContentResponseError
8
+ #
9
+ # @example Creating a Parliament::NoContentResponseError
10
+ # url = 'http://localhost:3030/foo/bar'
11
+ #
12
+ # response = Net::HTTP.get_response(URI(url))
13
+ #
14
+ # raise Parliament::NoContentResponseError.new(url, response) if response.code == '204'
15
+ def initialize(url, response)
16
+ super
17
+ end
18
+ end
19
+ end
@@ -1,26 +1,109 @@
1
1
  module Parliament
2
+ # API request object, allowing the user to build a request to a graph-based API, download n-triple data and create
3
+ # ruby objects from that data.
4
+ #
5
+ # @since 0.1.0
6
+ #
7
+ # @attr_reader [String] base_url the base url of our api. (expected: http://example.com - without the trailing slash).
2
8
  class Request
3
9
  attr_reader :base_url
4
10
 
11
+ # Creates a new instance of Parliament::Request.
12
+ #
13
+ # An interesting note for #initialize is that setting base_url on the class, or using the environment variable
14
+ # PARLIAMENT_BASE_URL means you don't need to pass in a base_url. You can pass one anyway to override the
15
+ # environment variable or class parameter.
16
+ #
17
+ # @example Setting the base_url on the class
18
+ # Parliament::Request.base_url = 'http://example.com'
19
+ #
20
+ # Parliament::Request.new.base_url #=> 'http://example.com'
21
+ #
22
+ # @example Setting the base_url via environment variable
23
+ # ENV['PARLIAMENT_BASE_URL'] #=> 'http://test.com'
24
+ #
25
+ # Parliament::Request.new.base_url #=> 'http://test.com'
26
+ #
27
+ # @example Setting the base_url via a parameter
28
+ # Parliament::Request.base_url #=> nil
29
+ # ENV['PARLIAMENT_BASE_URL'] #=> nil
30
+ #
31
+ # Parliament::Request.new(base_url: 'http://example.com').base_url #=> 'http://example.com'
32
+ #
33
+ # @example Overriding the base_url via a parameter
34
+ # ENV['PARLIAMENT_BASE_URL'] #=> 'http://test.com'
35
+ #
36
+ # Parliament::Request.new(base_url: 'http://example.com').base_url #=> 'http://example.com'
37
+ #
38
+ # @param [String] base_url the base url of our api. (expected: http://example.com - without the trailing slash).
5
39
  def initialize(base_url: nil)
6
40
  @endpoint_parts = []
7
41
  @base_url = base_url || self.class.base_url || ENV['PARLIAMENT_BASE_URL']
8
42
  end
9
43
 
44
+ # Overrides ruby's method_missing to allow creation of URLs through method calls.
45
+ #
46
+ # @example Adding a simple URL part
47
+ # request = Parliament::Request.new(base_url: 'http://example.com')
48
+ #
49
+ # # url: http://example.com/people
50
+ # request.people
51
+ #
52
+ # @example Adding a simple URL part with parameters
53
+ # request = Parliament::Request.new(base_url: 'http://example.com')
54
+ #
55
+ # # url: http://example.com/people/123456
56
+ # request.people('123456')
57
+ #
58
+ # @example Chaining URL parts and using hyphens
59
+ # request = Parliament::Request.new(base_url: 'http://example.com')
60
+ #
61
+ # # url: http://example.com/people/123456/foo/bar/hello-world/7890
62
+ # request.people('123456').foo.bar('hello-world', '7890')
63
+ #
64
+ # @param [Symbol] method the 'method' (url part) we are processing.
65
+ # @param [Array<Object>] params parameters passed to the specified method (url part).
66
+ # @param [Block] block additional block (kept for compatibility with method_missing API).
67
+ #
68
+ # @return [Parliament::Request] self.
10
69
  def method_missing(method, *params, &block)
11
- # TODO: Fix this smell
12
- super if method == :base_url=
13
-
14
70
  @endpoint_parts << method.to_s
15
71
  @endpoint_parts << params
16
72
  @endpoint_parts = @endpoint_parts.flatten!
17
- self
73
+
74
+ block&.call
75
+
76
+ self || super
18
77
  end
19
78
 
20
- def respond_to_missing?(method, include_private = false)
21
- (method != :base_url=) || super
79
+ # This class always responds to method calls, even those missing. Therefore, respond_to_missing? always returns true.
80
+ #
81
+ # @return [Boolean] always returns true.
82
+ def respond_to_missing?(_, _ = false)
83
+ true # responds to everything, always
22
84
  end
23
85
 
86
+ # Using our url built via #method_missing, make a HTTP GET request and process results into a response.
87
+ #
88
+ # @example HTTP GET request
89
+ # request = Parliament::Request.new(base_url: 'http://example.com')
90
+ #
91
+ # # url: http://example.com/people/123456
92
+ # response = request.people('123456').get #=> #<Parliament::Response ...>
93
+ #
94
+ # @example HTTP GET request with URI encoded form values
95
+ # request = Parliament::Request.new(base_url: 'http://example.com')
96
+ #
97
+ # # url: http://example.com/people/current?limit=10&page=4&lang=en-gb
98
+ # response = request.people.current.get({ limit: 10, page: 4, lang: 'en-gb' }) #=> #<Parliament::Response ...>
99
+ #
100
+ # @raise [Parliament::ServerError] when the server responds with a 5xx status code.
101
+ # @raise [Parliament::ClientError] when the server responds with a 4xx status code.
102
+ # @raise [Parliament::NoContentResponseError] when the server responds with a 204 status code.
103
+ #
104
+ # @param [Hash] params (optional) additional URI encoded form values to be added to the URI.
105
+ #
106
+ # @return [Parliament::Response] a Parliament::Response object containing all of the nodes returned from the URL.
24
107
  def get(params: nil)
25
108
  endpoint_uri = URI.parse(api_endpoint)
26
109
  endpoint_uri.query = URI.encode_www_form(params.to_a) unless params.nil?
@@ -32,29 +115,35 @@ module Parliament
32
115
  build_parliament_response(net_response)
33
116
  end
34
117
 
35
- def build_parliament_response(response)
36
- objects = Grom::Reader.new(response.body).objects
37
- objects.map { |object| assign_decorator(object) }
118
+ private
38
119
 
39
- Parliament::Response.new(objects)
120
+ # @attr [String] base_url the base url of our api. (expected: http://example.com - without the trailing slash).
121
+ class << self
122
+ attr_accessor :base_url
40
123
  end
41
124
 
42
- def handle_errors(response)
43
- handle_not_found_error(response)
44
- handle_server_error(response)
45
- handle_no_content_error(response)
125
+ def api_endpoint
126
+ [@base_url, @endpoint_parts].join('/')
46
127
  end
47
128
 
48
- def handle_server_error(response)
49
- raise StandardError, 'This is a HTTPServerError' if response.is_a?(Net::HTTPServerError)
129
+ def handle_errors(response)
130
+ case response
131
+ when Net::HTTPSuccess # 2xx Status
132
+ exception_class = Parliament::NoContentResponseError if response.code == '204'
133
+ when Net::HTTPClientError # 4xx Status
134
+ exception_class = Parliament::ClientError
135
+ when Net::HTTPServerError # 5xx Status
136
+ exception_class = Parliament::ServerError
137
+ end
138
+
139
+ raise exception_class.new(api_endpoint, response) if exception_class
50
140
  end
51
141
 
52
- def handle_not_found_error(response)
53
- raise StandardError, 'This is a HTTPClientError' if response.is_a?(Net::HTTPClientError)
54
- end
142
+ def build_parliament_response(response)
143
+ objects = Grom::Reader.new(response.body).objects
144
+ objects.map { |object| assign_decorator(object) }
55
145
 
56
- def handle_no_content_error(response)
57
- raise Parliament::NoContentError if response.code == '204'
146
+ Parliament::Response.new(objects)
58
147
  end
59
148
 
60
149
  def assign_decorator(object)
@@ -62,20 +151,10 @@ module Parliament
62
151
 
63
152
  object_type = Grom::Helper.get_id(object.type)
64
153
 
65
- return object unless Parliament::Decorators.constants.include?(object_type.to_sym)
154
+ return object unless Parliament::Decorator.constants.include?(object_type.to_sym)
66
155
 
67
- decorator_module = Object.const_get("Parliament::Decorators::#{object_type}")
156
+ decorator_module = Object.const_get("Parliament::Decorator::#{object_type}")
68
157
  object.extend(decorator_module)
69
158
  end
70
-
71
- private
72
-
73
- class << self
74
- attr_accessor :base_url
75
- end
76
-
77
- def api_endpoint
78
- [@base_url, @endpoint_parts].join('/')
79
- end
80
159
  end
81
160
  end
@@ -1,16 +1,65 @@
1
1
  require 'forwardable'
2
2
 
3
3
  module Parliament
4
+ # API response object that wraps an Array of Grom::Node objects with common helper methods.
5
+ #
6
+ # Delegates a number of common methods to the array of Grom::Nodes including, but not limited to, :size, :each, :map, :count etc.
7
+ #
8
+ # @since 0.1.0
9
+ #
10
+ # @attr_reader [Array<Grom::Node>] nodes Graph nodes.
4
11
  class Response
5
12
  include Enumerable
6
13
  extend Forwardable
7
14
  attr_reader :nodes
8
15
  def_delegators :@nodes, :size, :each, :select, :map, :select!, :map!, :count, :length, :[], :empty?
9
16
 
17
+ # @param [Array<Grom::Node>] nodes An array of nodes the response should wrap
10
18
  def initialize(nodes)
11
19
  @nodes = nodes
12
20
  end
13
21
 
22
+ # Given our array of Grom::Nodes, filter them into arrays of 'types' of nodes.
23
+ #
24
+ # Note: this method assumes all of your nodes include a #type attribute.
25
+ #
26
+ # @since 0.2.0
27
+ #
28
+ # @example Filtering for a single type
29
+ # node_1 = Grom::Node.new
30
+ # node_1.instance_variable_set(:type, 'type_1')
31
+ # node_2 = Grom::Node.new
32
+ # node_2.instance_variable_set(:type, 'type_3')
33
+ # node_3 = Grom::Node.new
34
+ # node_3.instance_variable_set(:type, 'type_1')
35
+ # node_4 = Grom::Node.new
36
+ # node_4.instance_variable_set(:type, 'type_2')
37
+ # nodes = [node_1, node_2, node_3, node_4]
38
+ #
39
+ # response = Parliament::Response.new(nodes)
40
+ # response.filter('type_2') #=> [#<Grom::Node @type='type_2'>]
41
+ #
42
+ # @example Filtering for multiple types
43
+ # node_1 = Grom::Node.new
44
+ # node_1.instance_variable_set(:type, 'type_1')
45
+ # node_2 = Grom::Node.new
46
+ # node_2.instance_variable_set(:type, 'type_3')
47
+ # node_3 = Grom::Node.new
48
+ # node_3.instance_variable_set(:type, 'type_1')
49
+ # node_4 = Grom::Node.new
50
+ # node_4.instance_variable_set(:type, 'type_2')
51
+ # nodes = [node_1, node_2, node_3, node_4]
52
+ #
53
+ # response = Parliament::Response.new(nodes)
54
+ # response.filter('type_2', 'type_1') #=> [[#<Grom::Node @type='type_2'>], [#<Grom::Node @type='type_1'>, #<Grom::Node @type='type_1'>]]
55
+ #
56
+ # # Also consider
57
+ # type_2, type_1 = response.filter('type_2', 'type_1')
58
+ # type_2 #=> [#<Grom::Node @type='type_2'>]
59
+ # type_1 #=> [#<Grom::Node @type='type_1'>, #<Grom::Node @type='type_1'>]
60
+ #
61
+ # @param [Array<String>] types An array of type strings that you are looking for.
62
+ # @return [Array<Grom::Node> || Array<*Array<Grom::Node>>] If you pass one type, this returns an Array of Grom::Node objects. If you pass multiple, it returns an array, of arrays of Grom::Node objects.
14
63
  def filter(*types)
15
64
  filtered_objects = Array.new(types.size) { [] }
16
65
 
@@ -26,15 +75,14 @@ module Parliament
26
75
  types.size == 1 ? result.first : result
27
76
  end
28
77
 
29
- def build_responses(filtered_objects)
30
- result = []
31
-
32
- filtered_objects.each do |objects|
33
- result << Parliament::Response.new(objects)
34
- end
35
- result
36
- end
37
-
78
+ # Sort the Parliament::Response nodes in ascending order by a set of attributes on each node.
79
+ #
80
+ # @see Parliament::Utils.sort_by
81
+ #
82
+ # @since 0.5.0
83
+ #
84
+ # @param [Array<Symbol>] parameters Attributes to sort on - left to right.
85
+ # @return [Array<Grom::Node>] A sorted array of nodes.
38
86
  def sort_by(*parameters)
39
87
  Parliament::Utils.sort_by({
40
88
  list: @nodes,
@@ -42,11 +90,30 @@ module Parliament
42
90
  })
43
91
  end
44
92
 
93
+ # Sort the Parliament::Response nodes in descending order by a set of attributes on each node.
94
+ #
95
+ # @see Parliament::Utils.reverse_sort_by
96
+ #
97
+ # @since 0.5.0
98
+ #
99
+ # @param [Array<Symbol>] parameters Attributes to sort on - left to right.
100
+ # @return [Array<Grom::Node>] A sorted array of nodes.
45
101
  def reverse_sort_by(*parameters)
46
102
  Parliament::Utils.reverse_sort_by({
47
103
  list: @nodes,
48
104
  parameters: parameters
49
105
  })
50
106
  end
107
+
108
+ private
109
+
110
+ def build_responses(filtered_objects)
111
+ result = []
112
+
113
+ filtered_objects.each do |objects|
114
+ result << Parliament::Response.new(objects)
115
+ end
116
+ result
117
+ end
51
118
  end
52
119
  end
@@ -0,0 +1,21 @@
1
+ module Parliament
2
+ # An error raised when a 5xx status code is returned by Net::HTTP inside of Parliament::Request.
3
+ #
4
+ # @see Parliament::ClientError
5
+ #
6
+ # @since 0.6.0
7
+ class ServerError < Parliament::NetworkError
8
+ # @param [String] url the url that caused the Parliament::ServerError
9
+ # @param [Net::HTTPServerError] response the Net:HTTPServerError that caused the Parliament::ServerError
10
+ #
11
+ # @example Creating a Parliament::ServerError
12
+ # url = 'http://localhost:3030/foo/bar'
13
+ #
14
+ # response = Net::HTTP.get_response(URI(url))
15
+ #
16
+ # raise Parliament::ServerError.new(url, response) if response.is_a?(Net::HTTPServerError)
17
+ def initialize(url, response)
18
+ super
19
+ end
20
+ end
21
+ end