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.
- checksums.yaml +4 -4
- data/README.md +274 -84
- data/lib/json_api_client.rb +9 -4
- data/lib/json_api_client/connection.rb +5 -5
- data/lib/json_api_client/error_collector.rb +29 -0
- data/lib/json_api_client/errors.rb +7 -5
- data/lib/json_api_client/helpers.rb +3 -0
- data/lib/json_api_client/helpers/associable.rb +4 -3
- data/lib/json_api_client/helpers/attributable.rb +15 -46
- data/lib/json_api_client/helpers/custom_endpoints.rb +3 -10
- data/lib/json_api_client/helpers/dynamic_attributes.rb +61 -0
- data/lib/json_api_client/helpers/linkable.rb +28 -18
- data/lib/json_api_client/helpers/paginatable.rb +13 -0
- data/lib/json_api_client/helpers/parsable.rb +1 -1
- data/lib/json_api_client/helpers/queryable.rb +4 -3
- data/lib/json_api_client/helpers/requestable.rb +60 -0
- data/lib/json_api_client/linking.rb +7 -0
- data/lib/json_api_client/linking/included_data.rb +40 -0
- data/lib/json_api_client/linking/links.rb +29 -0
- data/lib/json_api_client/linking/top_level_links.rb +30 -0
- data/lib/json_api_client/meta_data.rb +10 -0
- data/lib/json_api_client/middleware/json_request.rb +3 -4
- data/lib/json_api_client/middleware/status.rb +10 -1
- data/lib/json_api_client/paginating.rb +5 -0
- data/lib/json_api_client/paginating/paginator.rb +80 -0
- data/lib/json_api_client/parsers.rb +5 -0
- data/lib/json_api_client/parsers/parser.rb +55 -0
- data/lib/json_api_client/query.rb +2 -7
- data/lib/json_api_client/query/builder.rb +126 -0
- data/lib/json_api_client/query/requestor.rb +77 -0
- data/lib/json_api_client/resource.rb +3 -59
- data/lib/json_api_client/result_set.rb +11 -29
- data/lib/json_api_client/schema.rb +15 -30
- data/lib/json_api_client/version.rb +1 -1
- metadata +36 -19
- data/lib/json_api_client/link.rb +0 -11
- data/lib/json_api_client/link_definition.rb +0 -27
- data/lib/json_api_client/linked_data.rb +0 -75
- data/lib/json_api_client/parser.rb +0 -63
- data/lib/json_api_client/query/base.rb +0 -38
- data/lib/json_api_client/query/create.rb +0 -17
- data/lib/json_api_client/query/custom.rb +0 -22
- data/lib/json_api_client/query/destroy.rb +0 -12
- data/lib/json_api_client/query/find.rb +0 -19
- data/lib/json_api_client/query/linked.rb +0 -24
- data/lib/json_api_client/query/update.rb +0 -13
- data/lib/json_api_client/scope.rb +0 -48
@@ -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
|
@@ -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]["
|
6
|
-
|
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
|
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,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,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 :
|
4
|
-
autoload :
|
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
|