jsonapi-consumer 0.1.1 → 1.0.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 (76) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +27 -0
  3. data/.gitignore +1 -0
  4. data/Gemfile +6 -4
  5. data/README.md +9 -38
  6. data/Rakefile +17 -6
  7. data/bin/console +14 -0
  8. data/bin/setup +8 -0
  9. data/jsonapi-consumer.gemspec +10 -11
  10. data/lib/jsonapi/consumer/associations/base_association.rb +26 -0
  11. data/lib/jsonapi/consumer/associations/belongs_to.rb +30 -0
  12. data/lib/jsonapi/consumer/associations/has_many.rb +26 -0
  13. data/lib/jsonapi/consumer/associations/has_one.rb +19 -0
  14. data/lib/jsonapi/consumer/connection.rb +36 -0
  15. data/lib/jsonapi/consumer/error_collector.rb +91 -0
  16. data/lib/jsonapi/consumer/errors.rb +34 -76
  17. data/lib/jsonapi/consumer/formatter.rb +145 -0
  18. data/lib/jsonapi/consumer/helpers/callbacks.rb +27 -0
  19. data/lib/jsonapi/consumer/helpers/dirty.rb +71 -0
  20. data/lib/jsonapi/consumer/helpers/dynamic_attributes.rb +83 -0
  21. data/lib/jsonapi/consumer/helpers/uri.rb +9 -0
  22. data/lib/jsonapi/consumer/implementation.rb +12 -0
  23. data/lib/jsonapi/consumer/included_data.rb +49 -0
  24. data/lib/jsonapi/consumer/linking/links.rb +22 -0
  25. data/lib/jsonapi/consumer/linking/top_level_links.rb +39 -0
  26. data/lib/jsonapi/consumer/meta_data.rb +19 -0
  27. data/lib/jsonapi/consumer/middleware/json_request.rb +26 -0
  28. data/lib/jsonapi/consumer/middleware/parse_json.rb +22 -23
  29. data/lib/jsonapi/consumer/middleware/status.rb +41 -0
  30. data/lib/jsonapi/consumer/paginating/paginator.rb +89 -0
  31. data/lib/jsonapi/consumer/parsers/parser.rb +113 -0
  32. data/lib/jsonapi/consumer/query/builder.rb +212 -0
  33. data/lib/jsonapi/consumer/query/requestor.rb +67 -0
  34. data/lib/jsonapi/consumer/relationships/relations.rb +56 -0
  35. data/lib/jsonapi/consumer/relationships/top_level_relations.rb +30 -0
  36. data/lib/jsonapi/consumer/resource.rb +514 -54
  37. data/lib/jsonapi/consumer/result_set.rb +25 -0
  38. data/lib/jsonapi/consumer/schema.rb +153 -0
  39. data/lib/jsonapi/consumer/utils.rb +28 -0
  40. data/lib/jsonapi/consumer/version.rb +1 -1
  41. data/lib/jsonapi/consumer.rb +59 -34
  42. metadata +51 -111
  43. data/.rspec +0 -2
  44. data/CHANGELOG.md +0 -36
  45. data/lib/jsonapi/consumer/middleware/raise_error.rb +0 -21
  46. data/lib/jsonapi/consumer/middleware/request_headers.rb +0 -20
  47. data/lib/jsonapi/consumer/middleware/request_timeout.rb +0 -9
  48. data/lib/jsonapi/consumer/middleware.rb +0 -5
  49. data/lib/jsonapi/consumer/parser.rb +0 -75
  50. data/lib/jsonapi/consumer/query/base.rb +0 -34
  51. data/lib/jsonapi/consumer/query/create.rb +0 -9
  52. data/lib/jsonapi/consumer/query/delete.rb +0 -10
  53. data/lib/jsonapi/consumer/query/find.rb +0 -16
  54. data/lib/jsonapi/consumer/query/new.rb +0 -15
  55. data/lib/jsonapi/consumer/query/update.rb +0 -11
  56. data/lib/jsonapi/consumer/query.rb +0 -5
  57. data/lib/jsonapi/consumer/resource/association_concern.rb +0 -203
  58. data/lib/jsonapi/consumer/resource/attributes_concern.rb +0 -70
  59. data/lib/jsonapi/consumer/resource/connection_concern.rb +0 -99
  60. data/lib/jsonapi/consumer/resource/finders_concern.rb +0 -28
  61. data/lib/jsonapi/consumer/resource/object_build_concern.rb +0 -28
  62. data/lib/jsonapi/consumer/resource/serializer_concern.rb +0 -63
  63. data/spec/fixtures/.gitkeep +0 -0
  64. data/spec/fixtures/resources.rb +0 -45
  65. data/spec/fixtures/responses.rb +0 -64
  66. data/spec/jsonapi/consumer/associations_spec.rb +0 -166
  67. data/spec/jsonapi/consumer/attributes_spec.rb +0 -27
  68. data/spec/jsonapi/consumer/connection_spec.rb +0 -147
  69. data/spec/jsonapi/consumer/error_handling_spec.rb +0 -37
  70. data/spec/jsonapi/consumer/object_build_spec.rb +0 -20
  71. data/spec/jsonapi/consumer/parser_spec.rb +0 -39
  72. data/spec/jsonapi/consumer/resource_spec.rb +0 -62
  73. data/spec/jsonapi/consumer/serializer_spec.rb +0 -41
  74. data/spec/spec_helper.rb +0 -97
  75. data/spec/support/.gitkeep +0 -0
  76. data/spec/support/load_fixtures.rb +0 -4
