Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.10.0.pre does not include meta in response #876

Closed
stevenwu opened this issue Apr 22, 2015 · 16 comments
Closed

0.10.0.pre does not include meta in response #876

stevenwu opened this issue Apr 22, 2015 · 16 comments

Comments

@stevenwu
Copy link

I was able to include meta info when I set ActiveModel::Serializer.config.adapter = :json_api. However, when I remove this option, there is no meta in the response. meta is included in the response once I switch to 0.8.3 version.

@kurko kurko added this to the 0.10 milestone Apr 22, 2015
@bf4
Copy link
Member

bf4 commented Apr 23, 2015

Same missing meta on 0.10.rc1 using the json adapter using any combination of single resource and the options root, status, each_serializer so I guess this test is broken https://github.com/rails-api/active_model_serializers/blob/1577969cb763/test/serializers/meta_test.rb

@bf4
Copy link
Member

bf4 commented Apr 25, 2015

I'm happy to submit this as a PR, but here's some stuff I wrote to get meta data included

# Make the meta key just work
# {"items": [{}, {}], "meta": {} }
#
# Background:
# When Rails renders JSON, it calls the json renderer method
# "_render_with_renderer_#{key = json}"
# https://github.com/rails/rails/blob/8c2c1bccccb3ea1f22503a184429d94193a1d9aa/actionpack/lib/action_controller/metal/renderers.rb#L44-L45
# which is defined in AMS https://github.com/rails-api/active_model_serializers/blob/1577969cb76309d1f48c68facc73e44c49489744/lib/action_controller/serialization.rb#L36-L37
# It creates a serializer instance from the resource
# and then a serializer adapter from the serializer instance
# which it passes along.
# For some reason, AMS strips the root param from the serializer during render
# https://github.com/rails-api/active_model_serializers/blob/1577969cb76309d1f48c68facc73e44c49489744/lib/action_controller/serialization.rb#L39
# so that only the adapter gets the root option
# https://github.com/rails-api/active_model_serializers/blob/1577969cb76309d1f48c68facc73e44c49489744/lib/active_model/serializer.rb#L142-L161
# and since the adapter requires the serializer to have a root to output the meta tag
# https://github.com/rails-api/active_model_serializers/blob/1577969cb76309d1f48c68facc73e44c49489744/lib/active_model/serializer/adapter.rb#L81-L84
# it's never actually put out (by the JSON adapter)
#
# What doesn't work:
# Ref: https://github.com/rails-api/active_model_serializers/pull/166
#
# extending the adapter with
# def as_json(options = {})
#   if meta && serializer.is_a?(ActiveModel::Serializer::ArraySerializer)
#     serializer.root = true
#     super
#   else
#     super
#   end
# end
# serializes a collection as {"items": ["item": {}], "meta": {} }
# serializes a resource as {"items": {} }
#
# extending the adapter with
# def as_json(options = {})
#   if meta
#     serializer.root = true
#     super
#   else
#     super
#   end
# end
# serializes a collection as {"items": ["item": {}], "meta": {} }
# serializes a resource as {"items": {}, "meta": {} }
#
# We get some combination of the above with the vanilla JSON serializer
# and some combination of the below:
# ActiveModel::Serializer._root = true/nil
# ItemSerializer._root = true/nil
# ActiveModel::Serializer::ArraySerializer._root = true/nil
# ActiveSupport.on_load(:active_model_serializers) do
#   self._root = true/nil
# end
# ActiveRecord::Base.include_root_in_json = true/false
# ActiveSupport.on_load(:active_record) do
#   self.include_root_in_json = true/false
# end
#
# Using the json_api adapter does output the meta key correctly, but
# changes the resource root to "data", which we don't want
#
# Calling render with or without the root option doesn't help either
# render json: @json, root: root, meta: {}
# render json: @json, meta: {}
require "active_model/serializer/adapter/json"
class JsonMetaAdapter < ActiveModel::Serializer::Adapter::Json
  # extends the AMS adapter #as_json method
  # https://github.com/rails-api/active_model_serializers/blob/1577969cb7/lib/active_model/serializer/adapter.rb#L22-L24
  # def as_json(options = {})
  #   hash = serializable_hash(options)
  #   include_meta(hash)
  # end
  #
  # which is called by Rails at the last rendering step
  # https://github.com/rails/rails/blob/4-2-stable/actionpack/lib/action_controller/metal/renderers.rb#L66-L128
  # ref https://github.com/rails-api/active_model_serializers/issues/878#issuecomment-96073030
  # json = json.as_json(options) if json.respond_to?(:as_json)
  # json = JSON.pretty_generate(json, options)
  def as_json(options = {})
    if meta
      if serializer.is_a?(ActiveModel::Serializer::ArraySerializer)
        super.update(meta: meta)
      else
        serializer.root = true
        super
      end
    else
      super
    end
  end
