json_api_client 0.9.6 → 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
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