@@ -0,0 +1,145 @@
1
+ # Taken form jsonapi_resources formatter
2
+
3
+ require 'active_support/inflector'
4
+
5
+ module JSONAPI::Consumer
6
+ class Formatter
7
+ class << self
8
+ def format(arg)
9
+ arg.to_s
10
+ end
11
+
12
+ def unformat(arg)
13
+ # We call to_s() here so that unformat consistently returns a string
14
+ # (instead of a symbol) regardless which Formatter subclass it is called on
15
+ arg.to_s
16
+ end
17
+
18
+ def formatter_for(format)
19
+ formatter_class_name = "JSONAPI::Consumer::#{format.to_s.camelize}Formatter"
20
+ formatter_class_name.safe_constantize
21
+ end
22
+ end
23
+ end
24
+
25
+ class KeyFormatter < Formatter
26
+ class << self
27
+ def format(key)
28
+ super
29
+ end
30
+
31
+ def format_keys(hash)
32
+ Hash[
33
+ hash.map do |key, value|
34
+ [format(key).to_sym, value]
35
+ end
36
+ ]
37
+ end
38
+
39
+ def unformat(formatted_key)
40
+ super
41
+ end
42
+ end
43
+ end
44
+
45
+ class RouteFormatter < Formatter
46
+ class << self
47
+ def format(route)
48
+ super
49
+ end
50
+
51
+ def unformat(formatted_route)
52
+ super
53
+ end
54
+ end
55
+ end
56
+
57
+ class ValueFormatter < Formatter
58
+ class << self
59
+ def format(raw_value)
60
+ super(raw_value)
61
+ end
62
+
63
+ def unformat(value)
64
+ super(value)
65
+ end
66
+
67
+ def value_formatter_for(type)
68
+ formatter_name = "#{type.to_s.camelize}Value"
69
+ formatter_for(formatter_name)
70
+ end
71
+ end
72
+ end
73
+
74
+ class UnderscoredKeyFormatter < KeyFormatter
75
+ end
76
+
77
+ class CamelizedKeyFormatter < KeyFormatter
78
+ class << self
79
+ def format(key)
80
+ super.camelize(:lower)
81
+ end
82
+
83
+ def unformat(formatted_key)
84
+ formatted_key.to_s.underscore
85
+ end
86
+ end
87
+ end
88
+
89
+ class DasherizedKeyFormatter < KeyFormatter
90
+ class << self
91
+ def format(key)
92
+ super.dasherize
93
+ end
94
+
95
+ def unformat(formatted_key)
96
+ formatted_key.to_s.underscore
97
+ end
98
+ end
99
+ end
100
+
101
+ class DefaultValueFormatter < ValueFormatter
102
+ class << self
103
+ def format(raw_value)
104
+ raw_value
105
+ end
106
+ end
107
+ end
108
+
109
+ class IdValueFormatter < ValueFormatter
110
+ class << self
111
+ def format(raw_value)
112
+ return if raw_value.nil?
113
+ raw_value.to_s
114
+ end
115
+ end
116
+ end
117
+
118
+ class UnderscoredRouteFormatter < RouteFormatter
119
+ end
120
+
121
+ class CamelizedRouteFormatter < RouteFormatter
122
+ class << self
123
+ def format(route)
124
+ super.camelize(:lower)
125
+ end
126
+
127
+ def unformat(formatted_route)
128
+ formatted_route.to_s.underscore
129
+ end
130
+ end
131
+ end
132
+
133
+ class DasherizedRouteFormatter < RouteFormatter
134
+ class << self
135
+ def format(route)
136
+ super.dasherize
137
+ end
138
+
139
+ def unformat(formatted_route)
140
+ formatted_route.to_s.underscore
141
+ end
142
+ end
143
+ end
144
+
145
+ end
@@ -0,0 +1,27 @@
1
+ module JSONAPI::Consumer
2
+ module Helpers
3
+ module Callbacks
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ extend ActiveModel::Callbacks
8
+ define_model_callbacks :save, :destroy, :create, :update
9
+ end
10
+
11
+ def save
12
+ run_callbacks :save do
13
+ run_callbacks (persisted? ? :update : :create) do
14
+ super
15
+ end
16
+ end
17
+ end
18
+
19
+ def destroy
20
+ run_callbacks :destroy do
21
+ super
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,71 @@
1
+ module JSONAPI::Consumer
2
+ module Helpers
3
+ module Dirty
4
+
5
+ def changed?
6
+ changed_attributes.present?
7
+ end
8
+
9
+ def changed
10
+ changed_attributes.keys
11
+ end
12
+
13
+ def changed_attributes
14
+ @changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
15
+ end
16
+
17
+ def clear_changes_information
18
+ @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
19
+ end
20
+
21
+ def set_all_attributes_dirty
22
+ attributes.each do |k, v|
23
+ set_attribute_was(k, v)
24
+ end
25
+ end
26
+
27
+ def attribute_will_change!(attr)
28
+ return if attribute_changed?(attr)
29
+ set_attribute_was(attr, attributes[attr])
30
+ end
31
+
32
+ def set_attribute_was(attr, value)
33
+ begin
34
+ value = value.duplicable? ? value.clone : value
35
+ changed_attributes[attr] = value
36
+ rescue TypeError, NoMethodError
37
+ end
38
+ end
39
+
40
+ def attribute_was(attr) # :nodoc:
41
+ attribute_changed?(attr) ? changed_attributes[attr] : attributes[attr]
42
+ end
43
+
44
+ def attribute_changed?(attr)
45
+ changed.include?(attr.to_s)
46
+ end
47
+
48
+ def attribute_change(attr)
49
+ [changed_attributes[attr], attributes[attr]] if attribute_changed?(attr)
50
+ end
51
+
52
+ protected
53
+
54
+ def method_missing(method, *args, &block)
55
+ if method.to_s =~ /^(.*)_changed\?$/
56
+ has_attribute?($1) ? attribute_changed?($1) : nil
57
+ elsif method.to_s =~ /^(.*)_was$/
58
+ has_attribute?($1) ? attribute_was($1) : nil
59
+ else
60
+ super
61
+ end
62
+ end
63
+
64
+ def set_attribute(name, value)
65
+ attribute_will_change!(name) if value != attributes[name] || !attributes.has_key?(name)
66
+ super
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,83 @@
1
+ module JSONAPI::Consumer
2
+ module Helpers
3
+ module DynamicAttributes
4
+
5
+ def attributes
6
+ @attributes
7
+ end
8
+
9
+ def attributes=(attrs = {})
10
+ @attributes ||= ActiveSupport::HashWithIndifferentAccess.new
11
+
12
+ return @attributes unless attrs.present?
13
+ attrs.each do |key, value|
14
+ send("#{key}=", value)
15
+ end
16
+ end
17
+
18
+ def [](key)
19
+ read_attribute(key)
20
+ end
21
+
22
+ def []=(key, value)
23
+ set_attribute(key, value)
24
+ end
25
+
26
+ def respond_to_missing?(method, include_private = false)
27
+ if has_attribute?(method) || method.to_s.end_with?('=')
28
+ true
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ def has_attribute?(attr_name)
35
+ attributes.has_key?(attr_name)
36
+ end
37
+
38
+ protected
39
+
40
+ def method_missing(method, *args, &block)
41
+ if has_attribute?(method)
42
+ self.class.class_eval do
43
+ define_method(method) do
44
+ attributes[method]
45
+ end
46
+ end
47
+ return send(method)
48
+ end
49
+
50
+ normalized_method = safe_key_formatter.unformat(method.to_s)
51
+
52
+ if normalized_method.end_with?('=')
53
+ set_attribute(normalized_method[0..-2], args.first)
54
+ else
55
+ super
56
+ end
57
+ end
58
+
59
+ def read_attribute(name)
60
+ attributes.fetch(name, nil)
61
+ end
62
+
63
+ def set_attribute(name, value)
64
+ attributes[name] = value
65
+ end
66
+
67
+ def safe_key_formatter
68
+ @safe_key_formatter ||= (key_formatter || DefaultKeyFormatter.new)
69
+ end
70
+
71
+ def key_formatter
72
+ self.class.respond_to?(:key_formatter) && self.class.key_formatter
73
+ end
74
+
75
+ class DefaultKeyFormatter
76
+ def unformat(method)
77
+ method.to_s
78
+ end
79
+ end
80
+
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,9 @@
1
+ module JSONAPI::Consumer
2
+ module Helpers
3
+ module URI
4
+ def encode_part(part)
5
+ Addressable::URI.encode_component(part, Addressable::URI::CharacterClasses::UNRESERVED)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ module JSONAPI::Consumer
2
+ class Implementation
3
+ attr_reader :version, :meta
4
+
5
+ def initialize(data)
6
+ # If the version member is not present, clients should assume the server implements at least version 1.0 of the specification.
7
+ @version = data.fetch("version", "1.0")
8
+
9
+ @meta = MetaData.new(data.fetch("meta", {}))
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,49 @@
1
+ module JSONAPI::Consumer
2
+ class IncludedData
3
+ attr_reader :data
4
+
5
+ def initialize(result_set, data)
6
+ record_class = result_set.record_class
7
+ grouped_data = data.group_by{|datum| datum["type"]}
8
+ @data = grouped_data.inject({}) do |h, (type, records)|
9
+ klass = Utils.compute_type(record_class, record_class.key_formatter.unformat(type).singularize.classify)
10
+ h[type] = records.map do |datum|
11
+ params = klass.parser.parameters_from_resource(datum)
12
+ resource = klass.load(params)
13
+ resource.last_result_set = result_set
14
+ resource
15
+ end.index_by(&:id)
16
+ h
17
+ end
18
+ end
19
+
20
+ def data_for(method_name, definition)
21
+ # this method only returns an array. It's up to the caller to decide if it's going to return
22
+ # just the first element if it's a has_one relationship.
23
+ # If data is defined, pull the record from the included data
24
+ defined_data = definition["data"]
25
+ return nil unless defined_data
26
+ [defined_data].flatten.map do |link_def|
27
+ # should return a resource record of some type for this linked document
28
+ # even if there's no matching record included.
29
+ if data[link_def["type"]]
30
+ record_for(link_def)
31
+ else
32
+ # if there's no matching record in included then go and get it given the data
33
+ link_def["type"].underscore.classify.constantize.find(link_def["id"]).first
34
+ end
35
+ end
36
+ end
37
+
38
+ def has_link?(name)
39
+ data.has_key?(name.to_s)
40
+ end
41
+
42
+ private
43
+
44
+ # should return a resource record of some type for this linked document
45
+ def record_for(link_def)
46
+ data[link_def["type"]][link_def["id"]]
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,22 @@
1
+ module JSONAPI::Consumer
2
+ module Linking
3
+ class Links
4
+ include Helpers::DynamicAttributes
5
+
6
+ def initialize(links)
7
+ self.attributes = links
8
+ end
9
+
10
+ def present?
11
+ attributes.present?
12
+ end
13
+
14
+ protected
15
+
16
+ def set_attribute(name, value)
17
+ attributes[name] = value
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,39 @@
1
+ module JSONAPI::Consumer
2
+ module Linking
3
+ class TopLevelLinks
4
+
5
+ attr_reader :links, :record_class
6
+
7
+ def initialize(record_class, links)
8
+ @links = links
9
+ @record_class = record_class
10
+ end
11
+
12
+ def respond_to_missing?(method, include_private = false)
13
+ links.has_key?(method.to_s) || super
14
+ end
15
+
16
+ def method_missing(method, *args)
17
+ if respond_to_missing?(method)
18
+ fetch_link(method)
19
+ else
20
+ super
21
+ end
22
+ end
23
+
24
+ def link_url_for(link_name)
25
+ link_definition = links.fetch(link_name.to_s)
26
+ if link_definition.is_a?(Hash)
27
+ link_definition["href"]
28
+ else
29
+ link_definition
30
+ end
31
+ end
32
+
33
+ def fetch_link(link_name)
34
+ return unless respond_to_missing?(link_name)
35
+ record_class.requestor.linked(link_url_for(link_name))
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,19 @@
1
+ module JSONAPI::Consumer
2
+ class MetaData
3
+ include Helpers::DynamicAttributes
4
+
5
+ attr_accessor :record_class
6
+
7
+ def initialize(data, record_class = nil)
8
+ self.record_class = record_class
9
+ self.attributes = data
10
+ end
11
+
12
+ protected
13
+
14
+ def key_formatter
15
+ record_class && record_class.key_formatter
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ module JSONAPI::Consumer
2
+ module Middleware
3
+ class JsonRequest < Faraday::Middleware
4
+ def call(environment)
5
+ accept_header = update_accept_header(environment[:request_headers])
6
+
7
+ environment[:request_headers]["Content-Type"] = 'application/vnd.api+json'
8
+ environment[:request_headers]["Accept"] = accept_header
9
+ @app.call(environment)
10
+ end
11
+
12
+ private
13
+
14
+ def update_accept_header(headers)
15
+ return 'application/vnd.api+json' if headers["Accept"].nil?
16
+ accept_params = headers["Accept"].split(",")
17
+
18
+ unless accept_params.include?('application/vnd.api+json')
19
+ accept_params.unshift('application/vnd.api+json')
20
+ end
21
+
22
+ accept_params.join(",")
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,32 +1,31 @@
1
- module JSONAPI::Consumer::Middleware
2
- # Credit to chingor13/json_api_client for middleware.
3
- #
4
- class ParseJson < Faraday::Response::Middleware
1
+ module JSONAPI::Consumer
2
+ module Middleware
3
+ class ParseJson < Faraday::Middleware
5
4
 
