json_api_client 1.1.1 → 1.2.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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/lib/json_api_client/errors.rb +7 -1
- data/lib/json_api_client/formatter.rb +145 -0
- data/lib/json_api_client/helpers/dynamic_attributes.rb +11 -1
- data/lib/json_api_client/included_data.rb +1 -1
- data/lib/json_api_client/meta_data.rb +11 -2
- data/lib/json_api_client/middleware/status.rb +3 -1
- data/lib/json_api_client/paginating/paginator.rb +1 -1
- data/lib/json_api_client/parsers/parser.rb +2 -2
- data/lib/json_api_client/query/builder.rb +8 -3
- data/lib/json_api_client/resource.rb +43 -8
- data/lib/json_api_client/version.rb +1 -1
- data/lib/json_api_client.rb +2 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 48365ff770cd7617bd8d4ca4bdc47815b1f16096
|
4
|
+
data.tar.gz: 1691aa8373f863f00f9cf8020c810424aebd0fb2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 72606bea939fb0c0770fd85120dcd7fa09762b44abcb8fcb53f95c2f0e60420b7c3acebfc55e3fec6e586585f7dad97df62a5bc6fab50961205f35085a7461c0
|
7
|
+
data.tar.gz: f08837628c8717af1b6040f296d6609843b708d274da8d2197464445278eca13066f28d30a5e68da880b69080444f7b3f4be59b98dbf1f13ffb8a6f9081a9014
|
data/README.md
CHANGED
@@ -27,7 +27,7 @@ module MyApi
|
|
27
27
|
end
|
28
28
|
```
|
29
29
|
|
30
|
-
By convention, we
|
30
|
+
By convention, we guess the resource route from the class name. In the above example, `Article`'s path is "http://example.com/articles" and `Person`'s path would be "http://example.com/people".
|
31
31
|
|
32
32
|
Some basic example usage:
|
33
33
|
|
@@ -204,7 +204,7 @@ authors = results.map(&:author)
|
|
204
204
|
|
205
205
|
```ruby
|
206
206
|
# makes request to /articles?fields[articles]=title,body
|
207
|
-
article = Article.select("title,body").first
|
207
|
+
article = Article.select("title", "body").first
|
208
208
|
|
209
209
|
# should have fetched the requested fields
|
210
210
|
article.title
|
@@ -25,6 +25,12 @@ module JsonApiClient
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
+
class Conflict < ServerError
|
29
|
+
def message
|
30
|
+
"Resource already exists"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
28
34
|
class NotFound < ServerError
|
29
35
|
attr_reader :uri
|
30
36
|
def initialize(uri)
|
@@ -47,4 +53,4 @@ module JsonApiClient
|
|
47
53
|
end
|
48
54
|
|
49
55
|
end
|
50
|
-
end
|
56
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# Taken form jsonapi_resources formatter
|
2
|
+
|
3
|
+
require 'active_support/inflector'
|
4
|
+
|
5
|
+
module JsonApiClient
|
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 = "JsonApiClient::#{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
|
@@ -38,7 +38,13 @@ module JsonApiClient
|
|
38
38
|
protected
|
39
39
|
|
40
40
|
def method_missing(method, *args, &block)
|
41
|
-
|
41
|
+
normalized_method = if key_formatter
|
42
|
+
key_formatter.unformat(method.to_s)
|
43
|
+
else
|
44
|
+
method.to_s
|
45
|
+
end
|
46
|
+
|
47
|
+
if normalized_method =~ /^(.*)=$/
|
42
48
|
set_attribute($1, args.first)
|
43
49
|
elsif has_attribute?(method)
|
44
50
|
attributes[method]
|
@@ -55,6 +61,10 @@ module JsonApiClient
|
|
55
61
|
attributes[name] = value
|
56
62
|
end
|
57
63
|
|
64
|
+
def key_formatter
|
65
|
+
self.class.respond_to?(:key_formatter) && self.class.key_formatter
|
66
|
+
end
|
67
|
+
|
58
68
|
end
|
59
69
|
end
|
60
70
|
end
|
@@ -6,7 +6,7 @@ module JsonApiClient
|
|
6
6
|
record_class = result_set.record_class
|
7
7
|
grouped_data = data.group_by{|datum| datum["type"]}
|
8
8
|
@data = grouped_data.inject({}) do |h, (type, records)|
|
9
|
-
klass = Utils.compute_type(record_class, type.singularize.classify)
|
9
|
+
klass = Utils.compute_type(record_class, record_class.key_formatter.unformat(type).singularize.classify)
|
10
10
|
h[type] = records.map do |datum|
|
11
11
|
params = klass.parser.parameters_from_resource(datum)
|
12
12
|
resource = klass.load(params)
|
@@ -2,9 +2,18 @@ module JsonApiClient
|
|
2
2
|
class MetaData
|
3
3
|
include Helpers::DynamicAttributes
|
4
4
|
|
5
|
-
|
5
|
+
attr_accessor :record_class
|
6
|
+
|
7
|
+
def initialize(data, record_class = nil)
|
8
|
+
self.record_class = record_class
|
6
9
|
self.attributes = data
|
7
10
|
end
|
8
11
|
|
12
|
+
protected
|
13
|
+
|
14
|
+
def key_formatter
|
15
|
+
record_class && record_class.key_formatter
|
16
|
+
end
|
17
|
+
|
9
18
|
end
|
10
|
-
end
|
19
|
+
end
|
@@ -26,6 +26,8 @@ module JsonApiClient
|
|
26
26
|
raise Errors::AccessDenied, env
|
27
27
|
when 404
|
28
28
|
raise Errors::NotFound, env[:url]
|
29
|
+
when 409
|
30
|
+
raise Errors::Conflict, env
|
29
31
|
when 400..499
|
30
32
|
# some other error
|
31
33
|
when 500..599
|
@@ -36,4 +38,4 @@ module JsonApiClient
|
|
36
38
|
end
|
37
39
|
end
|
38
40
|
end
|
39
|
-
end
|
41
|
+
end
|
@@ -65,7 +65,7 @@ module JsonApiClient
|
|
65
65
|
|
66
66
|
# we will treat everything as an Array
|
67
67
|
results = [results] unless results.is_a?(Array)
|
68
|
-
resources = results.map do |res|
|
68
|
+
resources = results.compact.map do |res|
|
69
69
|
resource = result_set.record_class.load(parameters_from_resource(res))
|
70
70
|
resource.last_result_set = result_set
|
71
71
|
resource
|
@@ -78,7 +78,7 @@ module JsonApiClient
|
|
78
78
|
end
|
79
79
|
|
80
80
|
def handle_meta(result_set, data)
|
81
|
-
result_set.meta = MetaData.new(data.fetch("meta", {}))
|
81
|
+
result_set.meta = MetaData.new(data.fetch("meta", {}), result_set.record_class)
|
82
82
|
end
|
83
83
|
|
84
84
|
def handle_links(result_set, data)
|
@@ -3,6 +3,7 @@ module JsonApiClient
|
|
3
3
|
class Builder
|
4
4
|
|
5
5
|
attr_reader :klass, :path_params
|
6
|
+
delegate :key_formatter, to: :klass
|
6
7
|
|
7
8
|
def initialize(klass)
|
8
9
|
@klass = klass
|
@@ -33,8 +34,12 @@ module JsonApiClient
|
|
33
34
|
self
|
34
35
|
end
|
35
36
|
|
36
|
-
def select(fields)
|
37
|
-
|
37
|
+
def select(*fields)
|
38
|
+
fields = Array(fields).flatten
|
39
|
+
fields = fields.map { |i| i.to_s.split(",") }.flatten
|
40
|
+
|
41
|
+
@fields += fields.map(&:strip)
|
42
|
+
|
38
43
|
self
|
39
44
|
end
|
40
45
|
|
@@ -151,7 +156,7 @@ module JsonApiClient
|
|
151
156
|
parse_related_links(*v)
|
152
157
|
end
|
153
158
|
else
|
154
|
-
table
|
159
|
+
key_formatter.format(table)
|
155
160
|
end
|
156
161
|
end.flatten
|
157
162
|
end
|
@@ -29,6 +29,8 @@ module JsonApiClient
|
|
29
29
|
:read_only_attributes,
|
30
30
|
:requestor_class,
|
31
31
|
:associations,
|
32
|
+
:json_key_format,
|
33
|
+
:route_format,
|
32
34
|
instance_accessor: false
|
33
35
|
self.primary_key = :id
|
34
36
|
self.parser = Parsers::Parser
|
@@ -42,6 +44,12 @@ module JsonApiClient
|
|
42
44
|
self.requestor_class = Query::Requestor
|
43
45
|
self.associations = []
|
44
46
|
|
47
|
+
#:underscored_key, :camelized_key, :dasherized_key, or custom
|
48
|
+
self.json_key_format = :underscored_key
|
49
|
+
|
50
|
+
#:underscored_route, :camelized_route, :dasherized_route, or custom
|
51
|
+
self.route_format = :underscored_route
|
52
|
+
|
45
53
|
include Associations::BelongsTo
|
46
54
|
include Associations::HasMany
|
47
55
|
include Associations::HasOne
|
@@ -54,7 +62,7 @@ module JsonApiClient
|
|
54
62
|
#
|
55
63
|
# @return [String] The table name for this resource
|
56
64
|
def table_name
|
57
|
-
resource_name.pluralize
|
65
|
+
route_formatter.format(resource_name.pluralize)
|
58
66
|
end
|
59
67
|
|
60
68
|
# The name of a single resource. i.e. Article -> article, Person -> person
|
@@ -64,6 +72,22 @@ module JsonApiClient
|
|
64
72
|
name.demodulize.underscore
|
65
73
|
end
|
66
74
|
|
75
|
+
# Specifies the JSON API resource type. By default this is inferred
|
76
|
+
# from the resource class name.
|
77
|
+
#
|
78
|
+
# @return [String] Resource path
|
79
|
+
def type
|
80
|
+
table_name
|
81
|
+
end
|
82
|
+
|
83
|
+
# Specifies the relative path that should be used for this resource;
|
84
|
+
# by default, this is inferred from the resource class name.
|
85
|
+
#
|
86
|
+
# @return [String] Resource path
|
87
|
+
def resource_path
|
88
|
+
table_name
|
89
|
+
end
|
90
|
+
|
67
91
|
# Load a resource object from attributes and consider it persisted
|
68
92
|
#
|
69
93
|
# @return [Resource] Persisted resource object
|
@@ -92,14 +116,14 @@ module JsonApiClient
|
|
92
116
|
|
93
117
|
# Return the path or path pattern for this resource
|
94
118
|
def path(params = nil)
|
95
|
-
parts = [
|
96
|
-
if params
|
119
|
+
parts = [resource_path]
|
120
|
+
if params && _prefix_path.present?
|
97
121
|
path_params = params.delete(:path) || params
|
98
122
|
parts.unshift(_prefix_path % path_params.symbolize_keys)
|
99
123
|
else
|
100
124
|
parts.unshift(_prefix_path)
|
101
125
|
end
|
102
|
-
parts.reject!
|
126
|
+
parts.reject!(&:blank?)
|
103
127
|
File.join(*parts)
|
104
128
|
rescue KeyError
|
105
129
|
raise ArgumentError, "Not all prefix parameters specified"
|
@@ -147,7 +171,7 @@ module JsonApiClient
|
|
147
171
|
#
|
148
172
|
# @return [Hash] Default attributes
|
149
173
|
def default_attributes
|
150
|
-
{type:
|
174
|
+
{type: type}
|
151
175
|
end
|
152
176
|
|
153
177
|
# Returns the schema for this resource class
|
@@ -157,6 +181,14 @@ module JsonApiClient
|
|
157
181
|
@schema ||= Schema.new
|
158
182
|
end
|
159
183
|
|
184
|
+
def key_formatter
|
185
|
+
JsonApiClient::Formatter.formatter_for(json_key_format)
|
186
|
+
end
|
187
|
+
|
188
|
+
def route_formatter
|
189
|
+
JsonApiClient::Formatter.formatter_for(route_format)
|
190
|
+
end
|
191
|
+
|
160
192
|
protected
|
161
193
|
|
162
194
|
# Declares a new class/instance method that acts on the collection/member
|
@@ -258,6 +290,7 @@ module JsonApiClient
|
|
258
290
|
#
|
259
291
|
# @param params [Hash] Attributes, links, and relationships
|
260
292
|
def initialize(params = {})
|
293
|
+
@persisted = nil
|
261
294
|
self.links = self.class.linker.new(params.delete("links") || {})
|
262
295
|
self.relationships = self.class.relationship_linker.new(params.delete("relationships") || {})
|
263
296
|
self.class.associations.each do |association|
|
@@ -321,9 +354,9 @@ module JsonApiClient
|
|
321
354
|
def as_json_api(*)
|
322
355
|
attributes.slice(:id, :type).tap do |h|
|
323
356
|
relationships_for_serialization.tap do |r|
|
324
|
-
h[:relationships] = r unless r.empty?
|
357
|
+
h[:relationships] = self.class.key_formatter.format_keys(r) unless r.empty?
|
325
358
|
end
|
326
|
-
h[:attributes] = attributes_for_serialization
|
359
|
+
h[:attributes] = self.class.key_formatter.format_keys(attributes_for_serialization)
|
327
360
|
end
|
328
361
|
end
|
329
362
|
|
@@ -444,7 +477,9 @@ module JsonApiClient
|
|
444
477
|
end
|
445
478
|
|
446
479
|
def association_for(name)
|
447
|
-
self.class.associations.detect
|
480
|
+
self.class.associations.detect do |association|
|
481
|
+
association.attr_name.to_s == self.class.key_formatter.unformat(name)
|
482
|
+
end
|
448
483
|
end
|
449
484
|
|
450
485
|
def attributes_for_serialization
|
data/lib/json_api_client.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'faraday'
|
2
2
|
require 'faraday_middleware'
|
3
3
|
require 'json'
|
4
|
-
require
|
4
|
+
require 'addressable/uri'
|
5
5
|
|
6
6
|
module JsonApiClient
|
7
7
|
autoload :Associations, 'json_api_client/associations'
|
@@ -9,6 +9,7 @@ module JsonApiClient
|
|
9
9
|
autoload :Connection, 'json_api_client/connection'
|
10
10
|
autoload :Errors, 'json_api_client/errors'
|
11
11
|
autoload :ErrorCollector, 'json_api_client/error_collector'
|
12
|
+
autoload :Formatter, 'json_api_client/formatter'
|
12
13
|
autoload :Helpers, 'json_api_client/helpers'
|
13
14
|
autoload :Implementation, 'json_api_client/implementation'
|
14
15
|
autoload :IncludedData, 'json_api_client/included_data'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: json_api_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeff Ching
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-07-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -126,6 +126,7 @@ files:
|
|
126
126
|
- lib/json_api_client/connection.rb
|
127
127
|
- lib/json_api_client/error_collector.rb
|
128
128
|
- lib/json_api_client/errors.rb
|
129
|
+
- lib/json_api_client/formatter.rb
|
129
130
|
- lib/json_api_client/helpers.rb
|
130
131
|
- lib/json_api_client/helpers/callbacks.rb
|
131
132
|
- lib/json_api_client/helpers/dirty.rb
|
@@ -175,9 +176,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
175
176
|
version: '0'
|
176
177
|
requirements: []
|
177
178
|
rubyforge_project:
|
178
|
-
rubygems_version: 2.4.
|
179
|
+
rubygems_version: 2.4.5
|
179
180
|
signing_key:
|
180
181
|
specification_version: 4
|
181
182
|
summary: Build client libraries compliant with specification defined by jsonapi.org
|
182
183
|
test_files: []
|
183
|
-
has_rdoc:
|