From ac38e86d4e00056639e12fb28f973bc7db3a26eb Mon Sep 17 00:00:00 2001 From: Fabio Kuhn Date: Tue, 20 Mar 2018 10:22:45 -0400 Subject: [PATCH 001/118] Fix custom type comment to use TypeFactory --- lib/json_api_client/schema.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/json_api_client/schema.rb b/lib/json_api_client/schema.rb index 5555b592..a760d5fd 100644 --- a/lib/json_api_client/schema.rb +++ b/lib/json_api_client/schema.rb @@ -67,11 +67,11 @@ class TypeFactory # end # end # - # JsonApiClient::Schema::Types.register money: MyMoneyCaster + # JsonApiClient::Schema::TypeFactory.register money: MyMoneyCaster # # You can setup several at once: # - # JsonApiClient::Schema::Types.register money: MyMoneyCaster, + # JsonApiClient::Schema::TypeFactory.register money: MyMoneyCaster, # date: MyJsonDateTypeCaster # # From 12acf88206c1d9d9f8955090f3f4a1da7f69262b Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Thu, 24 May 2018 12:05:42 +0300 Subject: [PATCH 002/118] #278 updating faraday to fix issue when param value is an empty array --- json_api_client.gemspec | 2 +- test/unit/query_builder_test.rb | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/json_api_client.gemspec b/json_api_client.gemspec index 5e81a106..ff092157 100644 --- a/json_api_client.gemspec +++ b/json_api_client.gemspec @@ -12,7 +12,7 @@ Gem::Specification.new do |s| s.summary = 'Build client libraries compliant with specification defined by jsonapi.org' s.add_dependency "activesupport", '>= 3.2.0' - s.add_dependency "faraday", '~> 0.9' + s.add_dependency "faraday", ['~> 0.15', '>= 0.15.2'] s.add_dependency "faraday_middleware", '~> 0.9' s.add_dependency "addressable", '~> 2.2' s.add_dependency "activemodel", '>= 3.2.0' diff --git a/test/unit/query_builder_test.rb b/test/unit/query_builder_test.rb index c37ca9c9..24b5b2b5 100644 --- a/test/unit/query_builder_test.rb +++ b/test/unit/query_builder_test.rb @@ -169,4 +169,29 @@ def test_can_select_nested_fields_using_comma_separated_strings Article.select({comments: 'author,text'}, :tags).to_a end + def test_can_specify_array_filter_value + stub_request(:get, "http://example.com/articles?filter%5Bauthor.id%5D%5B0%5D=foo&filter%5Bauthor.id%5D%5B1%5D=bar") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [] + }.to_json) + Article.where(:'author.id' => ['foo', 'bar']).to_a + end + + def test_can_specify_empty_array_filter_value + stub_request(:get, "http://example.com/articles?filter%5Bauthor.id%5D%5B0%5D") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [] + }.to_json) + Article.where(:'author.id' => []).to_a + end + + def test_can_specify_empty_string_filter_value + stub_request(:get, "http://example.com/articles") + .with(query: {filter: {:'author.id' => ''}}) + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [] + }.to_json) + Article.where(:'author.id' => '').to_a + end + end From 4a4f757e86720104f51c31b2a3bac2de8d0d8754 Mon Sep 17 00:00:00 2001 From: Corey Ward Date: Fri, 4 Mar 2016 10:55:23 -0600 Subject: [PATCH 003/118] Exclude path params from attributes hash --- lib/json_api_client/resource.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index 25c31775..87b6dd8f 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -494,8 +494,15 @@ def association_for(name) end end + def non_serializing_attributes + [ + self.class.read_only_attributes, + self.class.prefix_params.map(&:to_s) + ].flatten + end + def attributes_for_serialization - attributes.except(*self.class.read_only_attributes).slice(*changed) + attributes.except(*non_serializing_attributes).slice(*changed) end def relationships_for_serialization From 5fecef5747556659cc269f3be41ee0c8d9be5095 Mon Sep 17 00:00:00 2001 From: Corey Ward Date: Fri, 4 Mar 2016 16:52:17 -0600 Subject: [PATCH 004/118] Test path params are excluded from serialized attributes --- test/unit/serializing_test.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/unit/serializing_test.rb b/test/unit/serializing_test.rb index 47290982..9fc14fca 100644 --- a/test/unit/serializing_test.rb +++ b/test/unit/serializing_test.rb @@ -6,6 +6,10 @@ class LimitedField < TestResource self.read_only_attributes += ['foo'] end + class NestedResource < TestResource + belongs_to :bar + end + class CustomSerializerAttributes < TestResource protected @@ -317,4 +321,19 @@ def test_underscored_relationship_key_serialization assert_equal expected, article.as_json_api['relationships'] end end + + def test_ensure_nested_path_params_not_serialized + resource = NestedResource.new(foo: 'bar', id: 1, bar_id: 99) + + expected = { + 'id' => 1, + 'type' => "nested_resources", + 'attributes' => { + 'foo' => 'bar' + } + } + + assert_equal expected, resource.as_json_api + end + end From 8dede9eca1cfa45f3e7354ae7e0c93dbbc8cb82e Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Fri, 25 May 2018 12:28:20 +0300 Subject: [PATCH 005/118] allow to send fields and/or includes on create/update resource --- README.md | 35 +++++ lib/json_api_client.rb | 1 + lib/json_api_client/connection.rb | 6 +- lib/json_api_client/query/builder.rb | 17 +-- lib/json_api_client/query/requestor.rb | 22 +-- lib/json_api_client/request_params.rb | 57 +++++++ lib/json_api_client/resource.rb | 35 ++++- lib/json_api_client/utils.rb | 21 ++- test/unit/creation_test.rb | 64 +++++++- test/unit/updating_test.rb | 203 +++++++++++++++++++++++++ 10 files changed, 431 insertions(+), 30 deletions(-) create mode 100644 lib/json_api_client/request_params.rb diff --git a/README.md b/README.md index a6710552..9500c2f6 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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_select(: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_select(: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_select!(:comments) # to clear for comments +article.reset_request_select! # to clear for all fields ``` ## Sorting diff --git a/lib/json_api_client.rb b/lib/json_api_client.rb index 01f18f37..2f84ba9a 100644 --- a/lib/json_api_client.rb +++ b/lib/json_api_client.rb @@ -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' diff --git a/lib/json_api_client/connection.rb b/lib/json_api_client/connection.rb index 87839bff..2f207ec4 100644 --- a/lib/json_api_client/connection.rb +++ b/lib/json_api_client/connection.rb @@ -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 diff --git a/lib/json_api_client/query/builder.rb b/lib/json_api_client/query/builder.rb index e21e2bf8..247b37da 100644 --- a/lib/json_api_client/query/builder.rb +++ b/lib/json_api_client/query/builder.rb @@ -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) diff --git a/lib/json_api_client/query/requestor.rb b/lib/json_api_client/query/requestor.rb index 522561a9..56ab54c6 100644 --- a/lib/json_api_client/query/requestor.rb +++ b/lib/json_api_client/query/requestor.rb @@ -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 @@ -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 diff --git a/lib/json_api_client/request_params.rb b/lib/json_api_client/request_params.rb new file mode 100644 index 00000000..301c7d85 --- /dev/null +++ b/lib/json_api_client/request_params.rb @@ -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 diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index 25c31775..5229caad 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -15,7 +15,8 @@ class Resource attr_accessor :last_result_set, :links, - :relationships + :relationships, + :request_params class_attribute :site, :primary_key, :parser, @@ -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 @@ -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 @@ -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 @@ -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 @@ -445,6 +453,31 @@ 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_select(*fields) + fields_by_type = fields.extract_options! + fields_by_type[type.to_sym] = fields if fields.any? + fields_by_type.each do |field_type, field_names| + self.request_params.set_fields(field_type, field_names) + end + self + end + + def reset_request_select!(*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) diff --git a/lib/json_api_client/utils.rb b/lib/json_api_client/utils.rb index 7c36ab43..73fd2dd8 100644 --- a/lib/json_api_client/utils.rb +++ b/lib/json_api_client/utils.rb @@ -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 \ No newline at end of file +end diff --git a/test/unit/creation_test.rb b/test/unit/creation_test.rb index 6d0b012a..455dea9e 100644 --- a/test/unit/creation_test.rb +++ b/test/unit/creation_test.rb @@ -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_select(: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" @@ -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", diff --git a/test/unit/updating_test.rb b/test/unit/updating_test.rb index c80e5154..02fe453a 100644 --- a/test/unit/updating_test.rb +++ b/test/unit/updating_test.rb @@ -661,5 +661,208 @@ def test_callbacks_on_update assert_equal 100, callback_test.bar end + def test_can_update_with_includes_and_fields + stub_request(:patch, "http://example.com/articles/1") + .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: { + id: "1", + type: "articles", + attributes: { + title: "Modified title" + } + } + }.to_json + ).to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: { + type: "articles", + id: "1", + attributes: { + title: "Modified title" + }, + relationships: { + comments: { + data: [ + { + id: "2", + type: "comments" + } + ] + }, + author: { + data: nil + } + } + }, + included: [ + { + id: "2", + type: "comments", + attributes: { + body: "it is isn't it ?" + } + } + ] + }.to_json) + stub_request(:patch, "http://example.com/articles/1") + .with( + headers: {content_type: "application/vnd.api+json", accept: "application/vnd.api+json"}, + body: { + data: { + id: "1", + type: "articles", + attributes: { + title: "Modified title 2" + } + } + }.to_json + ).to_return( + headers: {content_type: "application/vnd.api+json"}, + body: { + data: { + type: "articles", + id: "1", + attributes: { + title: "Modified title 2" + } + } + }.to_json + ) + article = Article.find(1).first + article.title = "Modified title" + article.request_includes(:comments, author: :comments). + request_select(articles: [:title], authors: [:name]) + + assert article.save + assert_equal "1", article.id + assert_equal "Modified title", article.title + 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 + + article.title = "Modified title 2" + assert article.save + assert_equal "Modified title 2", article.title + end + + def test_can_update_with_includes_and_fields_with_keep_request_params + stub_request(:patch, "http://example.com/articles/1") + .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: { + id: "1", + type: "articles", + attributes: { + title: "Modified title" + } + } + }.to_json + ).to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: { + type: "articles", + id: "1", + attributes: { + title: "Modified title" + }, + relationships: { + comments: { + data: [ + { + id: "2", + type: "comments" + } + ] + }, + author: { + data: nil + } + } + }, + included: [ + { + id: "2", + type: "comments", + attributes: { + body: "it is isn't it ?" + } + } + ] + }.to_json) + stub_request(:patch, "http://example.com/articles/1") + .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: { + id: "1", + type: "articles", + attributes: { + title: "Modified title 2" + } + } + }.to_json + ).to_return( + headers: {content_type: "application/vnd.api+json"}, + body: { + data: { + type: "articles", + id: "1", + attributes: { + title: "Modified title 2" + }, + 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.keep_request_params = true + article = Article.find(1).first + article.title = "Modified title" + article.request_includes(:comments, author: :comments). + request_select(:title, authors: [:name]) + + assert article.save + assert_equal "1", article.id + assert_equal "Modified title", article.title + 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 + + article.title = "Modified title 2" + assert article.save + assert_equal "Modified title 2", article.title + 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 + ensure + Article.keep_request_params = false + end end From b9b367e028cf2b802d839344d765ea5568b0ede9 Mon Sep 17 00:00:00 2001 From: Fumiaki MATSUSHIMA Date: Tue, 14 Jun 2016 17:53:54 +0900 Subject: [PATCH 006/118] Consider `Model.page(nil)` It is very happy if we can write `Model.page(params[:page])` like Kaminari. --- README.md | 4 ++++ lib/json_api_client/query/builder.rb | 2 +- test/unit/query_builder_test.rb | 26 ++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a6710552..81f81a55 100644 --- a/README.md +++ b/README.md @@ -246,6 +246,10 @@ articles = Article.page(2).per(30).to_a # also makes request to /articles?page=2&per_page=30 articles = Article.paginate(page: 2, per_page: 30).to_a + +# keep in mind that page number can be nil - in that case default number will be applied +# also makes request to /articles?page=1&per_page=30 +articles = Article.paginate(page: nil, per_page: 30).to_a ``` *Note: The mapping of pagination parameters is done by the `query_builder` which is [customizable](#custom-paginator).* diff --git a/lib/json_api_client/query/builder.rb b/lib/json_api_client/query/builder.rb index e21e2bf8..439ee59d 100644 --- a/lib/json_api_client/query/builder.rb +++ b/lib/json_api_client/query/builder.rb @@ -47,7 +47,7 @@ def paginate(conditions = {}) end def page(number) - @pagination_params[ klass.paginator.page_param ] = number + @pagination_params[ klass.paginator.page_param ] = number || 1 self end diff --git a/test/unit/query_builder_test.rb b/test/unit/query_builder_test.rb index c37ca9c9..7d82b8c6 100644 --- a/test/unit/query_builder_test.rb +++ b/test/unit/query_builder_test.rb @@ -38,6 +38,32 @@ def test_can_paginate Article.paginate(page: 3, per_page: 6).to_a end + def test_pagination_default_number + JsonApiClient::Paginating::Paginator.page_param = :number + stub_request(:get, "http://example.com/articles?#{{page: {number: 1}}.to_query}") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [{ + type: "articles", + id: "1", + attributes: { + title: "JSON API paints my bikeshed!" + } + }], + links: { + self: "http://example.com/articles?#{{page: {number: 1}}.to_query}", + next: "http://example.com/articles?#{{page: {number: 2}}.to_query}", + prev: nil, + first: "http://example.com/articles?#{{page: {number: 1}}.to_query}", + last: "http://example.com/articles?#{{page: {number: 6}}.to_query}" + } + }.to_json) + + articles = Article.page(nil) + assert_equal 1, articles.current_page + ensure + JsonApiClient::Paginating::Paginator.page_param = :page + end + def test_can_sort_asc stub_request(:get, "http://example.com/articles") .with(query: {sort: "foo"}) From c6d0f37229cc7b620ab7c5fec46d84ef42d11d45 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Fri, 24 Aug 2018 10:09:30 -0700 Subject: [PATCH 007/118] adding test that shows the error: chaining scopes modifies the parent scope --- test/unit/query_builder_test.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/unit/query_builder_test.rb b/test/unit/query_builder_test.rb index 6c1afbc7..9ab08be4 100644 --- a/test/unit/query_builder_test.rb +++ b/test/unit/query_builder_test.rb @@ -220,4 +220,19 @@ def test_can_specify_empty_string_filter_value Article.where(:'author.id' => '').to_a end + def test_scopes_are_nondestructive + first_stub = stub_request(:get, "http://example.com/articles?page[page]=1&page[per_page]=1") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { data: [] }.to_json) + + all_stub = stub_request(:get, "http://example.com/articles") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { data: [] }.to_json) + + scope = Article.where() + + scope.first + scope.all + + assert_requested first_stub, times: 1 + assert_requested all_stub, times: 1 + end end From 787fe52860dba8434cf63004babedfc27be86558 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Fri, 24 Aug 2018 10:40:08 -0700 Subject: [PATCH 008/118] fixing readme to update test and coverage icon targets --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4144d331..150b1bd7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# JsonApiClient [![Build Status](https://travis-ci.org/chingor13/json_api_client.png)](https://travis-ci.org/chingor13/json_api_client) [![Code Climate](https://codeclimate.com/github/chingor13/json_api_client.png)](https://codeclimate.com/github/chingor13/json_api_client) [![Code Coverage](https://codeclimate.com/github/chingor13/json_api_client/coverage.png)](https://codeclimate.com/github/chingor13/json_api_client) +# JsonApiClient [![Build Status](https://travis-ci.org/JsonApiClient/json_api_client.png)](https://travis-ci.org/JsonApiClient/json_api_client) [![Code Climate](https://codeclimate.com/github/JsonApiClient/json_api_client.png)](https://codeclimate.com/github/JsonApiClient/json_api_client) [![Code Coverage](https://codeclimate.com/github/JsonApiClient/json_api_client/coverage.png)](https://codeclimate.com/github/JsonApiClient/json_api_client) This gem is meant to help you build an API client for interacting with REST APIs as laid out by [http://jsonapi.org](http://jsonapi.org). It attempts to give you a query building framework that is easy to understand (it is similar to ActiveRecord scopes). From d9c3f17605a1939f53ccbb7f419ff92c1ad28b3e Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Fri, 24 Aug 2018 11:21:02 -0700 Subject: [PATCH 009/118] fixing sopes to be immutable --- lib/json_api_client/query/builder.rb | 58 +++++++++++++++------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/lib/json_api_client/query/builder.rb b/lib/json_api_client/query/builder.rb index 44df6c1f..cdcf1527 100644 --- a/lib/json_api_client/query/builder.rb +++ b/lib/json_api_client/query/builder.rb @@ -5,60 +5,55 @@ class Builder attr_reader :klass delegate :key_formatter, to: :klass - def initialize(klass) - @klass = klass - @primary_key = nil - @pagination_params = {} - @path_params = {} - @additional_params = {} - @filters = {} - @includes = [] - @orders = [] - @fields = [] + def initialize(klass, opts = {}) + @klass = klass + @primary_key = nil + @pagination_params = opts.fetch( :pagination_params, {} ) + @path_params = opts.fetch( :path_params, {} ) + @additional_params = opts.fetch( :additional_params, {} ) + @filters = opts.fetch( :filters, {} ) + @includes = opts.fetch( :includes, [] ) + @orders = opts.fetch( :orders, [] ) + @fields = opts.fetch( :fields, [] ) end def where(conditions = {}) # pull out any path params here - @path_params.merge!(conditions.slice(*klass.prefix_params)) - @filters.merge!(conditions.except(*klass.prefix_params)) - self + path_conditions = conditions.slice(*klass.prefix_params) + unpathed_conditions = conditions.except(*klass.prefix_params) + + _new_scope( path_params: path_conditions, filters: unpathed_conditions ) end def order(*args) - @orders += parse_orders(*args) - self + _new_scope( orders: parse_orders(*args) ) end def includes(*tables) - @includes += parse_related_links(*tables) - self + _new_scope( includes: parse_related_links(*tables) ) end def select(*fields) - @fields += parse_fields(*fields) - self + _new_scope( fields: parse_fields(*fields) ) end def paginate(conditions = {}) - scope = self + scope = _new_scope scope = scope.page(conditions[:page]) if conditions[:page] scope = scope.per(conditions[:per_page]) if conditions[:per_page] scope end def page(number) - @pagination_params[ klass.paginator.page_param ] = number || 1 - self + _new_scope( pagination_params: { klass.paginator.page_param => number || 1 } ) end def per(size) - @pagination_params[ klass.paginator.per_page_param ] = size - self + _new_scope( pagination_params: { klass.paginator.per_page_param => size } ) end def with_params(more_params) - @additional_params.merge!(more_params) - self + _new_scope( additional_params: more_params ) end def first @@ -106,6 +101,17 @@ def method_missing(method_name, *args, &block) private + def _new_scope( opts = {} ) + self.class.new( @klass, + pagination_params: @pagination_params.merge( opts.fetch( :pagination_params, {} ) ), + path_params: @path_params.merge( opts.fetch( :path_params, {} ) ), + additional_params: @additional_params.merge( opts.fetch( :additional_params, {} ) ), + filters: @filters.merge( opts.fetch( :filters, {} ) ), + includes: @includes + opts.fetch( :includes, [] ), + orders: @orders + opts.fetch( :orders, [] ), + fields: @fields + opts.fetch( :fields, [] ) ) + end + def path_params @path_params.empty? ? {} : {path: @path_params} end From b944c128bf6c2b92bb1fe6da4d0f30f04dfdec10 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Fri, 24 Aug 2018 13:48:28 -0700 Subject: [PATCH 010/118] fixing paginator to accept strings or symbols as paer_page key --- lib/json_api_client/paginating/paginator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/json_api_client/paginating/paginator.rb b/lib/json_api_client/paginating/paginator.rb index cc93066a..9a8b1f98 100644 --- a/lib/json_api_client/paginating/paginator.rb +++ b/lib/json_api_client/paginating/paginator.rb @@ -82,7 +82,7 @@ def next_page def params_for_uri(uri) return {} unless uri uri = Addressable::URI.parse(uri) - uri.query_values || {} + ( uri.query_values || {} ).with_indifferent_access end end end From 6bef918710df8899c186bde1141304f49c8ee8db Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Mon, 27 Aug 2018 09:34:13 -0700 Subject: [PATCH 011/118] adding test that demonstrates Query::Builder#find with args doesn't work --- test/unit/query_builder_test.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/unit/query_builder_test.rb b/test/unit/query_builder_test.rb index 9ab08be4..9bdf53f2 100644 --- a/test/unit/query_builder_test.rb +++ b/test/unit/query_builder_test.rb @@ -235,4 +235,20 @@ def test_scopes_are_nondestructive assert_requested first_stub, times: 1 assert_requested all_stub, times: 1 end + + def test_find_with_args + first_stub = stub_request(:get, "http://example.com/articles?filter[author.id]=foo") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { data: [] }.to_json) + + all_stub = stub_request(:get, "http://example.com/articles") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { data: [] }.to_json) + + scope = Article.where() + + scope.find( "author.id" => "foo" ) + scope.all + + assert_requested first_stub, times: 1 + assert_requested all_stub, times: 1 + end end From 9f31073d08914e71d4daff433bba3c49b1253590 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Mon, 27 Aug 2018 09:40:53 -0700 Subject: [PATCH 012/118] fixing Query::Builder#find with parameters --- lib/json_api_client/query/builder.rb | 9 +++++---- test/unit/query_builder_test.rb | 5 +++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/json_api_client/query/builder.rb b/lib/json_api_client/query/builder.rb index cdcf1527..7c4d9d05 100644 --- a/lib/json_api_client/query/builder.rb +++ b/lib/json_api_client/query/builder.rb @@ -7,7 +7,7 @@ class Builder def initialize(klass, opts = {}) @klass = klass - @primary_key = nil + @primary_key = opts.fetch( :primary_key, nil ) @pagination_params = opts.fetch( :pagination_params, {} ) @path_params = opts.fetch( :path_params, {} ) @additional_params = opts.fetch( :additional_params, {} ) @@ -87,12 +87,12 @@ def to_a def find(args = {}) case args when Hash - where(args) + scope = where(args) else - @primary_key = args + scope = _new_scope( primary_key: args ) end - klass.requestor.get(params) + klass.requestor.get(scope.params) end def method_missing(method_name, *args, &block) @@ -103,6 +103,7 @@ def method_missing(method_name, *args, &block) def _new_scope( opts = {} ) self.class.new( @klass, + primary_key: opts.fetch( :primary_key, @primary_key ), pagination_params: @pagination_params.merge( opts.fetch( :pagination_params, {} ) ), path_params: @path_params.merge( opts.fetch( :path_params, {} ) ), additional_params: @additional_params.merge( opts.fetch( :additional_params, {} ) ), diff --git a/test/unit/query_builder_test.rb b/test/unit/query_builder_test.rb index 9bdf53f2..2596437a 100644 --- a/test/unit/query_builder_test.rb +++ b/test/unit/query_builder_test.rb @@ -243,12 +243,17 @@ def test_find_with_args all_stub = stub_request(:get, "http://example.com/articles") .to_return(headers: {content_type: "application/vnd.api+json"}, body: { data: [] }.to_json) + find_stub = stub_request(:get, "http://example.com/articles/6") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { data: [] }.to_json) + scope = Article.where() scope.find( "author.id" => "foo" ) + scope.find(6) scope.all assert_requested first_stub, times: 1 assert_requested all_stub, times: 1 + assert_requested find_stub, times: 1 end end From d228cada8ed03f69336953792ba657fa70917286 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Mon, 27 Aug 2018 13:47:37 -0700 Subject: [PATCH 013/118] version bump v1.6.0 --- CHANGELOG.md | 9 +++++++++ lib/json_api_client/version.rb | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2c75573..6457151f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## Unreleased +## v1.6.0 + +- [#281](https://github.com/JsonApiClient/json_api_client/pull/281) - Optimize dynamic attribute deref +- [#280](https://github.com/JsonApiClient/json_api_client/pull/280) - Fix custom headers inheritance +- [#287](https://github.com/JsonApiClient/json_api_client/pull/287) - Allow pagination params to be `nil` +- [#284](https://github.com/JsonApiClient/json_api_client/pull/284) - Fix filter to not filter out `[]` values +- [#285](https://github.com/JsonApiClient/json_api_client/pull/285) - Add include params for create/update +- [#293](https://github.com/JsonApiClient/json_api_client/pull/293) - Fix side-effects in scopes + ## v1.5.3 - [#266](https://github.com/chingor13/json_api_client/pull/266) - Fix default attributes being overridden diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index 6da8fff8..8e85820f 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.5.3" + VERSION = "1.6.0" end From fbaeafa224392ca75ef31ccc76214d3e68373ec4 Mon Sep 17 00:00:00 2001 From: Luis Edymerchk Laverde Date: Sat, 1 Sep 2018 16:52:03 -0500 Subject: [PATCH 014/118] Use mocha/minitest instead of mocha/mini_test on test_helper --- test/test_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index a637e903..b580ca38 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -3,7 +3,7 @@ Bundler.require(:default, :test) require 'minitest/autorun' require 'webmock/minitest' -require 'mocha/mini_test' +require 'mocha/minitest' require 'pp' # shim for ActiveSupport 4.0.x requiring minitest 4.2 From f6fd3da509112427d0ac06a0bafa0bddcaf96494 Mon Sep 17 00:00:00 2001 From: Mark Oleson Date: Thu, 6 Sep 2018 16:24:31 -0400 Subject: [PATCH 015/118] update arguments for custom connections run method --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4144d331..58647cf8 100644 --- a/README.md +++ b/README.md @@ -399,7 +399,7 @@ class NullConnection def initialize(*args) end - def run(request_method, path, params = {}, headers = {}) + def run(request_method, path, params: nil, headers: {}, body: nil) end def use(*args); end From f76c5c88e350f2fec869bb29869ebdf3bcdc9ccd Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Wed, 3 Oct 2018 11:23:19 +0300 Subject: [PATCH 016/118] #303 optional add default to changes --- lib/json_api_client/resource.rb | 16 ++++- test/unit/association_test.rb | 107 +++++++++++++++++++++++++++++++- 2 files changed, 119 insertions(+), 4 deletions(-) diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index 01748945..74793e1f 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -35,6 +35,8 @@ class Resource :request_params_class, :keep_request_params, instance_accessor: false + class_attribute :add_defaults_to_changes, + instance_writer: false self.primary_key = :id self.parser = Parsers::Parser self.paginator = Paginating::Paginator @@ -48,6 +50,7 @@ class Resource self.associations = [] self.request_params_class = RequestParams self.keep_request_params = false + self.add_defaults_to_changes = false #:underscored_key, :camelized_key, :dasherized_key, or custom self.json_key_format = :underscored_key @@ -314,9 +317,7 @@ def initialize(params = {}) self.relationships = self.class.relationship_linker.new(self.class, params.delete("relationships") || {}) self.attributes = self.class.default_attributes.merge(params) - self.class.schema.each_property do |property| - attributes[property.name] = property.default unless attributes.has_key?(property.name) || property.default.nil? - end + setup_default_properties self.class.associations.each do |association| if params.has_key?(association.attr_name.to_s) @@ -480,6 +481,15 @@ def reset_request_select!(*resource_types) protected + def setup_default_properties + self.class.schema.each_property do |property| + unless attributes.has_key?(property.name) || property.default.nil? + attribute_will_change!(property.name) if add_defaults_to_changes + attributes[property.name] = property.default + end + end + end + def method_missing(method, *args) association = association_for(method) diff --git a/test/unit/association_test.rb b/test/unit/association_test.rb index f31ece36..1fdd3833 100644 --- a/test/unit/association_test.rb +++ b/test/unit/association_test.rb @@ -46,12 +46,89 @@ class MultiWordParent < Formatted class MultiWordChild < Formatted belongs_to :multi_word_parent + self.read_only_attributes = read_only_attributes + [:multi_word_parent_id] + + def self.key_formatter + JsonApiClient::DasherizedKeyFormatter + end + + def self.route_formatter + JsonApiClient::UnderscoredKeyFormatter + end +end + +class Account < TestResource + property :name + property :is_active, default: true + property :balance +end + +class UserAccount < TestResource + self.add_defaults_to_changes = true + property :name + property :is_active, default: true + property :balance end class AssociationTest < MiniTest::Test + def test_default_properties_no_changes + stub_request(:post, 'http://example.com/accounts'). + with(headers: { content_type: 'application/vnd.api+json', accept: 'application/vnd.api+json' }, body: { + data: { + type: 'accounts', + attributes: { + name: 'foo' + } + } + }.to_json) + .to_return(headers: { content_type: 'application/vnd.api+json' }, body: { + data: { + id: '1', + type: 'accounts', + attributes: { + name: 'foo', + is_active: false, + balance: '0.0' + } + } + }.to_json) + record = Account.new(name: 'foo') + assert record.save + assert_equal(false, record.is_active) + assert_equal('0.0', record.balance) + end + + def test_default_properties_changes + stub_request(:post, 'http://example.com/user_accounts'). + with(headers: { content_type: 'application/vnd.api+json', accept: 'application/vnd.api+json' }, body: { + data: { + type: 'user_accounts', + attributes: { + name: 'foo', + is_active: true + } + } + }.to_json) + .to_return(headers: { content_type: 'application/vnd.api+json' }, body: { + data: { + id: '1', + type: 'user_accounts', + attributes: { + name: 'foo', + is_active: true, + balance: '0.0' + } + } + }.to_json) + record = UserAccount.new(name: 'foo') + assert record.save + assert_equal(true, record.is_active) + assert_equal('0.0', record.balance) + end + def test_belongs_to_urls_are_formatted - request = stub_request(:get, "http://example.com/multi-word-parents/1/multi-word-children") + request = stub_request(:get, "http://example.com/multi_word_parents/1/multi_word_children") .to_return(headers: {content_type: "application/vnd.api+json"}, body: { data: [] }.to_json) MultiWordChild.where(multi_word_parent_id: 1).to_a @@ -59,6 +136,34 @@ def test_belongs_to_urls_are_formatted assert_requested(request) end + def test_belongs_to_urls_create_record + stub_request(:post, 'http://example.com/multi_word_parents/1/multi_word_children'). + with(headers: { content_type: 'application/vnd.api+json', accept: 'application/vnd.api+json' }, body: { + data: { + type: 'multi_word_children', + attributes: { + foo: 'bar', + 'multi-word-field': true + } + } + }.to_json) + .to_return(headers: { content_type: 'application/vnd.api+json' }, body: { + data: { + id: '2', + type: 'multi_word_children', + attributes: { + foo: 'bar', + 'multi-word-field': true + } + } + }.to_json) + + record = MultiWordChild.new(multi_word_parent_id: 1, foo: 'bar', multi_word_field: true) + result = record.save + assert result + assert_equal('2', record.id) + end + def test_load_has_one stub_request(:get, "http://example.com/properties/1") .to_return(headers: {content_type: "application/vnd.api+json"}, body: { From 79fedecf7fd9a13e538b5c03f041d0addbc2bc89 Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Wed, 3 Oct 2018 12:40:48 +0300 Subject: [PATCH 017/118] #291 import README - pagination override examples --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 92b0972f..f94eb5c6 100644 --- a/README.md +++ b/README.md @@ -493,8 +493,8 @@ You can customize how your resources find pagination information from the respon If the [existing paginator](https://github.com/chingor13/json_api_client/blob/master/lib/json_api_client/paginating/paginator.rb) fits your requirements but you don't use the default `page` and `per_page` params for pagination, you can customise the param keys as follows: ```ruby -JsonApiClient::Paginating::Paginator.page_param = "page[number]" -JsonApiClient::Paginating::Paginator.per_page_param = "page[size]" +JsonApiClient::Paginating::Paginator.page_param = "number" +JsonApiClient::Paginating::Paginator.per_page_param = "size" ``` Please note that this is a global configuration, so library authors should create a custom paginator that inherits `JsonApiClient::Paginating::Paginator` and configure the custom paginator to avoid modifying global config. From c098c66b00b868312e90af0b68aa30558b0008a9 Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Wed, 3 Oct 2018 12:47:48 +0300 Subject: [PATCH 018/118] #290 symbolize params keys on model initialize it will fix behavior when relationships and links are passed as symbol keys --- lib/json_api_client/resource.rb | 5 +++-- test/unit/creation_test.rb | 37 +++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index 01748945..1948935d 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -309,9 +309,10 @@ def _build_connection(rebuild = false) # # @param params [Hash] Attributes, links, and relationships def initialize(params = {}) + params = params.symbolize_keys @persisted = nil - self.links = self.class.linker.new(params.delete("links") || {}) - self.relationships = self.class.relationship_linker.new(self.class, params.delete("relationships") || {}) + self.links = self.class.linker.new(params.delete(:links) || {}) + self.relationships = self.class.relationship_linker.new(self.class, params.delete(:relationships) || {}) self.attributes = self.class.default_attributes.merge(params) self.class.schema.each_property do |property| diff --git a/test/unit/creation_test.rb b/test/unit/creation_test.rb index 455dea9e..b5e3f2d9 100644 --- a/test/unit/creation_test.rb +++ b/test/unit/creation_test.rb @@ -271,4 +271,41 @@ def test_callbacks_on_update assert_equal 100, callback_test.bar end + def test_create_with_relationships_in_payload + stub_request(:post, 'http://example.com/articles') + .with(headers: {content_type: 'application/vnd.api+json', accept: 'application/vnd.api+json'}, body: { + data: { + type: 'articles', + attributes: { + title: 'Rails is Omakase' + }, + relationships: { + comments: { + data: [ + { + id: '2', + type: 'comments' + } + ] + } + } + } + }.to_json) + .to_return(headers: {content_type: 'application/vnd.api+json'}, body: { + data: { + type: 'articles', + id: '1', + attributes: { + title: 'Rails is Omakase' + } + } + }.to_json) + + article = Article.new(title: 'Rails is Omakase', relationships: {comments: [Comment.new(id: 2)]}) + + assert article.save + assert article.persisted? + assert_equal "1", article.id + end + end From 3048ea371e8fc5e5f5b6788c870503c09d2391f4 Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Wed, 3 Oct 2018 12:21:31 +0300 Subject: [PATCH 019/118] cache values for defined properties and relationships [performance optimization] --- .../associations/belongs_to.rb | 9 -- lib/json_api_client/associations/has_many.rb | 8 -- lib/json_api_client/associations/has_one.rb | 8 -- lib/json_api_client/helpers.rb | 1 + lib/json_api_client/helpers/associatable.rb | 64 +++++++++++++ .../helpers/dynamic_attributes.rb | 7 +- lib/json_api_client/resource.rb | 51 ++++++---- test/unit/resource_test.rb | 4 +- test/unit/updating_test.rb | 92 +++++++++++++++++++ 9 files changed, 193 insertions(+), 51 deletions(-) create mode 100644 lib/json_api_client/helpers/associatable.rb diff --git a/lib/json_api_client/associations/belongs_to.rb b/lib/json_api_client/associations/belongs_to.rb index 14f0df68..8f9b27ec 100644 --- a/lib/json_api_client/associations/belongs_to.rb +++ b/lib/json_api_client/associations/belongs_to.rb @@ -1,15 +1,6 @@ module JsonApiClient module Associations module BelongsTo - extend ActiveSupport::Concern - - module ClassMethods - def belongs_to(attr_name, options = {}) - # self.associations = self.associations + [HasOne::Association.new(attr_name, self, options)] - self.associations += [BelongsTo::Association.new(attr_name, self, options)] - end - end - class Association < BaseAssociation include Helpers::URI def param diff --git a/lib/json_api_client/associations/has_many.rb b/lib/json_api_client/associations/has_many.rb index b5f52d0d..861ec3b8 100644 --- a/lib/json_api_client/associations/has_many.rb +++ b/lib/json_api_client/associations/has_many.rb @@ -1,14 +1,6 @@ module JsonApiClient module Associations module HasMany - extend ActiveSupport::Concern - - module ClassMethods - def has_many(attr_name, options = {}) - self.associations = self.associations + [HasMany::Association.new(attr_name, self, options)] - end - end - class Association < BaseAssociation end end diff --git a/lib/json_api_client/associations/has_one.rb b/lib/json_api_client/associations/has_one.rb index 20edc533..4d1418a2 100644 --- a/lib/json_api_client/associations/has_one.rb +++ b/lib/json_api_client/associations/has_one.rb @@ -1,14 +1,6 @@ module JsonApiClient module Associations module HasOne - extend ActiveSupport::Concern - - module ClassMethods - def has_one(attr_name, options = {}) - self.associations += [HasOne::Association.new(attr_name, self, options)] - end - end - class Association < BaseAssociation def from_result_set(result_set) result_set.first diff --git a/lib/json_api_client/helpers.rb b/lib/json_api_client/helpers.rb index f80794e1..6f6f71ab 100644 --- a/lib/json_api_client/helpers.rb +++ b/lib/json_api_client/helpers.rb @@ -4,5 +4,6 @@ module Helpers autoload :Dirty, 'json_api_client/helpers/dirty' autoload :DynamicAttributes, 'json_api_client/helpers/dynamic_attributes' autoload :URI, 'json_api_client/helpers/uri' + autoload :Associatable, 'json_api_client/helpers/associatable' end end diff --git a/lib/json_api_client/helpers/associatable.rb b/lib/json_api_client/helpers/associatable.rb new file mode 100644 index 00000000..ba6a4d8c --- /dev/null +++ b/lib/json_api_client/helpers/associatable.rb @@ -0,0 +1,64 @@ +module JsonApiClient + module Helpers + module Associatable + extend ActiveSupport::Concern + + included do + class_attribute :associations, instance_accessor: false + self.associations = [] + attr_accessor :__cached_associations + end + + module ClassMethods + def _define_association(attr_name, association_klass, options = {}) + attr_name = attr_name.to_sym + association = association_klass.new(attr_name, self, options) + self.associations += [association] + + define_method(attr_name) do + _cached_relationship(attr_name) do + relationship_definition = relationship_definition_for(attr_name) + return unless relationship_definition + relationship_data_for(attr_name, relationship_definition) + end + end + + define_method("#{attr_name}=") do |value| + _clear_cached_relationship(attr_name) + relationships.public_send("#{attr_name}=", value) + end + end + + def belongs_to(attr_name, options = {}) + _define_association(attr_name, JsonApiClient::Associations::BelongsTo::Association, options) + end + + def has_many(attr_name, options = {}) + _define_association(attr_name, JsonApiClient::Associations::HasMany::Association, options) + end + + def has_one(attr_name, options = {}) + _define_association(attr_name, JsonApiClient::Associations::HasOne::Association, options) + end + end + + def _cached_associations + self.__cached_associations ||= {} + end + + def _clear_cached_relationships + self.__cached_associations = {} + end + + def _clear_cached_relationship(attr_name) + _cached_associations.delete(attr_name) + end + + def _cached_relationship(attr_name) + return _cached_associations[attr_name] if _cached_associations.has_key?(attr_name) + _cached_associations[attr_name] = yield + end + + end + end +end diff --git a/lib/json_api_client/helpers/dynamic_attributes.rb b/lib/json_api_client/helpers/dynamic_attributes.rb index ac23ac3e..18816ba1 100644 --- a/lib/json_api_client/helpers/dynamic_attributes.rb +++ b/lib/json_api_client/helpers/dynamic_attributes.rb @@ -39,12 +39,7 @@ def has_attribute?(attr_name) def method_missing(method, *args, &block) if has_attribute?(method) - self.class.class_eval do - define_method(method) do - attributes[method] - end - end - return send(method) + return attributes[method] end normalized_method = safe_key_formatter.unformat(method.to_s) diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index 7c914222..cb176ca9 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -12,6 +12,7 @@ class Resource include Helpers::DynamicAttributes include Helpers::Dirty + include Helpers::Associatable attr_accessor :last_result_set, :links, @@ -29,7 +30,6 @@ class Resource :relationship_linker, :read_only_attributes, :requestor_class, - :associations, :json_key_format, :route_format, :request_params_class, @@ -47,7 +47,6 @@ class Resource self.relationship_linker = Relationships::Relations 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 self.add_defaults_to_changes = false @@ -58,10 +57,6 @@ class Resource #:underscored_route, :camelized_route, :dasherized_route, or custom self.route_format = :underscored_route - include Associations::BelongsTo - include Associations::HasMany - include Associations::HasOne - class << self extend Forwardable def_delegators :_new_scope, :where, :order, :includes, :select, :all, :paginate, :page, :with_params, :first, :find, :last @@ -253,6 +248,12 @@ def member_endpoint(name, options = {}) # @option options [Symbol] :default The default value for the property def property(name, options = {}) schema.add(name, options) + define_method(name) do + attributes[name] + end + define_method("#{name}=") do |value| + set_attribute(name, value) + end end # Declare multiple properties with the same optional options @@ -430,8 +431,10 @@ def save self.attributes = updated.attributes self.links.attributes = updated.links.attributes self.relationships.attributes = updated.relationships.attributes + self.relationships.last_result_set = last_result_set clear_changes_information self.relationships.clear_changes_information + _clear_cached_relationships end true end @@ -447,6 +450,9 @@ def destroy false else self.attributes.clear + self.relationships.attributes.clear + self.relationships.last_result_set = nil + _clear_cached_relationships true end end @@ -491,27 +497,36 @@ def setup_default_properties end end - def method_missing(method, *args) - association = association_for(method) - - return super unless association || (relationships && relationships.has_attribute?(method)) + def relationship_definition_for(name) + relationships[name] if relationships && relationships.has_attribute?(name) + end - return nil unless relationship_definitions = relationships[method] + def included_data_for(name, relationship_definition) + last_result_set.included.data_for(name, relationship_definition) + end + def relationship_data_for(name, relationship_definition) # look in included data - if relationship_definitions.key?("data") - return last_result_set.included.data_for(method, relationship_definitions) + if relationship_definition.key?("data") + return included_data_for(name, relationship_definition) end - if association = association_for(method) - # look for a defined relationship url - if relationship_definitions["links"] && url = relationship_definitions["links"]["related"] - return association.data(url) - end + url = relationship_definition["links"]["related"] + if relationship_definition["links"] && url + return association_for(name).data(url) end + nil end + def method_missing(method, *args) + relationship_definition = relationship_definition_for(method) + + return super unless relationship_definition + + relationship_data_for(method, relationship_definition) + end + def respond_to_missing?(symbol, include_all = false) return true if relationships && relationships.has_attribute?(symbol) return true if association_for(symbol) diff --git a/test/unit/resource_test.rb b/test/unit/resource_test.rb index edb4e4ca..5af0c5b1 100644 --- a/test/unit/resource_test.rb +++ b/test/unit/resource_test.rb @@ -86,8 +86,8 @@ def test_formatted_key_accessors def test_associations_as_params article = Article.new(foo: 'bar', 'author' => {'type' => 'authors', 'id' => 1}) assert_equal(article.foo, 'bar') - assert_equal(article.attributes['author']['type'], 'authors') - assert_equal(article.attributes['author']['id'], 1) + assert_equal({'type' => 'authors', 'id' => 1}, article.relationships.author) + assert article.relationships.attribute_changed?(:author) end def test_default_params_overrideable diff --git a/test/unit/updating_test.rb b/test/unit/updating_test.rb index 02fe453a..445c1cdb 100644 --- a/test/unit/updating_test.rb +++ b/test/unit/updating_test.rb @@ -212,6 +212,49 @@ def test_can_update_single_relationship assert article.save end + def test_can_update_single_relationship_via_setter + articles = Article.find(1) + article = articles.first + + stub_request(:patch, "http://example.com/articles/1") + .with(headers: {content_type: "application/vnd.api+json", accept: "application/vnd.api+json"}, body: { + data: { + id: "1", + type: "articles", + relationships: { + author: { + data: { + type: "people", + id: "1" + } + } + }, + attributes: {} + } + }.to_json) + .to_return(headers: {status: 200, content_type: "application/vnd.api+json"}, body: { + data: { + type: "articles", + id: "1", + attributes: { + title: "Rails is Omakase" + }, + relationships: { + author: { + links: { + self: "/articles/1/links/author", + related: "/articles/1/author", + }, + data: { type: "people", id: "1" } + } + } + } + }.to_json) + + article.author = Person.new(id: "1") + assert article.save + end + def test_can_update_single_relationship_with_all_attributes_dirty articles = Article.find(1) article = articles.first @@ -313,6 +356,55 @@ def test_can_update_has_many_relationships assert article.save end + def test_can_update_has_many_relationships_via_setter + articles = Article.find(1) + article = articles.first + + stub_request(:patch, "http://example.com/articles/1") + .with(headers: {content_type: "application/vnd.api+json", accept: "application/vnd.api+json"}, body: { + data: { + id: "1", + type: "articles", + relationships: { + comments: { + data: [{ + type: "comments", + id: "2" + },{ + type: "comments", + id: "3" + }] + } + }, + attributes: {} + } + }.to_json) + .to_return(headers: {status: 200, content_type: "application/vnd.api+json"}, body: { + data: { + id: "1", + type: "articles", + relationships: { + author: { + links: { + self: "/articles/1/links/author", + related: "/articles/1/author", + }, + data: { type: "people", id: "1" } + } + }, + attributes: { + title: "Rails is Omakase" + } + } + }.to_json) + + article.comments = [ + Comment.new(id: "2"), + Comment.new(id: "3") + ] + assert article.save + end + def test_can_update_has_many_relationships_with_all_attributes_dirty articles = Article.find(1) article = articles.first From 96746b5b31aa9947799633b355eae174f355dff1 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Mon, 8 Oct 2018 08:28:23 -0700 Subject: [PATCH 020/118] version bump 1.6.1 --- CHANGELOG.md | 9 +++++++++ lib/json_api_client/version.rb | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6457151f..79112f2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## Unreleased +## v1.6.1 + +- [#297](https://githup.com/JsonApiClient/json_api_client/pull/297) - Fix test_helper +- [#298](https://githup.com/JsonApiClient/json_api_client/pull/298) - README update: arguments for custom connections run method +- [#306](https://githup.com/JsonApiClient/json_api_client/pull/306) - README update: pagination override examples +- [#307](https://githup.com/JsonApiClient/json_api_client/pull/307) - Symbolize params keys on model initialize +- [#304](https://githup.com/JsonApiClient/json_api_client/pull/304) - Optional add default to changes +- [#300](https://githup.com/JsonApiClient/json_api_client/pull/300) - Define methods for properties and associations getter/setter + ## v1.6.0 - [#281](https://github.com/JsonApiClient/json_api_client/pull/281) - Optimize dynamic attribute deref diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index 8e85820f..0909b49d 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.6.0" + VERSION = "1.6.1" end From dee26fd794e09f00b0e543e0f05ce2d4c1ee19ff Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Mon, 8 Oct 2018 08:28:23 -0700 Subject: [PATCH 021/118] version bump 1.6.1 --- CHANGELOG.md | 9 +++++++++ lib/json_api_client/version.rb | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6457151f..79112f2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## Unreleased +## v1.6.1 + +- [#297](https://githup.com/JsonApiClient/json_api_client/pull/297) - Fix test_helper +- [#298](https://githup.com/JsonApiClient/json_api_client/pull/298) - README update: arguments for custom connections run method +- [#306](https://githup.com/JsonApiClient/json_api_client/pull/306) - README update: pagination override examples +- [#307](https://githup.com/JsonApiClient/json_api_client/pull/307) - Symbolize params keys on model initialize +- [#304](https://githup.com/JsonApiClient/json_api_client/pull/304) - Optional add default to changes +- [#300](https://githup.com/JsonApiClient/json_api_client/pull/300) - Define methods for properties and associations getter/setter + ## v1.6.0 - [#281](https://github.com/JsonApiClient/json_api_client/pull/281) - Optimize dynamic attribute deref diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index 8e85820f..0909b49d 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.6.0" + VERSION = "1.6.1" end From 6cf547355c5e905eb9a0ec9ef89c134354bbbce7 Mon Sep 17 00:00:00 2001 From: James Stonehill Date: Tue, 9 Oct 2018 22:25:45 +0100 Subject: [PATCH 022/118] Raise client error for unhandled 4xx responses --- lib/json_api_client/middleware/status.rb | 2 +- test/unit/status_test.rb | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/json_api_client/middleware/status.rb b/lib/json_api_client/middleware/status.rb index 033bd3da..99a0c644 100644 --- a/lib/json_api_client/middleware/status.rb +++ b/lib/json_api_client/middleware/status.rb @@ -29,7 +29,7 @@ def handle_status(code, env) when 409 raise Errors::Conflict, env when 400..499 - # some other error + raise Errors::ClientError, env when 500..599 raise Errors::ServerError, env else diff --git a/test/unit/status_test.rb b/test/unit/status_test.rb index e28880bd..e33829e4 100644 --- a/test/unit/status_test.rb +++ b/test/unit/status_test.rb @@ -56,4 +56,17 @@ def test_server_responding_with_404_status_meta end end + def test_server_responding_with_408_status + stub_request(:get, "http://example.com/users/1") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + meta: { + status: 408, + message: "Request timeout" + } + }.to_json) + + assert_raises JsonApiClient::Errors::ClientError do + User.find(1) + end + end end From 0fecb2f34775c353fb5ff994b0fb1714eccc8626 Mon Sep 17 00:00:00 2001 From: James Stonehill Date: Wed, 10 Oct 2018 19:02:03 +0100 Subject: [PATCH 023/118] Add PR #311 to CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79112f2e..898f5c73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- [#311](https://githup.com/JsonApiClient/json_api_client/pull/311) - Raise JsonApiClient::Errors::ClientError for unhandled 4xx responses + ## v1.6.1 - [#297](https://githup.com/JsonApiClient/json_api_client/pull/297) - Fix test_helper From 845f80925262ff1be953eadbca52aed5eda07683 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Wed, 10 Oct 2018 11:25:45 -0700 Subject: [PATCH 024/118] version bump v1.6.2 --- lib/json_api_client/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index 0909b49d..83c41cae 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.6.1" + VERSION = "1.6.2" end From 118fa6cd3a9a5445f4e7ad9085efa680f5828d2d Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Wed, 10 Oct 2018 11:28:23 -0700 Subject: [PATCH 025/118] updating chantgelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 898f5c73..f1d90653 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## v1.6.2 + - [#311](https://githup.com/JsonApiClient/json_api_client/pull/311) - Raise JsonApiClient::Errors::ClientError for unhandled 4xx responses ## v1.6.1 From 99d1de50a764903a3f886e6b3c61cf874960c760 Mon Sep 17 00:00:00 2001 From: James Stonehill Date: Thu, 11 Oct 2018 15:54:32 +0100 Subject: [PATCH 026/118] Do not raise errors on 422 responses JSON APIs often return validation errors with 422 so the client would be notified that the response was not successful by the existence of the resource errors. --- lib/json_api_client/middleware/status.rb | 2 ++ test/unit/status_test.rb | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/lib/json_api_client/middleware/status.rb b/lib/json_api_client/middleware/status.rb index 99a0c644..9f5909a9 100644 --- a/lib/json_api_client/middleware/status.rb +++ b/lib/json_api_client/middleware/status.rb @@ -28,6 +28,8 @@ def handle_status(code, env) raise Errors::NotFound, env[:url] when 409 raise Errors::Conflict, env + when 422 + # Allow to proceed as resource errors will be populated when 400..499 raise Errors::ClientError, env when 500..599 diff --git a/test/unit/status_test.rb b/test/unit/status_test.rb index e33829e4..848fa2f9 100644 --- a/test/unit/status_test.rb +++ b/test/unit/status_test.rb @@ -69,4 +69,16 @@ def test_server_responding_with_408_status User.find(1) end end + + def test_server_responding_with_422_status + stub_request(:get, "http://example.com/users/1") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + meta: { + status: 422 + } + }.to_json) + + # We want to test that this response does not raise an error + User.find(1) + end end From 2de436435ae7f1345809f8588d68a4ade812dde9 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Fri, 12 Oct 2018 13:14:40 -0700 Subject: [PATCH 027/118] verion bump v1.6.3 --- CHANGELOG.md | 4 ++++ lib/json_api_client/version.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1d90653..c3c6a954 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## v1.6.3 + +- [#211](https://githup.com/JsonApiClient/json_api_client/pull/312) - Don't raise on `422` + ## v1.6.2 - [#311](https://githup.com/JsonApiClient/json_api_client/pull/311) - Raise JsonApiClient::Errors::ClientError for unhandled 4xx responses diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index 83c41cae..d6ed79c3 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.6.2" + VERSION = "1.6.3" end From 34601622bee659e2bed2b103d005cec3b7bebeff Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Fri, 12 Oct 2018 13:16:20 -0700 Subject: [PATCH 028/118] fixing typo in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3c6a954..f0fd8af1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ## v1.6.3 -- [#211](https://githup.com/JsonApiClient/json_api_client/pull/312) - Don't raise on `422` +- [#312](https://githup.com/JsonApiClient/json_api_client/pull/312) - Don't raise on `422` ## v1.6.2 From 387c6b06b17b66dcacfd9b69cde8008a257b8f83 Mon Sep 17 00:00:00 2001 From: Hashrocket Workstation Date: Tue, 23 Oct 2018 11:42:21 -0400 Subject: [PATCH 029/118] Mimics ActiveRecord behavior when destroying resource * Add `destroyed?` method * Return false on `persisted?` * Do not clear resource attributes Co-authored-by: Gabriel Reis --- lib/json_api_client/resource.rb | 19 ++++++++++++++++--- test/unit/creation_test.rb | 2 ++ test/unit/destroying_test.rb | 19 +++++++++++++++++-- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index cb176ca9..c43b8282 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -315,6 +315,7 @@ def _build_connection(rebuild = false) def initialize(params = {}) params = params.symbolize_keys @persisted = nil + @destroyed = nil self.links = self.class.linker.new(params.delete(:links) || {}) self.relationships = self.class.relationship_linker.new(self.class, params.delete(:relationships) || {}) self.attributes = self.class.default_attributes.merge(params) @@ -355,14 +356,26 @@ def mark_as_persisted! # # @return [Boolean] def persisted? - !!@persisted && has_attribute?(self.class.primary_key) + !!@persisted && !destroyed? && has_attribute?(self.class.primary_key) + end + + # Mark the record as destroyed + def mark_as_destroyed! + @destroyed = true + end + + # Whether or not this record has been destroyed to the database previously + # + # @return [Boolean] + def destroyed? + !!@destroyed end # Returns true if this is a new record (never persisted to the database) # # @return [Boolean] def new_record? - !persisted? + !persisted? && !destroyed? end # When we represent this resource as a relationship, we do so with id & type @@ -449,7 +462,7 @@ def destroy fill_errors false else - self.attributes.clear + mark_as_destroyed! self.relationships.attributes.clear self.relationships.last_result_set = nil _clear_cached_relationships diff --git a/test/unit/creation_test.rb b/test/unit/creation_test.rb index b5e3f2d9..abcf78cd 100644 --- a/test/unit/creation_test.rb +++ b/test/unit/creation_test.rb @@ -69,8 +69,10 @@ def test_can_create_with_new_record_and_save title: "Rails is Omakase" }) + assert article.new_record? assert article.save assert article.persisted? + assert_equal(false, article.new_record?) assert_equal "1", article.id end diff --git a/test/unit/destroying_test.rb b/test/unit/destroying_test.rb index 5b2896aa..fc52d72f 100644 --- a/test/unit/destroying_test.rb +++ b/test/unit/destroying_test.rb @@ -11,14 +11,29 @@ class CallbackTest < TestResource end def test_destroy - stub_request(:delete, "http://example.com/users/6") + stub_request(:get, "http://example.com/users/1") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [ + {id: 1, attributes: {name: "Jeff Ching", email_address: "ching.jeff@gmail.com"}} + ] + }.to_json) + + users = User.find(1) + user = users.first + assert(user.persisted?) + assert_equal(false, user.new_record?) + assert_equal(false, user.destroyed?) + + stub_request(:delete, "http://example.com/users/1") .to_return(headers: {content_type: "application/vnd.api+json"}, body: { data: [] }.to_json) - user = User.new(id: 6) assert(user.destroy, "successful deletion should return truish value") assert_equal(false, user.persisted?) + assert_equal(false, user.new_record?) + assert(user.destroyed?) + assert_equal(1, user.id) end def test_destroy_no_content From 08b91eed921193121981ff9f4730d4da6e415ca7 Mon Sep 17 00:00:00 2001 From: Hashrocket Workstation Date: Thu, 25 Oct 2018 14:53:21 -0400 Subject: [PATCH 030/118] Do not clear relationships on destroy Co-authored-by: Mary Lee Co-authored-by: Gabriel Reis --- lib/json_api_client/resource.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index c43b8282..fd531f52 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -463,7 +463,6 @@ def destroy false else mark_as_destroyed! - self.relationships.attributes.clear self.relationships.last_result_set = nil _clear_cached_relationships true From 3c8edef86661bfffc7897354257afea67d2e572d Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Fri, 26 Oct 2018 13:43:59 -0700 Subject: [PATCH 031/118] readme update; changelog update; version bump v1.6.4 --- CHANGELOG.md | 7 +++++++ README.md | 15 +++++++++++++++ lib/json_api_client/version.rb | 2 +- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0fd8af1..cb0b64ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +## v1.6.4 +- [#314](https://github.com/JsonApiClient/json_api_client/pull/314) - Mimic ActiveRecord behavior when destroying a resource: + * Add `destroyed?` method + * Do not clear resource attributes + * Return `false` on `persisted?` after being destroyed + * Return `false` on `new_record?` after being destroyed + ## v1.6.3 - [#312](https://githup.com/JsonApiClient/json_api_client/pull/312) - Don't raise on `422` diff --git a/README.md b/README.md index f94eb5c6..dc63932f 100644 --- a/README.md +++ b/README.md @@ -39,14 +39,29 @@ MyApi::Article.where(author_id: 1).all MyApi::Person.where(name: "foo").order(created_at: :desc).includes(:preferences, :cars).all u = MyApi::Person.new(first_name: "bar", last_name: "foo") +u.new_record? +# => true u.save +u.new_record? +# => false + u = MyApi::Person.find(1).first u.update_attributes( a: "b", c: "d" ) +u.persisted? +# => true + +u.destroy + +u.destroyed? +# => true +u.persisted? +# => false + u = MyApi::Person.create( a: "b", c: "d" diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index d6ed79c3..35292c1c 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.6.3" + VERSION = "1.6.4" end From dfdbd64b1967cffe630f10cae87ff633a7e17e5e Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Fri, 26 Oct 2018 12:24:40 +0300 Subject: [PATCH 032/118] add shallow path for belongs_to decouple attributes and path params fix #180 --- README.md | 28 ++++++- .../associations/belongs_to.rb | 14 +++- lib/json_api_client/helpers/associatable.rb | 24 ++++++ lib/json_api_client/query/builder.rb | 10 ++- lib/json_api_client/query/requestor.rb | 6 +- lib/json_api_client/resource.rb | 13 ++-- test/unit/association_test.rb | 73 +++++++++++++++++++ 7 files changed, 155 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index dc63932f..4c4c9b22 100644 --- a/README.md +++ b/README.md @@ -157,13 +157,17 @@ articles.links.related You can force nested resource paths for your models by using a `belongs_to` association. -**Note: Using belongs_to is only necessary for setting a nested path.** +**Note: Using belongs_to is only necessary for setting a nested path unless you provide `shallow_path: true` option.** ```ruby module MyApi class Account < JsonApiClient::Resource belongs_to :user end + + class Customer < JsonApiClient::Resource + belongs_to :user, shallow_path: true + end end # try to find without the nested parameter @@ -173,6 +177,28 @@ MyApi::Account.find(1) # makes request to /users/2/accounts/1 MyApi::Account.where(user_id: 2).find(1) # => returns ResultSet + +# makes request to /customers/1 +MyApi::Customer.find(1) +# => returns ResultSet + +# makes request to /users/2/customers/1 +MyApi::Customer.where(user_id: 2).find(1) +# => returns ResultSet +``` + +you can also override param name for `belongs_to` association + +```ruby +module MyApi + class Account < JsonApiClient::Resource + belongs_to :user, param: :customer_id + end +end + +# makes request to /users/2/accounts/1 +MyApi::Account.where(customer_id: 2).find(1) +# => returns ResultSet ``` ## Custom Methods diff --git a/lib/json_api_client/associations/belongs_to.rb b/lib/json_api_client/associations/belongs_to.rb index 8f9b27ec..a18cfa67 100644 --- a/lib/json_api_client/associations/belongs_to.rb +++ b/lib/json_api_client/associations/belongs_to.rb @@ -3,8 +3,17 @@ module Associations module BelongsTo class Association < BaseAssociation include Helpers::URI - def param - :"#{attr_name}_id" + + attr_reader :param + + def initialize(attr_name, klass, options = {}) + super + @param = options.fetch(:param, :"#{attr_name}_id").to_sym + @shallow_path = options.fetch(:shallow_path, false) + end + + def shallow_path? + @shallow_path end def to_prefix_path(formatter) @@ -12,6 +21,7 @@ def to_prefix_path(formatter) end def set_prefix_path(attrs, formatter) + return if shallow_path? && !attrs[param] attrs[param] = encode_part(attrs[param]) if attrs.key?(param) to_prefix_path(formatter) % attrs end diff --git a/lib/json_api_client/helpers/associatable.rb b/lib/json_api_client/helpers/associatable.rb index ba6a4d8c..ce60f029 100644 --- a/lib/json_api_client/helpers/associatable.rb +++ b/lib/json_api_client/helpers/associatable.rb @@ -7,6 +7,7 @@ module Associatable class_attribute :associations, instance_accessor: false self.associations = [] attr_accessor :__cached_associations + attr_accessor :__belongs_to_params end module ClassMethods @@ -14,6 +15,10 @@ def _define_association(attr_name, association_klass, options = {}) attr_name = attr_name.to_sym association = association_klass.new(attr_name, self, options) self.associations += [association] + end + + def _define_relationship_methods(attr_name) + attr_name = attr_name.to_sym define_method(attr_name) do _cached_relationship(attr_name) do @@ -31,17 +36,36 @@ def _define_association(attr_name, association_klass, options = {}) def belongs_to(attr_name, options = {}) _define_association(attr_name, JsonApiClient::Associations::BelongsTo::Association, options) + + param = associations.last.param + define_method(param) do + _belongs_to_params[param] + end + + define_method(:"#{param}=") do |value| + _belongs_to_params[param] = value + end end def has_many(attr_name, options = {}) _define_association(attr_name, JsonApiClient::Associations::HasMany::Association, options) + _define_relationship_methods(attr_name) end def has_one(attr_name, options = {}) _define_association(attr_name, JsonApiClient::Associations::HasOne::Association, options) + _define_relationship_methods(attr_name) end end + def _belongs_to_params + self.__belongs_to_params ||= {} + end + + def _clear_belongs_to_params + self.__belongs_to_params = {} + end + def _cached_associations self.__cached_associations ||= {} end diff --git a/lib/json_api_client/query/builder.rb b/lib/json_api_client/query/builder.rb index 7c4d9d05..44e48a42 100644 --- a/lib/json_api_client/query/builder.rb +++ b/lib/json_api_client/query/builder.rb @@ -1,3 +1,5 @@ +require 'active_support/all' + module JsonApiClient module Query class Builder @@ -64,8 +66,12 @@ def last paginate(page: 1, per_page: 1).pages.last.to_a.last end - def build - klass.new(params) + def build(attrs = {}) + klass.new @path_params.merge(attrs.symbolize_keys) + end + + def create(attrs = {}) + klass.create @path_params.merge(attrs.symbolize_keys) end def params diff --git a/lib/json_api_client/query/requestor.rb b/lib/json_api_client/query/requestor.rb index 56ab54c6..6d0704bb 100644 --- a/lib/json_api_client/query/requestor.rb +++ b/lib/json_api_client/query/requestor.rb @@ -10,14 +10,14 @@ def initialize(klass) # expects a record def create(record) - request(:post, klass.path(record.attributes), { + request(:post, klass.path(record.path_attributes), { body: { data: record.as_json_api }, params: record.request_params.to_params }) end def update(record) - request(:patch, resource_path(record.attributes), { + request(:patch, resource_path(record.path_attributes), { body: { data: record.as_json_api }, params: record.request_params.to_params }) @@ -30,7 +30,7 @@ def get(params = {}) end def destroy(record) - request(:delete, resource_path(record.attributes)) + request(:delete, resource_path(record.path_attributes)) end def linked(path) diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index fd531f52..ea5b0d8a 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -318,7 +318,8 @@ def initialize(params = {}) @destroyed = nil self.links = self.class.linker.new(params.delete(:links) || {}) self.relationships = self.class.relationship_linker.new(self.class, params.delete(:relationships) || {}) - self.attributes = self.class.default_attributes.merge(params) + self.attributes = self.class.default_attributes.merge params.except(*self.class.prefix_params) + self.__belongs_to_params = params.slice(*self.class.prefix_params) setup_default_properties @@ -465,6 +466,7 @@ def destroy mark_as_destroyed! self.relationships.last_result_set = nil _clear_cached_relationships + _clear_belongs_to_params true end end @@ -498,6 +500,10 @@ def reset_request_select!(*resource_types) self end + def path_attributes + _belongs_to_params.merge attributes.slice('id').symbolize_keys + end + protected def setup_default_properties @@ -566,10 +572,7 @@ def association_for(name) end def non_serializing_attributes - [ - self.class.read_only_attributes, - self.class.prefix_params.map(&:to_s) - ].flatten + self.class.read_only_attributes end def attributes_for_serialization diff --git a/test/unit/association_test.rb b/test/unit/association_test.rb index 1fdd3833..6031f226 100644 --- a/test/unit/association_test.rb +++ b/test/unit/association_test.rb @@ -13,6 +13,10 @@ class Specified < TestResource has_many :bars, class_name: "Owner" end +class Shallowed < TestResource + belongs_to :foo, class_name: "Property", shallow_path: true +end + class PrefixedOwner < TestResource has_many :prefixed_properties end @@ -630,6 +634,14 @@ def test_belongs_to_path assert_equal("foos/%D0%99%D0%A6%D0%A3%D0%9A%D0%95%D0%9D/specifieds", Specified.path({foo_id: 'ЙЦУКЕН'})) end + def test_belongs_to_shallowed_path + assert_equal([:foo_id], Shallowed.prefix_params) + assert_equal "shalloweds", Shallowed.path({}) + assert_equal("foos/%{foo_id}/shalloweds", Shallowed.path) + assert_equal("foos/1/shalloweds", Shallowed.path({foo_id: 1})) + assert_equal("foos/%D0%99%D0%A6%D0%A3%D0%9A%D0%95%D0%9D/shalloweds", Shallowed.path({foo_id: 'ЙЦУКЕН'})) + end + def test_find_belongs_to stub_request(:get, "http://example.com/foos/1/specifieds") .to_return(headers: {content_type: "application/vnd.api+json"}, body: { @@ -642,6 +654,30 @@ def test_find_belongs_to assert_equal(1, specifieds.length) end + def test_find_belongs_to_shallowed + stub_request(:get, "http://example.com/foos/1/shalloweds") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [ + { id: 1, type: "shalloweds", attributes: { name: "nested" } } + ] + }.to_json) + + stub_request(:get, "http://example.com/shalloweds") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [ + { id: 1, type: "shalloweds", attributes: { name: "global" } } + ] + }.to_json) + + nested_records = Shallowed.where(foo_id: 1).all + assert_equal(1, nested_records.length) + assert_equal("nested", nested_records.first.name) + + global_records = Shallowed.all + assert_equal(1, global_records.length) + assert_equal("global", global_records.first.name) + end + def test_can_handle_creating stub_request(:post, "http://example.com/foos/10/specifieds") .to_return(headers: {content_type: "application/vnd.api+json"}, body: { @@ -657,6 +693,28 @@ def test_can_handle_creating }) end + def test_can_handle_creating_shallowed + stub_request(:post, "http://example.com/foos/10/shalloweds") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: { id: 12, type: "shalloweds", attributes: { name: "nested" } } + }.to_json) + + stub_request(:post, "http://example.com/shalloweds") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: { id: 13, type: "shalloweds", attributes: { name: "global" } } + }.to_json) + + Shallowed.create({ + :id => 12, + :foo_id => 10, + :name => "nested" + }) + Shallowed.create({ + :id => 13, + :name => "global" + }) + end + def test_find_belongs_to_params_unchanged stub_request(:get, "http://example.com/foos/1/specifieds") .to_return(headers: { @@ -692,4 +750,19 @@ def test_nested_create Specified.create(foo_id: 1) end + def test_nested_create_from_scope + stub_request(:post, "http://example.com/foos/1/specifieds") + .to_return(headers: { + content_type: "application/vnd.api+json" + }, body: { + data: { + id: 1, + name: "Jeff Ching", + bars: [{id: 1, attributes: {address: "123 Main St."}}] + } + }.to_json) + + Specified.where(foo_id: 1).create + end + end From 17b9a05854b96c9f5102dd3831eb2ba78adaccc6 Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Thu, 1 Nov 2018 11:57:18 +0200 Subject: [PATCH 033/118] add tests for update resource for correct relationships dirty behavior ensures fetched relationships will not be added to payload --- test/unit/updating_test.rb | 134 +++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/test/unit/updating_test.rb b/test/unit/updating_test.rb index 445c1cdb..08a4e78f 100644 --- a/test/unit/updating_test.rb +++ b/test/unit/updating_test.rb @@ -8,6 +8,9 @@ def relationships_for_serialization end end + class Editor < TestResource + end + class CallbackTest < TestResource include JsonApiClient::Helpers::Callbacks before_update do @@ -957,4 +960,135 @@ def test_can_update_with_includes_and_fields_with_keep_request_params Article.keep_request_params = false end + def test_fetch_with_relationships_and_update_attribute + stub_request(:get, "http://example.com/authors/1?include=editor") + .to_return(headers: { + content_type: "application/vnd.api+json" + }, body: { + data: { + type: "authors", + id: "1", + attributes: { + name: "John Doe" + }, + relationships: { + editor: { + links: { + self: "/articles/1/links/editor", + related: "/articles/1/editor" + }, + data: {id: "2", type: "editors"} + } + } + } + }.to_json) + + authors = Author.includes(:editor).find(1) + author = authors.first + + stub_request(:patch, "http://example.com/authors/1") + .with(headers: { + content_type: "application/vnd.api+json", + accept: "application/vnd.api+json" + }, body: { + data: { + id: "1", + type: "authors", + attributes: { + name: "Jane Doe" + } + } + }.to_json) + .to_return(headers: { + status: 200, + content_type: "application/vnd.api+json" + }, body: { + data: { + type: "authors", + id: "1", + relationships: { + editor: { + links: { + self: "/articles/1/links/editor", + related: "/articles/1/editor" + } + } + }, + attributes: { + name: "Jane Doe" + } + } + }.to_json) + + author.name = "Jane Doe" + assert author.save + end + + def test_fetch_with_relationships_and_update_relationships + stub_request(:get, "http://example.com/authors/1?include=editor") + .to_return(headers: { + content_type: "application/vnd.api+json" + }, body: { + data: { + type: "authors", + id: "1", + attributes: { + name: "John Doe" + }, + relationships: { + editor: { + links: { + self: "/articles/1/links/editor", + related: "/articles/1/editor" + }, + data: {id: "2", type: "editors"} + } + } + } + }.to_json) + + authors = Author.includes(:editor).find(1) + author = authors.first + + stub_request(:patch, "http://example.com/authors/1") + .with(headers: { + content_type: "application/vnd.api+json", + accept: "application/vnd.api+json" + }, body: { + data: { + id: "1", + type: "authors", + relationships: { + editor: { + data: {type: "editors", id: "3"} + } + }, + attributes: {} + } + }.to_json) + .to_return(headers: { + status: 200, + content_type: "application/vnd.api+json" + }, body: { + data: { + type: "authors", + id: "1", + relationships: { + editor: { + links: { + self: "/articles/1/links/editor", + related: "/articles/1/editor" + } + } + }, + attributes: { + name: "John Doe" + } + } + }.to_json) + + author.relationships.editor = Editor.new(id: '3') + assert author.save + end + end From 524f9681057c5bf3caf478c5665d24db1e851172 Mon Sep 17 00:00:00 2001 From: Fletcher Fowler Date: Wed, 31 Oct 2018 13:41:26 -0700 Subject: [PATCH 034/118] Allow custom error messages * Adds ability to override key in resource to surface different error messages --- lib/json_api_client/resource.rb | 6 ++++- test/unit/error_collector_test.rb | 42 +++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index cb176ca9..f9bc0af2 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -568,10 +568,14 @@ def relationships_for_serialization relationships.as_json_api end + def error_message_for(error) + error.error_msg + end + def fill_errors last_result_set.errors.each do |error| key = self.class.key_formatter.unformat(error.error_key) - errors.add(key, error.error_msg) + errors.add(key, error_message_for(error)) end end end diff --git a/test/unit/error_collector_test.rb b/test/unit/error_collector_test.rb index 6db9f08d..ae166763 100644 --- a/test/unit/error_collector_test.rb +++ b/test/unit/error_collector_test.rb @@ -215,6 +215,48 @@ def test_can_handle_parameter_error assert_equal "include", error.source_parameter end + describe 'custom error_msg_key' do + class CustomErrorArticle < TestResource + def error_message_for(error) + error.detail + end + end + + def test_can_handle_custom_parameter_error + stub_request(:post, "http://example.com/custom_error_articles") + .with(headers: {content_type: "application/vnd.api+json", accept: "application/vnd.api+json"}, body: { + data: { + type: "custom_error_articles", + attributes: { + title: "Rails is Omakase", + email_address: "bar" + } + } + }.to_json) + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + errors: [ + { + id: "1234-abcd", + status: "400", + code: "1337", + title: "bar is required", + detail: "bar include is required for creation", + source: { + parameter: "include" + } + } + ] + }.to_json) + + article = CustomErrorArticle.create({ + title: "Rails is Omakase", + email_address: "bar" + }) + + assert_equal ["bar include is required for creation"], article.errors[:base] + end + end + def test_can_handle_explicit_null_error_values stub_request(:post, "http://example.com/articles") .with(headers: {content_type: "application/vnd.api+json", accept: "application/vnd.api+json"}, body: { From ce8963ced680eb55131057189c5bcc2e70f8c2d9 Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Thu, 8 Nov 2018 17:59:04 +0200 Subject: [PATCH 035/118] 290 fix passing relationships on create also fix false positive tests --- .../relationships/relations.rb | 1 - lib/json_api_client/resource.rb | 1 + test/unit/creation_test.rb | 20 +++++++++++-------- test/unit/updating_test.rb | 15 ++++++++++++-- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/lib/json_api_client/relationships/relations.rb b/lib/json_api_client/relationships/relations.rb index 9d14a937..a51033d2 100644 --- a/lib/json_api_client/relationships/relations.rb +++ b/lib/json_api_client/relationships/relations.rb @@ -11,7 +11,6 @@ class Relations def initialize(record_class, relations) @record_class = record_class self.attributes = relations - clear_changes_information end def present? diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index ea5b0d8a..4ec14576 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -98,6 +98,7 @@ def load(params) new(params).tap do |resource| resource.mark_as_persisted! resource.clear_changes_information + resource.relationships.clear_changes_information end end diff --git a/test/unit/creation_test.rb b/test/unit/creation_test.rb index abcf78cd..9932469f 100644 --- a/test/unit/creation_test.rb +++ b/test/unit/creation_test.rb @@ -18,8 +18,7 @@ def after_create_method class Author < TestResource end - def setup - super + def stub_simple_creation stub_request(:post, "http://example.com/articles") .with(headers: {content_type: "application/vnd.api+json", accept: "application/vnd.api+json"}, body: { data: { @@ -47,6 +46,7 @@ def setup end def test_can_create_with_class_method + stub_simple_creation article = Article.create({ title: "Rails is Omakase" }) @@ -57,6 +57,7 @@ def test_can_create_with_class_method end def test_changed_attributes_empty_after_create_with_class_method + stub_simple_creation article = Article.create({ title: "Rails is Omakase" }) @@ -65,6 +66,7 @@ def test_changed_attributes_empty_after_create_with_class_method end def test_can_create_with_new_record_and_save + stub_simple_creation article = Article.new({ title: "Rails is Omakase" }) @@ -139,6 +141,7 @@ def test_can_create_with_includes_and_fields end def test_can_create_with_links + stub_simple_creation article = Article.new({ title: "Rails is Omakase" }) @@ -234,6 +237,7 @@ def test_correct_create_with_nil_attribute_value end def test_changed_attributes_empty_after_create_with_new_record_and_save + stub_simple_creation article = Article.new({title: "Rails is Omakase"}) article.save @@ -278,18 +282,18 @@ def test_create_with_relationships_in_payload .with(headers: {content_type: 'application/vnd.api+json', accept: 'application/vnd.api+json'}, body: { data: { type: 'articles', - attributes: { - title: 'Rails is Omakase' - }, relationships: { comments: { data: [ { - id: '2', - type: 'comments' + type: 'comments', + id: '2' } ] } + }, + attributes: { + title: 'Rails is Omakase' } } }.to_json) @@ -303,7 +307,7 @@ def test_create_with_relationships_in_payload } }.to_json) - article = Article.new(title: 'Rails is Omakase', relationships: {comments: [Comment.new(id: 2)]}) + article = Article.new(title: 'Rails is Omakase', relationships: {comments: [Comment.new(id: '2')]}) assert article.save assert article.persisted? diff --git a/test/unit/updating_test.rb b/test/unit/updating_test.rb index 08a4e78f..4a96f32f 100644 --- a/test/unit/updating_test.rb +++ b/test/unit/updating_test.rb @@ -23,8 +23,7 @@ def after_save_method end end - def setup - super + def stub_simple_fetch stub_request(:get, "http://example.com/articles/1") .to_return(headers: {content_type: "application/vnd.api+json"}, body: { data: { @@ -38,6 +37,7 @@ def setup end def test_can_update_found_record + stub_simple_fetch articles = Article.find(1) article = articles.first @@ -69,6 +69,7 @@ def test_can_update_found_record end def test_changed_attributes_blank_after_update + stub_simple_fetch articles = Article.find(1) article = articles.first @@ -109,6 +110,7 @@ def test_changed_attributes_blank_after_update end def test_can_update_found_record_in_bulk + stub_simple_fetch articles = Article.find(1) article = articles.first @@ -141,6 +143,7 @@ def test_can_update_found_record_in_bulk end def test_can_update_found_record_in_builk_using_update_method + stub_simple_fetch articles = Article.find(1) article = articles.first @@ -173,6 +176,7 @@ def test_can_update_found_record_in_builk_using_update_method end def test_can_update_single_relationship + stub_simple_fetch articles = Article.find(1) article = articles.first @@ -216,6 +220,7 @@ def test_can_update_single_relationship end def test_can_update_single_relationship_via_setter + stub_simple_fetch articles = Article.find(1) article = articles.first @@ -259,6 +264,7 @@ def test_can_update_single_relationship_via_setter end def test_can_update_single_relationship_with_all_attributes_dirty + stub_simple_fetch articles = Article.find(1) article = articles.first @@ -311,6 +317,7 @@ def test_can_update_single_relationship_with_all_attributes_dirty end def test_can_update_has_many_relationships + stub_simple_fetch articles = Article.find(1) article = articles.first @@ -360,6 +367,7 @@ def test_can_update_has_many_relationships end def test_can_update_has_many_relationships_via_setter + stub_simple_fetch articles = Article.find(1) article = articles.first @@ -409,6 +417,7 @@ def test_can_update_has_many_relationships_via_setter end def test_can_update_has_many_relationships_with_all_attributes_dirty + stub_simple_fetch articles = Article.find(1) article = articles.first @@ -757,6 +766,7 @@ def test_callbacks_on_update end def test_can_update_with_includes_and_fields + stub_simple_fetch stub_request(:patch, "http://example.com/articles/1") .with( headers: {content_type: "application/vnd.api+json", accept: "application/vnd.api+json"}, @@ -844,6 +854,7 @@ def test_can_update_with_includes_and_fields end def test_can_update_with_includes_and_fields_with_keep_request_params + stub_simple_fetch stub_request(:patch, "http://example.com/articles/1") .with( headers: {content_type: "application/vnd.api+json", accept: "application/vnd.api+json"}, From d801b866133ff2ce03f278adaa3125da249f990d Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Tue, 13 Nov 2018 12:21:17 +0200 Subject: [PATCH 036/118] add missing changelog add #315 and #320 to unreleased section of CHANGELOG.md --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb0b64ea..191a24d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +- [#320](https://github.com/JsonApiClient/json_api_client/pull/320) - fix passing relationships on create + * fix relationships passing to `new` and `create` methods + * fix false positive tests on create + * refactor tests on create/update to prevent false same positive tests in future + +- [#315](https://github.com/JsonApiClient/json_api_client/pull/315) - add shallow_path feature to belongs_to + * add `shallow_path` options to belongs_to to use model w/ and w/o nesting in parent resource + ## v1.6.4 - [#314](https://github.com/JsonApiClient/json_api_client/pull/314) - Mimic ActiveRecord behavior when destroying a resource: * Add `destroyed?` method From 0f9da74e341ad214f11c92d7cf5f11cb82bef416 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Sat, 24 Nov 2018 08:12:21 -0800 Subject: [PATCH 037/118] version bump: 1.7.0 --- CHANGELOG.md | 2 ++ lib/json_api_client/version.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 191a24d1..fd046d72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 1.7.0 + - [#320](https://github.com/JsonApiClient/json_api_client/pull/320) - fix passing relationships on create * fix relationships passing to `new` and `create` methods * fix false positive tests on create diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index 35292c1c..0d56a3d8 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.6.4" + VERSION = "1.7.0" end From 710cbb959843e3dcc3ab227c2a79788b1be3a19e Mon Sep 17 00:00:00 2001 From: Josh Mostafa Date: Tue, 4 Dec 2018 12:05:56 +1100 Subject: [PATCH 038/118] Retain context from Faraday errors --- lib/json_api_client/errors.rb | 12 ++++++------ lib/json_api_client/middleware/status.rb | 4 ++-- test/unit/errors_test.rb | 6 ++++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/json_api_client/errors.rb b/lib/json_api_client/errors.rb index 3eb703be..5d8d6aa6 100644 --- a/lib/json_api_client/errors.rb +++ b/lib/json_api_client/errors.rb @@ -2,7 +2,8 @@ module JsonApiClient module Errors class ApiError < StandardError attr_reader :env - def initialize(env) + def initialize(env, msg = nil) + super msg @env = env end end @@ -20,14 +21,14 @@ class ConnectionError < ApiError end class ServerError < ApiError - def message - "Internal server error" + def initialize(env, msg = 'Internal server error') + super env, msg end end class Conflict < ServerError - def message - "Resource already exists" + def initialize(env, msg = 'Resource already exists') + super env, msg end end @@ -51,6 +52,5 @@ def message "Unexpected response status: #{code} from: #{uri.to_s}" end end - end end diff --git a/lib/json_api_client/middleware/status.rb b/lib/json_api_client/middleware/status.rb index 9f5909a9..ad31de29 100644 --- a/lib/json_api_client/middleware/status.rb +++ b/lib/json_api_client/middleware/status.rb @@ -11,8 +11,8 @@ def call(environment) handle_status(code, env) end end - rescue Faraday::ConnectionFailed, Faraday::TimeoutError - raise Errors::ConnectionError, environment + rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e + raise Errors::ConnectionError.new environment, e.to_s end protected diff --git a/test/unit/errors_test.rb b/test/unit/errors_test.rb index 3e8f2c32..d11e2ff5 100644 --- a/test/unit/errors_test.rb +++ b/test/unit/errors_test.rb @@ -4,11 +4,13 @@ class ErrorsTest < MiniTest::Test def test_connection_errors stub_request(:get, "http://example.com/users") - .to_raise(Faraday::ConnectionFailed) + .to_raise(Faraday::ConnectionFailed.new("specific message")) - assert_raises JsonApiClient::Errors::ConnectionError do + err = assert_raises JsonApiClient::Errors::ConnectionError do User.all end + + assert_match /specific message/, err.message end def test_timeout_errors From fcbdf1381b044fb268e094afdae40ce617655388 Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Fri, 11 Jan 2019 12:55:26 +0200 Subject: [PATCH 039/118] fix travis for ruby 2.2 latest bundler drop support of ruby 2.2 we will install bundler < 2.0 for ruby < 2.3 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index daa06977..1ab55760 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,4 +26,4 @@ matrix: # We need to install latest version of bundler, because one in travis # image is too old to recognize platform => :mri_22 in Gemfile. before_install: - - gem install bundler + - gem install bundler || gem install bundler -v 1.17.3 From e8b0a0a61cbafa59ef69e7b19940c9c755e98ac7 Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Tue, 15 Jan 2019 13:01:50 +0200 Subject: [PATCH 040/118] add #316 to changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd046d72..2ecb38b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- [#316](https://github.com/JsonApiClient/json_api_client/pull/316) - Allow custom error messages + ## 1.7.0 - [#320](https://github.com/JsonApiClient/json_api_client/pull/320) - fix passing relationships on create From e5f82d9e709a56a90e0b1ef75d7304610a258a93 Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Wed, 3 Oct 2018 12:37:04 +0300 Subject: [PATCH 041/118] #299 optional get relationship from dataset --- CHANGELOG.md | 2 + lib/json_api_client/included_data.rb | 19 ++--- lib/json_api_client/resource.rb | 2 + test/unit/association_test.rb | 104 +++++++++++++++++++++++++++ 4 files changed, 119 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ecb38b1..e8fbab43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - [#316](https://github.com/JsonApiClient/json_api_client/pull/316) - Allow custom error messages +- [#305](https://github.com/JsonApiClient/json_api_client/pull/305) - optional search relationship data in result set + ## 1.7.0 - [#320](https://github.com/JsonApiClient/json_api_client/pull/320) - fix passing relationships on create diff --git a/lib/json_api_client/included_data.rb b/lib/json_api_client/included_data.rb index 2ae26591..c62e1f73 100644 --- a/lib/json_api_client/included_data.rb +++ b/lib/json_api_client/included_data.rb @@ -4,15 +4,18 @@ class IncludedData def initialize(result_set, data) record_class = result_set.record_class - grouped_data = data.group_by{|datum| datum["type"]} - @data = grouped_data.inject({}) do |h, (type, records)| + included_set = data.map do |datum| + type = datum["type"] klass = Utils.compute_type(record_class, record_class.key_formatter.unformat(type).singularize.classify) - h[type] = records.map do |datum| - params = klass.parser.parameters_from_resource(datum) - resource = klass.load(params) - resource.last_result_set = result_set - resource - end.index_by(&:id) + params = klass.parser.parameters_from_resource(datum) + resource = klass.load(params) + resource.last_result_set = result_set + resource + end + + included_set.concat(result_set) if record_class.search_included_in_result_set + @data = included_set.group_by(&:type).inject({}) do |h, (type, resources)| + h[type] = resources.index_by(&:id) h end end diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index 0c44f9d4..56991b2b 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -34,6 +34,7 @@ class Resource :route_format, :request_params_class, :keep_request_params, + :search_included_in_result_set, instance_accessor: false class_attribute :add_defaults_to_changes, instance_writer: false @@ -50,6 +51,7 @@ class Resource self.request_params_class = RequestParams self.keep_request_params = false self.add_defaults_to_changes = false + self.search_included_in_result_set = false #:underscored_key, :camelized_key, :dasherized_key, or custom self.json_key_format = :underscored_key diff --git a/test/unit/association_test.rb b/test/unit/association_test.rb index 6031f226..89624d0b 100644 --- a/test/unit/association_test.rb +++ b/test/unit/association_test.rb @@ -74,6 +74,10 @@ class UserAccount < TestResource property :balance end +class Employee < TestResource + has_one :chief, klass: 'Employee' +end + class AssociationTest < MiniTest::Test def test_default_properties_no_changes @@ -765,4 +769,104 @@ def test_nested_create_from_scope Specified.where(foo_id: 1).create end + def test_load_include_from_dataset + stub_request(:get, 'http://example.com/employees?include=chief&page[per_page]=2') + .to_return( + headers: { + content_type: 'application/vnd.api+json' + }, body: { + data: [ + { + id: '1', + type: 'employees', + attributes: { + name: 'John Doe' + }, + relationships: { + chief: { + data: {id: '2', type: 'employees'} + } + } + }, + { + id: '2', + attributes: { + name: 'Jane Doe' + }, + relationships: { + chief: { + data: {id: '3', type: 'employees'} + } + } + } + ], + included: [ + { + id: '3', + type: 'employees', + attributes: { + name: 'Richard Reed' + } + } + ] + }.to_json) + Employee.search_included_in_result_set = true + records = Employee.includes(:chief).per(2).to_a + assert_equal(2, records.size) + assert_equal('1', records.first.id) + assert_equal('2', records.second.id) + assert_equal('3', records.second.chief.id) + assert_equal('2', records.first.chief.id) + end + + def test_does_not_load_include_from_dataset + stub_request(:get, 'http://example.com/employees?include=chief&page[per_page]=2') + .to_return( + headers: { + content_type: 'application/vnd.api+json' + }, body: { + data: [ + { + id: '1', + type: 'employees', + attributes: { + name: 'John Doe' + }, + relationships: { + chief: { + data: {id: '2', type: 'employees'} + } + } + }, + { + id: '2', + attributes: { + name: 'Jane Doe' + }, + relationships: { + chief: { + data: {id: '3', type: 'employees'} + } + } + } + ], + included: [ + { + id: '3', + type: 'employees', + attributes: { + name: 'Richard Reed' + } + } + ] + }.to_json) + Employee.search_included_in_result_set = false + records = Employee.includes(:chief).per(2).to_a + assert_equal(2, records.size) + assert_equal('1', records.first.id) + assert_equal('2', records.second.id) + assert_equal('3', records.second.chief.id) + assert_nil(records.first.chief) + end + end From 826d3dc33f28336925ba457bf1df8add1e0d6c01 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Wed, 23 Jan 2019 05:51:35 -0800 Subject: [PATCH 042/118] version bump: 1.8.0 --- CHANGELOG.md | 2 ++ lib/json_api_client/version.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8fbab43..af31383b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 1.8.0 + - [#316](https://github.com/JsonApiClient/json_api_client/pull/316) - Allow custom error messages - [#305](https://github.com/JsonApiClient/json_api_client/pull/305) - optional search relationship data in result set diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index 0d56a3d8..8c766e48 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.7.0" + VERSION = "1.8.0" end From 238365ee1e187a3c5bbe16024829a985a0d43954 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Wed, 23 Jan 2019 07:43:40 -0800 Subject: [PATCH 043/118] adding test for destruction with custom primary key --- test/unit/destroying_test.rb | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/unit/destroying_test.rb b/test/unit/destroying_test.rb index fc52d72f..bc673a7e 100644 --- a/test/unit/destroying_test.rb +++ b/test/unit/destroying_test.rb @@ -36,6 +36,33 @@ def test_destroy assert_equal(1, user.id) end + def test_destroy_custom_primary_key + stub_request(:get, "http://example.com/user_preferences/105") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [ + {attributes: { user_id: 105, name: "Jeff Ching", email_address: "ching.jeff@gmail.com"}} + ] + }.to_json) + + $print_load = true + user_prefrence = UserPreference.find(105).first + assert(user_prefrence.persisted?) + $print_load = false + assert_equal(false, user_prefrence.new_record?) + assert_equal(false, user_prefrence.destroyed?) + + stub_request(:delete, "http://example.com/user_preferences/105") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [] + }.to_json) + + assert(user_prefrence.destroy, "successful deletion should return truish value") + assert_equal(false, user_prefrence.persisted?) + assert_equal(false, user_prefrence.new_record?) + assert(user_prefrence.destroyed?) + assert_equal(105, user_prefrence.user_id) + end + def test_destroy_no_content stub_request(:delete, "http://example.com/users/6") .to_return(headers: {content_type: "application/vnd.api+json"}, body: nil) From 0b9ead265927e9592d08f29aa5f3254b9b698414 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Wed, 23 Jan 2019 07:44:30 -0800 Subject: [PATCH 044/118] allowing resources to be deleted with a custom primary key --- lib/json_api_client/resource.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index 56991b2b..cae0f7e4 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -394,7 +394,7 @@ def as_relation # # @return [Hash] Representation of this object as JSONAPI object def as_json_api(*) - attributes.slice(:id, :type).tap do |h| + attributes.slice(self.class.primary_key, :type).tap do |h| relationships_for_serialization.tap do |r| h[:relationships] = self.class.key_formatter.format_keys(r) unless r.empty? end @@ -403,11 +403,11 @@ def as_json_api(*) end def as_json(*) - attributes.slice(:id, :type).tap do |h| + attributes.slice(self.class.primary_key, :type).tap do |h| relationships.as_json.tap do |r| h[:relationships] = r unless r.empty? end - h[:attributes] = attributes.except(:id, :type).as_json + h[:attributes] = attributes.except(self.class.primary_key, :type).as_json end end @@ -504,7 +504,7 @@ def reset_request_select!(*resource_types) end def path_attributes - _belongs_to_params.merge attributes.slice('id').symbolize_keys + _belongs_to_params.merge attributes.slice( self.class.primary_key ).symbolize_keys end protected From bea14d8d7bee723f5eb0e10e5729202d1887c653 Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Tue, 15 Jan 2019 13:57:33 +0200 Subject: [PATCH 045/118] allow custom type for models fix #288 --- CHANGELOG.md | 2 + README.md | 16 +++++++ lib/json_api_client/resource.rb | 7 +++ lib/json_api_client/utils.rb | 1 + test/test_helper.rb | 15 ++++++ test/unit/association_test.rb | 85 +++++++++++++++++++++++++++++++++ test/unit/creation_test.rb | 27 +++++++++++ test/unit/finding_test.rb | 22 +++++++++ 8 files changed, 175 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index af31383b..082e5bb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ - [#305](https://github.com/JsonApiClient/json_api_client/pull/305) - optional search relationship data in result set +- [#328](https://github.com/JsonApiClient/json_api_client/pull/328) - allow custom type for models + ## 1.7.0 - [#320](https://github.com/JsonApiClient/json_api_client/pull/320) - fix passing relationships on create diff --git a/README.md b/README.md index 4c4c9b22..d1425d5f 100644 --- a/README.md +++ b/README.md @@ -553,6 +553,22 @@ class MyApi::Base < JsonApiClient::Resource end ``` +### Custom type + +If your model must be named differently from classified type of resource you can easily customize it. +It will work both for defined and not defined relationships + +```ruby +class MyApi::Base < JsonApiClient::Resource + resolve_custom_type 'document--files', 'File' +end + +class MyApi::File < MyApi::Base + def self.resource_name + 'document--files' + end +end +``` ### Type Casting diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index 56991b2b..fa0d4211 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -35,6 +35,7 @@ class Resource :request_params_class, :keep_request_params, :search_included_in_result_set, + :custom_type_to_class, instance_accessor: false class_attribute :add_defaults_to_changes, instance_writer: false @@ -52,6 +53,7 @@ class Resource self.keep_request_params = false self.add_defaults_to_changes = false self.search_included_in_result_set = false + self.custom_type_to_class = {} #:underscored_key, :camelized_key, :dasherized_key, or custom self.json_key_format = :underscored_key @@ -63,6 +65,11 @@ class << self extend Forwardable def_delegators :_new_scope, :where, :order, :includes, :select, :all, :paginate, :page, :with_params, :first, :find, :last + def resolve_custom_type(type_name, class_name) + classified_type = key_formatter.unformat(type_name.to_s).singularize.classify + self.custom_type_to_class = custom_type_to_class.merge(classified_type => class_name.to_s) + end + # The table name for this resource. i.e. Article -> articles, Person -> people # # @return [String] The table name for this resource diff --git a/lib/json_api_client/utils.rb b/lib/json_api_client/utils.rb index 73fd2dd8..e91e566b 100644 --- a/lib/json_api_client/utils.rb +++ b/lib/json_api_client/utils.rb @@ -2,6 +2,7 @@ module JsonApiClient module Utils def self.compute_type(klass, type_name) + return klass.custom_type_to_class.fetch(type_name).constantize if klass.custom_type_to_class.key?(type_name) # If the type is prefixed with a scope operator then we assume that # the type_name is an absolute reference. return type_name.constantize if type_name.match(/^::/) diff --git a/test/test_helper.rb b/test/test_helper.rb index b580ca38..35ae039b 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -35,6 +35,21 @@ class UserPreference < TestResource self.primary_key = :user_id end +class DocumentUser < TestResource + resolve_custom_type 'document--files', 'DocumentFile' +end + +class DocumentStore < TestResource + resolve_custom_type 'document--files', 'DocumentFile' + has_many :files, class_name: 'DocumentFile' +end + +class DocumentFile < TestResource + def self.resource_name + 'document--files' + end +end + def with_altered_config(resource_class, changes) # remember and overwrite config old_config_values = {} diff --git a/test/unit/association_test.rb b/test/unit/association_test.rb index 89624d0b..dcdd4216 100644 --- a/test/unit/association_test.rb +++ b/test/unit/association_test.rb @@ -769,6 +769,91 @@ def test_nested_create_from_scope Specified.where(foo_id: 1).create end + def test_get_with_relationship_for_model_with_custom_type + stub_request(:get, "http://example.com/document_users/1?include=file") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [ + { + id: '1', + type: 'document_users', + attributes: { + name: 'John Doe' + }, + relationships: { + file: { + links: { + self: 'http://example.com/document_users/1/relationships/file', + related: 'http://example.com/document_users/1/file' + }, + data: { + id: '2', + type: 'document--files' + } + } + } + } + ], + included: [ + { + id: '2', + type: 'document--files', + attributes: { + url: 'http://example.com/downloads/2.pdf' + } + } + ] + }.to_json) + + user = DocumentUser.includes('file').find(1).first + + assert_equal 'document--files', user.file.type + assert user.file.is_a?(DocumentFile) + end + + def test_get_with_defined_relationship_for_model_with_custom_type + stub_request(:get, "http://example.com/document_stores/1?include=files") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [ + { + id: '1', + type: 'document_stores', + attributes: { + name: 'store #1' + }, + relationships: { + files: { + links: { + self: 'http://example.com/document_stores/1/relationships/files', + related: 'http://example.com/document_stores/1/files' + }, + data: [ + { + id: '2', + type: 'document--files' + } + ] + } + } + } + ], + included: [ + { + id: '2', + type: 'document--files', + attributes: { + url: 'http://example.com/downloads/2.pdf' + } + } + ] + }.to_json) + + user = DocumentStore.includes('files').find(1).first + + assert_equal 1, user.files.size + assert_equal 'document--files', user.files.first.type + assert user.files.first.is_a?(DocumentFile) + end + def test_load_include_from_dataset stub_request(:get, 'http://example.com/employees?include=chief&page[per_page]=2') .to_return( diff --git a/test/unit/creation_test.rb b/test/unit/creation_test.rb index 9932469f..a793a926 100644 --- a/test/unit/creation_test.rb +++ b/test/unit/creation_test.rb @@ -314,4 +314,31 @@ def test_create_with_relationships_in_payload assert_equal "1", article.id end + def test_create_with_custom_type + stub_request(:post, 'http://example.com/document--files') + .with(headers: {content_type: 'application/vnd.api+json', accept: 'application/vnd.api+json'}, body: { + data: { + type: 'document--files', + attributes: { + url: 'http://example.com/downloads/1.pdf' + } + } + }.to_json) + .to_return(headers: {content_type: 'application/vnd.api+json'}, body: { + data: { + type: 'document--files', + id: '1', + attributes: { + url: 'http://example.com/downloads/1.pdf' + } + } + }.to_json) + + file = DocumentFile.new(url: 'http://example.com/downloads/1.pdf') + + assert file.save + assert file.persisted? + assert_equal '1', file.id + end + end diff --git a/test/unit/finding_test.rb b/test/unit/finding_test.rb index e1e20c97..34675aab 100644 --- a/test/unit/finding_test.rb +++ b/test/unit/finding_test.rb @@ -77,4 +77,26 @@ def test_find_all assert_equal ["2", "3"], articles.map(&:id) end + def test_find_by_id_with_custom_type + stub_request(:get, "http://example.com/document--files/1") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: { + type: "document--files", + id: "1", + attributes: { + url: 'http://example.com/downloads/1.pdf' + } + } + }.to_json) + + articles = DocumentFile.find(1) + + assert articles.is_a?(JsonApiClient::ResultSet) + assert_equal 1, articles.length + + article = articles.first + assert_equal '1', article.id + assert_equal 'http://example.com/downloads/1.pdf', article.url + end + end From 4c1b9fab1cc27f8f063100af15dd61e25b5236e1 Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Tue, 15 Jan 2019 12:47:43 +0200 Subject: [PATCH 046/118] correct changes after initialize resource remove type from changes on initialize ensure that query builder doesn't propagate query values to resource attributes via #build method --- CHANGELOG.md | 4 ++++ lib/json_api_client/helpers/dirty.rb | 6 +++++- lib/json_api_client/resource.rb | 1 + test/test_helper.rb | 6 ++++++ test/unit/query_builder_test.rb | 23 +++++++++++++++++++++++ 5 files changed, 39 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 082e5bb2..c9ae165e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ - [#328](https://github.com/JsonApiClient/json_api_client/pull/328) - allow custom type for models +- [#326](https://github.com/JsonApiClient/json_api_client/pull/326) - correct changes after initialize resource + * remove type from changes on initialize + * ensure that query builder doesn't propagate query values to resource attributes via #build method + ## 1.7.0 - [#320](https://github.com/JsonApiClient/json_api_client/pull/320) - fix passing relationships on create diff --git a/lib/json_api_client/helpers/dirty.rb b/lib/json_api_client/helpers/dirty.rb index 09b6512b..0e2636fb 100644 --- a/lib/json_api_client/helpers/dirty.rb +++ b/lib/json_api_client/helpers/dirty.rb @@ -18,6 +18,10 @@ def clear_changes_information @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new end + def forget_change!(attr) + @changed_attributes.delete(attr.to_s) + end + def set_all_attributes_dirty attributes.each do |k, v| set_attribute_was(k, v) @@ -68,4 +72,4 @@ def set_attribute(name, value) end end -end \ No newline at end of file +end diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index fa0d4211..3e6dc000 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -329,6 +329,7 @@ def initialize(params = {}) self.links = self.class.linker.new(params.delete(:links) || {}) self.relationships = self.class.relationship_linker.new(self.class, params.delete(:relationships) || {}) self.attributes = self.class.default_attributes.merge params.except(*self.class.prefix_params) + self.forget_change!(:type) self.__belongs_to_params = params.slice(*self.class.prefix_params) setup_default_properties diff --git a/test/test_helper.rb b/test/test_helper.rb index 35ae039b..28cfdde1 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -22,6 +22,12 @@ class Article < TestResource has_one :author end +class ArticleNested < TestResource + belongs_to :author, shallow_path: true + has_many :comments + has_one :author +end + class Person < TestResource end diff --git a/test/unit/query_builder_test.rb b/test/unit/query_builder_test.rb index 2596437a..753b9047 100644 --- a/test/unit/query_builder_test.rb +++ b/test/unit/query_builder_test.rb @@ -256,4 +256,27 @@ def test_find_with_args assert_requested all_stub, times: 1 assert_requested find_stub, times: 1 end + + def test_build_does_not_propagate_values + query = Article.where(name: 'John'). + includes(:author). + order(id: :desc). + select(:id, :name). + page(1). + per(20). + with_params(sort: "foo") + + record = query.build + assert_equal [], record.changed + assert_equal [], record.relationships.changed + end + + def test_build_propagate_only_path_params + query = ArticleNested.where(author_id: '123', name: 'John') + record = query.build + assert_equal [], record.changed + assert_equal({author_id: '123'}, record.__belongs_to_params) + assert_equal '123', record.author_id + assert_equal [], record.relationships.changed + end end From 4d16613bdc57db8c3a30be9aa1e6c59bae5cd5ab Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Wed, 23 Jan 2019 23:47:26 +0200 Subject: [PATCH 047/118] changelog fix after 1.8.0 release --- CHANGELOG.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9ae165e..f87a33e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,18 +2,18 @@ ## Unreleased -## 1.8.0 - -- [#316](https://github.com/JsonApiClient/json_api_client/pull/316) - Allow custom error messages - -- [#305](https://github.com/JsonApiClient/json_api_client/pull/305) - optional search relationship data in result set - - [#328](https://github.com/JsonApiClient/json_api_client/pull/328) - allow custom type for models - [#326](https://github.com/JsonApiClient/json_api_client/pull/326) - correct changes after initialize resource * remove type from changes on initialize * ensure that query builder doesn't propagate query values to resource attributes via #build method +## 1.8.0 + +- [#316](https://github.com/JsonApiClient/json_api_client/pull/316) - Allow custom error messages + +- [#305](https://github.com/JsonApiClient/json_api_client/pull/305) - optional search relationship data in result set + ## 1.7.0 - [#320](https://github.com/JsonApiClient/json_api_client/pull/320) - fix passing relationships on create From a2dd3d83a66515de8b1ab121b0e03709a6bb1943 Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Fri, 11 Jan 2019 12:35:17 +0200 Subject: [PATCH 048/118] add status_handlers to JsonApiClient::Resource.connection_options it allows to override status handling --- CHANGELOG.md | 3 + README.md | 38 ++++++++++++ lib/json_api_client/connection.rb | 4 +- lib/json_api_client/middleware/status.rb | 14 ++++- test/test_helper.rb | 24 +++++++ test/unit/status_test.rb | 79 ++++++++++++++++++++++++ 6 files changed, 160 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f87a33e2..95ad0a3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ * remove type from changes on initialize * ensure that query builder doesn't propagate query values to resource attributes via #build method +- [#324](https://github.com/JsonApiClient/json_api_client/pull/324) - add possibility to override status handling + * add status_handlers to JsonApiClient::Resource.connection_options + ## 1.8.0 - [#316](https://github.com/JsonApiClient/json_api_client/pull/316) - Allow custom error messages diff --git a/README.md b/README.md index d1425d5f..a7b0966b 100644 --- a/README.md +++ b/README.md @@ -474,6 +474,44 @@ module MyApi end ``` +##### Custom status handler + +You can change handling of response status using `connection_options`. For example you can override 400 status handling. +By default it raises `JsonApiClient::Errors::ClientError` but you can skip exception if you want to process errors from the server. +You need to provide a `proc` which should call `throw(:handled)` default handler for this status should be skipped. +```ruby +class ApiBadRequestHandler + def self.call(_env) + # do not raise exception + end +end + +class CustomUnauthorizedError < StandardError + attr_reader :env + + def initialize(env) + @env = env + super('not authorized') + end +end + +MyApi::Base.connection_options[:status_handlers] = { + 400 => ApiBadRequestHandler, + 401 => ->(env) { raise CustomUnauthorizedError, env } +} + +module MyApi + class User < Base + # will use the customized status_handlers + end +end + +user = MyApi::User.create(name: 'foo') +# server responds with { errors: [ { detail: 'bad request' } ] } +user.errors.messages # { base: ['bad request'] } +# on 401 it will raise CustomUnauthorizedError instead of JsonApiClient::Errors::NotAuthorized +``` + ##### Specifying an HTTP Proxy All resources have a class method ```connection_options``` used to pass options to the JsonApiClient::Connection initializer. diff --git a/lib/json_api_client/connection.rb b/lib/json_api_client/connection.rb index 2f207ec4..227628a1 100644 --- a/lib/json_api_client/connection.rb +++ b/lib/json_api_client/connection.rb @@ -7,10 +7,12 @@ def initialize(options = {}) site = options.fetch(:site) connection_options = options.slice(:proxy, :ssl, :request, :headers, :params) adapter_options = Array(options.fetch(:adapter, Faraday.default_adapter)) + status_middleware_options = {} + status_middleware_options[:custom_handlers] = options[:status_handlers] if options[:status_handlers].present? @faraday = Faraday.new(site, connection_options) do |builder| builder.request :json builder.use Middleware::JsonRequest - builder.use Middleware::Status + builder.use Middleware::Status, status_middleware_options builder.use Middleware::ParseJson builder.adapter(*adapter_options) end diff --git a/lib/json_api_client/middleware/status.rb b/lib/json_api_client/middleware/status.rb index ad31de29..71b22a1c 100644 --- a/lib/json_api_client/middleware/status.rb +++ b/lib/json_api_client/middleware/status.rb @@ -1,6 +1,11 @@ module JsonApiClient module Middleware class Status < Faraday::Middleware + def initialize(app, options) + super(app) + @options = options + end + def call(environment) @app.call(environment).on_complete do |env| handle_status(env[:status], env) @@ -15,9 +20,16 @@ def call(environment) raise Errors::ConnectionError.new environment, e.to_s end - protected + private + + def custom_handler_for(code) + @options.fetch(:custom_handlers, {})[code] + end def handle_status(code, env) + custom_handler = custom_handler_for(code) + return custom_handler.call(env) if custom_handler.present? + case code when 200..399 when 401 diff --git a/test/test_helper.rb b/test/test_helper.rb index 28cfdde1..dac3d5cf 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -37,6 +37,30 @@ class Comment < TestResource class User < TestResource end +class ApiBadRequestHandler + def self.call(_env) + # do not raise exception + end +end + +class CustomUnauthorizedError < StandardError + attr_reader :env + + def initialize(env) + @env = env + super('not authorized') + end +end + +class UserWithCustomStatusHandler < TestResource + self.connection_options = { + status_handlers: { + 400 => ApiBadRequestHandler, + 401 => ->(env) { raise CustomUnauthorizedError, env } + } + } +end + class UserPreference < TestResource self.primary_key = :user_id end diff --git a/test/unit/status_test.rb b/test/unit/status_test.rb index 848fa2f9..aedb5951 100644 --- a/test/unit/status_test.rb +++ b/test/unit/status_test.rb @@ -70,6 +70,85 @@ def test_server_responding_with_408_status end end + def test_server_responding_with_400_status + stub_request(:get, "http://example.com/users/1") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + meta: { + status: 400, + message: "Bad Request" + } + }.to_json) + + assert_raises JsonApiClient::Errors::ClientError do + User.find(1) + end + end + + def test_server_responding_with_401_status + stub_request(:get, "http://example.com/users/1") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + meta: { + status: 401, + message: "Not Authorized" + } + }.to_json) + + assert_raises JsonApiClient::Errors::NotAuthorized do + User.find(1) + end + end + + def test_server_responding_with_400_status_in_meta_with_custom_status_handler + stub_request(:get, "http://example.com/user_with_custom_status_handlers/1") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + meta: { + status: 400, + message: "Bad Request" + } + }.to_json) + + UserWithCustomStatusHandler.find(1) + end + + def test_server_responding_with_401_status_in_meta_with_custom_status_handler + stub_request(:get, "http://example.com/user_with_custom_status_handlers/1") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + meta: { + status: 401, + message: "Not Authorized" + } + }.to_json) + + assert_raises CustomUnauthorizedError do + UserWithCustomStatusHandler.find(1) + end + end + + def test_server_responding_with_400_status_with_custom_status_handler + stub_request(:post, "http://example.com/user_with_custom_status_handlers") + .with(headers: { content_type: 'application/vnd.api+json', accept: 'application/vnd.api+json' }, body: { + data: { + type: 'user_with_custom_status_handlers', + attributes: { + name: 'foo' + } + } + }.to_json) + .to_return(status: 400, headers: { content_type: "application/vnd.api+json" }, body: { + errors: [ + { + status: '400', + detail: 'Bad Request' + } + ] + }.to_json) + + user = UserWithCustomStatusHandler.create(name: 'foo') + refute user.persisted? + expected_errors = { base: ['Bad Request'] } + assert_equal expected_errors, user.errors.messages + end + def test_server_responding_with_422_status stub_request(:get, "http://example.com/users/1") .to_return(headers: {content_type: "application/vnd.api+json"}, body: { From 5b5922ac453bf6b08690b59693a27c2599af7aeb Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Thu, 24 Jan 2019 10:55:29 +0200 Subject: [PATCH 049/118] upgrade changelog (add #330) --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95ad0a3e..f37d4c7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ - [#324](https://github.com/JsonApiClient/json_api_client/pull/324) - add possibility to override status handling * add status_handlers to JsonApiClient::Resource.connection_options +- [#330](https://github.com/JsonApiClient/json_api_client/pull/330) - deletion use overridden primary key + ## 1.8.0 - [#316](https://github.com/JsonApiClient/json_api_client/pull/316) - Allow custom error messages From 8d5bbf57221f433d49969b3d22abce75d8ea17d1 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Fri, 25 Jan 2019 05:41:10 -0800 Subject: [PATCH 050/118] version bump: 1.9.0 --- CHANGELOG.md | 2 ++ lib/json_api_client/version.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f37d4c7a..be6501fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 1.9.0 + - [#328](https://github.com/JsonApiClient/json_api_client/pull/328) - allow custom type for models - [#326](https://github.com/JsonApiClient/json_api_client/pull/326) - correct changes after initialize resource diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index 8c766e48..e8365ada 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.8.0" + VERSION = "1.9.0" end From 43b023604ed9df1b0ec17f98578d0e4e054b520e Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Tue, 19 Mar 2019 10:47:37 +0200 Subject: [PATCH 051/118] add failing test that reproduces issue #334 --- test/unit/creation_test.rb | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/test/unit/creation_test.rb b/test/unit/creation_test.rb index a793a926..68bbeb61 100644 --- a/test/unit/creation_test.rb +++ b/test/unit/creation_test.rb @@ -18,6 +18,16 @@ def after_create_method class Author < TestResource end + class User < TestResource + has_one :skill_level + + properties :first_name, type: :string + end + + class SkillLevel < TestResource + property :title, type: :string + end + def stub_simple_creation stub_request(:post, "http://example.com/articles") .with(headers: {content_type: "application/vnd.api+json", accept: "application/vnd.api+json"}, body: { @@ -341,4 +351,40 @@ def test_create_with_custom_type assert_equal '1', file.id end + def test_access_loaded_relationship_instance + stub_request(:get, 'http://example.com/skill_levels/1') + .to_return(headers: {content_type: 'application/vnd.api+json'}, body: { + data: { + type: 'skill_levels', + id: '1', + attributes: { + title: 'newbie' + } + } + }.to_json) + + stub_request(:get, 'http://example.com/skill_levels/2') + .to_return(headers: {content_type: 'application/vnd.api+json'}, body: { + data: { + type: 'skill_levels', + id: '1', + attributes: { + title: 'pro' + } + } + }.to_json) + + skill_level = SkillLevel.find(1).first + user = User.new(first_name: 'Joe', relationships: { skill_level: skill_level }) + + assert_equal ({'data'=>{'type'=>'skill_levels', 'id'=>'1'}}), user.relationships.skill_level + assert_kind_of SkillLevel, user.skill_level + assert_equal '1', user.skill_level.id + # test that object is cached and not recreated each time + assert_equal user.skill_level.object_id, user.skill_level.object_id + + user.relationships.skill_level = SkillLevel.find(2).first + assert_equal '2', user.skill_level.id + end + end From 361178010c6592a0c2d6a2e9c77e261bb174db9a Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Mon, 25 Mar 2019 15:12:26 +0200 Subject: [PATCH 052/118] retrieve assigned relationship record via association getter method with caching --- .../associations/base_association.rb | 7 +++++++ lib/json_api_client/associations/has_one.rb | 7 ++++++- lib/json_api_client/resource.rb | 13 ++++++++++++- test/unit/creation_test.rb | 4 ++-- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/lib/json_api_client/associations/base_association.rb b/lib/json_api_client/associations/base_association.rb index dfc8166e..6894b353 100644 --- a/lib/json_api_client/associations/base_association.rb +++ b/lib/json_api_client/associations/base_association.rb @@ -21,6 +21,13 @@ def data(url) def from_result_set(result_set) result_set.to_a end + + def load_records(data) + data.map do |d| + record_class = Utils.compute_type(klass, d["type"].classify) + record_class.load id: d["id"] + end + end end end end diff --git a/lib/json_api_client/associations/has_one.rb b/lib/json_api_client/associations/has_one.rb index 4d1418a2..fd29872e 100644 --- a/lib/json_api_client/associations/has_one.rb +++ b/lib/json_api_client/associations/has_one.rb @@ -5,7 +5,12 @@ class Association < BaseAssociation def from_result_set(result_set) result_set.first end + + def load_records(data) + record_class = Utils.compute_type(klass, data["type"].classify) + record_class.load id: data["id"] + end end end end -end \ No newline at end of file +end diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index 68505520..9dbcde95 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -537,7 +537,11 @@ def included_data_for(name, relationship_definition) def relationship_data_for(name, relationship_definition) # look in included data if relationship_definition.key?("data") - return included_data_for(name, relationship_definition) + if relationships.attribute_changed?(name) + return relation_objects_for(name, relationship_definition) + else + return included_data_for(name, relationship_definition) + end end url = relationship_definition["links"]["related"] @@ -548,6 +552,13 @@ def relationship_data_for(name, relationship_definition) nil end + def relation_objects_for(name, relationship_definition) + data = relationship_definition["data"] + assoc = association_for(name) + return if data.nil? || assoc.nil? + assoc.load_records(data) + end + def method_missing(method, *args) relationship_definition = relationship_definition_for(method) diff --git a/test/unit/creation_test.rb b/test/unit/creation_test.rb index 68bbeb61..9798b902 100644 --- a/test/unit/creation_test.rb +++ b/test/unit/creation_test.rb @@ -367,7 +367,7 @@ def test_access_loaded_relationship_instance .to_return(headers: {content_type: 'application/vnd.api+json'}, body: { data: { type: 'skill_levels', - id: '1', + id: '2', attributes: { title: 'pro' } @@ -383,7 +383,7 @@ def test_access_loaded_relationship_instance # test that object is cached and not recreated each time assert_equal user.skill_level.object_id, user.skill_level.object_id - user.relationships.skill_level = SkillLevel.find(2).first + user.skill_level = SkillLevel.find(2).first assert_equal '2', user.skill_level.id end From 18997e59d22a5a79e8f56a560fbc6681a17be2ae Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Tue, 26 Mar 2019 09:34:36 +0200 Subject: [PATCH 053/118] fix byebug version fo travis ci --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 163acf72..0b5a6e5c 100644 --- a/Gemfile +++ b/Gemfile @@ -11,5 +11,5 @@ gem 'addressable', '~> 2.2' gem "codeclimate-test-reporter", group: :test, require: nil group :development, :test do - gem 'byebug', platforms: [:mri_20, :mri_21, :mri_22] + gem 'byebug', '~> 10.0', platforms: [:mri_20, :mri_21, :mri_22] end From e9ee232f5eaeebe74235150667ee0de28fd43136 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Mon, 20 May 2019 13:48:22 -0700 Subject: [PATCH 054/118] version bump: 1.10.0 --- lib/json_api_client/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index e8365ada..4f8b2894 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.9.0" + VERSION = "1.10.0" end From ec35cde38a2d9028522eb98a3800882e30008872 Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Tue, 21 May 2019 11:10:25 +0300 Subject: [PATCH 055/118] update changelog for version 1.10.0 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index be6501fe..a999d261 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 1.10.0 + +- [#335](https://github.com/JsonApiClient/json_api_client/pull/335) - access to assigned relationship + ## 1.9.0 - [#328](https://github.com/JsonApiClient/json_api_client/pull/328) - allow custom type for models From 3b48cfc5af8e0497a3946e7d2babc5de04dc32bd Mon Sep 17 00:00:00 2001 From: Sergey Tokarenko Date: Fri, 24 May 2019 19:20:43 +0300 Subject: [PATCH 056/118] Add `raise_on_blank_find_param` resource setting --- CHANGELOG.md | 4 ++- README.md | 41 ++++++++++++++++++++++++---- lib/json_api_client/query/builder.rb | 14 ++++++++-- lib/json_api_client/resource.rb | 2 ++ test/unit/query_builder_test.rb | 33 ++++++++++++++++++++++ 5 files changed, 86 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be6501fe..4cd1a826 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- [#344](https://github.com/JsonApiClient/json_api_client/pull/344) - introduce safe singular resource fetching with `raise_on_blank_find_param` resource setting + ## 1.9.0 - [#328](https://github.com/JsonApiClient/json_api_client/pull/328) - allow custom type for models @@ -27,7 +29,7 @@ * fix relationships passing to `new` and `create` methods * fix false positive tests on create * refactor tests on create/update to prevent false same positive tests in future - + - [#315](https://github.com/JsonApiClient/json_api_client/pull/315) - add shallow_path feature to belongs_to * add `shallow_path` options to belongs_to to use model w/ and w/o nesting in parent resource diff --git a/README.md b/README.md index a7b0966b..12617c30 100644 --- a/README.md +++ b/README.md @@ -52,14 +52,14 @@ u.update_attributes( c: "d" ) -u.persisted? +u.persisted? # => true u.destroy -u.destroyed? +u.destroyed? # => true -u.persisted? +u.persisted? # => false u = MyApi::Person.create( @@ -164,7 +164,7 @@ module MyApi class Account < JsonApiClient::Resource belongs_to :user end - + class Customer < JsonApiClient::Resource belongs_to :user, shallow_path: true end @@ -476,7 +476,7 @@ end ##### Custom status handler -You can change handling of response status using `connection_options`. For example you can override 400 status handling. +You can change handling of response status using `connection_options`. For example you can override 400 status handling. By default it raises `JsonApiClient::Errors::ClientError` but you can skip exception if you want to process errors from the server. You need to provide a `proc` which should call `throw(:handled)` default handler for this status should be skipped. ```ruby @@ -636,6 +636,37 @@ end ``` +### Safe singular resource fetching + +That is a bit curios, but `json_api_client` returns an array from `.find` method, always. +The history of this fact was discussed [here](https://github.com/JsonApiClient/json_api_client/issues/75) + +So, when we searching for a single resource by primary key, we typically write the things like + +```ruby +admin = User.find(id).first +``` + +The next thing which we need to notice - `json_api_client` will just interpolate the incoming `.find` param to the end of API URL, just like that: + +> http://somehost/api/v1/users/{id} + +What will happen if we pass the blank id (nil or empty string) to the `.find` method then?.. Yeah, `json_api_client` will try to call the INDEX API endpoint instead of SHOW one: + +> http://somehost/api/v1/users/ + +Lets sum all together - in case if `id` comes blank (from CGI for instance), we can silently receive the `admin` variable equal to some existing resource, with all the consequences. + +Even worse, `admin` variable can equal to *random* resource, depends on ordering applied by INDEX endpoint. + +If you prefer to get `JsonApiClient::Errors::NotFound` raised, please define in your base Resource class: + +```ruby +class Resource < JsonApiClient::Resource + self.raise_on_blank_find_param = true +end +``` + ## Contributing Contributions are welcome! Please fork this repo and send a pull request. Your pull request should have: diff --git a/lib/json_api_client/query/builder.rb b/lib/json_api_client/query/builder.rb index 44e48a42..8e82e77f 100644 --- a/lib/json_api_client/query/builder.rb +++ b/lib/json_api_client/query/builder.rb @@ -86,11 +86,15 @@ def params end def to_a - @to_a ||= find + @to_a ||= _fetch end alias all to_a def find(args = {}) + if klass.raise_on_blank_find_param && args.blank? + raise Errors::NotFound, 'blank .find param' + end + case args when Hash scope = where(args) @@ -98,13 +102,19 @@ def find(args = {}) scope = _new_scope( primary_key: args ) end - klass.requestor.get(scope.params) + scope._fetch end def method_missing(method_name, *args, &block) to_a.send(method_name, *args, &block) end + protected + + def _fetch + klass.requestor.get(params) + end + private def _new_scope( opts = {} ) diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index 9dbcde95..dd1f187d 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -36,6 +36,7 @@ class Resource :keep_request_params, :search_included_in_result_set, :custom_type_to_class, + :raise_on_blank_find_param, instance_accessor: false class_attribute :add_defaults_to_changes, instance_writer: false @@ -54,6 +55,7 @@ class Resource self.add_defaults_to_changes = false self.search_included_in_result_set = false self.custom_type_to_class = {} + self.raise_on_blank_find_param = false #:underscored_key, :camelized_key, :dasherized_key, or custom self.json_key_format = :underscored_key diff --git a/test/unit/query_builder_test.rb b/test/unit/query_builder_test.rb index 753b9047..87a6233e 100644 --- a/test/unit/query_builder_test.rb +++ b/test/unit/query_builder_test.rb @@ -257,6 +257,39 @@ def test_find_with_args assert_requested find_stub, times: 1 end + def test_find_with_blank_args + blank_params = [nil, '', {}] + + with_altered_config(Article, :raise_on_blank_find_param => true) do + blank_params.each do |blank_param| + assert_raises JsonApiClient::Errors::NotFound do + Article.find(blank_param) + end + end + end + + # `.find('')` hits the INDEX URL with trailing slash, the others without one.. + collection_stub = stub_request(:get, %r{\Ahttp://example.com/articles/?\z}) + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { data: [] }.to_json) + + blank_params.each do |blank_param| + Article.find(blank_param) + end + + assert_requested collection_stub, times: 3 + end + + def test_all_on_blank_raising_resource + with_altered_config(Article, :raise_on_blank_find_param => true) do + all_stub = stub_request(:get, "http://example.com/articles") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { data: [] }.to_json) + + Article.all + + assert_requested all_stub, times: 1 + end + end + def test_build_does_not_propagate_values query = Article.where(name: 'John'). includes(:author). From 5c6745c090ea373e0c071197555e406806c48556 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Fri, 24 May 2019 10:55:18 -0700 Subject: [PATCH 057/118] version bump: v1.11.0 --- CHANGELOG.md | 4 ++++ lib/json_api_client/version.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cd1a826..ae67f443 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,12 @@ ## Unreleased +## 1.11.0 + - [#344](https://github.com/JsonApiClient/json_api_client/pull/344) - introduce safe singular resource fetching with `raise_on_blank_find_param` resource setting +## 1.10.0 + ## 1.9.0 - [#328](https://github.com/JsonApiClient/json_api_client/pull/328) - allow custom type for models diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index 4f8b2894..6ec4aa4e 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.10.0" + VERSION = "1.11.0" end From 87718d8712d6140ea274e39a17c732c80b70d84c Mon Sep 17 00:00:00 2001 From: Lachlan Priest Date: Tue, 28 May 2019 15:47:22 +0800 Subject: [PATCH 058/118] Add an immutable option to JSON API resources. Discussed here: https://github.com/JsonApiClient/json_api_client/issues/343 --- lib/json_api_client/errors.rb | 6 ++++++ lib/json_api_client/resource.rb | 25 +++++++++++++++++++++++++ test/unit/resource_test.rb | 10 ++++++++++ 3 files changed, 41 insertions(+) diff --git a/lib/json_api_client/errors.rb b/lib/json_api_client/errors.rb index 5d8d6aa6..8cf41d51 100644 --- a/lib/json_api_client/errors.rb +++ b/lib/json_api_client/errors.rb @@ -11,6 +11,12 @@ def initialize(env, msg = nil) class ClientError < ApiError end + class ResourceImmutableError < StandardError + def initialize(msg = 'Resource immutable') + super msg + end + end + class AccessDenied < ClientError end diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index dd1f187d..ff47f849 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -40,6 +40,11 @@ class Resource instance_accessor: false class_attribute :add_defaults_to_changes, instance_writer: false + + class_attribute :_immutable, + instance_writer: false, + default: false + self.primary_key = :id self.parser = Parsers::Parser self.paginator = Paginating::Paginator @@ -94,6 +99,18 @@ def type table_name end + # Indicates whether this resource is mutable or immutable; + # by default, all resources are mutable. + # + # @return [Boolean] + def immutable(flag = true) + self._immutable = flag + end + + def inherited(subclass) + subclass._immutable = false + end + # Specifies the relative path that should be used for this resource; # by default, this is inferred from the resource class name. # @@ -215,6 +232,11 @@ def route_formatter # @option [Symbol] :on One of [:collection or :member] to decide whether it's a collect or member method # @option [Symbol] :request_method The request method (:get, :post, etc) def custom_endpoint(name, options = {}) + if _immutable + request_method = options.fetch(:request_method, :get).to_sym + raise JsonApiClient::Errors::ResourceImmutableError if request_method != :get + end + if :collection == options.delete(:on) collection_endpoint(name, options) else @@ -440,6 +462,7 @@ def valid?(context = nil) # @return [Boolean] Whether or not the save succeeded def save return false unless valid? + raise JsonApiClient::Errors::ResourceImmutableError if _immutable self.last_result_set = if persisted? self.class.requestor.update(self) @@ -471,6 +494,8 @@ def save # # @return [Boolean] Whether or not the destroy succeeded def destroy + raise JsonApiClient::Errors::ResourceImmutableError if _immutable + self.last_result_set = self.class.requestor.destroy(self) if last_result_set.has_errors? fill_errors diff --git a/test/unit/resource_test.rb b/test/unit/resource_test.rb index 5af0c5b1..088bdff0 100644 --- a/test/unit/resource_test.rb +++ b/test/unit/resource_test.rb @@ -95,4 +95,14 @@ def test_default_params_overrideable assert_equal(article.type, 'Story') end + def test_immutable + Article.immutable(true) + article = Article.new(type: 'Story') + assert_raises JsonApiClient::Errors::ResourceImmutableError do + article.save + end + ensure + Article.immutable(false) + end + end From 60e2a640a54bc3debf8d3d3fe5c5ac41e2450072 Mon Sep 17 00:00:00 2001 From: Sergey Tokarenko Date: Sat, 25 May 2019 12:34:25 +0300 Subject: [PATCH 059/118] Track the real HTTP reason of ApiErrors --- CHANGELOG.md | 2 ++ README.md | 16 ++++++--- json_api_client.gemspec | 3 +- lib/json_api_client/errors.rb | 46 +++++++++++++++++++----- lib/json_api_client/middleware/status.rb | 4 ++- test/unit/errors_test.rb | 37 ++++++++++++++++--- test/unit/status_test.rb | 4 +-- 7 files changed, 92 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae67f443..a60fce12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- [#345](https://github.com/JsonApiClient/json_api_client/pull/345) - track the real HTTP reason of ApiErrors + ## 1.11.0 - [#344](https://github.com/JsonApiClient/json_api_client/pull/344) - introduce safe singular resource fetching with `raise_on_blank_find_param` resource setting diff --git a/README.md b/README.md index 12617c30..a21e6284 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This gem is meant to help you build an API client for interacting with REST APIs as laid out by [http://jsonapi.org](http://jsonapi.org). It attempts to give you a query building framework that is easy to understand (it is similar to ActiveRecord scopes). -*Note: master is currently tracking the 1.0.0 specification. If you're looking for the older code, see [0.x branch](https://github.com/chingor13/json_api_client/tree/0.x)* +*Note: master is currently tracking the 1.0.0 specification. If you're looking for the older code, see [0.x branch](https://github.com/JsonApiClient/json_api_client/tree/0.x)* ## Usage @@ -474,6 +474,14 @@ module MyApi end ``` +##### Server errors handling + +Non-success API response will cause the specific `JsonApiClient::Errors::SomeException` raised, depends on responded HTTP status. +Please refer to [JsonApiClient::Middleware::Status#handle_status](https://github.com/JsonApiClient/json_api_client/blob/master/lib/json_api_client/middleware/status.rb) +method for concrete status-to-exception mapping used out of the box. + +JsonApiClient will try determine is failed API response JsonApi-compatible, if so - JsonApi error messages will be parsed from response body, and tracked as a part of particular exception message. In additional, `JsonApiClient::Errors::ServerError` exception will keep the actual HTTP status and message within its message. + ##### Custom status handler You can change handling of response status using `connection_options`. For example you can override 400 status handling. @@ -569,7 +577,7 @@ end You can customize how your resources find pagination information from the response. -If the [existing paginator](https://github.com/chingor13/json_api_client/blob/master/lib/json_api_client/paginating/paginator.rb) fits your requirements but you don't use the default `page` and `per_page` params for pagination, you can customise the param keys as follows: +If the [existing paginator](https://github.com/JsonApiClient/json_api_client/blob/master/lib/json_api_client/paginating/paginator.rb) fits your requirements but you don't use the default `page` and `per_page` params for pagination, you can customise the param keys as follows: ```ruby JsonApiClient::Paginating::Paginator.page_param = "number" @@ -578,7 +586,7 @@ JsonApiClient::Paginating::Paginator.per_page_param = "size" Please note that this is a global configuration, so library authors should create a custom paginator that inherits `JsonApiClient::Paginating::Paginator` and configure the custom paginator to avoid modifying global config. -If the [existing paginator](https://github.com/chingor13/json_api_client/blob/master/lib/json_api_client/paginating/paginator.rb) does not fit your needs, you can create a custom paginator: +If the [existing paginator](https://github.com/JsonApiClient/json_api_client/blob/master/lib/json_api_client/paginating/paginator.rb) does not fit your needs, you can create a custom paginator: ```ruby class MyPaginator @@ -680,4 +688,4 @@ required. The commits will be squashed into master once accepted. ## Changelog -See [changelog](https://github.com/chingor13/json_api_client/blob/master/CHANGELOG.md) +See [changelog](https://github.com/JsonApiClient/json_api_client/blob/master/CHANGELOG.md) diff --git a/json_api_client.gemspec b/json_api_client.gemspec index ff092157..e837c5d0 100644 --- a/json_api_client.gemspec +++ b/json_api_client.gemspec @@ -16,8 +16,9 @@ Gem::Specification.new do |s| s.add_dependency "faraday_middleware", '~> 0.9' s.add_dependency "addressable", '~> 2.2' s.add_dependency "activemodel", '>= 3.2.0' + s.add_dependency "rack", '>= 0.2' - s.add_development_dependency "webmock" + s.add_development_dependency "webmock", '~> 3.5.1' s.add_development_dependency "mocha" s.license = "MIT" diff --git a/lib/json_api_client/errors.rb b/lib/json_api_client/errors.rb index 5d8d6aa6..6529fe11 100644 --- a/lib/json_api_client/errors.rb +++ b/lib/json_api_client/errors.rb @@ -1,10 +1,31 @@ +require 'rack' + module JsonApiClient module Errors class ApiError < StandardError attr_reader :env + def initialize(env, msg = nil) - super msg @env = env + # Try to fetch json_api errors from response + msg = track_json_api_errors(msg) + + super msg + end + + private + + # Try to fetch json_api errors from response + def track_json_api_errors(msg) + return msg unless env.try(:body).kind_of?(Hash) || env.body.key?('errors') + + errors_msg = env.body['errors'].map { |e| e['title'] }.compact.join('; ').presence + return msg unless errors_msg + + msg.nil? ? errors_msg : "#{msg} (#{errors_msg})" + # Just to be sure that it is back compatible + rescue StandardError + msg end end @@ -21,7 +42,13 @@ class ConnectionError < ApiError end class ServerError < ApiError - def initialize(env, msg = 'Internal server error') + def initialize(env, msg = nil) + msg ||= begin + status = env.status + message = ::Rack::Utils::HTTP_STATUS_CODES[status] + "#{status} #{message}" + end + super env, msg end end @@ -36,20 +63,23 @@ class NotFound < ServerError attr_reader :uri def initialize(uri) @uri = uri - end - def message - "Couldn't find resource at: #{uri.to_s}" + + msg = "Couldn't find resource at: #{uri.to_s}" + super nil, msg end end + class InternalServerError < ServerError + end + class UnexpectedStatus < ServerError attr_reader :code, :uri def initialize(code, uri) @code = code @uri = uri - end - def message - "Unexpected response status: #{code} from: #{uri.to_s}" + + msg = "Unexpected response status: #{code} from: #{uri.to_s}" + super nil, msg end end end diff --git a/lib/json_api_client/middleware/status.rb b/lib/json_api_client/middleware/status.rb index 71b22a1c..223f4aa3 100644 --- a/lib/json_api_client/middleware/status.rb +++ b/lib/json_api_client/middleware/status.rb @@ -44,7 +44,9 @@ def handle_status(code, env) # Allow to proceed as resource errors will be populated when 400..499 raise Errors::ClientError, env - when 500..599 + when 500 + raise Errors::InternalServerError, env + when 501..599 raise Errors::ServerError, env else raise Errors::UnexpectedStatus.new(code, env[:url]) diff --git a/test/unit/errors_test.rb b/test/unit/errors_test.rb index d11e2ff5..dbfb04ac 100644 --- a/test/unit/errors_test.rb +++ b/test/unit/errors_test.rb @@ -22,13 +22,42 @@ def test_timeout_errors end end - def test_500_errors + def test_internal_server_error_with_plain_text_response stub_request(:get, "http://example.com/users") .to_return(headers: {content_type: "text/plain"}, status: 500, body: "something went wrong") - assert_raises JsonApiClient::Errors::ServerError do - User.all - end + exception = assert_raises(JsonApiClient::Errors::InternalServerError) { User.all } + assert_equal '500 Internal Server Error', exception.message + end + + def test_internal_server_error_with_json_api_response + stub_request(:get, "http://example.com/users").to_return( + headers: {content_type: "application/vnd.api+json"}, + status: 500, + body: {errors: [{title: "Some special error"}]}.to_json + ) + + exception = assert_raises(JsonApiClient::Errors::InternalServerError) { User.all } + assert_equal '500 Internal Server Error (Some special error)', exception.message + end + + def test_500_errors_with_plain_text_response + stub_request(:get, "http://example.com/users") + .to_return(headers: {content_type: "text/plain"}, status: 503, body: "service unavailable") + + exception = assert_raises(JsonApiClient::Errors::ServerError) { User.all } + assert_equal '503 Service Unavailable', exception.message + end + + def test_500_errors_with_with_json_api_response + stub_request(:get, "http://example.com/users").to_return( + headers: {content_type: "application/vnd.api+json"}, + status: 503, + body: {errors: [{title: "Timeout error"}]}.to_json + ) + + exception = assert_raises(JsonApiClient::Errors::ServerError) { User.all } + assert_equal '503 Service Unavailable (Timeout error)', exception.message end def test_not_found diff --git a/test/unit/status_test.rb b/test/unit/status_test.rb index aedb5951..ab69e2b4 100644 --- a/test/unit/status_test.rb +++ b/test/unit/status_test.rb @@ -11,7 +11,7 @@ def test_server_responding_with_status_meta } }.to_json) - assert_raises JsonApiClient::Errors::ServerError do + assert_raises JsonApiClient::Errors::InternalServerError do User.find(1) end end @@ -24,7 +24,7 @@ def test_server_responding_with_http_status status: 500, body: "something irrelevant") - assert_raises JsonApiClient::Errors::ServerError do + assert_raises JsonApiClient::Errors::InternalServerError do User.find(1) end end From 424c9721509fb8e5c4d1853575b7de6b40d5ee69 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Tue, 11 Jun 2019 12:37:24 -0700 Subject: [PATCH 060/118] version bump: v 1.12.0 --- CHANGELOG.md | 2 ++ lib/json_api_client/version.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a60fce12..23016f2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 1.12.0 + - [#345](https://github.com/JsonApiClient/json_api_client/pull/345) - track the real HTTP reason of ApiErrors ## 1.11.0 diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index 6ec4aa4e..29c79849 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.11.0" + VERSION = "1.12.0" end From c8995de473e6f953092a8d1d02a6a083d1ceb19f Mon Sep 17 00:00:00 2001 From: Sergey Tokarenko Date: Fri, 21 Jun 2019 10:52:16 +0300 Subject: [PATCH 061/118] Fix resource including for STI objects --- CHANGELOG.md | 2 ++ lib/json_api_client/included_data.rb | 30 +++++++++++++------- test/unit/association_test.rb | 42 ++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23016f2a..314daa3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- [#349](https://github.com/JsonApiClient/json_api_client/pull/349) - fix resource including for STI objects + ## 1.12.0 - [#345](https://github.com/JsonApiClient/json_api_client/pull/345) - track the real HTTP reason of ApiErrors diff --git a/lib/json_api_client/included_data.rb b/lib/json_api_client/included_data.rb index c62e1f73..0b7063f4 100644 --- a/lib/json_api_client/included_data.rb +++ b/lib/json_api_client/included_data.rb @@ -4,20 +4,30 @@ class IncludedData def initialize(result_set, data) record_class = result_set.record_class - included_set = data.map do |datum| - type = datum["type"] + grouped_data = data.group_by{|datum| datum["type"]} + grouped_included_set = grouped_data.each_with_object({}) do |(type, records), h| klass = Utils.compute_type(record_class, record_class.key_formatter.unformat(type).singularize.classify) - params = klass.parser.parameters_from_resource(datum) - resource = klass.load(params) - resource.last_result_set = result_set - resource + h[type] = records.map do |record| + params = klass.parser.parameters_from_resource(record) + klass.load(params).tap do |resource| + resource.last_result_set = result_set + end + end end - included_set.concat(result_set) if record_class.search_included_in_result_set - @data = included_set.group_by(&:type).inject({}) do |h, (type, resources)| - h[type] = resources.index_by(&:id) - h + if record_class.search_included_in_result_set + # deep_merge overrides the nested Arrays o_O + # {a: [1,2]}.deep_merge(a: [3,4]) # => {a: [3,4]} + grouped_included_set.merge!(result_set.group_by(&:type)) do |_, resources1, resources2| + resources1 + resources2 + end end + + grouped_included_set.each do |type, resources| + grouped_included_set[type] = resources.index_by(&:id) + end + + @data = grouped_included_set end def data_for(method_name, definition) diff --git a/test/unit/association_test.rb b/test/unit/association_test.rb index dcdd4216..65e86e1b 100644 --- a/test/unit/association_test.rb +++ b/test/unit/association_test.rb @@ -854,6 +854,48 @@ def test_get_with_defined_relationship_for_model_with_custom_type assert user.files.first.is_a?(DocumentFile) end + def test_get_with_type_attribute + stub_request(:get, "http://example.com/document_users/1?include=file") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [ + { + id: '1', + type: 'document_users', + attributes: { + name: 'John Doe' + }, + relationships: { + file: { + links: { + self: 'http://example.com/document_users/1/relationships/file', + related: 'http://example.com/document_users/1/file' + }, + data: { + id: '2', + type: 'document--files' + } + } + } + } + ], + included: [ + { + id: '2', + type: 'document--files', + attributes: { + type: 'STIDocumentFile', + url: 'http://example.com/downloads/2.pdf' + } + } + ] + }.to_json) + + user = DocumentUser.includes('file').find(1).first + + assert_equal 'STIDocumentFile', user.file.type + assert user.file.is_a?(DocumentFile) + end + def test_load_include_from_dataset stub_request(:get, 'http://example.com/employees?include=chief&page[per_page]=2') .to_return( From 184c3d5c94c581ebae391a84cc6e3203657bfdda Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Tue, 25 Jun 2019 07:47:39 -0700 Subject: [PATCH 062/118] version bump: v1.12.1 --- CHANGELOG.md | 2 ++ lib/json_api_client/version.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 314daa3e..463ea271 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 1.12.1 + - [#349](https://github.com/JsonApiClient/json_api_client/pull/349) - fix resource including for STI objects ## 1.12.0 diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index 29c79849..114e1908 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.12.0" + VERSION = "1.12.1" end From 2483f48b2bdfc289f1871d4896710c2f243e1e9d Mon Sep 17 00:00:00 2001 From: Sergey Tokarenko Date: Tue, 25 Jun 2019 20:51:52 +0300 Subject: [PATCH 063/118] Fix including with blank relationships --- CHANGELOG.md | 2 ++ lib/json_api_client/resource.rb | 8 +++----- test/unit/association_test.rb | 22 ++++++++++++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 463ea271..088e8017 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- [#350](https://github.com/JsonApiClient/json_api_client/pull/350) - fix resource including with blank `relationships` response data + ## 1.12.1 - [#349](https://github.com/JsonApiClient/json_api_client/pull/349) - fix resource including for STI objects diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index dd1f187d..bae824e4 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -546,12 +546,10 @@ def relationship_data_for(name, relationship_definition) end end - url = relationship_definition["links"]["related"] - if relationship_definition["links"] && url - return association_for(name).data(url) - end + return unless links = relationship_definition["links"] + return unless url = links["related"] - nil + association_for(name).data(url) end def relation_objects_for(name, relationship_definition) diff --git a/test/unit/association_test.rb b/test/unit/association_test.rb index 65e86e1b..9e83fca5 100644 --- a/test/unit/association_test.rb +++ b/test/unit/association_test.rb @@ -896,6 +896,28 @@ def test_get_with_type_attribute assert user.file.is_a?(DocumentFile) end + def test_include_with_blank_relationships + stub_request(:get, "http://example.com/document_users/1?include=file") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [ + { + id: '1', + type: 'document_users', + attributes: { + name: 'John Doe' + }, + relationships: { + file: { + } + } + } + ], + }.to_json) + + user = DocumentUser.includes('file').find(1).first + assert_nil user.file + end + def test_load_include_from_dataset stub_request(:get, 'http://example.com/employees?include=chief&page[per_page]=2') .to_return( From 02e0fb3dd426b7f4b59e610bbf5a6d7b0cde5cc9 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Tue, 25 Jun 2019 13:21:57 -0700 Subject: [PATCH 064/118] version bump: v1.12.2 --- CHANGELOG.md | 2 ++ lib/json_api_client/version.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 088e8017..e94256fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 1.12.2 + - [#350](https://github.com/JsonApiClient/json_api_client/pull/350) - fix resource including with blank `relationships` response data ## 1.12.1 diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index 114e1908..18a87278 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.12.1" + VERSION = "1.12.2" end From d675edaf26547bf234881c876d41cd432994d150 Mon Sep 17 00:00:00 2001 From: Doug Smith Date: Tue, 11 Jun 2019 09:28:25 -0500 Subject: [PATCH 065/118] chore: update .gitignore to ignore IntelliJ and asdf files --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index 7374a56d..6efdc659 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,10 @@ Gemfile.lock *.gemfile.lock /coverage + +# IntellJ/RubyMine +*.iml +.idea/ + +# asdf config +.tool-versions From cc4b6977a50248e79112386d7b115b3b0961508c Mon Sep 17 00:00:00 2001 From: Doug Smith Date: Sat, 15 Jun 2019 14:58:16 -0500 Subject: [PATCH 066/118] feat: add NestedParamPaginator to make pagination params consistent fixes #347 --- lib/json_api_client/paginating.rb | 3 +- .../paginating/nested_param_paginator.rb | 140 +++++++++ lib/json_api_client/query/builder.rb | 8 +- test/unit/nested_param_paginator_test.rb | 82 +++++ ...ed_param_paginator_top_level_links_test.rb | 282 ++++++++++++++++++ 5 files changed, 513 insertions(+), 2 deletions(-) create mode 100644 lib/json_api_client/paginating/nested_param_paginator.rb create mode 100644 test/unit/nested_param_paginator_test.rb create mode 100644 test/unit/nested_param_paginator_top_level_links_test.rb diff --git a/lib/json_api_client/paginating.rb b/lib/json_api_client/paginating.rb index 9443fff8..f49563a2 100644 --- a/lib/json_api_client/paginating.rb +++ b/lib/json_api_client/paginating.rb @@ -1,5 +1,6 @@ module JsonApiClient module Paginating autoload :Paginator, 'json_api_client/paginating/paginator' + autoload :NestedParamPaginator, 'json_api_client/paginating/nested_param_paginator' end -end \ No newline at end of file +end diff --git a/lib/json_api_client/paginating/nested_param_paginator.rb b/lib/json_api_client/paginating/nested_param_paginator.rb new file mode 100644 index 00000000..27e5ca4e --- /dev/null +++ b/lib/json_api_client/paginating/nested_param_paginator.rb @@ -0,0 +1,140 @@ +module JsonApiClient + module Paginating + # An alternate, more consistent Paginator that always wraps + # pagination query string params in a top-level wrapper_name, + # e.g. page[offset]=2, page[limit]=10. + class NestedParamPaginator + DEFAULT_WRAPPER_NAME = "page".freeze + DEFAULT_PAGE_PARAM = "page".freeze + DEFAULT_PER_PAGE_PARAM = "per_page".freeze + + # Define class accessors as methods to enforce standard way + # of defining pagination related query string params. + class << self + + def wrapper_name + @_wrapper_name ||= DEFAULT_WRAPPER_NAME + end + + def wrapper_name=(param = DEFAULT_WRAPPER_NAME) + raise ArgumentError, "don't wrap wrapper_name" unless valid_param?(param) + + @_wrapper_name = param.to_s + end + + def page_param + @_page_param ||= DEFAULT_PAGE_PARAM + "#{wrapper_name}[#{@_page_param}]" + end + + def page_param=(param = DEFAULT_PAGE_PARAM) + raise ArgumentError, "don't wrap page_param" unless valid_param?(param) + + @_page_param = param.to_s + end + + def per_page_param + @_per_page_param ||= DEFAULT_PER_PAGE_PARAM + "#{wrapper_name}[#{@_per_page_param}]" + end + + def per_page_param=(param = DEFAULT_PER_PAGE_PARAM) + raise ArgumentError, "don't wrap per_page_param" unless valid_param?(param) + + @_per_page_param = param + end + + private + + def valid_param?(param) + !(param.nil? || param.to_s.include?("[") || param.to_s.include?("]")) + end + + end + + attr_reader :params, :result_set, :links + + def initialize(result_set, data) + @params = params_for_uri(result_set.uri) + @result_set = result_set + @links = data["links"] + end + + def next + result_set.links.fetch_link("next") + end + + def prev + result_set.links.fetch_link("prev") + end + + def first + result_set.links.fetch_link("first") + end + + def last + result_set.links.fetch_link("last") + end + + def total_pages + if links["last"] + uri = result_set.links.link_url_for("last") + last_params = params_for_uri(uri) + last_params.fetch(page_param, &method(:current_page)).to_i + else + current_page + end + end + + # this is an estimate, not necessarily an exact count + def total_entries + per_page * total_pages + end + def total_count; total_entries; end + + def offset + per_page * (current_page - 1) + end + + def per_page + params.fetch(per_page_param) do + result_set.length + end.to_i + end + + def current_page + params.fetch(page_param, 1).to_i + end + + def out_of_bounds? + current_page > total_pages + end + + def previous_page + current_page > 1 ? (current_page - 1) : nil + end + + def next_page + current_page < total_pages ? (current_page + 1) : nil + end + + def page_param + self.class.page_param + end + + def per_page_param + self.class.per_page_param + end + + alias limit_value per_page + + protected + + def params_for_uri(uri) + return {} unless uri + uri = Addressable::URI.parse(uri) + ( uri.query_values || {} ).with_indifferent_access + end + end + end +end diff --git a/lib/json_api_client/query/builder.rb b/lib/json_api_client/query/builder.rb index 8e82e77f..7799e67c 100644 --- a/lib/json_api_client/query/builder.rb +++ b/lib/json_api_client/query/builder.rb @@ -146,7 +146,13 @@ def primary_key_params end def pagination_params - @pagination_params.empty? ? {} : {page: @pagination_params} + if klass.paginator.ancestors.include?(Paginating::Paginator) + # Original Paginator inconsistently wraps pagination params here. Keeping + # default behavior for now so as not to break backward compatibility. + @pagination_params.empty? ? {} : {page: @pagination_params} + else + @pagination_params + end end def includes_params diff --git a/test/unit/nested_param_paginator_test.rb b/test/unit/nested_param_paginator_test.rb new file mode 100644 index 00000000..f6e788a5 --- /dev/null +++ b/test/unit/nested_param_paginator_test.rb @@ -0,0 +1,82 @@ +require 'test_helper' + +class NestedParamPaginatorTest < MiniTest::Test + + class Book < JsonApiClient::Resource + self.site = "http://example.com/" + end + + def setup + @nested_param_paginator = JsonApiClient::Paginating::NestedParamPaginator + @default_paginator = JsonApiClient::Paginating::Paginator + Article.paginator = @nested_param_paginator + end + + def teardown + @nested_param_paginator.page_param = @nested_param_paginator::DEFAULT_PAGE_PARAM + @nested_param_paginator.per_page_param = @nested_param_paginator::DEFAULT_PER_PAGE_PARAM + Article.paginator = @default_paginator + end + + def test_default_page_params_wrapped_consistently + assert_equal "page[page]", @nested_param_paginator.page_param + assert_equal "page[per_page]", @nested_param_paginator.per_page_param + end + + def test_custom_page_params_wrapped_consistently + @nested_param_paginator.page_param = "offset" + @nested_param_paginator.per_page_param = "limit" + assert_equal "page[offset]", @nested_param_paginator.page_param + assert_equal "page[limit]", @nested_param_paginator.per_page_param + end + + def test_custom_page_param_does_not_allow_double_wrap + assert_raises ArgumentError do + @nested_param_paginator.page_param = "page[number]" + end + end + + def test_custom_per_page_param_does_not_allow_double_wrap + assert_raises ArgumentError do + @nested_param_paginator.per_page_param = "page[size]" + end + end + + def test_pagination_params_total_calculations + @nested_param_paginator.page_param = "number" + @nested_param_paginator.per_page_param = "size" + stub_request(:get, "http://example.com/articles?page[number]=1&page[size]=2") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [{ + type: "articles", + id: "1", + attributes: { + title: "JSON API paints my bikeshed!" + } + }, + { + type: "articles", + id: "2", + attributes: { + title: "json_api_client counts pages correctly" + } + }], + links: { + self: "http://example.com/articles?page[number]=1&page[size]=2", + next: "http://example.com/articles?page[number]=2&page[size]=2", + prev: nil, + first: "http://example.com/articles?page[number]=1&page[size]=2", + last: "http://example.com/articles?page[number]=4&page[size]=2" + } + }.to_json) + + articles = Article.paginate(page: 1, per_page: 2).to_a + assert_equal 1, articles.current_page + assert_equal 4, articles.total_pages + assert_equal 8, articles.total_entries + ensure + @nested_param_paginator.page_param = "page" + @nested_param_paginator.per_page_param = "per_page" + end + +end diff --git a/test/unit/nested_param_paginator_top_level_links_test.rb b/test/unit/nested_param_paginator_top_level_links_test.rb new file mode 100644 index 00000000..faad42e6 --- /dev/null +++ b/test/unit/nested_param_paginator_top_level_links_test.rb @@ -0,0 +1,282 @@ +require 'test_helper' + +# Copied from TopLevelLinksTest and modified to have consistent +# pagination params using the new NestedParamPaginator +class NestedParamPaginatorTopLevelLinksTest < MiniTest::Test + + def setup + @nested_param_paginator = JsonApiClient::Paginating::NestedParamPaginator + @default_paginator = JsonApiClient::Paginating::Paginator + Article.paginator = @nested_param_paginator + end + + def teardown + @nested_param_paginator.page_param = @nested_param_paginator::DEFAULT_PAGE_PARAM + @nested_param_paginator.per_page_param = @nested_param_paginator::DEFAULT_PER_PAGE_PARAM + Article.paginator = @default_paginator + end + + def test_can_parse_global_links + stub_request(:get, "http://example.com/articles/1") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: { + type: "articles", + id: "1", + attributes: { + title: "JSON API paints my bikeshed!" + } + }, + links: { + self: "http://example.com/articles/1", + related: "http://example.com/articles/1/related" + } + }.to_json) + stub_request(:get, "http://example.com/articles/1/related") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: { + type: "article-image", + id: "14", + image: "http://foo.com/cat.png" + } + }.to_json) + + articles = Article.find(1) + links = articles.links + assert links + assert links.respond_to?(:related), "ResultSet links should respond to related" + + related = links.related + assert related.is_a?(JsonApiClient::ResultSet), "expected related link to return another ResultSet" + end + + def test_can_parse_pagination_links + stub_request(:get, "http://example.com/articles") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [{ + type: "articles", + id: "1", + attributes: { + title: "JSON API paints my bikeshed!" + } + }], + links: { + self: "http://example.com/articles", + next: "http://example.com/articles?page[page]=2", + prev: nil, + first: "http://example.com/articles", + last: "http://example.com/articles?page[page]=6" + } + }.to_json) + stub_request(:get, "http://example.com/articles?page[page]=2") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [{ + type: "articles", + id: "2", + attributes: { + title: "This is tha BOMB" + } + }], + links: { + self: "http://example.com/articles?page[page]=2", + next: "http://example.com/articles?page[page]=3", + prev: "http://example.com/articles", + first: "http://example.com/articles", + last: "http://example.com/articles?page[page]=6" + } + }.to_json) + + assert_pagination + end + + def test_can_parse_pagination_links_with_custom_config + @nested_param_paginator.page_param = "number" + + stub_request(:get, "http://example.com/articles") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [{ + type: "articles", + id: "1", + attributes: { + title: "JSON API paints my bikeshed!" + } + }], + links: { + self: "http://example.com/articles", + next: "http://example.com/articles?#{{page: {number: 2}}.to_query}", + prev: nil, + first: "http://example.com/articles", + last: "http://example.com/articles?#{{page: {number: 6}}.to_query}" + } + }.to_json) + stub_request(:get, "http://example.com/articles?#{{page: {number: 2}}.to_query}") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [{ + type: "articles", + id: "2", + attributes: { + title: "This is tha BOMB" + } + }], + links: { + self: "http://example.com/articles?#{{page: {number: 2}}.to_query}", + next: "http://example.com/articles?#{{page: {number: 3}}.to_query}", + prev: "http://example.com/articles", + first: "http://example.com/articles", + last: "http://example.com/articles?#{{page: {number: 6}}.to_query}" + } + }.to_json) + + assert_pagination + end + + def test_can_parse_pagination_links_when_no_next_page + stub_request(:get, "http://example.com/articles") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [{ + type: "articles", + id: "1", + attributes: { + title: "JSON API paints my bikeshed!" + } + }], + links: { + self: "http://example.com/articles", + prev: nil, + first: "http://example.com/articles", + last: "http://example.com/articles?page[page]=1" + } + }.to_json) + + assert_pagination_when_no_next_page + end + + def test_can_parse_complex_pagination_links + stub_request(:get, "http://example.com/articles") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [{ + type: "articles", + id: "1", + attributes: { + title: "JSON API paints my bikeshed!" + } + }], + links: { + self: { + href: "http://example.com/articles", + meta: {} + }, + next: { + href: "http://example.com/articles?page[page]=2", + meta: {} + }, + prev: nil, + first: { + href: "http://example.com/articles", + meta: {} + }, + last: { + href: "http://example.com/articles?page[page]=6", + meta: {} + } + } + }.to_json) + stub_request(:get, "http://example.com/articles?page[page]=2") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [{ + type: "articles", + id: "2", + attributes: { + title: "This is tha BOMB" + } + }], + links: { + self: { + href: "http://example.com/articles?page[page]=2", + meta: {} + }, + next: { + href: "http://example.com/articles?page[page]=3", + meta: {} + }, + prev: { + href: "http://example.com/articles", + meta: {} + }, + first: { + href: "http://example.com/articles", + meta: {} + }, + last: { + href: "http://example.com/articles?page[page]=6", + meta: {} + } + } + }.to_json) + + assert_pagination + end + + private + + def assert_pagination + articles = Article.all + + # test kaminari pagination params + assert_equal 1, articles.current_page + assert_equal 1, articles.per_page + assert_equal 6, articles.total_pages + assert_equal 0, articles.offset + assert_equal 6, articles.total_entries + assert_equal 1, articles.limit_value + assert_equal 2, articles.next_page + assert_equal 1, articles.per_page + assert_equal false, articles.out_of_bounds? + + # test browsing to next page + pages = articles.pages + assert pages.respond_to?(:next) + assert pages.respond_to?(:prev) + assert pages.respond_to?(:last) + assert pages.respond_to?(:first) + + page2 = articles.pages.next + assert page2.is_a?(JsonApiClient::ResultSet) + assert_equal 1, page2.length + article = page2.first + assert_equal "2", article.id + assert_equal "This is tha BOMB", article.title + + # test browsing to the previous page + page1 = page2.pages.prev + assert page1.is_a?(JsonApiClient::ResultSet) + assert_equal 1, page1.length + article = page1.first + assert_equal "1", article.id + assert_equal "JSON API paints my bikeshed!", article.title + end + + def assert_pagination_when_no_next_page + articles = Article.all + + # test kaminari pagination params + assert_equal 1, articles.current_page + assert_equal 1, articles.per_page + assert_equal 1, articles.total_pages + assert_equal 0, articles.offset + assert_equal 1, articles.total_entries + assert_equal 1, articles.limit_value + assert_nil articles.next_page + assert_equal 1, articles.per_page + assert_equal false, articles.out_of_bounds? + + # test browsing to next page + pages = articles.pages + assert pages.respond_to?(:next) + assert pages.respond_to?(:prev) + assert pages.respond_to?(:last) + assert pages.respond_to?(:first) + + page2 = articles.pages.next + assert page2.nil? + end +end From 0cd860ba44b71ce90495ca5275e7ba69d3794ea2 Mon Sep 17 00:00:00 2001 From: Doug Smith Date: Thu, 27 Jun 2019 15:21:34 -0500 Subject: [PATCH 067/118] chore: add NestedParamPaginator to README and CHANGELOG --- CHANGELOG.md | 2 ++ README.md | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23016f2a..f995fd91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- [#348](https://github.com/JsonApiClient/json_api_client/pull/348) - add NestedParamPaginator to address inconsistency in handling of pagination query string params (issue [#347](https://github.com/JsonApiClient/json_api_client/issues/347)). + ## 1.12.0 - [#345](https://github.com/JsonApiClient/json_api_client/pull/345) - track the real HTTP reason of ApiErrors diff --git a/README.md b/README.md index a21e6284..a3ad7a1c 100644 --- a/README.md +++ b/README.md @@ -599,6 +599,20 @@ class MyApi::Base < JsonApiClient::Resource end ``` +### NestedParamPaginator + +The current default `JsonApiClient::Paginating::Paginator` is inconsistent in the way it handles pagination query string params. The JSON:API spec seems to favor a nested structure like `page[page]=1&page[per_page]=10`. However, the default paginator doesn't always return or enforce that nested structure. See issue [#347](https://github.com/JsonApiClient/json_api_client/issues/347) for more details. + +If you'd like a more consistent pagination query string param behavior, assign the `JsonApiClient::Paginating::NestedParamPaginator` to your resource as a custom paginator, like so: + +```ruby +class Order < JsonApiClient::Resource + self.paginator = JsonApiClient::Paginating::NestedParamPaginator +end +``` + +You can also extend `NestedParamPaginator` in your custom paginators or assign the `page_param` or `per_page_param` as with the default version above. + ### Custom type If your model must be named differently from classified type of resource you can easily customize it. From c2deea55cccba646d80f4405b2b31cc523f6784b Mon Sep 17 00:00:00 2001 From: Sergey Tokarenko Date: Fri, 28 Jun 2019 18:21:02 +0300 Subject: [PATCH 068/118] Fix resource serialization involving last_result_set --- CHANGELOG.md | 2 ++ lib/json_api_client/resource.rb | 2 -- test/unit/serializing_test.rb | 23 +++++++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e94256fe..35c1a72a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- [#351](https://github.com/JsonApiClient/json_api_client/pull/351) - Remove rudimental `last_result_set` relationship from serializer + ## 1.12.2 - [#350](https://github.com/JsonApiClient/json_api_client/pull/350) - fix resource including with blank `relationships` response data diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index bae824e4..ed03eab3 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -458,7 +458,6 @@ def save self.attributes = updated.attributes self.links.attributes = updated.links.attributes self.relationships.attributes = updated.relationships.attributes - self.relationships.last_result_set = last_result_set clear_changes_information self.relationships.clear_changes_information _clear_cached_relationships @@ -477,7 +476,6 @@ def destroy false else mark_as_destroyed! - self.relationships.last_result_set = nil _clear_cached_relationships _clear_belongs_to_params true diff --git a/test/unit/serializing_test.rb b/test/unit/serializing_test.rb index 9fc14fca..e2460809 100644 --- a/test/unit/serializing_test.rb +++ b/test/unit/serializing_test.rb @@ -52,6 +52,29 @@ def test_as_json assert_equal expected, resource.first.as_json end + def test_as_json_involving_last_result_set + expected = { + 'type' => 'articles', + 'id' => '1', + 'attributes' => { + 'title' => 'Rails is Omakase' + } + } + stub_request(:post, "http://example.com/articles") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [{ + type: "articles", + id: "1", + attributes: { + title: "Rails is Omakase" + } + }] + }.to_json) + + resource = Article.create + assert_equal expected, resource.as_json + end + def test_as_json_api expected = { 'type' => 'articles', From 94f45e24b827e406570b5dde19c00056d1d90bde Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Fri, 28 Jun 2019 09:20:06 -0700 Subject: [PATCH 069/118] version bump: v1.13.0; readme update --- CHANGELOG.md | 2 ++ README.md | 4 ++-- lib/json_api_client/version.rb | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c58fa0c..e3372f7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 1.13.0 + - [#348](https://github.com/JsonApiClient/json_api_client/pull/348) - add NestedParamPaginator to address inconsistency in handling of pagination query string params (issue [#347](https://github.com/JsonApiClient/json_api_client/issues/347)). ## 1.12.2 diff --git a/README.md b/README.md index a3ad7a1c..9a5b764c 100644 --- a/README.md +++ b/README.md @@ -601,9 +601,9 @@ end ### NestedParamPaginator -The current default `JsonApiClient::Paginating::Paginator` is inconsistent in the way it handles pagination query string params. The JSON:API spec seems to favor a nested structure like `page[page]=1&page[per_page]=10`. However, the default paginator doesn't always return or enforce that nested structure. See issue [#347](https://github.com/JsonApiClient/json_api_client/issues/347) for more details. +The default `JsonApiClient::Paginating::Paginator` is not strict about how it handles the param keys ([#347](https://github.com/JsonApiClient/json_api_client/issues/347)). There is a second paginator that more rigorously adheres to the JSON:API pagination recommendation style of `page[page]=1&page[per_page]=10`. -If you'd like a more consistent pagination query string param behavior, assign the `JsonApiClient::Paginating::NestedParamPaginator` to your resource as a custom paginator, like so: +If this second style suits your needs better, it is available as a class override: ```ruby class Order < JsonApiClient::Resource diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index 18a87278..a9291ef5 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.12.2" + VERSION = "1.13.0" end From dbadb812630d77bed6d244f8fb8dd5a945a8eedc Mon Sep 17 00:00:00 2001 From: Christian Bruckmayer Date: Fri, 29 Mar 2019 16:07:36 +0000 Subject: [PATCH 070/118] Implement eql and hash for Builder class --- CHANGELOG.md | 2 ++ lib/json_api_client/query/builder.rb | 14 ++++++++++++++ test/unit/query_builder_test.rb | 20 ++++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce5ef983..8bab6b81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- [#338](https://github.com/JsonApiClient/json_api_client/pull/338) - implement hash and eql? for builder class + ## 1.13.0 - [#348](https://github.com/JsonApiClient/json_api_client/pull/348) - add NestedParamPaginator to address inconsistency in handling of pagination query string params (issue [#347](https://github.com/JsonApiClient/json_api_client/issues/347)). diff --git a/lib/json_api_client/query/builder.rb b/lib/json_api_client/query/builder.rb index 7799e67c..0740bbb5 100644 --- a/lib/json_api_client/query/builder.rb +++ b/lib/json_api_client/query/builder.rb @@ -109,6 +109,20 @@ def method_missing(method_name, *args, &block) to_a.send(method_name, *args, &block) end + def hash + [ + klass, + params + ].hash + end + + def ==(other) + return false unless other.is_a?(self.class) + + hash == other.hash + end + alias_method :eql?, :== + protected def _fetch diff --git a/test/unit/query_builder_test.rb b/test/unit/query_builder_test.rb index 87a6233e..4fdeaa29 100644 --- a/test/unit/query_builder_test.rb +++ b/test/unit/query_builder_test.rb @@ -312,4 +312,24 @@ def test_build_propagate_only_path_params assert_equal '123', record.author_id assert_equal [], record.relationships.changed end + + def test_build_hash_sum + a = ArticleNested.where(author_id: '123', name: 'John') + b = ArticleNested.where(author_id: '123', name: 'John') + c = ArticleNested.where(author_id: '123') + d = Article.where(author_id: '123', name: 'John') + assert(a.hash == b.hash) + assert_equal(false, a.hash == c.hash) + assert_equal(false, a.hash == d.hash) + end + + def test_build_eql + a = ArticleNested.where(author_id: '123', name: 'John') + b = ArticleNested.where(author_id: '123', name: 'John') + c = ArticleNested.where(author_id: '123') + d = Article.where(author_id: '123', name: 'John') + assert(a == b) + assert_equal(false, a == c) + assert_equal(false, a == d) + end end From 2b2033a2e1195ade957e2b0ec89ff0b9c8053cf4 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Wed, 3 Jul 2019 16:05:09 -0700 Subject: [PATCH 071/118] version bump: v1.14.0 --- CHANGELOG.md | 2 ++ lib/json_api_client/version.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0586c6ac..4ba42d90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 1.14.0 + - [#338](https://github.com/JsonApiClient/json_api_client/pull/338) - implement hash and eql? for builder class - [#351](https://github.com/JsonApiClient/json_api_client/pull/351) - Remove rudimental `last_result_set` relationship from serializer diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index a9291ef5..dba22994 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.13.0" + VERSION = "1.14.0" end From 548e94f8c3511215e2ab4c9e781028b7a1725208 Mon Sep 17 00:00:00 2001 From: Doug Smith Date: Fri, 19 Jul 2019 15:31:00 -0500 Subject: [PATCH 072/118] test: show relationships w/o included error (issue #352) --- .../compound_non_included_document_test.rb | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 test/unit/compound_non_included_document_test.rb diff --git a/test/unit/compound_non_included_document_test.rb b/test/unit/compound_non_included_document_test.rb new file mode 100644 index 00000000..853d52e1 --- /dev/null +++ b/test/unit/compound_non_included_document_test.rb @@ -0,0 +1,63 @@ +require 'test_helper' + +class CompoundNonIncludedDocumentTest < MiniTest::Test + + TEST_DATA = %{ + { + "links": { + "self": "http://example.com/posts", + "next": "http://example.com/posts?page[offset]=2", + "last": "http://example.com/posts?page[offset]=10" + }, + "data": [{ + "type": "posts", + "id": "1", + "attributes": { + "title": "JSON API paints my bikeshed!" + }, + "relationships": { + "author": { + "links": { + "self": "http://example.com/posts/1/relationships/author", + "related": "http://example.com/posts/1/author" + }, + "data": { "type": "people", "id": "9" } + }, + "comments": { + "links": { + "self": "http://example.com/posts/1/relationships/comments", + "related": "http://example.com/posts/1/comments" + }, + "data": [ + { "type": "comments", "id": "5" }, + { "type": "comments", "id": "12" } + ] + } + }, + "links": { + "self": "http://example.com/posts/1" + } + }] + } + } + + def test_can_handle_related_data_without_included + stub_request(:get, "http://example.com/articles") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: TEST_DATA) + + articles = Article.all + + assert articles.is_a?(JsonApiClient::ResultSet) + assert_equal 1, articles.length + + article = articles.first + assert_equal "1", article.id + assert_equal "JSON API paints my bikeshed!", article.title + + # has_one is nil if not included + assert_nil article.author + + # has_many is empty if not included + assert_equal 0, article.comments.size + end +end From 3939323394fe06f8b6ecb1b7a2d0e60913b4d235 Mon Sep 17 00:00:00 2001 From: Doug Smith Date: Fri, 19 Jul 2019 15:32:12 -0500 Subject: [PATCH 073/118] fix: resolve issue #352 --- CHANGELOG.md | 2 ++ lib/json_api_client/included_data.rb | 7 +++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ba42d90..a6d0ffa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- [#353](https://github.com/JsonApiClient/json_api_client/pull/353) - fix to support deserializing resources with relationships without those related resources being included in the response (issue [#352](https://github.com/JsonApiClient/json_api_client/issues/352)). + ## 1.14.0 - [#338](https://github.com/JsonApiClient/json_api_client/pull/338) - implement hash and eql? for builder class diff --git a/lib/json_api_client/included_data.rb b/lib/json_api_client/included_data.rb index 0b7063f4..8cf689c7 100644 --- a/lib/json_api_client/included_data.rb +++ b/lib/json_api_client/included_data.rb @@ -36,9 +36,7 @@ def data_for(method_name, definition) if data.is_a?(Array) # has_many link - data.map do |link_def| - record_for(link_def) - end + data.map(&method(:record_for)).compact else # has_one link record_for(data) @@ -53,7 +51,8 @@ def has_link?(name) # should return a resource record of some type for this linked document def record_for(link_def) - data[link_def["type"]][link_def["id"]] + record = data[link_def["type"]] + record[link_def["id"]] if record end end end From 1458441eeaceb19368af299fdc2e87a7980b1958 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Mon, 22 Jul 2019 22:25:37 -0700 Subject: [PATCH 074/118] version bump: 1.14.1 --- CHANGELOG.md | 2 ++ lib/json_api_client/version.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6d0ffa3..fc5dcb50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 1.14.1 + - [#353](https://github.com/JsonApiClient/json_api_client/pull/353) - fix to support deserializing resources with relationships without those related resources being included in the response (issue [#352](https://github.com/JsonApiClient/json_api_client/issues/352)). ## 1.14.0 diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index dba22994..d9e0b794 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.14.0" + VERSION = "1.14.1" end From f402577885df81646ce4ad147c452d1e14a2e2b1 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Tue, 23 Jul 2019 08:30:11 -0700 Subject: [PATCH 075/118] adding gemfile for rails 5.2.3; adding ruby 2.5.1, 2.6.0; trying to dd ruby-head --- .travis.yml | 4 ++++ gemfiles/5.2.3.gemfile | 15 +++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 gemfiles/5.2.3.gemfile diff --git a/.travis.yml b/.travis.yml index 1ab55760..4456fc39 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,9 @@ rvm: - 2.2.6 - 2.3.3 - 2.4.1 + - 2.5.1 + - 2.6.0 + - ruby-head env: global: - CODECLIMATE_REPO_TOKEN=396d4263adb6febf1e6e9b0c0e176fbde35e1a116a3c1ecf8dd4f9384e41979b @@ -12,6 +15,7 @@ gemfile: - gemfiles/4.1.gemfile - gemfiles/4.2.gemfile - gemfiles/5.0.gemfile + - gemfiles/5.2.3.gemfile matrix: fast_finish: true exclude: diff --git a/gemfiles/5.2.3.gemfile b/gemfiles/5.2.3.gemfile new file mode 100644 index 00000000..9a9fda0a --- /dev/null +++ b/gemfiles/5.2.3.gemfile @@ -0,0 +1,15 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rake" +gem "appraisal" +gem "activesupport", "~> 5.2.3" +gem "addressable", "~> 2.2" +gem "codeclimate-test-reporter", :group => :test, :require => nil + +group :development, :test do + gem "byebug", :platforms => [:mri_20, :mri_21, :mri_22] +end + +gemspec :path => "../" From 4a68b2d00147e0c4a6052e658f2b578409e6d723 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Tue, 23 Jul 2019 10:08:24 -0700 Subject: [PATCH 076/118] removing incompatible ruby-rails versions from travis --- .travis.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.travis.yml b/.travis.yml index 4456fc39..f285d31c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,24 @@ matrix: gemfile: gemfiles/4.1.gemfile - rvm: 2.4.1 gemfile: gemfiles/4.2.gemfile + - rvm: 2.5.1 + gemfile: gemfiles/4.0.gemfile + - rvm: 2.5.1 + gemfile: gemfiles/4.1.gemfile + - rvm: 2.5.1 + gemfile: gemfiles/4.2.gemfile + - rvm: 2.6.0 + gemfile: gemfiles/4.0.gemfile + - rvm: 2.6.0 + gemfile: gemfiles/4.1.gemfile + - rvm: 2.6.0 + gemfile: gemfiles/4.2.gemfile + - rvm: ruby-head + gemfile: gemfiles/4.0.gemfile + - rvm: ruby-head + gemfile: gemfiles/4.1.gemfile + - rvm: ruby-head + gemfile: gemfiles/4.2.gemfile # We need to install latest version of bundler, because one in travis # image is too old to recognize platform => :mri_22 in Gemfile. before_install: From b27006c2f59cc8de4127dbfde56e251d0d7df886 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Tue, 23 Jul 2019 10:13:34 -0700 Subject: [PATCH 077/118] removing incompatible ruby-rails versions from travis --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index f285d31c..53ec0048 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,8 @@ matrix: gemfile: gemfiles/4.1.gemfile - rvm: 2.4.1 gemfile: gemfiles/4.2.gemfile + - rvm: 2.5.1 + gemfile: gemfiles/3.2.gemfile - rvm: 2.5.1 gemfile: gemfiles/4.0.gemfile - rvm: 2.5.1 From 60137293ffbaafb9adef6d6016f391e317aa67ab Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Wed, 31 Jul 2019 11:42:31 +0900 Subject: [PATCH 078/118] Add gemfile for Rails 6.0 * The latest version of Rails 6.0 is Rails 6.0.0.rc2 * Rails 6.0 requires Ruby 2.5.0 then excluded CI agains Ruby 2.4 or lower Refer Rails 6.0 required_ruby_version https://github.com/rails/rails/blob/16f2cd88e3ee92de341ba30b102e9206be3c68a7/rails.gemspec#L12 --- .travis.yml | 7 +++++++ gemfiles/6.0.gemfile | 15 +++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 gemfiles/6.0.gemfile diff --git a/.travis.yml b/.travis.yml index 53ec0048..c74fbaa0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,9 +16,14 @@ gemfile: - gemfiles/4.2.gemfile - gemfiles/5.0.gemfile - gemfiles/5.2.3.gemfile + - gemfiles/6.0.gemfile matrix: fast_finish: true exclude: + - rvm: 2.2.6 + gemfile: gemfiles/6.0.gemfile + - rvm: 2.3.3 + gemfile: gemfiles/6.0.gemfile - rvm: 2.4.1 gemfile: gemfiles/3.2.gemfile - rvm: 2.4.1 @@ -27,6 +32,8 @@ matrix: gemfile: gemfiles/4.1.gemfile - rvm: 2.4.1 gemfile: gemfiles/4.2.gemfile + - rvm: 2.4.1 + gemfile: gemfiles/6.0.gemfile - rvm: 2.5.1 gemfile: gemfiles/3.2.gemfile - rvm: 2.5.1 diff --git a/gemfiles/6.0.gemfile b/gemfiles/6.0.gemfile new file mode 100644 index 00000000..bc1669af --- /dev/null +++ b/gemfiles/6.0.gemfile @@ -0,0 +1,15 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rake" +gem "appraisal" +gem "activesupport", "~> 6.0.0.rc2" +gem "addressable", "~> 2.2" +gem "codeclimate-test-reporter", :group => :test, :require => nil + +group :development, :test do + gem "byebug", :platforms => [:mri_25] +end + +gemspec :path => "../" From 8bc64f39b8d50f801998dd5e6c62ff2b75bd9759 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Sat, 17 Aug 2019 03:22:49 +0000 Subject: [PATCH 079/118] Support Rails 6.0.0 --- gemfiles/6.0.gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gemfiles/6.0.gemfile b/gemfiles/6.0.gemfile index bc1669af..e046e114 100644 --- a/gemfiles/6.0.gemfile +++ b/gemfiles/6.0.gemfile @@ -4,7 +4,7 @@ source "https://rubygems.org" gem "rake" gem "appraisal" -gem "activesupport", "~> 6.0.0.rc2" +gem "activesupport", "~> 6.0.0" gem "addressable", "~> 2.2" gem "codeclimate-test-reporter", :group => :test, :require => nil From 27c381b1b1fb783ea5492d29f503aa98b1830af6 Mon Sep 17 00:00:00 2001 From: Eric Otto Date: Thu, 22 Aug 2019 13:10:26 -0400 Subject: [PATCH 080/118] Fix indent in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9a5b764c..14bdaa08 100644 --- a/README.md +++ b/README.md @@ -166,8 +166,8 @@ module MyApi end class Customer < JsonApiClient::Resource - belongs_to :user, shallow_path: true - end + belongs_to :user, shallow_path: true + end end # try to find without the nested parameter From af722c53e0e7817b6870318b6ca07877d6630075 Mon Sep 17 00:00:00 2001 From: Denis Talakevich Date: Sun, 18 Aug 2019 17:02:38 +0400 Subject: [PATCH 081/118] fix missing constant formatter https://github.com/JsonApiClient/json_api_client/issues/333 --- CHANGELOG.md | 2 ++ lib/json_api_client.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc5dcb50..36bc58a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- [#357](https://github.com/JsonApiClient/json_api_client/pull/357) - fix missing constant formatter + ## 1.14.1 - [#353](https://github.com/JsonApiClient/json_api_client/pull/353) - fix to support deserializing resources with relationships without those related resources being included in the response (issue [#352](https://github.com/JsonApiClient/json_api_client/issues/352)). diff --git a/lib/json_api_client.rb b/lib/json_api_client.rb index 2f84ba9a..b4082727 100644 --- a/lib/json_api_client.rb +++ b/lib/json_api_client.rb @@ -2,6 +2,7 @@ require 'faraday_middleware' require 'json' require 'addressable/uri' +require 'json_api_client/formatter' module JsonApiClient autoload :Associations, 'json_api_client/associations' @@ -9,7 +10,6 @@ module JsonApiClient autoload :Connection, 'json_api_client/connection' autoload :Errors, 'json_api_client/errors' autoload :ErrorCollector, 'json_api_client/error_collector' - autoload :Formatter, 'json_api_client/formatter' autoload :Helpers, 'json_api_client/helpers' autoload :Implementation, 'json_api_client/implementation' autoload :IncludedData, 'json_api_client/included_data' From 7791de23ef907ca6b864ff0894b90d3b3590838c Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Thu, 29 Aug 2019 09:55:46 -0700 Subject: [PATCH 082/118] updating changelog; removing ruby-head from travis matrix --- .travis.yml | 6 ------ CHANGELOG.md | 3 +++ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index c74fbaa0..276ad868 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,12 +48,6 @@ matrix: gemfile: gemfiles/4.1.gemfile - rvm: 2.6.0 gemfile: gemfiles/4.2.gemfile - - rvm: ruby-head - gemfile: gemfiles/4.0.gemfile - - rvm: ruby-head - gemfile: gemfiles/4.1.gemfile - - rvm: ruby-head - gemfile: gemfiles/4.2.gemfile # We need to install latest version of bundler, because one in travis # image is too old to recognize platform => :mri_22 in Gemfile. before_install: diff --git a/CHANGELOG.md b/CHANGELOG.md index 36bc58a3..7346ac43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +## 1.15.0 + +- [#346](https://github.com/JsonApiClient/json_api_client/pull/346) - add the option to have immutable resources - [#357](https://github.com/JsonApiClient/json_api_client/pull/357) - fix missing constant formatter ## 1.14.1 From df414ec9030e634049d493ff6403e479000025c6 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Thu, 29 Aug 2019 09:58:40 -0700 Subject: [PATCH 083/118] version bump: v1.15.0 --- lib/json_api_client/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index d9e0b794..17850454 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.14.1" + VERSION = "1.15.0" end From bc8fc0940891c266e842893f178959a5374b6d4a Mon Sep 17 00:00:00 2001 From: Sergey Tokarenko Date: Sun, 22 Sep 2019 15:44:48 +0300 Subject: [PATCH 084/118] Support gzip content encoding --- .travis.yml | 6 +++++ CHANGELOG.md | 2 ++ lib/json_api_client/connection.rb | 1 + test/unit/connection_test.rb | 39 +++++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+) diff --git a/.travis.yml b/.travis.yml index 276ad868..c74fbaa0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,6 +48,12 @@ matrix: gemfile: gemfiles/4.1.gemfile - rvm: 2.6.0 gemfile: gemfiles/4.2.gemfile + - rvm: ruby-head + gemfile: gemfiles/4.0.gemfile + - rvm: ruby-head + gemfile: gemfiles/4.1.gemfile + - rvm: ruby-head + gemfile: gemfiles/4.2.gemfile # We need to install latest version of bundler, because one in travis # image is too old to recognize platform => :mri_22 in Gemfile. before_install: diff --git a/CHANGELOG.md b/CHANGELOG.md index 7346ac43..f70d40d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- [#359](https://github.com/JsonApiClient/json_api_client/pull/359) - Support gzip content encoding + ## 1.15.0 - [#346](https://github.com/JsonApiClient/json_api_client/pull/346) - add the option to have immutable resources diff --git a/lib/json_api_client/connection.rb b/lib/json_api_client/connection.rb index 227628a1..d7e9ab33 100644 --- a/lib/json_api_client/connection.rb +++ b/lib/json_api_client/connection.rb @@ -14,6 +14,7 @@ def initialize(options = {}) builder.use Middleware::JsonRequest builder.use Middleware::Status, status_middleware_options builder.use Middleware::ParseJson + builder.use ::FaradayMiddleware::Gzip builder.adapter(*adapter_options) end yield(self) if block_given? diff --git a/test/unit/connection_test.rb b/test/unit/connection_test.rb index 2298a61a..7b029b4c 100644 --- a/test/unit/connection_test.rb +++ b/test/unit/connection_test.rb @@ -15,6 +15,9 @@ def self.parse(*args) end end +class RegularResource < TestResource +end + class CustomConnectionResource < TestResource self.connection_class = NullConnection self.parser = NullParser @@ -69,4 +72,40 @@ def test_can_specify_http_proxy assert_equal proxy.uri.to_s, 'http://proxy.example.com' end + def test_gzipping_without_server_support + stub_request(:get, "http://example.com/regular_resources") + .with(headers: {'Accept-Encoding'=>'gzip,deflate'}) + .to_return( + status: 200, + body: {data: [{id: "1", type: "regular_resources", attributes: {foo: "bar"}}]}.to_json, + headers: {content_type: "application/vnd.api+json"} + ) + + resources = RegularResource.all + assert_equal 1, resources.length + resource = resources.first + assert_equal "bar", resource.foo + end + + def test_gzipping_with_server_support + io = StringIO.new + gz = Zlib::GzipWriter.new(io) + gz.write({data: [{id: "1", type: "regular_resources", attributes: {foo: "bar"}}]}.to_json) + gz.close + body = io.string + body.force_encoding('BINARY') if body.respond_to?(:force_encoding) + + stub_request(:get, "http://example.com/regular_resources") + .with(headers: {'Accept-Encoding'=>'gzip,deflate'}) + .to_return( + status: 200, + body: body, + headers: {content_type: "application/vnd.api+json", content_encoding: 'gzip'} + ) + + resources = RegularResource.all + assert_equal 1, resources.length + resource = resources.first + assert_equal "bar", resource.foo + end end From 7b7e8ec154eeb332421766b174f2e0e9b962c440 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Tue, 24 Sep 2019 12:57:10 -0700 Subject: [PATCH 085/118] version bump: v1.16.0 --- .travis.yml | 6 ------ CHANGELOG.md | 2 ++ lib/json_api_client/version.rb | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index c74fbaa0..276ad868 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,12 +48,6 @@ matrix: gemfile: gemfiles/4.1.gemfile - rvm: 2.6.0 gemfile: gemfiles/4.2.gemfile - - rvm: ruby-head - gemfile: gemfiles/4.0.gemfile - - rvm: ruby-head - gemfile: gemfiles/4.1.gemfile - - rvm: ruby-head - gemfile: gemfiles/4.2.gemfile # We need to install latest version of bundler, because one in travis # image is too old to recognize platform => :mri_22 in Gemfile. before_install: diff --git a/CHANGELOG.md b/CHANGELOG.md index f70d40d9..b457e3bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 1.16.0 + - [#359](https://github.com/JsonApiClient/json_api_client/pull/359) - Support gzip content encoding ## 1.15.0 diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index 17850454..2581ede6 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.15.0" + VERSION = "1.16.0" end From 61fb3beb178a3b92dbcdcd6c4d427aba9367ab2d Mon Sep 17 00:00:00 2001 From: Quan Chau Date: Wed, 25 Sep 2019 11:05:00 -0700 Subject: [PATCH 086/118] call from method so that it will execute parent classes' implementation --- lib/json_api_client/resource.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index 6ffdcc77..b0b0110b 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -109,6 +109,7 @@ def immutable(flag = true) def inherited(subclass) subclass._immutable = false + super end # Specifies the relative path that should be used for this resource; From 3f3861146a4a3022667385f487e6b1dad50449b0 Mon Sep 17 00:00:00 2001 From: Quan Chau Date: Thu, 3 Oct 2019 11:16:39 -0700 Subject: [PATCH 087/118] update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b457e3bc..e679dc7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- [#361](https://github.com/JsonApiClient/json_api_client/pull/361) - Call super from inherited method so that it will execute parent classes' implementations + ## 1.16.0 - [#359](https://github.com/JsonApiClient/json_api_client/pull/359) - Support gzip content encoding From db890adf3d7175e91829d359336ab7c98ed90a34 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Fri, 4 Oct 2019 11:36:53 -0700 Subject: [PATCH 088/118] version bump: v1.16.1 --- .travis.yml | 1 - CHANGELOG.md | 2 ++ lib/json_api_client/version.rb | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 276ad868..a1a8ce8e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ rvm: - 2.4.1 - 2.5.1 - 2.6.0 - - ruby-head env: global: - CODECLIMATE_REPO_TOKEN=396d4263adb6febf1e6e9b0c0e176fbde35e1a116a3c1ecf8dd4f9384e41979b diff --git a/CHANGELOG.md b/CHANGELOG.md index e679dc7b..a9b80bfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## 1.16.1 + - [#361](https://github.com/JsonApiClient/json_api_client/pull/361) - Call super from inherited method so that it will execute parent classes' implementations ## 1.16.0 diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index 2581ede6..56bde4cc 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.16.0" + VERSION = "1.16.1" end From 2f5dfefa743011b89616a5e760d01e4b43312de4 Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Mon, 6 Apr 2020 21:08:12 +0900 Subject: [PATCH 089/118] Relax faraday and faraday_middleware version --- json_api_client.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/json_api_client.gemspec b/json_api_client.gemspec index e837c5d0..30e962fb 100644 --- a/json_api_client.gemspec +++ b/json_api_client.gemspec @@ -12,8 +12,8 @@ Gem::Specification.new do |s| s.summary = 'Build client libraries compliant with specification defined by jsonapi.org' s.add_dependency "activesupport", '>= 3.2.0' - s.add_dependency "faraday", ['~> 0.15', '>= 0.15.2'] - s.add_dependency "faraday_middleware", '~> 0.9' + s.add_dependency "faraday", '>= 0.15.2', '< 1.2.0' + s.add_dependency "faraday_middleware", '>= 0.9.0', '< 1.2.0' s.add_dependency "addressable", '~> 2.2' s.add_dependency "activemodel", '>= 3.2.0' s.add_dependency "rack", '>= 0.2' From c94d6c78257a08326a7c8cb76fa2738d42842b68 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Mon, 17 Aug 2020 09:16:33 -0700 Subject: [PATCH 090/118] version bump: v1.17.0 --- CHANGELOG.md | 4 ++++ lib/json_api_client/version.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9b80bfb..0f868316 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 1.17.0 + +- [#364](https://github.com/JsonApiClient/json_api_client/pull/364) - Relax faraday and faraday middleware versions + ## 1.16.1 - [#361](https://github.com/JsonApiClient/json_api_client/pull/361) - Call super from inherited method so that it will execute parent classes' implementations diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index 56bde4cc..3a9c0bf4 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.16.1" + VERSION = "1.17.0" end From ac372b8bab3b71bb0aa5c4dcc6ced1d9c3fe8d14 Mon Sep 17 00:00:00 2001 From: Daniel Pepper Date: Fri, 21 Aug 2020 15:06:48 -0700 Subject: [PATCH 091/118] bigdecimal 2 BigDecimal 2.0 no longer supports `.new`. looks like this gem uses both `BigDecimal(x)` and `BigDecimal.new(x)` - mind if we upgrade these last few calls? --- CHANGELOG.md | 3 +++ lib/json_api_client/schema.rb | 2 +- lib/json_api_client/version.rb | 2 +- test/unit/coercion_test.rb | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f868316..d28cb00c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +## 1.17.1 +- [#370](https://github.com/JsonApiClient/json_api_client/pull/370) - bigdecimal 2 support + ## 1.17.0 - [#364](https://github.com/JsonApiClient/json_api_client/pull/364) - Relax faraday and faraday middleware versions diff --git a/lib/json_api_client/schema.rb b/lib/json_api_client/schema.rb index a760d5fd..db1860ed 100644 --- a/lib/json_api_client/schema.rb +++ b/lib/json_api_client/schema.rb @@ -29,7 +29,7 @@ def self.cast(value, _) class Decimal def self.cast(value, _) - BigDecimal.new(value) + BigDecimal(value) end end diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index 3a9c0bf4..873511ed 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.17.0" + VERSION = "1.17.1" end diff --git a/test/unit/coercion_test.rb b/test/unit/coercion_test.rb index c0d8d489..805a7982 100644 --- a/test/unit/coercion_test.rb +++ b/test/unit/coercion_test.rb @@ -88,6 +88,6 @@ def validate_coercion_targets(target) assert_equal target.integer_me, 2 assert_equal target.string_me, '1.0' assert_equal target.time_me, Time.parse(TIME_STRING) - assert_equal target.decimal_me, BigDecimal.new('1.5') + assert_equal target.decimal_me, BigDecimal('1.5') end end From 58584c0e540d3b791586912a5e9a59d00846cfcb Mon Sep 17 00:00:00 2001 From: Daniel Pepper Date: Sat, 29 Aug 2020 18:33:07 -0700 Subject: [PATCH 092/118] updating travis matrix --- .travis.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.travis.yml b/.travis.yml index a1a8ce8e..41c52274 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ rvm: - 2.4.1 - 2.5.1 - 2.6.0 + - 2.7.1 env: global: - CODECLIMATE_REPO_TOKEN=396d4263adb6febf1e6e9b0c0e176fbde35e1a116a3c1ecf8dd4f9384e41979b @@ -47,6 +48,14 @@ matrix: gemfile: gemfiles/4.1.gemfile - rvm: 2.6.0 gemfile: gemfiles/4.2.gemfile + - rvm: 2.7.1 + gemfile: gemfiles/3.2.gemfile + - rvm: 2.7.1 + gemfile: gemfiles/4.0.gemfile + - rvm: 2.7.1 + gemfile: gemfiles/4.1.gemfile + - rvm: 2.7.1 + gemfile: gemfiles/4.2.gemfile # We need to install latest version of bundler, because one in travis # image is too old to recognize platform => :mri_22 in Gemfile. before_install: From a7a5985b0fe08bab3af5c3f775d5ee53eaf635d2 Mon Sep 17 00:00:00 2001 From: Daniel Pepper Date: Sat, 29 Aug 2020 21:14:03 -0700 Subject: [PATCH 093/118] fix warnings Ruby 2.7 deprecates automatic conversion from a hash to keyword arguments https://blog.saeloun.com/2019/10/07/ruby-2-7-keyword-arguments-redesign.html fixing those up, plus one more I noticed in the logs from https://github.com/JsonApiClient/json_api_client/pull/370#issuecomment-683349721 https://travis-ci.org/github/JsonApiClient/json_api_client/jobs/722402404 thanks @sharshenov --- lib/json_api_client/query/requestor.rb | 20 ++++++++++++-------- test/unit/errors_test.rb | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/json_api_client/query/requestor.rb b/lib/json_api_client/query/requestor.rb index 6d0704bb..d7f32c93 100644 --- a/lib/json_api_client/query/requestor.rb +++ b/lib/json_api_client/query/requestor.rb @@ -10,17 +10,21 @@ def initialize(klass) # expects a record def create(record) - request(:post, klass.path(record.path_attributes), { - body: { data: record.as_json_api }, - params: record.request_params.to_params - }) + request( + :post, + klass.path(record.path_attributes), + body: { data: record.as_json_api }, + params: record.request_params.to_params + ) end def update(record) - request(:patch, resource_path(record.path_attributes), { - body: { data: record.as_json_api }, - params: record.request_params.to_params - }) + request( + :patch, + resource_path(record.path_attributes), + body: { data: record.as_json_api }, + params: record.request_params.to_params + ) end def get(params = {}) diff --git a/test/unit/errors_test.rb b/test/unit/errors_test.rb index dbfb04ac..0627f134 100644 --- a/test/unit/errors_test.rb +++ b/test/unit/errors_test.rb @@ -10,7 +10,7 @@ def test_connection_errors User.all end - assert_match /specific message/, err.message + assert_match(/specific message/, err.message) end def test_timeout_errors From e304514a27e12e454566421ad4b4a8ee32ea73f3 Mon Sep 17 00:00:00 2001 From: Inderpal Singh Date: Fri, 27 Nov 2020 08:56:11 +0100 Subject: [PATCH 094/118] Fix: Cannot access associations of 'dashed' type for a newly initialised resource. --- .../associations/base_association.rb | 2 +- lib/json_api_client/associations/has_one.rb | 2 +- test/unit/association_test.rb | 52 +++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/lib/json_api_client/associations/base_association.rb b/lib/json_api_client/associations/base_association.rb index 6894b353..ca9ffe62 100644 --- a/lib/json_api_client/associations/base_association.rb +++ b/lib/json_api_client/associations/base_association.rb @@ -24,7 +24,7 @@ def from_result_set(result_set) def load_records(data) data.map do |d| - record_class = Utils.compute_type(klass, d["type"].classify) + record_class = Utils.compute_type(klass, klass.key_formatter.unformat(d["type"]).classify) record_class.load id: d["id"] end end diff --git a/lib/json_api_client/associations/has_one.rb b/lib/json_api_client/associations/has_one.rb index fd29872e..df514f52 100644 --- a/lib/json_api_client/associations/has_one.rb +++ b/lib/json_api_client/associations/has_one.rb @@ -7,7 +7,7 @@ def from_result_set(result_set) end def load_records(data) - record_class = Utils.compute_type(klass, data["type"].classify) + record_class = Utils.compute_type(klass, klass.key_formatter.unformat(data["type"]).classify) record_class.load id: data["id"] end end diff --git a/test/unit/association_test.rb b/test/unit/association_test.rb index 9e83fca5..2c01b1e2 100644 --- a/test/unit/association_test.rb +++ b/test/unit/association_test.rb @@ -61,6 +61,17 @@ def self.route_formatter end end +class DashedOwner < Formatted +end + +class DashedProperty < Formatted + has_one :dashed_owner +end + +class DashedRegion < Formatted + has_many :dashed_properties +end + class Account < TestResource property :name property :is_active, default: true @@ -256,6 +267,26 @@ def test_has_one_loads_nil assert_nil property.owner, "expected to be able to ask for explicitly declared association even if it's not present" end + def test_load_has_one_with_dasherized_key_type + stub_request(:get, "http://example.com/dashed-owners/1") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [ + { + id: 1, + type: 'dashed-owners', + attributes: { + name: "Arjuna" + } + } + ], + }.to_json) + dashed_owner = DashedOwner.find(1).first + dashed_property = DashedProperty.new(dashed_owner: dashed_owner) + + assert_equal(DashedOwner, dashed_property.dashed_owner.class) + assert_equal(1, dashed_property.dashed_owner.id) + end + def test_has_one_fetches_relationship stub_request(:get, "http://example.com/properties/1") .to_return(headers: {content_type: "application/vnd.api+json"}, body: { @@ -554,6 +585,27 @@ def test_load_has_many_single_entry assert_equal("123 Main St.", owner.properties.first.address) end + def test_load_has_many_with_dasherized_key_type + stub_request(:get, "http://example.com/dashed-properties") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + data: [ + { + id: 1, + type: 'dashed-properties', + attributes: { + address: "78 Street No. 9, Ludhiana" + } + } + ], + }.to_json) + + dashed_properties = DashedProperty.all + dashed_region = DashedRegion.new(dashed_properties: dashed_properties) + + assert_equal(1, dashed_region.dashed_properties.count) + assert_equal(DashedProperty, dashed_region.dashed_properties[0].class) + end + def test_respect_included_has_many_relationship_empty_data stub_request(:get, "http://example.com/owners/1?include=properties") .to_return(headers: {content_type: "application/vnd.api+json"}, body: { From 1af57e2791f2f1d2ca5dc8090afdf1d9d0e59456 Mon Sep 17 00:00:00 2001 From: Inderpal Singh Date: Wed, 2 Dec 2020 08:05:44 +0100 Subject: [PATCH 095/118] #372 - Fix handling of dashed-types associations correctly --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d28cb00c..aa7ea085 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Unreleased +- [#372](https://github.com/JsonApiClient/json_api_client/pull/372) - Fix handling of dashed-types associations correctly ## 1.17.1 - [#370](https://github.com/JsonApiClient/json_api_client/pull/370) - bigdecimal 2 support From 9b9835bab0c03d29209d6006ce7602da255bfcb1 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Wed, 2 Dec 2020 12:41:43 -0800 Subject: [PATCH 096/118] version bump: v1.18.0 --- CHANGELOG.md | 2 ++ lib/json_api_client/version.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa7ea085..a45a9056 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## Unreleased + +## 1.18.0 - [#372](https://github.com/JsonApiClient/json_api_client/pull/372) - Fix handling of dashed-types associations correctly ## 1.17.1 diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index 873511ed..627fddf2 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.17.1" + VERSION = "1.18.0" end From 3ceaafdf8a57f17de9cb22a4ab89e882629dee7f Mon Sep 17 00:00:00 2001 From: Murat Toygar Date: Wed, 9 Dec 2020 23:51:44 +0300 Subject: [PATCH 097/118] fix broken links on CHANGELOG.md --- CHANGELOG.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a45a9056..d751e41a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,20 +96,20 @@ ## v1.6.3 -- [#312](https://githup.com/JsonApiClient/json_api_client/pull/312) - Don't raise on `422` +- [#312](https://github.com/JsonApiClient/json_api_client/pull/312) - Don't raise on `422` ## v1.6.2 -- [#311](https://githup.com/JsonApiClient/json_api_client/pull/311) - Raise JsonApiClient::Errors::ClientError for unhandled 4xx responses +- [#311](https://github.com/JsonApiClient/json_api_client/pull/311) - Raise JsonApiClient::Errors::ClientError for unhandled 4xx responses ## v1.6.1 -- [#297](https://githup.com/JsonApiClient/json_api_client/pull/297) - Fix test_helper -- [#298](https://githup.com/JsonApiClient/json_api_client/pull/298) - README update: arguments for custom connections run method -- [#306](https://githup.com/JsonApiClient/json_api_client/pull/306) - README update: pagination override examples -- [#307](https://githup.com/JsonApiClient/json_api_client/pull/307) - Symbolize params keys on model initialize -- [#304](https://githup.com/JsonApiClient/json_api_client/pull/304) - Optional add default to changes -- [#300](https://githup.com/JsonApiClient/json_api_client/pull/300) - Define methods for properties and associations getter/setter +- [#297](https://github.com/JsonApiClient/json_api_client/pull/297) - Fix test_helper +- [#298](https://github.com/JsonApiClient/json_api_client/pull/298) - README update: arguments for custom connections run method +- [#306](https://github.com/JsonApiClient/json_api_client/pull/306) - README update: pagination override examples +- [#307](https://github.com/JsonApiClient/json_api_client/pull/307) - Symbolize params keys on model initialize +- [#304](https://github.com/JsonApiClient/json_api_client/pull/304) - Optional add default to changes +- [#300](https://github.com/JsonApiClient/json_api_client/pull/300) - Define methods for properties and associations getter/setter ## v1.6.0 From 468325ee6fc767390b77cc936d01a391d3af5e71 Mon Sep 17 00:00:00 2001 From: Tilo Sloboda Date: Mon, 12 Apr 2021 14:17:36 -0700 Subject: [PATCH 098/118] update --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e4afc8a..5eb35275 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +Using a new major version can cause problems when the upstream moves to their v2.0 + +A better way to handle this in a forked repository is: + + - to not fork in the first place, but rather: + - create PRs to contribute to the upstream repository + - put modifications into an initializer until the upstream incorporates your PR + - if a forked repositiory is already in place: + - keep the master of the fork always in-sync with the upstream master + - keep any custom modifications on a private branch, e.g. chime, and use that branch in all your projects + # Chime ChangeLog: ## v2.18.0 @@ -11,6 +22,8 @@ ## v2.0.0 - Adding `bigdecimal ~> 2.0.0` as a gem dependency and updating BigDecimal constructors +------- + # Upstream ChangeLog: ## 1.18.0 From 9c50f6479f1d11dee188aec9afb12a05595c78d3 Mon Sep 17 00:00:00 2001 From: Tilo Sloboda Date: Mon, 12 Apr 2021 14:18:36 -0700 Subject: [PATCH 099/118] update --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eb35275..a3362f74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Using a new major version can cause problems when the upstream moves to their v2.0 -A better way to handle this in a forked repository is: +A better way to handle this is: - to not fork in the first place, but rather: - create PRs to contribute to the upstream repository From 37649122ec615f75debc132c1209709153d5631f Mon Sep 17 00:00:00 2001 From: Brian Lesperance Date: Thu, 29 Apr 2021 10:49:22 -0500 Subject: [PATCH 100/118] Display CI Status of "master" Branch Only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Travis CI, without the "branch" query param, will show the latest build regardless of the branch, which is a misleading indication of the health of the repo. At the time of this commit, "master" is 🟢 while [the latest build](https://travis-ci.org/github/JsonApiClient/json_api_client/builds/768058352) is 🔴 . --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 14bdaa08..cb60758d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# JsonApiClient [![Build Status](https://travis-ci.org/JsonApiClient/json_api_client.png)](https://travis-ci.org/JsonApiClient/json_api_client) [![Code Climate](https://codeclimate.com/github/JsonApiClient/json_api_client.png)](https://codeclimate.com/github/JsonApiClient/json_api_client) [![Code Coverage](https://codeclimate.com/github/JsonApiClient/json_api_client/coverage.png)](https://codeclimate.com/github/JsonApiClient/json_api_client) +# JsonApiClient [![Build Status](https://travis-ci.org/JsonApiClient/json_api_client.png?branch=master)](https://travis-ci.org/JsonApiClient/json_api_client) [![Code Climate](https://codeclimate.com/github/JsonApiClient/json_api_client.png)](https://codeclimate.com/github/JsonApiClient/json_api_client) [![Code Coverage](https://codeclimate.com/github/JsonApiClient/json_api_client/coverage.png)](https://codeclimate.com/github/JsonApiClient/json_api_client) This gem is meant to help you build an API client for interacting with REST APIs as laid out by [http://jsonapi.org](http://jsonapi.org). It attempts to give you a query building framework that is easy to understand (it is similar to ActiveRecord scopes). From 37f34276cb485a133525a0408e9c6141863f30d2 Mon Sep 17 00:00:00 2001 From: Emiliano Mancuso Date: Thu, 6 May 2021 11:42:17 +0100 Subject: [PATCH 101/118] Add extra error classes to handle server errors. In order to provide more granularity on the exceptions, we are adding some additional error classes for the common Server errors. --- lib/json_api_client/errors.rb | 36 ++++++++++++++++-------- lib/json_api_client/middleware/status.rb | 8 ++++++ test/unit/errors_test.rb | 36 ++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 12 deletions(-) diff --git a/lib/json_api_client/errors.rb b/lib/json_api_client/errors.rb index 712b9900..0d1e354d 100644 --- a/lib/json_api_client/errors.rb +++ b/lib/json_api_client/errors.rb @@ -44,6 +44,25 @@ class AccessDenied < ClientError class NotAuthorized < ClientError end + class NotFound < ClientError + attr_reader :uri + def initialize(uri) + @uri = uri + + msg = "Couldn't find resource at: #{uri.to_s}" + super nil, msg + end + end + + class Conflict < ClientError + def initialize(env, msg = 'Resource already exists') + super env, msg + end + end + + class TooManyRequests < ClientError + end + class ConnectionError < ApiError end @@ -59,23 +78,16 @@ def initialize(env, msg = nil) end end - class Conflict < ServerError - def initialize(env, msg = 'Resource already exists') - super env, msg - end + class InternalServerError < ServerError end - class NotFound < ServerError - attr_reader :uri - def initialize(uri) - @uri = uri + class BadGateway < ServerError + end - msg = "Couldn't find resource at: #{uri.to_s}" - super nil, msg - end + class ServiceUnavailable < ServerError end - class InternalServerError < ServerError + class GatewayTimeout < ServerError end class UnexpectedStatus < ServerError diff --git a/lib/json_api_client/middleware/status.rb b/lib/json_api_client/middleware/status.rb index 223f4aa3..545130cc 100644 --- a/lib/json_api_client/middleware/status.rb +++ b/lib/json_api_client/middleware/status.rb @@ -42,10 +42,18 @@ def handle_status(code, env) raise Errors::Conflict, env when 422 # Allow to proceed as resource errors will be populated + when 429 + raise Errors::TooManyRequests, env when 400..499 raise Errors::ClientError, env when 500 raise Errors::InternalServerError, env + when 502 + raise Errors::BadGateway, env + when 503 + raise Errors::ServiceUnavailable, env + when 504 + raise Errors::GatewayTimeout, env when 501..599 raise Errors::ServerError, env else diff --git a/test/unit/errors_test.rb b/test/unit/errors_test.rb index 0627f134..7f272d2f 100644 --- a/test/unit/errors_test.rb +++ b/test/unit/errors_test.rb @@ -96,6 +96,42 @@ def test_not_authorized end end + def test_too_many_requests + stub_request(:get, "http://example.com/users") + .to_return(headers: {content_type: "text/plain"}, status: 429, body: "too many requests") + + assert_raises JsonApiClient::Errors::TooManyRequests do + User.all + end + end + + def test_bad_gateway + stub_request(:get, "http://example.com/users") + .to_return(headers: {content_type: "text/plain"}, status: 502, body: "bad gateway") + + assert_raises JsonApiClient::Errors::BadGateway do + User.all + end + end + + def test_service_unavailable + stub_request(:get, "http://example.com/users") + .to_return(headers: {content_type: "text/plain"}, status: 503, body: "service unavailable") + + assert_raises JsonApiClient::Errors::ServiceUnavailable do + User.all + end + end + + def test_gateway_timeout + stub_request(:get, "http://example.com/users") + .to_return(headers: {content_type: "text/plain"}, status: 504, body: "gateway timeout") + + assert_raises JsonApiClient::Errors::GatewayTimeout do + User.all + end + end + def test_errors_are_rescuable_by_default_rescue begin raise JsonApiClient::Errors::ApiError, "Something bad happened" From f407acdaabec24011fa21bae1cfec73b9fae0e64 Mon Sep 17 00:00:00 2001 From: Emiliano Mancuso Date: Thu, 6 May 2021 12:21:54 +0100 Subject: [PATCH 102/118] Add 408-RequestTimeout class --- lib/json_api_client/errors.rb | 3 +++ lib/json_api_client/middleware/status.rb | 2 ++ test/unit/errors_test.rb | 9 +++++++++ 3 files changed, 14 insertions(+) diff --git a/lib/json_api_client/errors.rb b/lib/json_api_client/errors.rb index 0d1e354d..795235c4 100644 --- a/lib/json_api_client/errors.rb +++ b/lib/json_api_client/errors.rb @@ -54,6 +54,9 @@ def initialize(uri) end end + class RequestTimeout < ClientError + end + class Conflict < ClientError def initialize(env, msg = 'Resource already exists') super env, msg diff --git a/lib/json_api_client/middleware/status.rb b/lib/json_api_client/middleware/status.rb index 545130cc..37f69a70 100644 --- a/lib/json_api_client/middleware/status.rb +++ b/lib/json_api_client/middleware/status.rb @@ -38,6 +38,8 @@ def handle_status(code, env) raise Errors::AccessDenied, env when 404 raise Errors::NotFound, env[:url] + when 408 + raise Errors::RequestTimeout, env when 409 raise Errors::Conflict, env when 422 diff --git a/test/unit/errors_test.rb b/test/unit/errors_test.rb index 7f272d2f..6d533598 100644 --- a/test/unit/errors_test.rb +++ b/test/unit/errors_test.rb @@ -96,6 +96,15 @@ def test_not_authorized end end + def test_request_timeout + stub_request(:get, "http://example.com/users") + .to_return(headers: {content_type: "text/plain"}, status: 408, body: "request timeout") + + assert_raises JsonApiClient::Errors::RequestTimeout do + User.all + end + end + def test_too_many_requests stub_request(:get, "http://example.com/users") .to_return(headers: {content_type: "text/plain"}, status: 429, body: "too many requests") From 318b52d33c17578fc2c33a41ffce157f77a490ad Mon Sep 17 00:00:00 2001 From: Tilo Sloboda Date: Wed, 18 Aug 2021 13:59:34 -0700 Subject: [PATCH 103/118] use HashWithIndifferentAccess, instead of symbolize_keys --- json_api_client.gemspec | 1 + lib/json_api_client/query/builder.rb | 4 ++-- lib/json_api_client/resource.rb | 5 +++-- test/test_helper.rb | 1 + test/unit/query_builder_test.rb | 3 ++- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/json_api_client.gemspec b/json_api_client.gemspec index 30e962fb..a52e190e 100644 --- a/json_api_client.gemspec +++ b/json_api_client.gemspec @@ -20,6 +20,7 @@ Gem::Specification.new do |s| s.add_development_dependency "webmock", '~> 3.5.1' s.add_development_dependency "mocha" + s.add_development_dependency "pry" s.license = "MIT" diff --git a/lib/json_api_client/query/builder.rb b/lib/json_api_client/query/builder.rb index 0740bbb5..9d60dd27 100644 --- a/lib/json_api_client/query/builder.rb +++ b/lib/json_api_client/query/builder.rb @@ -67,11 +67,11 @@ def last end def build(attrs = {}) - klass.new @path_params.merge(attrs.symbolize_keys) + klass.new @path_params.merge(attrs.with_indifferent_access) end def create(attrs = {}) - klass.create @path_params.merge(attrs.symbolize_keys) + klass.create @path_params.merge(attrs.with_indifferent_access) end def params diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index b0b0110b..99db0c2d 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -152,6 +152,7 @@ def path(params = nil) parts = [resource_path] if params && _prefix_path.present? path_params = params.delete(:path) || params + parts.unshift(_set_prefix_path(path_params.symbolize_keys)) else parts.unshift(_prefix_path) @@ -348,7 +349,7 @@ def _build_connection(rebuild = false) # # @param params [Hash] Attributes, links, and relationships def initialize(params = {}) - params = params.symbolize_keys + params = params.with_indifferent_access @persisted = nil @destroyed = nil self.links = self.class.linker.new(params.delete(:links) || {}) @@ -538,7 +539,7 @@ def reset_request_select!(*resource_types) end def path_attributes - _belongs_to_params.merge attributes.slice( self.class.primary_key ).symbolize_keys + _belongs_to_params.merge attributes.slice( self.class.primary_key ).with_indifferent_access end protected diff --git a/test/test_helper.rb b/test/test_helper.rb index dac3d5cf..5f7b6446 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -5,6 +5,7 @@ require 'webmock/minitest' require 'mocha/minitest' require 'pp' +require 'pry' # shim for ActiveSupport 4.0.x requiring minitest 4.2 unless defined?(Minitest::Test) diff --git a/test/unit/query_builder_test.rb b/test/unit/query_builder_test.rb index 4fdeaa29..6fbb134b 100644 --- a/test/unit/query_builder_test.rb +++ b/test/unit/query_builder_test.rb @@ -308,7 +308,8 @@ def test_build_propagate_only_path_params query = ArticleNested.where(author_id: '123', name: 'John') record = query.build assert_equal [], record.changed - assert_equal({author_id: '123'}, record.__belongs_to_params) + assert_equal(record.__belongs_to_params[:author_id], '123') + assert_equal(record.__belongs_to_params['author_id'], '123') assert_equal '123', record.author_id assert_equal [], record.relationships.changed end From 36609375404d8db906cacc713d85fc9a740feb28 Mon Sep 17 00:00:00 2001 From: Emiliano Mancuso Date: Wed, 18 Aug 2021 23:14:07 +0100 Subject: [PATCH 104/118] Update lib/json_api_client/errors.rb Co-authored-by: Tilo --- lib/json_api_client/errors.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/json_api_client/errors.rb b/lib/json_api_client/errors.rb index 795235c4..925a5a1f 100644 --- a/lib/json_api_client/errors.rb +++ b/lib/json_api_client/errors.rb @@ -49,7 +49,7 @@ class NotFound < ClientError def initialize(uri) @uri = uri - msg = "Couldn't find resource at: #{uri.to_s}" + msg = "Resource not found: #{uri.to_s}" super nil, msg end end From 274f57e7e24e0dc157ac0040f119948852d1ecc8 Mon Sep 17 00:00:00 2001 From: Emiliano Mancuso Date: Wed, 18 Aug 2021 23:18:13 +0100 Subject: [PATCH 105/118] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a45a9056..72c599a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Unreleased +- [#382](https://github.com/JsonApiClient/json_api_client/pull/382) - Add extra error classes to hande server errors. ## 1.18.0 - [#372](https://github.com/JsonApiClient/json_api_client/pull/372) - Fix handling of dashed-types associations correctly From cbb54ab27338c0547bb6fee382289a431cd6da8e Mon Sep 17 00:00:00 2001 From: Tilo Sloboda Date: Wed, 18 Aug 2021 15:18:47 -0700 Subject: [PATCH 106/118] update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a45a9056..1942b23c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- (#386)[https://github.com/JsonApiClient/json_api_client/pull/386] - use HashWithIndifferentAccess + ## 1.18.0 - [#372](https://github.com/JsonApiClient/json_api_client/pull/372) - Fix handling of dashed-types associations correctly From 675775226ef18454e73bcc1d3e817b52b036c7e9 Mon Sep 17 00:00:00 2001 From: Tilo Sloboda Date: Wed, 18 Aug 2021 19:49:36 -0700 Subject: [PATCH 107/118] update --- lib/json_api_client/resource.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index 99db0c2d..12f7c906 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -152,7 +152,6 @@ def path(params = nil) parts = [resource_path] if params && _prefix_path.present? path_params = params.delete(:path) || params - parts.unshift(_set_prefix_path(path_params.symbolize_keys)) else parts.unshift(_prefix_path) From 40f0c1d04231e8cee31dfb60c33f999d33d5a3b0 Mon Sep 17 00:00:00 2001 From: Tilo Sloboda Date: Wed, 18 Aug 2021 19:59:22 -0700 Subject: [PATCH 108/118] merge Chime master into branch --- json_api_client.gemspec | 1 + lib/json_api_client/errors.rb | 38 ++++++++++++++---------- lib/json_api_client/middleware/status.rb | 8 ++--- lib/json_api_client/query/builder.rb | 4 +-- lib/json_api_client/resource.rb | 4 +-- test/test_helper.rb | 1 + test/unit/query_builder_test.rb | 4 ++- test/unit/serializing_test.rb | 1 - 8 files changed, 34 insertions(+), 27 deletions(-) diff --git a/json_api_client.gemspec b/json_api_client.gemspec index 30e962fb..a52e190e 100644 --- a/json_api_client.gemspec +++ b/json_api_client.gemspec @@ -20,6 +20,7 @@ Gem::Specification.new do |s| s.add_development_dependency "webmock", '~> 3.5.1' s.add_development_dependency "mocha" + s.add_development_dependency "pry" s.license = "MIT" diff --git a/lib/json_api_client/errors.rb b/lib/json_api_client/errors.rb index 8cfd2e64..4c6d2683 100644 --- a/lib/json_api_client/errors.rb +++ b/lib/json_api_client/errors.rb @@ -44,6 +44,28 @@ class AccessDenied < ClientError class NotAuthorized < ClientError end + class NotFound < ClientError + attr_reader :uri + def initialize(uri) + @uri = uri + + msg = "Resource not found: #{uri.to_s}" + super nil, msg + end + end + + class RequestTimeout < ClientError + end + + class Conflict < ClientError + def initialize(env, msg = 'Resource already exists') + super env, msg + end + end + + class TooManyRequests < ClientError + end + class ConnectionError < ApiError end @@ -59,22 +81,6 @@ def initialize(env, msg = nil) end end - class Conflict < ServerError - def initialize(env, msg = 'Resource already exists') - super env, msg - end - end - - class NotFound < ServerError - attr_reader :uri - def initialize(uri) - @uri = uri - - msg = "Couldn't find resource at: #{uri.to_s}" - super nil, msg - end - end - class InternalServerError < ServerError end diff --git a/lib/json_api_client/middleware/status.rb b/lib/json_api_client/middleware/status.rb index 0160c5ff..24e88a2f 100644 --- a/lib/json_api_client/middleware/status.rb +++ b/lib/json_api_client/middleware/status.rb @@ -38,17 +38,15 @@ def handle_status(code, env) raise Errors::AccessDenied, env when 404 raise Errors::NotFound, env[:url] + when 408 + raise Errors::RequestTimeout, env when 409 raise Errors::Conflict, env when 422 # Allow to proceed as resource errors will be populated when 429 - # too many requests - raise Errors::ApiError, env + raise Errors::ApiError, env # too many requests when 400..499 - # some other error - # TODO: raise Errors::ApiError, env - # https://github.com/JsonApiClient/json_api_client/blame/master/lib/json_api_client/middleware/status.rb#L46 raise Errors::ClientError, env when 500 raise Errors::InternalServerError, env diff --git a/lib/json_api_client/query/builder.rb b/lib/json_api_client/query/builder.rb index 0740bbb5..9d60dd27 100644 --- a/lib/json_api_client/query/builder.rb +++ b/lib/json_api_client/query/builder.rb @@ -67,11 +67,11 @@ def last end def build(attrs = {}) - klass.new @path_params.merge(attrs.symbolize_keys) + klass.new @path_params.merge(attrs.with_indifferent_access) end def create(attrs = {}) - klass.create @path_params.merge(attrs.symbolize_keys) + klass.create @path_params.merge(attrs.with_indifferent_access) end def params diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index 57de2bb5..0665c996 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -354,7 +354,7 @@ def _build_connection(rebuild = false) # # @param params [Hash] Attributes, links, and relationships def initialize(params = {}) - params = params.with_indifferent_access # upstream has: params = params.symbolize_keys + params = params.with_indifferent_access @persisted = nil @destroyed = nil self.links = self.class.linker.new(params.delete(:links) || {}) @@ -553,7 +553,7 @@ def reset_request_select!(*resource_types) end def path_attributes - _belongs_to_params.merge attributes.slice( self.class.primary_key ).symbolize_keys + _belongs_to_params.merge attributes.slice( self.class.primary_key ).with_indifferent_access end protected diff --git a/test/test_helper.rb b/test/test_helper.rb index dac3d5cf..5f7b6446 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -5,6 +5,7 @@ require 'webmock/minitest' require 'mocha/minitest' require 'pp' +require 'pry' # shim for ActiveSupport 4.0.x requiring minitest 4.2 unless defined?(Minitest::Test) diff --git a/test/unit/query_builder_test.rb b/test/unit/query_builder_test.rb index 4fdeaa29..b72d7711 100644 --- a/test/unit/query_builder_test.rb +++ b/test/unit/query_builder_test.rb @@ -307,8 +307,10 @@ def test_build_does_not_propagate_values def test_build_propagate_only_path_params query = ArticleNested.where(author_id: '123', name: 'John') record = query.build + assert_equal [], record.changed - assert_equal({author_id: '123'}, record.__belongs_to_params) + assert_equal(record.__belongs_to_params[:author_id], '123') + assert_equal(record.__belongs_to_params['author_id'], '123') assert_equal '123', record.author_id assert_equal [], record.relationships.changed end diff --git a/test/unit/serializing_test.rb b/test/unit/serializing_test.rb index b422080e..e2460809 100644 --- a/test/unit/serializing_test.rb +++ b/test/unit/serializing_test.rb @@ -148,7 +148,6 @@ def test_update_data_only_includes_relationship_data expected = { "type" => "articles", "id" => "1", - "relationships"=>{"author"=>{"data"=>{"type"=>"people", "id"=>"9"}}}, "attributes" => {} } assert_equal expected, article.as_json_api From ae3db3871959488c1bbad427f9e4229157cceb6d Mon Sep 17 00:00:00 2001 From: Tilo Sloboda Date: Thu, 19 Aug 2021 09:33:52 -0700 Subject: [PATCH 109/118] removing pry --- json_api_client.gemspec | 1 - test/test_helper.rb | 1 - 2 files changed, 2 deletions(-) diff --git a/json_api_client.gemspec b/json_api_client.gemspec index a52e190e..30e962fb 100644 --- a/json_api_client.gemspec +++ b/json_api_client.gemspec @@ -20,7 +20,6 @@ Gem::Specification.new do |s| s.add_development_dependency "webmock", '~> 3.5.1' s.add_development_dependency "mocha" - s.add_development_dependency "pry" s.license = "MIT" diff --git a/test/test_helper.rb b/test/test_helper.rb index 5f7b6446..dac3d5cf 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -5,7 +5,6 @@ require 'webmock/minitest' require 'mocha/minitest' require 'pp' -require 'pry' # shim for ActiveSupport 4.0.x requiring minitest 4.2 unless defined?(Minitest::Test) From bd759fbc8c6c0e7b5816aa4b1a92f0b253bb7418 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Thu, 19 Aug 2021 09:45:56 -0700 Subject: [PATCH 110/118] version bump: v1.19.0 --- CHANGELOG.md | 4 +++- lib/json_api_client/version.rb | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0050b11e..e4fc82b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,11 @@ # Changelog ## Unreleased + +## 1.19.0 - [#382](https://github.com/JsonApiClient/json_api_client/pull/382) - Add extra error classes to hande server errors. -- (#386)[https://github.com/JsonApiClient/json_api_client/pull/386] - use HashWithIndifferentAccess +- [#386](https://github.com/JsonApiClient/json_api_client/pull/386)- use HashWithIndifferentAccess ## 1.18.0 - [#372](https://github.com/JsonApiClient/json_api_client/pull/372) - Fix handling of dashed-types associations correctly diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index 627fddf2..c7731408 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.18.0" + VERSION = "1.19.0" end From bbf1747ceb203e04aeb22eff9b3e1d7ed77f4540 Mon Sep 17 00:00:00 2001 From: Tilo Sloboda Date: Mon, 6 Sep 2021 04:42:55 -0700 Subject: [PATCH 111/118] adding bang-methods for create and update --- lib/json_api_client/errors.rb | 11 +++++++ lib/json_api_client/resource.rb | 15 +++++++++ test/unit/creation_test.rb | 58 +++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+) diff --git a/lib/json_api_client/errors.rb b/lib/json_api_client/errors.rb index 925a5a1f..4d5b5e40 100644 --- a/lib/json_api_client/errors.rb +++ b/lib/json_api_client/errors.rb @@ -103,5 +103,16 @@ def initialize(code, uri) super nil, msg end end + + class RecordNotSaved < ServerError + attr_reader :record + + def initialize(record = nil) + @record = record + msg = 'Record not saved' + + super nil, msg + end + end end end diff --git a/lib/json_api_client/resource.rb b/lib/json_api_client/resource.rb index 12f7c906..0665c996 100644 --- a/lib/json_api_client/resource.rb +++ b/lib/json_api_client/resource.rb @@ -172,6 +172,12 @@ def create(attributes = {}) end end + def create!(attributes = {}) + new(attributes).tap do |resource| + raise(Errors::RecordNotSaved.new("Failed to save the record", resource)) unless resource.save + end + end + # Within the given block, add these headers to all requests made by # the resource class # @@ -376,6 +382,11 @@ def update_attributes(attrs = {}) save end + def update_attributes!(attrs = {}) + self.attributes = attrs + save ? true : raise(Errors::RecordNotSaved.new("Failed to update the record", self)) + end + # Alias to update_attributes # # @param attrs [Hash] Attributes to update @@ -384,6 +395,10 @@ def update(attrs = {}) update_attributes(attrs) end + def update!(attrs = {}) + update_attributes!(attrs) + end + # Mark the record as persisted def mark_as_persisted! @persisted = true diff --git a/test/unit/creation_test.rb b/test/unit/creation_test.rb index 9798b902..61c8eb8f 100644 --- a/test/unit/creation_test.rb +++ b/test/unit/creation_test.rb @@ -206,7 +206,65 @@ def test_can_create_with_new_record_with_relationships_and_save assert article.persisted? assert_equal article.comments.length, 1 assert_equal "1", article.id + end + + def test_can_create_with_new_record_with_associated_relationships_and_save + stub_request(:post, "http://example.com/articles") + .with(headers: {content_type: "application/vnd.api+json", accept: "application/vnd.api+json"}, body: { + data: { + type: "articles", + relationships: { + author: { + data: { + id: 1, + type: "authors" + } + } + }, + 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: { + author: { + data: [ + { + id: "1", + type: "comments" + } + ] + } + } + }, + included: [ + { + id: "1", + type: "authors", + } + ] + }.to_json) + author_hash = { + author: { + data: { + id: 1, + type: 'authors' + } + } + } + article = Article.new({title: "Rails is Omakase", "relationships" => author_hash}) + + assert article.save + assert article.persisted? + assert_equal "1", article.id end def test_correct_create_with_nil_attribute_value From c2498097d7ae5f0cf3aa9e0ded12246008ffc584 Mon Sep 17 00:00:00 2001 From: Tilo Sloboda Date: Wed, 8 Sep 2021 02:10:19 -0700 Subject: [PATCH 112/118] adding tests --- lib/json_api_client/errors.rb | 8 ++++---- test/unit/creation_test.rb | 17 +++++++++++++++++ test/unit/updating_test.rb | 30 ++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/lib/json_api_client/errors.rb b/lib/json_api_client/errors.rb index 4d5b5e40..cb267e02 100644 --- a/lib/json_api_client/errors.rb +++ b/lib/json_api_client/errors.rb @@ -107,11 +107,11 @@ def initialize(code, uri) class RecordNotSaved < ServerError attr_reader :record - def initialize(record = nil) + def initialize(message = nil, record = nil) @record = record - msg = 'Record not saved' - - super nil, msg + end + def message + "Record not saved" end end end diff --git a/test/unit/creation_test.rb b/test/unit/creation_test.rb index 61c8eb8f..dc2171df 100644 --- a/test/unit/creation_test.rb +++ b/test/unit/creation_test.rb @@ -66,6 +66,23 @@ def test_can_create_with_class_method assert_equal "Rails is Omakase", article.title end + def test_failed_create! + stub_request(:post, "http://example.com/users") + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + errors: [ + { + status: "400", + title: "Error" + } + ] + }.to_json) + + exception = assert_raises JsonApiClient::Errors::RecordNotSaved do + User.create!(name: 'Hans') + end + assert_equal "Record not saved", exception.message + end + def test_changed_attributes_empty_after_create_with_class_method stub_simple_creation article = Article.create({ diff --git a/test/unit/updating_test.rb b/test/unit/updating_test.rb index 4a96f32f..b747254b 100644 --- a/test/unit/updating_test.rb +++ b/test/unit/updating_test.rb @@ -36,6 +36,36 @@ def stub_simple_fetch }.to_json) end + def test_failed_update! + stub_simple_fetch + articles = Article.find(1) + article = articles.first + + stub_request(:patch, "http://example.com/articles/1") + .with(headers: {content_type: "application/vnd.api+json", accept: "application/vnd.api+json"}, body: { + data: { + id: "1", + type: "articles", + attributes: { + title: "Modified title", + } + } + }.to_json) + .to_return(headers: {content_type: "application/vnd.api+json"}, body: { + errors: [ + { + status: "400", + title: "Error" + } + ] + }.to_json) + + exception = assert_raises JsonApiClient::Errors::RecordNotSaved do + article.update!(title: 'Modified title') + end + assert_equal "Record not saved", exception.message + end + def test_can_update_found_record stub_simple_fetch articles = Article.find(1) From 8a6626e5c82f91b2030512fb625ad2f526329c69 Mon Sep 17 00:00:00 2001 From: Tilo Sloboda Date: Wed, 8 Sep 2021 02:12:44 -0700 Subject: [PATCH 113/118] update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4fc82b8..f8c47953 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- [#389](https://github.com/JsonApiClient/json_api_client/pull/389)-adding create!, update_attributes!, update! methods + ## 1.19.0 - [#382](https://github.com/JsonApiClient/json_api_client/pull/382) - Add extra error classes to hande server errors. From 19f4840f97f87528f9534c40d82f63e5469091a3 Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Wed, 8 Sep 2021 09:23:54 -0700 Subject: [PATCH 114/118] version bump: 1.20.0 --- CHANGELOG.md | 3 ++- lib/json_api_client/version.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8c47953..a87eac13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ ## Unreleased -- [#389](https://github.com/JsonApiClient/json_api_client/pull/389)-adding create!, update_attributes!, update! methods +## 1.20.0 +- [#389](https://github.com/JsonApiClient/json_api_client/pull/389)-adding `create!`, `update_attributes!`, `update!` methods ## 1.19.0 - [#382](https://github.com/JsonApiClient/json_api_client/pull/382) - Add extra error classes to hande server errors. diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index c7731408..dee663bc 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.19.0" + VERSION = "1.20.0" end From 7583b8c3798d779f6675185a305051947e553728 Mon Sep 17 00:00:00 2001 From: Tilo Sloboda Date: Wed, 8 Sep 2021 12:09:39 -0700 Subject: [PATCH 115/118] update CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14c2c286..785e7c2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ A better way to handle this is: # Chime ChangeLog: +## v2.19.0 +- merge upstream/master 1.19.0 into our fork + ## v2.18.0 - merge upstream/master 1.18.0 into our fork From c2a18aca69cfd455529742e1f3145a7d74510316 Mon Sep 17 00:00:00 2001 From: Tilo Sloboda Date: Wed, 8 Sep 2021 12:21:13 -0700 Subject: [PATCH 116/118] update CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bc1083f..16b1320d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,11 @@ A better way to handle this is: # Chime ChangeLog: +DO NOT ADD ANY OTHER FUNCTIONALITY HERE! +CREATE UPSTREAM PRs IF YOU WANT TO CONTRIBUTE + +THE PLAN IS TO GET RID OF OUR FORK ALTOGETHER + ## v2.20.0 - merge upstream/master 1.20.0 into our fork From 8fb1797b2f0ef50c99411621869b071deb723484 Mon Sep 17 00:00:00 2001 From: Grant McLendon Date: Thu, 16 Dec 2021 15:24:47 -0600 Subject: [PATCH 117/118] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Bump=20Faraday=20d?= =?UTF-8?q?ependency=20to=20v2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile | 1 + json_api_client.gemspec | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 0b5a6e5c..75212726 100644 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,7 @@ source "https://rubygems.org" gemspec gem 'rake' +gem 'rdoc' gem 'appraisal' gem "activesupport" diff --git a/json_api_client.gemspec b/json_api_client.gemspec index 30e962fb..658f92f3 100644 --- a/json_api_client.gemspec +++ b/json_api_client.gemspec @@ -12,8 +12,8 @@ Gem::Specification.new do |s| s.summary = 'Build client libraries compliant with specification defined by jsonapi.org' s.add_dependency "activesupport", '>= 3.2.0' - s.add_dependency "faraday", '>= 0.15.2', '< 1.2.0' - s.add_dependency "faraday_middleware", '>= 0.9.0', '< 1.2.0' + s.add_dependency "faraday", '>= 0.15.2', '< 2.0' + s.add_dependency "faraday_middleware", '>= 0.9.0', '< 2.0' s.add_dependency "addressable", '~> 2.2' s.add_dependency "activemodel", '>= 3.2.0' s.add_dependency "rack", '>= 0.2' From 4277a862edf2f80a168c2c7e0206325a9eda5a4a Mon Sep 17 00:00:00 2001 From: Greg Orlov Date: Fri, 21 Jan 2022 15:15:23 -0800 Subject: [PATCH 118/118] version bump: v1.21.0 --- CHANGELOG.md | 7 +++++-- Gemfile | 1 - lib/json_api_client/version.rb | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a87eac13..92fd7a55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,16 @@ ## Unreleased +## 1.21.0 +- [#395](https://github.com/JsonApiClient/json_api_client/pull/395) - relaxing faraday dependency to anything less than 2.0 + ## 1.20.0 -- [#389](https://github.com/JsonApiClient/json_api_client/pull/389)-adding `create!`, `update_attributes!`, `update!` methods +- [#389](https://github.com/JsonApiClient/json_api_client/pull/389) - adding `create!`, `update_attributes!`, `update!` methods ## 1.19.0 - [#382](https://github.com/JsonApiClient/json_api_client/pull/382) - Add extra error classes to hande server errors. -- [#386](https://github.com/JsonApiClient/json_api_client/pull/386)- use HashWithIndifferentAccess +- [#386](https://github.com/JsonApiClient/json_api_client/pull/386) - use HashWithIndifferentAccess ## 1.18.0 - [#372](https://github.com/JsonApiClient/json_api_client/pull/372) - Fix handling of dashed-types associations correctly diff --git a/Gemfile b/Gemfile index 75212726..0b5a6e5c 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,6 @@ source "https://rubygems.org" gemspec gem 'rake' -gem 'rdoc' gem 'appraisal' gem "activesupport" diff --git a/lib/json_api_client/version.rb b/lib/json_api_client/version.rb index dee663bc..5379f494 100644 --- a/lib/json_api_client/version.rb +++ b/lib/json_api_client/version.rb @@ -1,3 +1,3 @@ module JsonApiClient - VERSION = "1.20.0" + VERSION = "1.21.0" end