Skip to content

Commit b4e5804

Browse files
authored
Merge pull request #285 from senid231/query-params-on-create-and-update-resource
allow to send fields and/or includes on create/update resource
2 parents 2f5db2c + 8dede9e commit b4e5804

File tree

10 files changed

+431
-30
lines changed

10 files changed

+431
-30
lines changed

README.md

+35
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,23 @@ results = Article.includes(:author, :comments => :author).find(1)
196196

197197
# should not have to make additional requests to the server
198198
authors = results.map(&:author)
199+
200+
# makes POST request to /articles?include=author,comments.author
201+
article = Article.new(title: 'New one').request_includes(:author, :comments => :author)
202+
article.save
203+
204+
# makes PATCH request to /articles/1?include=author,comments.author
205+
article = Article.find(1)
206+
article.title = 'Changed'
207+
article.request_includes(:author, :comments => :author)
208+
article.save
209+
210+
# request includes will be cleared if response is successful
211+
# to avoid this `keep_request_params` class attribute can be used
212+
Article.keep_request_params = true
213+
214+
# to clear request_includes use
215+
article.reset_request_includes!
199216
```
200217

201218
## Sparse Fieldsets
@@ -217,6 +234,24 @@ article.created_at
217234
# or you can use fieldsets from multiple resources
218235
# makes request to /articles?fields[articles]=title,body&fields[comments]=tag
219236
article = Article.select("title", "body",{comments: 'tag'}).first
237+
238+
# makes POST request to /articles?fields[articles]=title,body&fields[comments]=tag
239+
article = Article.new(title: 'New one').request_select(:title, :body, comments: 'tag')
240+
article.save
241+
242+
# makes PATCH request to /articles/1?fields[articles]=title,body&fields[comments]=tag
243+
article = Article.find(1)
244+
article.title = 'Changed'
245+
article.request_select(:title, :body, comments: 'tag')
246+
article.save
247+
248+
# request fields will be cleared if response is successful
249+
# to avoid this `keep_request_params` class attribute can be used
250+
Article.keep_request_params = true
251+
252+
# to clear request fields use
253+
article.reset_request_select!(:comments) # to clear for comments
254+
article.reset_request_select! # to clear for all fields
220255
```
221256

222257
## Sorting

lib/json_api_client.rb

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ module JsonApiClient
2121
autoload :Paginating, 'json_api_client/paginating'
2222
autoload :Parsers, 'json_api_client/parsers'
2323
autoload :Query, 'json_api_client/query'
24+
autoload :RequestParams, 'json_api_client/request_params'
2425
autoload :Resource, 'json_api_client/resource'
2526
autoload :ResultSet, 'json_api_client/result_set'
2627
autoload :Schema, 'json_api_client/schema'

lib/json_api_client/connection.rb

+4-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ def delete(middleware)
2828
faraday.builder.delete(middleware)
2929
end
3030

31-
def run(request_method, path, params = {}, headers = {})
32-
faraday.send(request_method, path, params, headers)
31+
def run(request_method, path, params: nil, headers: {}, body: nil)
32+
faraday.run_request(request_method, path, body, headers) do |request|
33+
request.params.update(params) if params
34+
end
3335
end
3436

3537
end

lib/json_api_client/query/builder.rb

+1-16
Original file line numberDiff line numberDiff line change
@@ -159,22 +159,7 @@ def select_params
159159
end
160160

161161
def parse_related_links(*tables)
162-
tables.map do |table|
163-
case table
164-
when Hash
165-
table.map do |k, v|
166-
parse_related_links(*v).map do |sub|
167-
"#{k}.#{sub}"
168-
end
169-
end
170-
when Array
171-
table.map do |v|
172-
parse_related_links(*v)
173-
end
174-
else
175-
key_formatter.format(table)
176-
end
177-
end.flatten
162+
Utils.parse_includes(klass, *tables)
178163
end
179164

180165
def parse_orders(*args)

lib/json_api_client/query/requestor.rb

+13-9
Original file line numberDiff line numberDiff line change
@@ -11,36 +11,39 @@ def initialize(klass)
1111
# expects a record
1212
def create(record)
1313
request(:post, klass.path(record.attributes), {
14-
data: record.as_json_api
14+
body: { data: record.as_json_api },
15+
params: record.request_params.to_params
1516
})
1617
end
1718