end
ActiveModel::Serializer.config.adapter = JsonMetaAdapter

@sebastianvera
Copy link

👍 bump

@Soulou
Copy link

Soulou commented May 7, 2015

Meta are not rendered anymore with the ArraySerializer

https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model/serializer/adapter.rb#L81-L84

      def include_meta(json)
        json[meta_key] = meta if meta && root
        json
      end

And root is always nil because of:

https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model/serializer/array_serializer.rb#L10

      def initialize(objects, options = {})
        options.merge!(root: nil)

@sebastianvera
Copy link

Meta
If you want a meta attribute in your response, specify it in the render call:

render json: @post, meta: { total: 10 }
The key can be customized using meta_key option.

render json: @post, meta: { total: 10 }, meta_key: "custom_meta"
meta will only be included in your response if there's a root. For instance, it won't be included in array responses.

Why in 0.10 is not posible to include meta in Array responses? I think it has a great potencial, for example: pagination attributes.

{
  "data": [ ],
  "meta": {
    "pagination": {
      "total_count": 475,
      "total_pages": 10,
      "total": 50,
      "current_page": 1,
      "per_page": 50
    }
  }

Is this a bug or there is a good reason behind this? I'll be glad to help if you need something.

@ryansch
Copy link
Contributor

ryansch commented May 18, 2015

Our v1 API requires this behavior. Our v2 is a jsonapi.org style api but I can't just suddenly convert the old stuff. I need meta on array responses.

@ryansch
Copy link
Contributor

ryansch commented May 18, 2015

Here's what I'm using in an initializer to work around this.

require 'active_model/serializer/adapter/json'

module ActiveModel
  class Serializer
    class Adapter
      class JsonMeta < Json
        def initialize(serializer, options = {})
          super
          serializer.root = true if @options[:root]
        end
      end
    end
  end
end

ActiveModel::Serializer.config.adapter = ActiveModel::Serializer::Adapter::JsonMeta

Edit: This doesn't work for empty arrays.

@ryansch
Copy link
Contributor

ryansch commented May 19, 2015

I took a stab at this in #915.

@joaomdmoura
Copy link
Member

Commit message, adding meta support:

Currently, 0.10.0.pre doesn't support meta option in render. This
way, there's no way to support features such as pagination. 0.9 had
this feature in place.

This adds support for it, as well as fixes small things in README.md.

This won't support meta in array responses because arrays don't have
keys, obviously. Also, the response should have a root key, otherwise
no meta will be included.

In some cases, for example using JsonApi, ArraySerializer will result in
a response with a root. In that case, meta will be included.

As this change was implemented by @kurko I'm not comfortable about moving forward without hearing from him.

IMO, add meta to ArraySerializer seems a good thing, of course that it only would work when a root is defined.

@chrisbranson
Copy link
Contributor

The issue seems to be that the base adapter and the json adapter use different logic to get the :root option value, if you change the root method in adapter.rb to use the same logic as the json adapter it works: -

def root
  @options.fetch(:root, serializer.json_key)
end

Current-RMS@cd65b40

This gets us meta back in the result when using the json adapter and specifying a root. The change has no visible impact on the json api adapter that I can see - it works as before the change.

Happy to create a PR?

@joaomdmoura
Copy link
Member

I tested it here, it's seem to work, all tests are still passing, @chrisbranson make yourself comfortable to open a PR! I'll check it, other people may test it too and then we merge it. 😄

@bf4
Copy link
Member

bf4 commented Jun 2, 2015

I'm happy to write a PR, too. I've been meaning to do it for a while, but I'll do it today unless @chrisbranson would like to (no toe stepping!) or we can work at https://github.com/rails-api/active_model_serializers/pull/915/files ?

@chrisbranson
Copy link
Contributor

PR #936 created including a test

@chrisbranson
Copy link
Contributor

@bf4 your branch has a lot more changes in different places to mine, so I created a new PR to keep it clean.

@bf4
Copy link
Member

bf4 commented Jun 3, 2015

@stevenwu looks like #936 is going to be the fix PR, good job @chrisbranson (btw, I didn't write the other pr I referenced)

@joaomdmoura
Copy link
Member

@stevenwu could you confirm if #936 solved it for you?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants