From 1844c162f14e64adb20bda20371efc7ff844c9ef Mon Sep 17 00:00:00 2001 From: Leandro Cesquini Pereira Date: Fri, 24 Jul 2015 23:05:52 -0300 Subject: [PATCH 1/3] Adds support for top-level links to JsonApi adapter http://jsonapi.org/format/#document-top-level fix failing tests support for top-level links limited to jsonapi adapter Move docs from README to docs/ dir move links to json-api adapter & create Links class to hold links data --- .gitignore | 1 + CHANGELOG.md | 2 + docs/README.md | 1 + docs/howto/add_top_level_links.md | 31 ++++++ .../serializer/adapter/json_api/links.rb | 25 +++++ test/action_controller/serialization_test.rb | 34 ++++++ test/adapter/json_api/top_level_links_test.rb | 101 ++++++++++++++++++ 7 files changed, 195 insertions(+) create mode 100644 docs/howto/add_top_level_links.md create mode 100644 lib/active_model/serializer/adapter/json_api/links.rb create mode 100644 test/adapter/json_api/top_level_links_test.rb diff --git a/.gitignore b/.gitignore index cd2acb28..2bc7e6c8 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ tmp .ruby-version .ruby-gemset vendor/bundle +tags diff --git a/CHANGELOG.md b/CHANGELOG.md index 721f42ac..fb9ae71b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,8 @@ Features: CollectionSerializer for clarity, add ActiveModelSerializers.config.collection_serializer (@bf4) - [#1295](https://github.com/rails-api/active_model_serializers/pull/1295) Add config `serializer_lookup_enabled` that, when disabled, requires serializers to explicitly specified. (@trek) +- [#1247](https://github.com/rails-api/active_model_serializers/pull/1247) Add top-level links (@beauby) + * Add more tests and docs for top-level links (@leandrocp) Fixes: diff --git a/docs/README.md b/docs/README.md index 7f0a8ac0..cf658fb6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,6 +23,7 @@ This is the documentation of ActiveModelSerializers, it's focused on the **0.10. - [How to add pagination links](howto/add_pagination_links.md) - [Using ActiveModelSerializers Outside Of Controllers](howto/outside_controller_use.md) - [Testing ActiveModelSerializers](howto/test.md) +- [How to add top-level links](howto/add_top_level_links.md) (```JSON-API``` only) ## Integrations diff --git a/docs/howto/add_top_level_links.md b/docs/howto/add_top_level_links.md new file mode 100644 index 00000000..a61775d2 --- /dev/null +++ b/docs/howto/add_top_level_links.md @@ -0,0 +1,31 @@ +# How to add top-level links + +JsonApi supports a [links object](http://jsonapi.org/format/#document-links) to be specified at top-level, that you can specify in the `render`: + +```ruby + render json: @posts, links: { "self": "http://example.com/api/posts" } +``` + +That's the result: + +```json +{ + "data": [ + { + "type": "posts", + "id": "1", + "attributes": { + "title": "JSON API is awesome!", + "body": "You should be using JSON API", + "created": "2015-05-22T14:56:29.000Z", + "updated": "2015-05-22T14:56:28.000Z" + } + } + ], + "links": { + "self": "http://example.com/api/posts" + } +} +``` + +This feature is specific to JsonApi, so you have to use the use the [JsonApi Adapter](https://github.com/rails-api/active_model_serializers/blob/master/docs/general/adapters.md#jsonapi) diff --git a/lib/active_model/serializer/adapter/json_api/links.rb b/lib/active_model/serializer/adapter/json_api/links.rb new file mode 100644 index 00000000..4e4c6127 --- /dev/null +++ b/lib/active_model/serializer/adapter/json_api/links.rb @@ -0,0 +1,25 @@ +module ActiveModel + class Serializer + class Adapter + class JsonApi < Adapter + class Links + def initialize(links = {}) + @links = links + end + + def serializable_hash + @links + end + + def update(links = {}) + @links.update(links) + end + + def present? + !@links.empty? + end + end + end + end + end +end diff --git a/test/action_controller/serialization_test.rb b/test/action_controller/serialization_test.rb index d2fe3959..c6489b29 100644 --- a/test/action_controller/serialization_test.rb +++ b/test/action_controller/serialization_test.rb @@ -45,6 +45,17 @@ module ActionController render json: @profiles, meta: { total: 10 } end + def render_array_using_implicit_serializer_and_links + with_adapter ActiveModel::Serializer::Adapter::JsonApi do + + @profiles = [ + Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + ] + + render json: @profiles, links: { self: "http://example.com/api/profiles/1" } + end + end + def render_object_with_cache_enabled @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @author = Author.new(id: 1, name: 'Joao Moura.') @@ -254,6 +265,29 @@ module ActionController assert_equal expected.to_json, @response.body end + def test_render_array_using_implicit_serializer_and_links + get :render_array_using_implicit_serializer_and_links + + expected = { + data: [ + { + id: assigns(:profiles).first.id.to_s, + type: "profiles", + attributes: { + name: "Name 1", + description: "Description 1" + } + } + ], + links: { + self: "http://example.com/api/profiles/1" + } + } + + assert_equal 'application/json', @response.content_type + assert_equal expected.to_json, @response.body + end + def test_render_with_cache_enable expected = { id: 1, diff --git a/test/adapter/json_api/top_level_links_test.rb b/test/adapter/json_api/top_level_links_test.rb new file mode 100644 index 00000000..3b849d5b --- /dev/null +++ b/test/adapter/json_api/top_level_links_test.rb @@ -0,0 +1,101 @@ +require 'test_helper' + +module ActiveModel + class Serializer + class Adapter + class JsonApi + class TopLevelLinksTest < Minitest::Test + URI = 'http://example.com' + + def setup + ActionController::Base.cache_store.clear + @blog = Blog.new(id: 1, + name: 'AMS Hints', + writer: Author.new(id: 2, name: "Steve"), + articles: [Post.new(id: 3, title: "AMS")]) + end + + def load_adapter(paginated_collection, options = {}) + options = options.merge(adapter: :json_api) + ActiveModel::SerializableResource.new(paginated_collection, options) + end + + def test_links_is_not_present_when_not_defined + adapter = load_adapter(@blog) + + expected = { + :data => { + :id => "1", + :type => "blogs", + :attributes => { + :name => "AMS Hints" + }, + :relationships => { + :writer=> {:data => {:type => "authors", :id => "2"}}, + :articles => {:data => [{:type => "posts", :id => "3"}]} + } + }} + + assert_equal expected, adapter.serializable_hash(@options) + end + + def test_links_is_present_when_defined + adapter = load_adapter(@blog, {links: links}) + + expected = { + :data => { + :id => "1", + :type => "blogs", + :attributes => { + :name => "AMS Hints" + }, + :relationships => { + :writer=> {:data => {:type => "authors", :id => "2"}}, + :articles => {:data => [{:type => "posts", :id => "3"}]} + } + }, + :links => {:self => "http://example.com/blogs/1"} + } + + assert_equal expected, adapter.serializable_hash(@options) + end + + def links + { + self: "#{URI}/blogs/1" + } + end + + # def test_links_is_not_present_when_not_declared + # serializer = AlternateBlogSerializer.new(@blog) + # adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) + # expected = { + # data: { + # id: "1", + # type: "blogs", + # attributes: { + # title: "AMS Hints" + # } + # } + # } + # assert_equal expected, adapter.as_json + # end + + # def test_links_is_not_present_on_flattenjson_adapter + # serializer = AlternateBlogSerializer.new(@blog, :links => {:self => "/blogs/1"}) + # adapter = ActiveModel::Serializer::Adapter::FlattenJson.new(serializer) + # expected = {:id=>1, :title=>"AMS Hints"} + # assert_equal expected, adapter.as_json + # end + + # def test_links_is_not_present_on_json_adapter + # serializer = AlternateBlogSerializer.new(@blog, :links => {:self => "/blogs/1"}) + # adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) + # expected = {:blog=>{:id=>1, :title=>"AMS Hints"}} + # assert_equal expected, adapter.as_json + # end + end + end + end + end +end From 37e4f1c30c20d6b29ea2f40b3264f8da26e88685 Mon Sep 17 00:00:00 2001 From: Leandro Cesquini Pereira Date: Wed, 23 Dec 2015 09:32:53 -0200 Subject: [PATCH 2/3] Update top-level link with PR #1247 update according rubocop rules --- .../serializer/adapter/json_api/links.rb | 25 ----- test/action_controller/serialization_test.rb | 13 ++- test/adapter/json_api/links_test.rb | 9 ++ test/adapter/json_api/top_level_links_test.rb | 101 ------------------ 4 files changed, 15 insertions(+), 133 deletions(-) delete mode 100644 lib/active_model/serializer/adapter/json_api/links.rb delete mode 100644 test/adapter/json_api/top_level_links_test.rb diff --git a/lib/active_model/serializer/adapter/json_api/links.rb b/lib/active_model/serializer/adapter/json_api/links.rb deleted file mode 100644 index 4e4c6127..00000000 --- a/lib/active_model/serializer/adapter/json_api/links.rb +++ /dev/null @@ -1,25 +0,0 @@ -module ActiveModel - class Serializer - class Adapter - class JsonApi < Adapter - class Links - def initialize(links = {}) - @links = links - end - - def serializable_hash - @links - end - - def update(links = {}) - @links.update(links) - end - - def present? - !@links.empty? - end - end - end - end - end -end diff --git a/test/action_controller/serialization_test.rb b/test/action_controller/serialization_test.rb index c6489b29..7e6ea6cc 100644 --- a/test/action_controller/serialization_test.rb +++ b/test/action_controller/serialization_test.rb @@ -47,12 +47,11 @@ module ActionController def render_array_using_implicit_serializer_and_links with_adapter ActiveModel::Serializer::Adapter::JsonApi do - @profiles = [ - Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1') ] - render json: @profiles, links: { self: "http://example.com/api/profiles/1" } + render json: @profiles, links: { self: 'http://example.com/api/profiles/1' } end end @@ -272,15 +271,15 @@ module ActionController data: [ { id: assigns(:profiles).first.id.to_s, - type: "profiles", + type: 'profiles', attributes: { - name: "Name 1", - description: "Description 1" + name: 'Name 1', + description: 'Description 1' } } ], links: { - self: "http://example.com/api/profiles/1" + self: 'http://example.com/api/profiles/1' } } diff --git a/test/adapter/json_api/links_test.rb b/test/adapter/json_api/links_test.rb index dbda88ea..5e65f3d5 100644 --- a/test/adapter/json_api/links_test.rb +++ b/test/adapter/json_api/links_test.rb @@ -47,6 +47,15 @@ module ActiveModel assert_equal(expected, hash[:links]) end + def test_nil_toplevel_links + hash = ActiveModel::SerializableResource.new( + @post, + adapter: :json_api, + links: nil + ).serializable_hash + assert_equal(nil, hash[:links]) + end + def test_resource_links hash = serializable(@author, adapter: :json_api).serializable_hash expected = { diff --git a/test/adapter/json_api/top_level_links_test.rb b/test/adapter/json_api/top_level_links_test.rb deleted file mode 100644 index 3b849d5b..00000000 --- a/test/adapter/json_api/top_level_links_test.rb +++ /dev/null @@ -1,101 +0,0 @@ -require 'test_helper' - -module ActiveModel - class Serializer - class Adapter - class JsonApi - class TopLevelLinksTest < Minitest::Test - URI = 'http://example.com' - - def setup - ActionController::Base.cache_store.clear - @blog = Blog.new(id: 1, - name: 'AMS Hints', - writer: Author.new(id: 2, name: "Steve"), - articles: [Post.new(id: 3, title: "AMS")]) - end - - def load_adapter(paginated_collection, options = {}) - options = options.merge(adapter: :json_api) - ActiveModel::SerializableResource.new(paginated_collection, options) - end - - def test_links_is_not_present_when_not_defined - adapter = load_adapter(@blog) - - expected = { - :data => { - :id => "1", - :type => "blogs", - :attributes => { - :name => "AMS Hints" - }, - :relationships => { - :writer=> {:data => {:type => "authors", :id => "2"}}, - :articles => {:data => [{:type => "posts", :id => "3"}]} - } - }} - - assert_equal expected, adapter.serializable_hash(@options) - end - - def test_links_is_present_when_defined - adapter = load_adapter(@blog, {links: links}) - - expected = { - :data => { - :id => "1", - :type => "blogs", - :attributes => { - :name => "AMS Hints" - }, - :relationships => { - :writer=> {:data => {:type => "authors", :id => "2"}}, - :articles => {:data => [{:type => "posts", :id => "3"}]} - } - }, - :links => {:self => "http://example.com/blogs/1"} - } - - assert_equal expected, adapter.serializable_hash(@options) - end - - def links - { - self: "#{URI}/blogs/1" - } - end - - # def test_links_is_not_present_when_not_declared - # serializer = AlternateBlogSerializer.new(@blog) - # adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) - # expected = { - # data: { - # id: "1", - # type: "blogs", - # attributes: { - # title: "AMS Hints" - # } - # } - # } - # assert_equal expected, adapter.as_json - # end - - # def test_links_is_not_present_on_flattenjson_adapter - # serializer = AlternateBlogSerializer.new(@blog, :links => {:self => "/blogs/1"}) - # adapter = ActiveModel::Serializer::Adapter::FlattenJson.new(serializer) - # expected = {:id=>1, :title=>"AMS Hints"} - # assert_equal expected, adapter.as_json - # end - - # def test_links_is_not_present_on_json_adapter - # serializer = AlternateBlogSerializer.new(@blog, :links => {:self => "/blogs/1"}) - # adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) - # expected = {:blog=>{:id=>1, :title=>"AMS Hints"}} - # assert_equal expected, adapter.as_json - # end - end - end - end - end -end From b55fc32ba25857c88d5b91d2158026a2f444ee26 Mon Sep 17 00:00:00 2001 From: Leandro Cesquini Pereira Date: Wed, 3 Feb 2016 10:22:17 -0200 Subject: [PATCH 3/3] improve doc as suggested by @bf4 --- docs/howto/add_top_level_links.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/howto/add_top_level_links.md b/docs/howto/add_top_level_links.md index a61775d2..bcb0ea18 100644 --- a/docs/howto/add_top_level_links.md +++ b/docs/howto/add_top_level_links.md @@ -3,7 +3,13 @@ JsonApi supports a [links object](http://jsonapi.org/format/#document-links) to be specified at top-level, that you can specify in the `render`: ```ruby - render json: @posts, links: { "self": "http://example.com/api/posts" } + links_object = { + href: "http://example.com/api/posts", + meta: { + count: 10 + } + } + render json: @posts, links: links_object ``` That's the result: @@ -23,7 +29,10 @@ That's the result: } ], "links": { - "self": "http://example.com/api/posts" + "href": "http://example.com/api/posts", + "meta": { + "count": 10 + } } } ```