1819
def update(record)
1920
request(:patch, resource_path(record.attributes), {
20-
data: record.as_json_api
21+
body: { data: record.as_json_api },
22+
params: record.request_params.to_params
2123
})
2224
end
2325

2426
def get(params = {})
2527
path = resource_path(params)
2628
params.delete(klass.primary_key)
27-
request(:get, path, params)
29+
request(:get, path, params: params)
2830
end
2931

3032
def destroy(record)
31-
request(:delete, resource_path(record.attributes), {})
33+
request(:delete, resource_path(record.attributes))
3234
end
3335

3436
def linked(path)
35-
request(:get, path, {})
37+
request(:get, path)
3638
end
3739

3840
def custom(method_name, options, params)
3941
path = resource_path(params)
4042
params.delete(klass.primary_key)
4143
path = File.join(path, method_name.to_s)
42-
43-
request(options.fetch(:request_method, :get), path, params)
44+
request_method = options.fetch(:request_method, :get).to_sym
45+
query_params, body_params = [:get, :delete].include?(request_method) ? [params, nil] : [nil, params]
46+
request(request_method, path, params: query_params, body: body_params)
4447
end
4548

4649
protected
@@ -56,8 +59,9 @@ def resource_path(parameters)
5659
end
5760
end
5861

59-
def request(type, path, params)
60-
klass.parser.parse(klass, connection.run(type, path, params, klass.custom_headers))
62+
def request(type, path, params: nil, body: nil)
63+
response = connection.run(type, path, params: params, body: body, headers: klass.custom_headers)
64+
klass.parser.parse(klass, response)
6165
end
6266

6367
end

lib/json_api_client/request_params.rb

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
module JsonApiClient
2+
class RequestParams
3+
attr_reader :klass, :includes, :fields
4+
5+
def initialize(klass, includes: [], fields: {})
6+
@klass = klass
7+
@includes = includes
8+
@fields = fields
9+
end
10+
11+
def add_includes(includes)
12+
Utils.parse_includes(klass, *includes).each do |name|
13+
name = name.to_sym
14+
self.includes.push(name) unless self.includes.include?(name)
15+
end
16+
end
17+
18+
def reset_includes!
19+
@includes = []
20+
end
21+
22+
def set_fields(type, field_names)
23+
self.fields[type.to_sym] = field_names.map(&:to_sym)
24+
end
25+
26+
def remove_fields(type)
27+
self.fields.delete(type.to_sym)
28+
end
29+
30+
def field_types
31+
self.fields.keys
32+
end
33+
34+
def clear
35+
reset_includes!
36+
@fields = {}
37+
end
38+
39+
def to_params
40+
return nil if field_types.empty? && includes.empty?
41+
parsed_fields.merge(parsed_includes)
42+
end
43+
44+
private
45+
46+
def parsed_includes
47+
return {} if includes.empty?
48+
{include: includes.join(",")}
49+
end
50+
51+
def parsed_fields
52+
return {} if field_types.empty?
53+
{fields: fields.map { |type, names| [type, names.join(",")] }.to_h}
54+
end
55+
56+
end
57+
end

lib/json_api_client/resource.rb

+34-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ class Resource
1515

1616
attr_accessor :last_result_set,
1717
:links,
18-
:relationships
18+
:relationships,
19+
:request_params
1920
class_attribute :site,
2021
:primary_key,
2122
:parser,
@@ -31,6 +32,8 @@ class Resource
3132
:associations,
3233
:json_key_format,
3334
:route_format,
35+
:request_params_class,
36+
:keep_request_params,
3437
instance_accessor: false
3538
self.primary_key = :id
3639
self.parser = Parsers::Parser
@@ -43,6 +46,8 @@ class Resource
4346
self.read_only_attributes = [:id, :type, :links, :meta, :relationships]
4447
self.requestor_class = Query::Requestor
4548
self.associations = []
49+
self.request_params_class = RequestParams
50+
self.keep_request_params = false
4651

4752
#:underscored_key, :camelized_key, :dasherized_key, or custom
4853
self.json_key_format = :underscored_key
@@ -318,6 +323,7 @@ def initialize(params = {})
318323
set_attribute(association.attr_name, params[association.attr_name.to_s])
319324
end
320325
end
326+
self.request_params = self.class.request_params_class.new(self.class)
321327
end
322328

