json_api_client 0.9.6 → 1.0.0.beta

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +274 -84
  3. data/lib/json_api_client.rb +9 -4
  4. data/lib/json_api_client/connection.rb +5 -5
  5. data/lib/json_api_client/error_collector.rb +29 -0
  6. data/lib/json_api_client/errors.rb +7 -5
  7. data/lib/json_api_client/helpers.rb +3 -0
  8. data/lib/json_api_client/helpers/associable.rb +4 -3
  9. data/lib/json_api_client/helpers/attributable.rb +15 -46
  10. data/lib/json_api_client/helpers/custom_endpoints.rb +3 -10
  11. data/lib/json_api_client/helpers/dynamic_attributes.rb +61 -0
  12. data/lib/json_api_client/helpers/linkable.rb +28 -18
  13. data/lib/json_api_client/helpers/paginatable.rb +13 -0
  14. data/lib/json_api_client/helpers/parsable.rb +1 -1
  15. data/lib/json_api_client/helpers/queryable.rb +4 -3
  16. data/lib/json_api_client/helpers/requestable.rb +60 -0
  17. data/lib/json_api_client/linking.rb +7 -0
  18. data/lib/json_api_client/linking/included_data.rb +40 -0
  19. data/lib/json_api_client/linking/links.rb +29 -0
  20. data/lib/json_api_client/linking/top_level_links.rb +30 -0
  21. data/lib/json_api_client/meta_data.rb +10 -0
  22. data/lib/json_api_client/middleware/json_request.rb +3 -4
  23. data/lib/json_api_client/middleware/status.rb +10 -1
  24. data/lib/json_api_client/paginating.rb +5 -0
  25. data/lib/json_api_client/paginating/paginator.rb +80 -0
  26. data/lib/json_api_client/parsers.rb +5 -0
  27. data/lib/json_api_client/parsers/parser.rb +55 -0
  28. data/lib/json_api_client/query.rb +2 -7
  29. data/lib/json_api_client/query/builder.rb +126 -0
  30. data/lib/json_api_client/query/requestor.rb +77 -0
  31. data/lib/json_api_client/resource.rb +3 -59
  32. data/lib/json_api_client/result_set.rb +11 -29
  33. data/lib/json_api_client/schema.rb +15 -30
  34. data/lib/json_api_client/version.rb +1 -1
  35. metadata +36 -19
  36. data/lib/json_api_client/link.rb +0 -11
  37. data/lib/json_api_client/link_definition.rb +0 -27
  38. data/lib/json_api_client/linked_data.rb +0 -75
  39. data/lib/json_api_client/parser.rb +0 -63
  40. data/lib/json_api_client/query/base.rb +0 -38
  41. data/lib/json_api_client/query/create.rb +0 -17
  42. data/lib/json_api_client/query/custom.rb +0 -22
  43. data/lib/json_api_client/query/destroy.rb +0 -12
  44. data/lib/json_api_client/query/find.rb +0 -19
  45. data/lib/json_api_client/query/linked.rb +0 -24
  46. data/lib/json_api_client/query/update.rb +0 -13
  47. data/lib/json_api_client/scope.rb +0 -48
@@ -0,0 +1,7 @@
1
+ module JsonApiClient
2
+ module Linking
3
+ autoload :IncludedData, "json_api_client/linking/included_data"
4
+ autoload :Links, "json_api_client/linking/links"
5
+ autoload :TopLevelLinks, "json_api_client/linking/top_level_links"
6
+ end
7
+ end
@@ -0,0 +1,40 @@
1
+ module JsonApiClient
2
+ module Linking
3
+ class IncludedData
4
+ attr_reader :data
5
+
6
+ def initialize(record_class, data)
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, type.singularize.classify)
10
+ h[type] = records.map{|datum| klass.new(datum)}.index_by(&:id)
11
+ h
12
+ end
13
+ end
14
+
15
+ def data_for(method_name, definition)
16
+ linkage = definition["linkage"]
17
+ if linkage.is_a?(Array)
18
+ # has_many link
19
+ linkage.map do |link_def|
20
+ record_for(link_def)
21
+ end
22
+ else
23
+ # has_one link
24
+ record_for(linkage)
25
+ end
26
+ end
27
+
28
+ def has_link?(name)
29
+ data.has_key?(name)
30
+ end
31
+
32
+ private
33
+
34
+ # should return a resource record of some type for this linked document
35
+ def record_for(link_def)
36
+ data[link_def["type"]][link_def["id"]]
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,29 @@
1
+ module JsonApiClient
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] = case value
18
+ when JsonApiClient::Resource
19
+ {linkage: value.as_link}
20
+ when Array
21
+ {linkage: value.map(&:as_link)}
22
+ else
23
+ value
24
+ end
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,30 @@
1
+ module JsonApiClient
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 fetch_link(link_name)
25
+ link_definition = links.fetch(link_name.to_s)
26
+ record_class.requestor.linked(link_definition)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,10 @@
1
+ module JsonApiClient
2
+ class MetaData
3
+ include Helpers::DynamicAttributes
4
+
5
+ def initialize(data)
6
+ self.attributes = data
7
+ end
8
+
9
+ end
10
+ end
@@ -2,11 +2,10 @@ module JsonApiClient
2
2
  module Middleware