6
- def call(environment)
7
- @app.call(environment).on_complete do |env|
8
- if process_response_type?(response_type(env))
9
- env[:raw_body] = env[:body]
10
- env[:body] = parse(env[:body])
5
+ def call(environment)
6
+ @app.call(environment).on_complete do |env|
7
+ if process_response_type?(response_type(env))
8
+ env[:raw_body] = env[:body]
9
+ env[:body] = parse(env[:body])
10
+ end
11
11
  end
12
12
  end
13
- end
14
13
 
15
- private
14
+ private
16
15
 
17
- def parse(body)
18
- ::JSON.parse(body) unless body.strip.empty?
19
- end
16
+ def parse(body)
17
+ ::JSON.parse(body) unless body.strip.empty?
18
+ end
20
19
 
21
- def response_type(env)
22
- type = env[:response_headers]['Content-Type'].to_s
23
- type = type.split(';', 2).first if type.index(';')
24
- type
25
- end
20
+ def response_type(env)
21
+ type = env[:response_headers]['Content-Type'].to_s
22
+ type = type.split(';', 2).first if type.index(';')
23
+ type
24
+ end
26
25
 
27
- def process_response_type?(type)
28
- !!type.match(/\bjson$/)
26
+ def process_response_type?(type)
27
+ !!type.match(/\bjson$/)
28
+ end
29
29
  end