323329
# Set the current attributes and try to save them
@@ -416,12 +422,14 @@ def save
416422
false
417423
else
418424
self.errors.clear if self.errors
425+
self.request_params.clear unless self.class.keep_request_params
419426
mark_as_persisted!
420427
if updated = last_result_set.first
421428
self.attributes = updated.attributes
422429
self.links.attributes = updated.links.attributes
423430
self.relationships.attributes = updated.relationships.attributes
424431
clear_changes_information
432+
self.relationships.clear_changes_information
425433
end
426434
true
427435
end
@@ -445,6 +453,31 @@ def inspect
445453
"#<#{self.class.name}:@attributes=#{attributes.inspect}>"
446454
end
447455

456+
def request_includes(*includes)
457+
self.request_params.add_includes(includes)
458+
self
459+
end
460+
461+
def reset_request_includes!
462+
self.request_params.reset_includes!
463+
self
464+
end
465+
466+
def request_select(*fields)
467+
fields_by_type = fields.extract_options!
468+
fields_by_type[type.to_sym] = fields if fields.any?
469+
fields_by_type.each do |field_type, field_names|
470+
self.request_params.set_fields(field_type, field_names)
471+
end
472+
self
473+
end
474+
475+
def reset_request_select!(*resource_types)
476+
resource_types = self.request_params.field_types if resource_types.empty?
477+
resource_types.each { |resource_type| self.request_params.remove_fields(resource_type) }
478+
self
479+
end
480+
448481
protected
449482

450483
def method_missing(method, *args)

lib/json_api_client/utils.rb

+20-1
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,24 @@ def self.compute_type(klass, type_name)
2424
raise NameError, "uninitialized constant #{candidates.first}"
2525
end
2626

27+
def self.parse_includes(klass, *tables)
28+
tables.map do |table|
29+
case table
30+
when Hash
31+
table.map do |k, v|
32+
parse_includes(klass, *v).map do |sub|
33+
"#{k}.#{sub}"
34+
end
35+
end
36+
when Array
37+
table.map do |v|
38+
parse_includes(klass, *v)
39+
end
40+
else
41+
klass.key_formatter.format(table)
42+
end
43+
end.flatten
44+
end
45+
2746
end
28-
end
47+
end

test/unit/creation_test.rb

+63-1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,68 @@ def test_can_create_with_new_record_and_save
7474
assert_equal "1", article.id
7575
end
7676

77+
def test_can_create_with_includes_and_fields
78+
stub_request(:post, "http://example.com/articles")
79+
.with(
80+
headers: { content_type: "application/vnd.api+json", accept: "application/vnd.api+json" },
81+
query: { include: 'comments,author.comments', fields: { articles: 'title', authors: 'name' } },
82+
body: {
83+
data: {
84+
type: "articles",
85+
attributes: {
86+
title: "Rails is Omakase"
87+
}
88+
}
89+
}.to_json
90+
).to_return(
91+
headers: { content_type: "application/vnd.api+json" },
92+
body: {
93+
data: {
94+
type: "articles",
95+
id: "1",
96+
attributes: {
97+
title: "Rails is Omakase"
98+
},
99+
relationships: {
100+
comments: {
101+
data: [
102+
{
103+
id: "2",
104+
type: "comments"
105+
}
106+
]
107+
},
108+
author: {
109+
data: nil
110+
}
111+
}
112+
},
113+
included: [
114+
{
115+
id: "2",
116+
type: "comments",
117+
attributes: {
118+
body: "it is isn't it ?"
119+
}
120+
}
121+
]
122+
}.to_json
123+
)
124+
article = Article.new({
125+
title: "Rails is Omakase"
126+
})
127+
article.request_includes(:comments, author: :comments).
128+
request_select(:title, authors: [:name])
129+
130+
assert article.save
131+
assert article.persisted?
132+
assert_equal "1", article.id
133+
assert_nil article.author
134+
assert_equal 1, article.comments.size
135+
assert_equal "2", article.comments.first.id
136+
assert_equal "it is isn't it ?", article.comments.first.body
137+
end
138+
77139
def test_can_create_with_links
78140
article = Article.new({
79141
title: "Rails is Omakase"
@@ -132,7 +194,7 @@ def test_can_create_with_new_record_with_relationships_and_save
132194

133195
end
134196

135-
def test_correct_create_with_nil_attirbute_value
197+
def test_correct_create_with_nil_attribute_value
136198
stub_request(:post, "http://example.com/authors")
137199
.with(headers: {
138200
content_type: "application/vnd.api+json",

0 commit comments

Comments
 (0)