diff --git a/CHANGELOG.md b/CHANGELOG.md index 06534b3de..304707bbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ Breaking changes: Features: +- [#1618](https://github.com/rails-api/active_model_serializers/issues/1618) Get collection root key for + empty collection from explicit serializer option, when possible. (@bf4) - [#1574](https://github.com/rails-api/active_model_serializers/pull/1574) Provide key translation. (@remear) - [#1494](https://github.com/rails-api/active_model_serializers/pull/1494) Make serializers serializalbe (using the Attributes adapter by default). (@bf4) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index d78b873fb..79478abb7 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -182,7 +182,7 @@ def as_json(adapter_opts = nil) # Used by adapter as resource root. def json_key - root || object.class.model_name.to_s.underscore + root || _type || object.class.model_name.to_s.underscore end def read_attribute_for_serialization(attr) diff --git a/lib/active_model/serializer/collection_serializer.rb b/lib/active_model/serializer/collection_serializer.rb index 7862e9949..f026ebfe8 100644 --- a/lib/active_model/serializer/collection_serializer.rb +++ b/lib/active_model/serializer/collection_serializer.rb @@ -8,10 +8,11 @@ class CollectionSerializer attr_reader :object, :root def initialize(resources, options = {}) - @root = options[:root] - @object = resources + @object = resources + @options = options + @root = options[:root] + serializer_context_class = options.fetch(:serializer_context_class, ActiveModel::Serializer) @serializers = resources.map do |resource| - serializer_context_class = options.fetch(:serializer_context_class, ActiveModel::Serializer) serializer_class = options.fetch(:serializer) { serializer_context_class.serializer_for(resource) } if serializer_class.nil? # rubocop:disable Style/GuardClause @@ -26,9 +27,28 @@ def success? true end + # TODO: unify naming of root, json_key, and _type. Right now, a serializer's + # json_key comes from the root option or the object's model name, by default. + # But, if a dev defines a custom `json_key` method with an explicit value, + # we have no simple way to know that it is safe to call that instance method. + # (which is really a class property at this point, anyhow). + # rubocop:disable Metrics/CyclomaticComplexity + # Disabling cop since it's good to highlight the complexity of this method by + # including all the logic right here. def json_key - root || derived_root + return root if root + # 1. get from options[:serializer] for empty resource collection + key = object.empty? && + (explicit_serializer_class = options[:serializer]) && + explicit_serializer_class._type + # 2. get from first serializer instance in collection + key ||= (serializer = serializers.first) && serializer.json_key + # 3. get from collection name, if a named collection + key ||= object.respond_to?(:name) ? object.name && object.name.underscore : nil + # 4. key may be nil for empty collection and no serializer option + key && key.pluralize end + # rubocop:enable Metrics/CyclomaticComplexity def paginated? object.respond_to?(:current_page) && @@ -38,14 +58,7 @@ def paginated? protected - attr_reader :serializers - - private - - def derived_root - key = serializers.first.try(:json_key) || object.try(:name).try(:underscore) - key.try(:pluralize) - end + attr_reader :serializers, :options end end end diff --git a/test/collection_serializer_test.rb b/test/collection_serializer_test.rb index a7c0fa021..5e5267e4e 100644 --- a/test/collection_serializer_test.rb +++ b/test/collection_serializer_test.rb @@ -3,6 +3,10 @@ module ActiveModel class Serializer class CollectionSerializerTest < ActiveSupport::TestCase + MessagesSerializer = Class.new(ActiveModel::Serializer) do + type 'messages' + end + def setup @comment = Comment.new @post = Post.new @@ -84,6 +88,12 @@ def test_json_key_with_resource_without_name_and_no_serializers assert_nil serializer.json_key end + def test_json_key_with_empty_resources_with_serializer + resource = [] + serializer = collection_serializer.new(resource, serializer: MessagesSerializer) + assert_equal 'messages', serializer.json_key + end + def test_json_key_with_root expected = 'custom_root' serializer = collection_serializer.new(@resource, root: expected)