30
30
  end
31
- end
32
-
31
+ end
@@ -0,0 +1,41 @@
1
+ module JSONAPI::Consumer
2
+ module Middleware
3
+ class Status < Faraday::Middleware
4
+ def call(environment)
5
+ @app.call(environment).on_complete do |env|
6
+ handle_status(env[:status], env)
7
+
8
+ # look for meta[:status]
9
+ if env[:body].is_a?(Hash)
10
+ code = env[:body].fetch("meta", {}).fetch("status", 200).to_i
11
+ handle_status(code, env)
12
+ end
13
+ end
14
+ rescue Faraday::ConnectionFailed, Faraday::TimeoutError
15
+ raise Errors::ConnectionError, environment
16
+ end
17
+
18
+ protected
19
+
20
+ def handle_status(code, env)
21
+ case code
22
+ when 200..399
23
+ when 401
24
+ raise Errors::NotAuthorized, env
25
+ when 403
26
+ raise Errors::AccessDenied, env
27
+ when 404
28
+ raise Errors::NotFound, env[:url]
29
+ when 409
30
+ raise Errors::Conflict, env
31
+ when 400..499
32
+ # some other error
33
+ when 500..599
34
+ raise Errors::ServerError, env
35
+ else
36
+ raise Errors::UnexpectedStatus.new(code, env[:url])
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,89 @@
1
+ module JSONAPI::Consumer
2
+ module Paginating
3
+ class Paginator
4
+ class_attribute :page_param,
5
+ :per_page_param
6
+
7
+ self.page_param = "page"
8
+ self.per_page_param = "per_page"
9
+
10
+ attr_reader :params, :result_set, :links
11
+
12
+ def initialize(result_set, data)
13
+ @params = params_for_uri(result_set.uri)
14
+ @result_set = result_set
15
+ @links = data["links"]
16
+ end
17
+
18
+ def next
19
+ result_set.links.fetch_link("next")
20
+ end
21
+
22
+ def prev
23
+ result_set.links.fetch_link("prev")
24
+ end
25
+
26
+ def first
27
+ result_set.links.fetch_link("first")
28
+ end
29
+
30
+ def last
31
+ result_set.links.fetch_link("last")
32
+ end
33
+
34
+ def total_pages
35
+ if links["last"]
36
+ uri = result_set.links.link_url_for("last")
37
+ last_params = params_for_uri(uri)
38
+ last_params.fetch(page_param) do
39
+ current_page
40
+ end.to_i
41
+ else
42
+ current_page
43
+ end
44
+ end
45
+
46
+ # this number may be off
47
+ def total_entries
48
+ per_page * total_pages
49
+ end
50
+ def total_count; total_entries; end
51
+
52
+ def offset
53
+ per_page * (current_page - 1)
54
+ end
55
+
56
+ def per_page
57
+ params.fetch(per_page_param) do
58
+ result_set.length
59
+ end.to_i
60
+ end
61
+
62
+ def current_page
63
+ params.fetch(page_param, 1).to_i
64
+ end
65
+
66
+ def out_of_bounds?
67
+ current_page > total_pages
68
+ end
69
+
70
+ def previous_page
71
+ current_page > 1 ? (current_page - 1) : nil
72
+ end
73
+
74
+ def next_page
75
+ current_page < total_pages ? (current_page + 1) : nil
76
+ end
77
+
78
+ alias limit_value per_page
79
+
80
+ protected
81
+
82
+ def params_for_uri(uri)
83
+ return {} unless uri
84
+ uri = Addressable::URI.parse(uri)
85
+ uri.query_values || {}
86
+ end
87
+ end
88
+ end
89
+ end