3
3
  class JsonRequest < Faraday::Middleware
4
4
  def call(environment)
5
- environment[:request_headers]["Accept"] = "application/json,*/*"
6
- uri = environment[:url]
7
- uri.path = uri.path + ".json" unless uri.path.match(/\.json$/)
5
+ environment[:request_headers]["Content-Type"] = 'application/vnd.api+json'
6
+ environment[:request_headers]["Accept"] = 'application/vnd.api+json'
8
7
  @app.call(environment)
9
8
  end
10
9
  end
11
10
  end
12
- end
11
+ end
@@ -11,16 +11,25 @@ module JsonApiClient
11
11
  handle_status(code, env)
12
12
  end
13
13
  end
14
+ rescue Faraday::ConnectionFailed, Faraday::TimeoutError
15
+ raise Errors::ConnectionError, environment
14
16
  end
15
17
 
16
18
  protected
17
19
 
18
20
  def handle_status(code, env)
19
21
  case code
22
+ when 200..399
23
+ when 403
24
+ raise Errors::AccessDenied, env
20
25
  when 404
21
26
  raise Errors::NotFound, env[:url]
27
+ when 400..499
28
+ # some other error
22
29
  when 500..599
23
- raise Errors::ServerError, env[:url]
30
+ raise Errors::ServerError, env
31
+ else
32
+ raise Errors::UnexpectedStatus.new(code, env[:url])
24
33
  end
25
34
  end
26
35
  end
@@ -0,0 +1,5 @@
1
+ module JsonApiClient
2
+ module Paginating
3
+ autoload :Paginator, 'json_api_client/paginating/paginator'
4
+ end
5
+ end
@@ -0,0 +1,80 @@
1
+ module JsonApiClient
2
+ module Paginating
3
+ class Paginator
4
+ attr_reader :params, :result_set, :links
5
+ def initialize(result_set, links)
6
+ @params = params_for_uri(result_set.uri)
7
+ @result_set = result_set
8
+ @links = links
9
+ end
10
+
11
+ def next
12
+ result_set.links.fetch_link("next")
13
+ end
14
+
15
+ def prev
16
+ result_set.links.fetch_link("next")
17
+ end
18
+
19
+ def first
20
+ result_set.links.fetch_link("first")
21
+ end
22
+
23
+ def last
24
+ result_set.links.fetch_link("last")
25
+ end
26
+
27
+ def total_pages
28
+ if links["last"]
29
+ last_params = params_for_uri(links["last"])
30
+ last_params.fetch("page") do
31
+ current_page
32
+ end.to_i
33
+ else
34
+ current_page
35
+ end
36
+ end
37
+
38
+ # this number may be off
39
+ def total_entries
40
+ per_page * total_pages
41
+ end
42
+
43
+ def offset
44
+ per_page * (current_page - 1)
45
+ end
46
+
47
+ def per_page
48
+ params.fetch("per_page") do
49
+ result_set.length
50
+ end.to_i
51
+ end
52
+
53
+ def current_page
54
+ params.fetch("page", 1).to_i
55
+ end
56
+
57
+ def out_of_bounds?
58
+ current_page > total_pages
59
+ end
60
+
61
+ def previous_page
62
+ current_page > 1 ? (current_page - 1) : nil
63
+ end
64
+
65
+ def next_page
66
+ current_page < total_pages ? (current_page + 1) : nil
67
+ end
68
+
69
+ alias limit_value per_page
70
+
71
+ protected
72
+
73
+ def params_for_uri(uri)
74
+ return {} unless uri
75
+ uri = Addressable::URI.parse(uri)
76
+ uri.query_values || {}
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,5 @@
1
+ module JsonApiClient
2
+ module Parsers
3
+ autoload :Parser, 'json_api_client/parsers/parser'
4
+ end
5
+ end
@@ -0,0 +1,55 @@
1
+ module JsonApiClient
2
+ module Parsers
3
+ class Parser
4
+ class << self
5
+ def parse(klass, response)
6
+ data = response.body
7
+ ResultSet.new.tap do |result_set|
8
+ result_set.record_class = klass
9
+ result_set.uri = response.env[:url]
10
+ handle_data(result_set, data)
11
+ handle_errors(result_set, data)
12
+ handle_meta(result_set, data)
13
+ handle_links(result_set, data)
14
+ handle_pagination(result_set, data)
15
+ handle_included(result_set, data)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def handle_data(result_set, data)
22
+ # all data lives under the "data" attribute
23
+ results = data.fetch("data", [])
24
+
25
+ # we will treat everything as an Array
26
+ results = [results] unless results.is_a?(Array)
27
+ result_set.concat(results.map{|res| result_set.record_class.load(res)})
28
+ end
29
+
30
+ def handle_errors(result_set, data)
31
+ result_set.errors = ErrorCollector.new(data.fetch("errors", []))
32
+ end
33
+
34
+ def handle_meta(result_set, data)
35
+ result_set.meta = MetaData.new(data.fetch("meta", {}))
36
+ end
37
+
38
+ def handle_links(result_set, data)
39
+ result_set.links = Linking::TopLevelLinks.new(result_set.record_class, data.fetch("links", {}))
40
+ end
41
+
42
+ def handle_pagination(result_set, data)
43
+ result_set.pages = result_set.record_class.paginator.new(result_set, data.fetch("links", {}))
44
+ end
45
+
46
+ def handle_included(result_set, data)
47
+ included = Linking::IncludedData.new(result_set.record_class, data.fetch("included", []))
48
+ result_set.each do |res|
49
+ res.linked_data = included
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -1,11 +1,6 @@
1
1
  module JsonApiClient
