Skip to content

Commit

Permalink
allow to send fields and/or includes on create/update resource
Browse files Browse the repository at this point in the history
  • Loading branch information
senid231 committed May 25, 2018
1 parent 3f36f74 commit 0c70c78
Show file tree
Hide file tree
Showing 10 changed files with 430 additions and 30 deletions.
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,23 @@ results = Article.includes(:author, :comments => :author).find(1)

# should not have to make additional requests to the server
authors = results.map(&:author)

# makes POST request to /articles?include=author,comments.author
article = Article.new(title: 'New one').request_includes(:author, :comments => :author)
article.save

# makes PATCH request to /articles/1?include=author,comments.author
article = Article.find(1)
article.title = 'Changed'
article.request_includes(:author, :comments => :author)
article.save

# request includes will be cleared if response is successful
# to avoid this `keep_request_params` class attribute can be used
Article.keep_request_params = true

# to clear request_includes use
article.reset_request_includes!
```

## Sparse Fieldsets
Expand All @@ -217,6 +234,24 @@ article.created_at
# or you can use fieldsets from multiple resources
# makes request to /articles?fields[articles]=title,body&fields[comments]=tag
article = Article.select("title", "body",{comments: 'tag'}).first

# makes POST request to /articles?fields[articles]=title,body&fields[comments]=tag
article = Article.new(title: 'New one').request_fields(:title, :body, comments: 'tag')
article.save

# makes PATCH request to /articles/1?fields[articles]=title,body&fields[comments]=tag
article = Article.find(1)
article.title = 'Changed'
article.request_fields(:title, :body, comments: 'tag')
article.save

# request fields will be cleared if response is successful
# to avoid this `keep_request_params` class attribute can be used
Article.keep_request_params = true

# to clear request fields use
article.reset_request_fields!(:comments) # to clear for comments
article.reset_request_fields! # to clear for all fields
```

## Sorting
Expand Down
1 change: 1 addition & 0 deletions lib/json_api_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module JsonApiClient
autoload :Paginating, 'json_api_client/paginating'
autoload :Parsers, 'json_api_client/parsers'
autoload :Query, 'json_api_client/query'
autoload :RequestParams, 'json_api_client/request_params'
autoload :Resource, 'json_api_client/resource'
autoload :ResultSet, 'json_api_client/result_set'
autoload :Schema, 'json_api_client/schema'
Expand Down
6 changes: 4 additions & 2 deletions lib/json_api_client/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ def delete(middleware)
faraday.builder.delete(middleware)
end

def run(request_method, path, params = {}, headers = {})
faraday.send(request_method, path, params, headers)
def run(request_method, path, params: nil, headers: {}, body: nil)
faraday.run_request(request_method, path, body, headers) do |request|
request.params.update(params) if params
end
end

end
Expand Down
17 changes: 1 addition & 16 deletions lib/json_api_client/query/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -159,22 +159,7 @@ def select_params
end

def parse_related_links(*tables)
tables.map do |table|
case table
when Hash
table.map do |k, v|
parse_related_links(*v).map do |sub|
"#{k}.#{sub}"
end
end
when Array
table.map do |v|
parse_related_links(*v)
end
else
key_formatter.format(table)
end
end.flatten
Utils.parse_includes(klass, *tables)
end

def parse_orders(*args)
Expand Down
22 changes: 13 additions & 9 deletions lib/json_api_client/query/requestor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,39 @@ def initialize(klass)
# expects a record
def create(record)
request(:post, klass.path(record.attributes), {
data: record.as_json_api
body: { data: record.as_json_api },
params: record.request_params.to_params
})
end

def update(record)
request(:patch, resource_path(record.attributes), {
data: record.as_json_api
body: { data: record.as_json_api },
params: record.request_params.to_params
})
end

def get(params = {})
path = resource_path(params)
params.delete(klass.primary_key)
request(:get, path, params)
request(:get, path, params: params)
end

def destroy(record)
request(:delete, resource_path(record.attributes), {})
request(:delete, resource_path(record.attributes))
end

def linked(path)
request(:get, path, {})
request(:get, path)
end

def custom(method_name, options, params)
path = resource_path(params)
params.delete(klass.primary_key)
path = File.join(path, method_name.to_s)

request(options.fetch(:request_method, :get), path, params)
request_method = options.fetch(:request_method, :get).to_sym
query_params, body_params = [:get, :delete].include?(request_method) ? [params, nil] : [nil, params]
request(request_method, path, params: query_params, body: body_params)
end

protected
Expand All @@ -56,8 +59,9 @@ def resource_path(parameters)
end
end

def request(type, path, params)
klass.parser.parse(klass, connection.run(type, path, params, klass.custom_headers))
def request(type, path, params: nil, body: nil)
response = connection.run(type, path, params: params, body: body, headers: klass.custom_headers)
klass.parser.parse(klass, response)
end

end
Expand Down
57 changes: 57 additions & 0 deletions lib/json_api_client/request_params.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
module JsonApiClient
class RequestParams
attr_reader :klass, :includes, :fields

def initialize(klass, includes: [], fields: {})
@klass = klass
@includes = includes
@fields = fields
end

def add_includes(includes)
Utils.parse_includes(klass, *includes).each do |name|
name = name.to_sym
self.includes.push(name) unless self.includes.include?(name)
end
end

