diff --git a/.gitignore b/.gitignore index 838a74e0..1ecf6e4d 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,4 @@ test/tmp test/version_tmp tmp *.swp -.ruby-version \ No newline at end of file +.ruby-version diff --git a/.travis.yml b/.travis.yml index d54edc2c..ad22ffe3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,8 @@ rvm: - jruby-19mode - rbx-2 - ruby-head +install: + - bundle install --retry=3 env: - "RAILS_VERSION=3.2" - "RAILS_VERSION=4.0" diff --git a/README.md b/README.md index 77f1a108..4121dfe2 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# ActiveModel::Serializers - +# ActiveModel::Serializers + [![Build Status](https://travis-ci.org/rails-api/active_model_serializers.svg)](https://travis-ci.org/rails-api/active_model_serializers) -ActiveModel::Serializers brings convention over configuration to your JSON generation. +ActiveModel::Serializers brings convention over configuration to your JSON generation. AMS does this through two components: **serializers** and **adapters**. Serializers describe which attributes and relationships should be serialized. Adapters describe how attributes and relationships should be serialized. @@ -32,7 +32,7 @@ serializers: ```ruby class PostSerializer < ActiveModel::Serializer attributes :title, :body - + has_many :comments url :post @@ -61,7 +61,7 @@ ActiveModel::Serializer.config.adapter = ActiveModel::Serializer::Adapter::HalAd ``` or - + ```ruby ActiveModel::Serializer.config.adapter = :hal ``` @@ -85,18 +85,27 @@ end In this case, Rails will look for a serializer named `PostSerializer`, and if it exists, use it to serialize the `Post`. -## Installation - -Add this line to your application's Gemfile: +### Built in Adapters -``` -gem 'active_model_serializers' +The `:json_api` adapter will include the associated resources in the `"linked"` +member when the resource names are included in the `include` option. + +```ruby + render @posts, include: 'authors,comments' ``` - -And then execute: -``` -$ bundle +## Installation + +Add this line to your application's Gemfile: + +``` +gem 'active_model_serializers' +``` + +And then execute: + +``` +$ bundle ``` ## Creating a Serializer @@ -141,29 +150,27 @@ class CommentSerializer < ActiveModel::Serializer end ``` -The attribute names are a **whitelist** of attributes to be serialized. - +The attribute names are a **whitelist** of attributes to be serialized. + The `has_many` and `belongs_to` declarations describe relationships between -resources. By default, when you serialize a `Post`, you will -get its `Comment`s as well. +resources. By default, when you serialize a `Post`, you will get its `Comment`s +as well. The `url` declaration describes which named routes to use while generating URLs for your JSON. Not every adapter will require URLs. ## Getting Help -If you find a bug, please report an -[Issue](https://github.com/rails-api/active_model_serializers/issues/new). +If you find a bug, please report an [Issue](https://github.com/rails-api/active_model_serializers/issues/new). -If you have a question, please [post to Stack -Overflow](http://stackoverflow.com/questions/tagged/active-model-serializers). +If you have a question, please [post to Stack Overflow](http://stackoverflow.com/questions/tagged/active-model-serializers). Thanks! - -## Contributing - -1. Fork it ( https://github.com/rails-api/active_model_serializers/fork ) -2. Create your feature branch (`git checkout -b my-new-feature`) -3. Commit your changes (`git commit -am 'Add some feature'`) -4. Push to the branch (`git push origin my-new-feature`) -5. Create a new Pull Request + +## Contributing + +1. Fork it ( https://github.com/rails-api/active_model_serializers/fork ) +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create a new Pull Request diff --git a/lib/action_controller/serialization.rb b/lib/action_controller/serialization.rb index f26f0112..2b460cb4 100644 --- a/lib/action_controller/serialization.rb +++ b/lib/action_controller/serialization.rb @@ -6,15 +6,18 @@ module ActionController include ActionController::Renderers + ADAPTER_OPTION_KEYS = [:include, :root] + [:_render_option_json, :_render_with_renderer_json].each do |renderer_method| define_method renderer_method do |resource, options| serializer = ActiveModel::Serializer.serializer_for(resource) if serializer + adapter_opts, serializer_opts = + options.partition { |k, _| ADAPTER_OPTION_KEYS.include? k } # omg hax - object = serializer.new(resource, options) - adapter = ActiveModel::Serializer.adapter.new(object) - + object = serializer.new(resource, Hash[serializer_opts]) + adapter = ActiveModel::Serializer.adapter.new(object, Hash[adapter_opts]) super(adapter, options) else super(resource, options) @@ -23,4 +26,3 @@ module ActionController end end end - diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 0f1fc2eb..d1e3cdc9 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -140,7 +140,7 @@ module ActiveModel self.class._associations.dup.each do |name, options| association = object.send(name) serializer_class = ActiveModel::Serializer.serializer_for(association) - serializer = serializer_class.new(association) + serializer = serializer_class.new(association) if serializer_class if block_given? block.call(name, serializer, options[:options]) diff --git a/lib/active_model/serializer/adapter.rb b/lib/active_model/serializer/adapter.rb index de46f12a..31b6ea57 100644 --- a/lib/active_model/serializer/adapter.rb +++ b/lib/active_model/serializer/adapter.rb @@ -10,13 +10,14 @@ module ActiveModel def initialize(serializer, options = {}) @serializer = serializer + @options = options end def serializable_hash(options = {}) raise NotImplementedError, 'This is an abstract method. Should be implemented at the concrete adapter.' end - def to_json(options = {}) + def as_json(options = {}) if fields = options.delete(:fields) options[:fieldset] = ActiveModel::Serializer::Fieldset.new(fields, serializer.json_key) end diff --git a/lib/active_model/serializer/adapter/json.rb b/lib/active_model/serializer/adapter/json.rb index 3b568194..8ad1e41d 100644 --- a/lib/active_model/serializer/adapter/json.rb +++ b/lib/active_model/serializer/adapter/json.rb @@ -13,7 +13,11 @@ module ActiveModel array_serializer = association @result[name] = array_serializer.map { |item| item.attributes(opts) } else - @result[name] = association.attributes(options) + if association + @result[name] = association.attributes(options) + else + @result[name] = nil + end end end end diff --git a/lib/active_model/serializer/adapter/json_api.rb b/lib/active_model/serializer/adapter/json_api.rb index 8f09b82b..d3d4dd17 100644 --- a/lib/active_model/serializer/adapter/json_api.rb +++ b/lib/active_model/serializer/adapter/json_api.rb @@ -5,25 +5,23 @@ module ActiveModel def initialize(serializer, options = {}) super serializer.root = true + @hash = {} + @top = @options.fetch(:top) { @hash } end def serializable_hash(options = {}) - @root = (options[:root] || serializer.json_key).to_s.pluralize.to_sym - @hash = {} + @root = (@options[:root] || serializer.json_key.to_s.pluralize).to_sym @fieldset = options[:fieldset] if serializer.respond_to?(:each) - opt = @fieldset ? {fieldset: @fieldset} : {} - - @hash[@root] = serializer.map{|s| self.class.new(s).serializable_hash(opt)[@root] } + @hash[@root] = serializer.map do |s| + self.class.new(s, @options.merge(top: @top)).serializable_hash[@root] + end else - @hash[@root] = attributes_for_serializer(serializer, {}) + @hash[@root] = attributes_for_serializer(serializer, @options) serializer.each_association do |name, association, opts| @hash[@root][:links] ||= {} - unless options[:embed] == :ids - @hash[:linked] ||= {} - end if association.respond_to?(:each) add_links(name, association, opts) @@ -37,23 +35,59 @@ module ActiveModel end def add_links(name, serializers, options) - @hash[@root][:links][name] ||= [] - @hash[@root][:links][name] += serializers.map{|serializer| serializer.id.to_s } + if serializers.first + type = serializers.first.object.class.to_s.underscore.pluralize + end + if name.to_s == type || !type + @hash[@root][:links][name] ||= [] + @hash[@root][:links][name] += serializers.map{|serializer| serializer.id.to_s } + else + @hash[@root][:links][name] ||= {} + @hash[@root][:links][name][:type] = type + @hash[@root][:links][name][:ids] ||= [] + @hash[@root][:links][name][:ids] += serializers.map{|serializer| serializer.id.to_s } + end - unless options[:embed] == :ids - @hash[:linked][name] ||= [] - @hash[:linked][name] += serializers.map { |item| attributes_for_serializer(item, options) } + unless serializers.none? || @options[:embed] == :ids + serializers.each do |serializer| + add_linked(name, serializer) + end end end def add_link(name, serializer, options) - @hash[@root][:links][name] = serializer.id.to_s + if serializer + type = serializer.object.class.to_s.underscore + if name.to_s == type || !type + @hash[@root][:links][name] = serializer.id.to_s + else + @hash[@root][:links][name] ||= {} + @hash[@root][:links][name][:type] = type + @hash[@root][:links][name][:id] = serializer.id.to_s + end - unless options[:embed] == :ids - plural_name = name.to_s.pluralize.to_sym + unless @options[:embed] == :ids + add_linked(name, serializer) + end + else + @hash[@root][:links][name] = nil + end + end - @hash[:linked][plural_name] ||= [] - @hash[:linked][plural_name].push attributes_for_serializer(serializer, options) + def add_linked(resource, serializer, parent = nil) + resource_path = [parent, resource].compact.join('.') + if include_assoc? resource_path + plural_name = resource.to_s.pluralize.to_sym + attrs = attributes_for_serializer(serializer, @options) + @top[:linked] ||= {} + @top[:linked][plural_name] ||= [] + @top[:linked][plural_name].push attrs unless @top[:linked][plural_name].include? attrs + end + + unless serializer.respond_to?(:each) + serializer.each_association do |name, association, opts| + add_linked(name, association, resource) if association + end end end @@ -69,6 +103,10 @@ module ActiveModel attributes[:id] = attributes[:id].to_s if attributes[:id] attributes end + + def include_assoc? assoc + @options[:include] && @options[:include].split(',').include?(assoc.to_s) + end end end end diff --git a/test/action_controller/json_api_linked_test.rb b/test/action_controller/json_api_linked_test.rb new file mode 100644 index 00000000..f02e90e2 --- /dev/null +++ b/test/action_controller/json_api_linked_test.rb @@ -0,0 +1,104 @@ +require 'test_helper' + +module ActionController + module Serialization + class JsonApiLinkedTest < ActionController::TestCase + class MyController < ActionController::Base + def setup_post + @author = Author.new(id: 1, name: 'Steve K.') + @author.posts = [] + @author2 = Author.new(id: 2, name: 'Anonymous') + @author2.posts = [] + @post = Post.new(id: 1, title: 'New Post', body: 'Body') + @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') + @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') + @post.comments = [@first_comment, @second_comment] + @post.author = @author + @first_comment.post = @post + @first_comment.author = @author2 + @second_comment.post = @post + @second_comment.author = nil + end + + def with_json_api_adapter + old_adapter = ActiveModel::Serializer.config.adapter + ActiveModel::Serializer.config.adapter = :json_api + yield + ensure + ActiveModel::Serializer.config.adapter = old_adapter + end + + def render_resource_without_include + with_json_api_adapter do + setup_post + render json: @post + end + end + + def render_resource_with_include + with_json_api_adapter do + setup_post + render json: @post, include: 'author' + end + end + + def render_resource_with_nested_include + with_json_api_adapter do + setup_post + render json: @post, include: 'comments.author' + end + end + + def render_collection_without_include + with_json_api_adapter do + setup_post + render json: [@post] + end + end + + def render_collection_with_include + with_json_api_adapter do + setup_post + render json: [@post], include: 'author,comments' + end + end + end + + tests MyController + + def test_render_resource_without_include + get :render_resource_without_include + response = JSON.parse(@response.body) + refute response.key? 'linked' + end + + def test_render_resource_with_include + get :render_resource_with_include + response = JSON.parse(@response.body) + assert response.key? 'linked' + assert_equal 1, response['linked']['authors'].size + assert_equal 'Steve K.', response['linked']['authors'].first['name'] + end + + def test_render_resource_with_nested_include + get :render_resource_with_nested_include + response = JSON.parse(@response.body) + assert response.key? 'linked' + assert_equal 1, response['linked']['authors'].size + assert_equal 'Anonymous', response['linked']['authors'].first['name'] + end + + def test_render_collection_without_include + get :render_collection_without_include + response = JSON.parse(@response.body) + refute response.key? 'linked' + end + + def test_render_collection_with_include + get :render_collection_with_include + response = JSON.parse(@response.body) + assert response.key? 'linked' + end + end + end +end diff --git a/test/action_controller/serialization_test.rb b/test/action_controller/serialization_test.rb index 4103e273..fbbb48f9 100644 --- a/test/action_controller/serialization_test.rb +++ b/test/action_controller/serialization_test.rb @@ -24,6 +24,16 @@ module ActionController ActiveModel::Serializer.config.adapter = old_adapter end + def render_using_custom_root_in_adapter_with_a_default + old_adapter = ActiveModel::Serializer.config.adapter + # JSON-API adapter sets root by default + ActiveModel::Serializer.config.adapter = ActiveModel::Serializer::Adapter::JsonApi + @profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + render json: @profile, root: "profile" + ensure + ActiveModel::Serializer.config.adapter = old_adapter + end + def render_array_using_implicit_serializer array = [ Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }), @@ -57,6 +67,13 @@ module ActionController assert_equal '{"profiles":{"name":"Name 1","description":"Description 1"}}', @response.body end + def test_render_using_custom_root_in_adapter_with_a_default + get :render_using_custom_root_in_adapter_with_a_default + + assert_equal 'application/json', @response.content_type + assert_equal '{"profile":{"name":"Name 1","description":"Description 1"}}', @response.body + end + def test_render_array_using_implicit_serializer get :render_array_using_implicit_serializer assert_equal 'application/json', @response.content_type diff --git a/test/adapter/json/belongs_to_test.rb b/test/adapter/json/belongs_to_test.rb index 498f1d98..ea118647 100644 --- a/test/adapter/json/belongs_to_test.rb +++ b/test/adapter/json/belongs_to_test.rb @@ -7,9 +7,14 @@ module ActiveModel class BelongsToTest < Minitest::Test def setup @post = Post.new(id: 42, title: 'New Post', body: 'Body') + @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!') @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @post.comments = [@comment] + @anonymous_post.comments = [] + @post.author = @author @comment.post = @post + @comment.author = nil + @anonymous_post.author = nil @serializer = CommentSerializer.new(@comment) @adapter = ActiveModel::Serializer::Adapter::Json.new(@serializer) @@ -18,6 +23,13 @@ module ActiveModel def test_includes_post assert_equal({id: 42, title: 'New Post', body: 'Body'}, @adapter.serializable_hash[:post]) end + + def test_include_nil_author + serializer = PostSerializer.new(@anonymous_post) + adapter = ActiveModel::Serializer::Adapter::Json.new(serializer) + + assert_equal({title: "Hello!!", body: "Hello, world!!", id: 43, comments: [], author: nil}, adapter.serializable_hash) + end end end end diff --git a/test/adapter/json/collection_test.rb b/test/adapter/json/collection_test.rb index 4d02f65a..51af5448 100644 --- a/test/adapter/json/collection_test.rb +++ b/test/adapter/json/collection_test.rb @@ -6,10 +6,13 @@ module ActiveModel class Json class Collection < Minitest::Test def setup + @author = Author.new(id: 1, name: 'Steve K.') @first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!') @second_post = Post.new(id: 2, title: 'New Post', body: 'Body') @first_post.comments = [] @second_post.comments = [] + @first_post.author = @author + @second_post.author = @author @serializer = ArraySerializer.new([@first_post, @second_post]) @adapter = ActiveModel::Serializer::Adapter::Json.new(@serializer) @@ -17,8 +20,8 @@ module ActiveModel def test_include_multiple_posts assert_equal([ - {title: "Hello!!", body: "Hello, world!!", id: 1, comments: []}, - {title: "New Post", body: "Body", id: 2, comments: []} + {title: "Hello!!", body: "Hello, world!!", id: 1, comments: [], author: {id: 1, name: "Steve K."}}, + {title: "New Post", body: "Body", id: 2, comments: [], author: {id: 1, name: "Steve K."}} ], @adapter.serializable_hash) end end diff --git a/test/adapter/json/has_many_test.rb b/test/adapter/json/has_many_test.rb index d1534e6b..77f672c7 100644 --- a/test/adapter/json/has_many_test.rb +++ b/test/adapter/json/has_many_test.rb @@ -6,10 +6,12 @@ module ActiveModel class Json class HasManyTestTest < Minitest::Test def setup + @author = Author.new(id: 1, name: 'Steve K.') @post = Post.new(title: 'New Post', body: 'Body') @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') @post.comments = [@first_comment, @second_comment] + @post.author = @author @first_comment.post = @post @second_comment.post = @post diff --git a/test/adapter/json_api/belongs_to_test.rb b/test/adapter/json_api/belongs_to_test.rb index 0a43e531..e25a71d8 100644 --- a/test/adapter/json_api/belongs_to_test.rb +++ b/test/adapter/json_api/belongs_to_test.rb @@ -6,10 +6,20 @@ module ActiveModel class JsonApi class BelongsToTest < Minitest::Test def setup + @author = Author.new(id: 1, name: 'Steve K.') @post = Post.new(id: 42, title: 'New Post', body: 'Body') + @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!') @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @post.comments = [@comment] + @anonymous_post.comments = [] @comment.post = @post + @comment.author = nil + @post.author = @author + @anonymous_post.author = nil + @blog = Blog.new(id: 1, name: "My Blog!!") + @blog.writer = @author + @blog.articles = [@post, @anonymous_post] + @author.posts = [] @serializer = CommentSerializer.new(@comment) @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer) @@ -20,8 +30,22 @@ module ActiveModel end def test_includes_linked_post + @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: 'post') assert_equal([{id: "42", title: 'New Post', body: 'Body'}], @adapter.serializable_hash[:linked][:posts]) end + + def test_include_nil_author + serializer = PostSerializer.new(@anonymous_post) + adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) + + assert_equal({comments: [], author: nil}, adapter.serializable_hash[:posts][:links]) + end + + def test_include_type_for_association_when_is_different_than_name + serializer = BlogSerializer.new(@blog) + adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) + assert_equal({type: "author", id: "1"}, adapter.serializable_hash[:blogs][:links][:writer]) + end end end end diff --git a/test/adapter/json_api/collection_test.rb b/test/adapter/json_api/collection_test.rb index 566581a8..922103ea 100644 --- a/test/adapter/json_api/collection_test.rb +++ b/test/adapter/json_api/collection_test.rb @@ -4,12 +4,16 @@ module ActiveModel class Serializer class Adapter class JsonApi - class Collection < Minitest::Test + class CollectionTest < Minitest::Test def setup + @author = Author.new(id: 1, name: 'Steve K.') @first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!') @second_post = Post.new(id: 2, title: 'New Post', body: 'Body') @first_post.comments = [] @second_post.comments = [] + @first_post.author = @author + @second_post.author = @author + @author.posts = [@first_post, @second_post] @serializer = ArraySerializer.new([@first_post, @second_post]) @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer) @@ -17,8 +21,8 @@ module ActiveModel def test_include_multiple_posts assert_equal([ - {title: "Hello!!", body: "Hello, world!!", id: "1", links: {comments: []}}, - {title: "New Post", body: "Body", id: "2", links: {comments: []}} + { title: "Hello!!", body: "Hello, world!!", id: "1", links: { comments: [], author: "1" } }, + { title: "New Post", body: "Body", id: "2", links: { comments: [], author: "1" } } ], @adapter.serializable_hash[:posts]) end end diff --git a/test/adapter/json_api/has_many_embed_ids.rb b/test/adapter/json_api/has_many_embed_ids_test.rb similarity index 92% rename from test/adapter/json_api/has_many_embed_ids.rb rename to test/adapter/json_api/has_many_embed_ids_test.rb index d5c448b5..4690e3c6 100644 --- a/test/adapter/json_api/has_many_embed_ids.rb +++ b/test/adapter/json_api/has_many_embed_ids_test.rb @@ -12,6 +12,8 @@ module ActiveModel @author.posts = [@first_post, @second_post] @first_post.author = @author @second_post.author = @author + @first_post.comments = [] + @second_post.comments = [] @serializer = AuthorSerializer.new(@author) @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer) diff --git a/test/adapter/json_api/has_many_test.rb b/test/adapter/json_api/has_many_test.rb index f558c7d4..975c5b63 100644 --- a/test/adapter/json_api/has_many_test.rb +++ b/test/adapter/json_api/has_many_test.rb @@ -6,12 +6,23 @@ module ActiveModel class JsonApi class HasManyTest < Minitest::Test def setup - @post = Post.new(title: 'New Post', body: 'Body') + @author = Author.new(id: 1, name: 'Steve K.') + @author.posts = [] + @post = Post.new(id: 1, title: 'New Post', body: 'Body') + @post_without_comments = Post.new(id: 2, title: 'Second Post', body: 'Second') @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') + @first_comment.author = nil @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') + @second_comment.author = nil @post.comments = [@first_comment, @second_comment] + @post_without_comments.comments = [] @first_comment.post = @post @second_comment.post = @post + @post.author = @author + @post_without_comments.author = nil + @blog = Blog.new(id: 1, name: "My Blog!!") + @blog.writer = @author + @blog.articles = [@post] @serializer = PostSerializer.new(@post) @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer) @@ -22,11 +33,25 @@ module ActiveModel end def test_includes_linked_comments + @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: 'comments') assert_equal([ {id: "1", body: 'ZOMG A COMMENT'}, {id: "2", body: 'ZOMG ANOTHER COMMENT'} ], @adapter.serializable_hash[:linked][:comments]) end + + def test_no_include_linked_if_comments_is_empty + serializer = PostSerializer.new(@post_without_comments) + adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) + + assert_nil adapter.serializable_hash[:linked] + end + + def test_include_type_for_association_when_is_different_than_name + serializer = BlogSerializer.new(@blog) + adapter = ActiveModel::Serializer::Adapter::JsonApi.new(serializer) + assert_equal({type: "posts", ids: ["1"]}, adapter.serializable_hash[:blogs][:links][:articles]) + end end end end diff --git a/test/adapter/json_api/linked_test.rb b/test/adapter/json_api/linked_test.rb new file mode 100644 index 00000000..160d3daa --- /dev/null +++ b/test/adapter/json_api/linked_test.rb @@ -0,0 +1,40 @@ +require 'test_helper' + +module ActiveModel + class Serializer + class Adapter + class JsonApi + class LinkedTest < Minitest::Test + def setup + @author = Author.new(id: 1, name: 'Steve K.') + @first_post = Post.new(id: 1, title: 'Hello!!', body: 'Hello, world!!') + @second_post = Post.new(id: 2, title: 'New Post', body: 'Body') + @first_post.comments = [] + @second_post.comments = [] + @first_post.author = @author + @second_post.author = @author + @author.posts = [@first_post, @second_post] + + @serializer = ArraySerializer.new([@first_post, @second_post]) + @adapter = ActiveModel::Serializer::Adapter::JsonApi.new(@serializer, include: 'author,comments') + end + + def test_include_multiple_posts_and_linked + @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') + @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') + @first_post.comments = [@first_comment, @second_comment] + @first_comment.post = @first_post + @first_comment.author = nil + @second_comment.post = @first_post + @second_comment.author = nil + assert_equal([ + { title: "Hello!!", body: "Hello, world!!", id: "1", links: { comments: ['1', '2'], author: "1" } }, + { title: "New Post", body: "Body", id: "2", links: { comments: [], :author => "1" } } + ], @adapter.serializable_hash[:posts]) + assert_equal({ :comments => [{ :id => "1", :body => "ZOMG A COMMENT" }, { :id => "2", :body => "ZOMG ANOTHER COMMENT" }], :authors => [{ :id => "1", :name => "Steve K." }] }, @adapter.serializable_hash[:linked]) + end + end + end + end + end +end diff --git a/test/adapter/json_test.rb b/test/adapter/json_test.rb index 1d2e35a6..fe573f7b 100644 --- a/test/adapter/json_test.rb +++ b/test/adapter/json_test.rb @@ -5,12 +5,14 @@ module ActiveModel class Adapter class JsonTest < Minitest::Test def setup + @author = Author.new(id: 1, name: 'Steve K.') @post = Post.new(title: 'New Post', body: 'Body') @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT') @post.comments = [@first_comment, @second_comment] @first_comment.post = @post @second_comment.post = @post + @post.author = @author @serializer = PostSerializer.new(@post) @adapter = ActiveModel::Serializer::Adapter::Json.new(@serializer) diff --git a/test/fixtures/poro.rb b/test/fixtures/poro.rb index 2f4ffdbf..7d8d57b4 100644 --- a/test/fixtures/poro.rb +++ b/test/fixtures/poro.rb @@ -38,11 +38,13 @@ end Post = Class.new(Model) Comment = Class.new(Model) Author = Class.new(Model) +Blog = Class.new(Model) PostSerializer = Class.new(ActiveModel::Serializer) do attributes :title, :body, :id has_many :comments + belongs_to :author url :comments end @@ -50,6 +52,7 @@ CommentSerializer = Class.new(ActiveModel::Serializer) do attributes :id, :body belongs_to :post + belongs_to :author end AuthorSerializer = Class.new(ActiveModel::Serializer) do @@ -57,3 +60,10 @@ AuthorSerializer = Class.new(ActiveModel::Serializer) do has_many :posts, embed: :ids end + +BlogSerializer = Class.new(ActiveModel::Serializer) do + attributes :id, :name + + belongs_to :writer + has_many :articles +end diff --git a/test/serializers/associations_test.rb b/test/serializers/associations_test.rb index 6d69b563..99162acc 100644 --- a/test/serializers/associations_test.rb +++ b/test/serializers/associations_test.rb @@ -2,7 +2,7 @@ require 'test_helper' module ActiveModel class Serializer - class AssocationsTest < Minitest::Test + class AssociationsTest < Minitest::Test class Model def initialize(hash={}) @attributes = hash @@ -25,30 +25,40 @@ module ActiveModel def setup + @author = Author.new(name: 'Steve K.') @post = Post.new({ title: 'New Post', body: 'Body' }) @comment = Comment.new({ id: 1, body: 'ZOMG A COMMENT' }) @post.comments = [@comment] @comment.post = @post + @comment.author = nil + @post.author = @author + @author.posts = [@post] - @post_serializer = PostSerializer.new(@post) + @author_serializer = AuthorSerializer.new(@author) @comment_serializer = CommentSerializer.new(@comment) end def test_has_many - assert_equal({comments: {type: :has_many, options: {}}}, @post_serializer.class._associations) - @post_serializer.each_association do |name, serializer, options| - assert_equal(:comments, name) - assert_equal({}, options) + assert_equal({posts: {type: :has_many, options: {embed: :ids}}}, @author_serializer.class._associations) + @author_serializer.each_association do |name, serializer, options| + assert_equal(:posts, name) + assert_equal({embed: :ids}, options) assert_kind_of(ActiveModel::Serializer.config.array_serializer, serializer) end end def test_has_one - assert_equal({post: {type: :belongs_to, options: {}}}, @comment_serializer.class._associations) + assert_equal({post: {type: :belongs_to, options: {}}, :author=>{:type=>:belongs_to, :options=>{}}}, @comment_serializer.class._associations) @comment_serializer.each_association do |name, serializer, options| - assert_equal(:post, name) - assert_equal({}, options) - assert_kind_of(PostSerializer, serializer) + if name == :post + assert_equal({}, options) + assert_kind_of(PostSerializer, serializer) + elsif name == :author + assert_equal({}, options) + assert_nil serializer + else + flunk "Unknown association: #{name}" + end end end end