2
2
  module Query
3
- autoload :Base, 'json_api_client/query/base'
4
- autoload :Create, 'json_api_client/query/create'
5
- autoload :Custom, 'json_api_client/query/custom'
6
- autoload :Destroy, 'json_api_client/query/destroy'
7
- autoload :Find, 'json_api_client/query/find'
8
- autoload :Update, 'json_api_client/query/update'
9
- autoload :Linked, 'json_api_client/query/linked'
3
+ autoload :Builder, 'json_api_client/query/builder'
4
+ autoload :Requestor, 'json_api_client/query/requestor'
10
5
  end
11
6
  end
@@ -0,0 +1,126 @@
1
+ module JsonApiClient
2
+ module Query
3
+ class Builder
4
+
5
+ attr_reader :klass
6
+
7
+ def initialize(klass)
8
+ @klass = klass
9
+ @pagination_params = {}
10
+ @filters = {}
11
+ @includes = []
12
+ @orders = []
13
+ @fields = []
14
+ end
15
+
16
+ def where(conditions = {})
17
+ @filters.merge!(conditions)
18
+ self
19
+ end
20
+
21
+ def order(*args)
22
+ @orders += parse_orders(*args)
23
+ self
24
+ end
25
+
26
+ def includes(*tables)
27
+ @includes += parse_related_links(*tables)
28
+ self
29
+ end
30
+
31
+ def select(fields)
32
+ @fields += fields.split(",").map(&:strip)
33
+ self
34
+ end
35
+
36
+ def paginate(conditions = {})
37
+ @pagination_params.merge!(conditions.slice(:page, :per_page))
38
+ self
39
+ end
40
+
41
+ def page(number)
42
+ @pagination_params[:page] = number
43
+ self
44
+ end
45
+
46
+ def first
47
+ paginate(page: 1, per_page: 1).to_a.first
48
+ end
49
+
50
+ def build
51
+ klass.new(params)
52
+ end
53
+
54
+ def params
55
+ filter_params
56
+ .merge(pagination_params)
57
+ .merge(includes_params)
58
+ .merge(order_params)
59
+ .merge(select_params)
60
+ end
61
+
62
+ def to_a
63
+ @to_a ||= klass.find(params)
64
+ end
65
+ alias all to_a
66
+
67
+ def method_missing(method_name, *args, &block)
68
+ to_a.send(method_name, *args, &block)
69
+ end
70
+
71
+ private
72
+
73
+ attr_reader :pagination_params
74
+
75
+ def includes_params
76
+ @includes.empty? ? {} : {include: @includes.join(",")}
77
+ end
78
+
79
+ def filter_params
80
+ @filters.empty? ? {} : {filter: @filters}
81
+ end
82
+
83
+ def order_params
84
+ @orders.empty? ? {} : {sort: @orders.join(",")}
85
+ end
86
+
87
+ def select_params
88
+ @fields.empty? ? {} : {fields: {klass.table_name => @fields.join(",")}}
89
+ end
90
+
91
+ def parse_related_links(*tables)
92
+ tables.map do |table|
93
+ case table
94
+ when Hash
95
+ table.map do |k, v|
96
+ parse_related_links(*v).map do |sub|
97
+ "#{k}.#{sub}"
98
+ end
99
+ end
100
+ when Array
101
+ table.map do |v|
102
+ parse_related_links(*v)
103
+ end
104
+ else
105
+ table
106
+ end
107
+ end.flatten
108
+ end
109
+
110
+ def parse_orders(*args)
111
+ args.map do |arg|
112
+ case arg
113
+ when Hash
114
+ arg.map do |k, v|
115
+ operator = (v == :desc ? "-" : "+")
116
+ "#{operator}#{k}"
117
+ end
118
+ else
119
+ "+#{arg}"
120
+ end
121
+ end.flatten
122
+ end
123
+
124
+ end
125
+ end
126
+ end