def reset_includes!
@includes = []
end

def set_fields(type, field_names)
self.fields[type.to_sym] = field_names.map(&:to_sym)
end

def remove_fields(type)
self.fields.delete(type.to_sym)
end

def field_types
self.fields.keys
end

def clear
reset_includes!
@fields = {}
end

def to_params
return nil if field_types.empty? && includes.empty?
parsed_fields.merge(parsed_includes)
end

private

def parsed_includes
return {} if includes.empty?
{include: includes.join(",")}
end

def parsed_fields
return {} if field_types.empty?
{fields: fields.map { |type, names| [type, names.join(",")] }.to_h}
end

end
end
34 changes: 33 additions & 1 deletion lib/json_api_client/resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ class Resource

attr_accessor :last_result_set,
:links,
:relationships
:relationships,
:request_params
class_attribute :site,
:primary_key,
:parser,
Expand All @@ -31,6 +32,8 @@ class Resource
:associations,
:json_key_format,
:route_format,
:request_params_class,
:keep_request_params,
instance_accessor: false
self.primary_key = :id
self.parser = Parsers::Parser
Expand All @@ -43,6 +46,8 @@ class Resource
self.read_only_attributes = [:id, :type, :links, :meta, :relationships]
self.requestor_class = Query::Requestor
self.associations = []
self.request_params_class = RequestParams
self.keep_request_params = false

#:underscored_key, :camelized_key, :dasherized_key, or custom
self.json_key_format = :underscored_key
Expand Down Expand Up @@ -318,6 +323,7 @@ def initialize(params = {})
set_attribute(association.attr_name, params[association.attr_name.to_s])
end
end
self.request_params = self.class.request_params_class.new(self.class)
end

# Set the current attributes and try to save them
Expand Down Expand Up @@ -416,12 +422,14 @@ def save
false
else
self.errors.clear if self.errors
self.request_params.clear unless self.class.keep_request_params
mark_as_persisted!
if updated = last_result_set.first
self.attributes = updated.attributes
self.links.attributes = updated.links.attributes
self.relationships.attributes = updated.relationships.attributes
clear_changes_information
self.relationships.clear_changes_information
end
true
end
Expand All @@ -445,6 +453,30 @@ def inspect
"#<#{self.class.name}:@attributes=#{attributes.inspect}>"
end

def request_includes(*includes)
self.request_params.add_includes(includes)
self
end

def reset_request_includes!
self.request_params.reset_includes!
self
end

def request_fields(fields)
fields = {type.to_sym => fields} if fields.is_a?(Array)
fields.each do |resource_type, field_values|
self.request_params.set_fields(resource_type, field_values)
end
self
end

def reset_request_fields!(*resource_types)
resource_types = self.request_params.field_types if resource_types.empty?
resource_types.each { |resource_type| self.request_params.remove_fields(resource_type) }
self
end

protected

def method_missing(method, *args)
Expand Down
21 changes: 20 additions & 1 deletion lib/json_api_client/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,24 @@ def self.compute_type(klass, type_name)
raise NameError, "uninitialized constant #{candidates.first}"
end

def self.parse_includes(klass, *tables)
tables.map do |table|
case table
when Hash
table.map do |k, v|
parse_includes(klass, *v).map do |sub|
"#{k}.#{sub}"
end
end
when Array
table.map do |v|
parse_includes(klass, *v)
end
else
klass.key_formatter.format(table)
end
end.flatten
end

end
end
end
64 changes: 63 additions & 1 deletion test/unit/creation_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,68 @@ def test_can_create_with_new_record_and_save
assert_equal "1", article.id
end

def test_can_create_with_includes_and_fields
stub_request(:post, "http://example.com/articles")
.with(
headers: { content_type: "application/vnd.api+json", accept: "application/vnd.api+json" },
query: { include: 'comments,author.comments', fields: { articles: 'title', authors: 'name' } },
body: {
data: {
type: "articles",
attributes: {
title: "Rails is Omakase"
}
}
}.to_json
).to_return(
headers: { content_type: "application/vnd.api+json" },
body: {
data: {
type: "articles",
id: "1",
attributes: {
title: "Rails is Omakase"
},
relationships: {
comments: {
data: [
{
id: "2",
type: "comments"
}
]
},
author: {
data: nil
}
}
},
included: [
{
id: "2",
type: "comments",
attributes: {
body: "it is isn't it ?"
}
}
]
}.to_json
)
article = Article.new({
title: "Rails is Omakase"
})
article.request_includes(:comments, author: :comments).
request_fields(articles: [:title], authors: [:name])

assert article.save
assert article.persisted?
assert_equal "1", article.id
assert_nil article.author
assert_equal 1, article.comments.size
assert_equal "2", article.comments.first.id
assert_equal "it is isn't it ?", article.comments.first.body
end

def test_can_create_with_links
article = Article.new({
title: "Rails is Omakase"
Expand Down Expand Up @@ -132,7 +194,7 @@ def test_can_create_with_new_record_with_relationships_and_save

end

def test_correct_create_with_nil_attirbute_value
def test_correct_create_with_nil_attribute_value
stub_request(:post, "http://example.com/authors")
.with(headers: {
content_type: "application/vnd.api+json",
Expand Down
Loading

0 comments on commit 0c70c78

Please sign in to comment.