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