rails-api / active_model_serializers

ActiveModel::Serializer implementation and Rails hooks
MIT License
5.33k stars 1.39k forks source link

JsonApi PaginationLinks doesn't work outside of ActionController #1268

Closed jpaas closed 8 years ago

jpaas commented 9 years ago

I'm trying to use Grape+Kaminari+AMS 1.0 (head) and I get a stack trace like so...

(NoMethodError)undefined method `query_parameters' for nil:NilClass
/Users/jpaas/.rvm/gems/ruby-2.2.2@imdcloud/bundler/gems/active_model_serializers-da7e6dc795ac/lib/active_model/serializer/adapter/json_api/pagination_links.rb:52:in `query_parameters'
/Users/jpaas/.rvm/gems/ruby-2.2.2@imdcloud/bundler/gems/active_model_serializers-da7e6dc795ac/lib/active_model/serializer/adapter/json_api/pagination_links.rb:17:in `block in serializable_hash'
/Users/jpaas/.rvm/gems/ruby-2.2.2@imdcloud/bundler/gems/active_model_serializers-da7e6dc795ac/lib/active_model/serializer/adapter/json_api/pagination_links.rb:16:in `each'
/Users/jpaas/.rvm/gems/ruby-2.2.2@imdcloud/bundler/gems/active_model_serializers-da7e6dc795ac/lib/active_model/serializer/adapter/json_api/pagination_links.rb:16:in `each_with_object'
/Users/jpaas/.rvm/gems/ruby-2.2.2@imdcloud/bundler/gems/active_model_serializers-da7e6dc795ac/lib/active_model/serializer/adapter/json_api/pagination_links.rb:16:in `serializable_hash'
/Users/jpaas/.rvm/gems/ruby-2.2.2@imdcloud/bundler/gems/active_model_serializers-da7e6dc795ac/lib/active_model/serializer/adapter/json_api.rb:212:in `links_for'
/Users/jpaas/.rvm/gems/ruby-2.2.2@imdcloud/bundler/gems/active_model_serializers-da7e6dc795ac/lib/active_model/serializer/adapter/json_api.rb:103:in `serializable_hash_for_collection'
/Users/jpaas/.rvm/gems/ruby-2.2.2@imdcloud/bundler/gems/active_model_serializers-da7e6dc795ac/lib/active_model/serializer/adapter/json_api.rb:61:in `serializable_hash'
/Users/jpaas/.rvm/gems/ruby-2.2.2@imdcloud/bundler/gems/active_model_serializers-da7e6dc795ac/lib/active_model/serializable_resource.rb:14:in `serializable_hash'

It seems to assume that the context has been set as an ActionDispatch::Request. I would try setting it, but I can't figure out how to get it in Grape.

jpaas commented 9 years ago

Looking at the code, I can't even see how this would work for ActionController. The SerializableResource filters the adapter options, so that they must be one of [:include, :fields, :adapter, :meta, :meta_key, :links]. Which means there's no way the adapter options could contain a :context entry.

bf4 commented 9 years ago

@jpaas good bug report. https://github.com/rails-api/active_model_serializers/blob/da7e6dc795ac4f6eb0af63c19c46638b13d0d04e/test/action_controller/json_api/pagination_test.rb should cover that it works

I agree that we are sometime too eager to add foreign objects to the app. It should really be encapsulated. context should maybe be called 'request_context' and has two methods we use: original_url and query_parameters

jpaas commented 9 years ago

I was using a fork of grape-active_model_serializers https://github.com/Thanx/grape-active_model_serializers.git, but once I started using the jsonapi adapter, I built my own very simple grape json formatter:

module API
  module AmsFormatter
    class << self
      def call(resource, env)
        serializer_options = {}
        serializer_options.merge!(env['ams_serializer_options']) if env['ams_serializer_options']
        # For some reason serializable_hash doesn't include meta in the response, so I have to use as_json
        ActiveModel::SerializableResource.new(resource, serializer_options).as_json.to_json
      end
    end
  end
end

I also had to create this little grape helper to pass my serializer options to the formatter via the env:

        def render(resources, ams_serializer_options = {})
          env['ams_serializer_options'] = ams_serializer_options
          resources
        end

So now I can implement a grape endpoint like this and put metadata into the jsonapi response:

    formatter :json, API::AmsFormatter

     get ':id' do
       render(Resource.find(params[:id]), meta: { foo: 'bar' })
     end
jpaas commented 9 years ago

Oh and BTW, my workaround for this problem at the moment was to subclass the JsonApi adapter to simply skip links for now...

module API
  class AmsJsonapiAdapter < ActiveModel::Serializer::Adapter::JsonApi
    def links_for(serializer, options)
      {}
    end
  end
end
bf4 commented 9 years ago

@jpaas would you want to submit the formatter here as a PR?

Also, you should be able to just to_json not as_json.to_json

jpaas commented 9 years ago

Ah yes @bf4 to_json alone works. I thought I tried that at one point but had trouble. I can submit as a PR. Where would you like it in the namespace?

bf4 commented 9 years ago

I'm thinking Grape::ActiveModelSerializers in lib/grape/active_model_serializers or something like that and maybe in lib grape-active_model_serializers.rb that requires active_model_serializers and grape/active_model_serializers. Just try something that makes sense to you

hut8 commented 9 years ago

I just spent like 6 hours figuring this out. I'm using the same set of tools as @jpaas and I can get the links to work if I pass in the (bastardized) context where I would least expect it :interrobang:

So I changed the code at https://github.com/rails-api/active_model_serializers/issues/1268#issuecomment-148153186 to:


# A grape response formatter that can be used as 'formatter :json, Grape::Formatters::ActiveModelSerializers'
#                                                                                                                                                                                # Serializer options can be passed as a hash from your grape endpoint using env[:active_model_serializer_options],
# or better yet user the render helper in Grape::Helpers::ActiveModelSerializers
module Grape
  module Formatters
    module ActiveModelSerializers
      RequestContext = Struct.new(:original_url, :query_parameters)                                                                                                              
      class << self
        def call(resource, env)                                                                                                                                                            serializer_options = {}
          serializer_options.merge!(env[:active_model_serializer_options]) if env[:active_model_serializer_options]

          serializer_options[:context] = RequestContext.new(env['REQUEST_URI'],
                                                            env['rack.request.query_hash'])
          ActiveModel::SerializableResource.new(resource, serializer_options).to_json(serializer_options)
        end
      end
    end
  end
end

Without passing my little context hack into to_json, they don't make their way into PaginationLinks's context constructor parameter. Interestingly, when I pass them to as_json instead, I get Ruby out! :open_mouth:

vipin8169 commented 8 years ago

I am also getting the same issue. On executing ActiveModel::SerializableResource.new(@admins, adapter: :json_api).to_json

Although, ActiveModel::SerializableResource.new(@admins, adapter: :json_api) gives the following response: #<ActiveModel::SerializableResource:0x000000054789e8 @resource=#<ActiveRecord::Relation [#<Admin id: 36, email: "aubrey_schmitt@feeneykoch.io", encrypted_password: "$2a$10$u1E0eoEymfPs.rmUu9KZce9dXWQ/cIE5PCjKJbJFPmf...", remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, failed_attempts: 0, locked_at: nil, created_at: "2016-03-28 05:15:17", updated_at: "2016-03-28 05:15:17">, #<Admin id: 20, email: "alysa_johnston@thompson.io", encrypted_password: "$2a$10$e5J4nS2wlUtVyoP95qVWvux36bihsE2Pk8.Rn/n8.3f...", remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, failed_attempts: 0, locked_at: nil, created_at: "2016-03-28 05:15:16", updated_at: "2016-03-28 05:15:16">, #<Admin id: 22, email: "kristofer.langosh@kunzeluettgen.com", encrypted_password: "$2a$10$Fi0FaprD1t4MH1uK29IiVuM6M5ZBlb7CIJteIpdHyYF...", remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, failed_attempts: 0, locked_at: nil, created_at: "2016-03-28 05:15:16", updated_at: "2016-03-28 05:15:16">, #<Admin id: 37, email: "beryl_keler@wiza.biz", encrypted_password: "$2a$10$2MGzfCyarGQJcmp1RV2BBeU0xNKqalkyukwszutS5/t...", remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, failed_attempts: 0, locked_at: nil, created_at: "2016-03-28 05:15:17", updated_at: "2016-03-28 05:15:17">, #<Admin id: 5, email: "wilhelmine_buckridge@crona.io", encrypted_password: "$2a$10$d5dXU.HH3SQjPPNEijzoau7di9dwwVk4TsgnCV9Xxga...", remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, failed_attempts: 0, locked_at: nil, created_at: "2016-03-28 05:15:15", updated_at: "2016-03-28 05:15:15">, #<Admin id: 14, email: "edward_wisoky@corkery.net", encrypted_password: "$2a$10$sM9p3J6g04TnfMl/nTtezuXdwW3uHoebIhvuW1t1ptI...", remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, failed_attempts: 0, locked_at: nil, created_at: "2016-03-28 05:15:15", updated_at: "2016-03-28 05:15:15">, #<Admin id: 27, email: "leonor@jerde.biz", encrypted_password: "$2a$10$ztJkxiM88UnROguht0mEUea/GVRK0KPO856vQimaIV3...", remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, failed_attempts: 0, locked_at: nil, created_at: "2016-03-28 05:15:16", updated_at: "2016-03-28 05:15:16">, #<Admin id: 2, email: "carley@wyman.net", encrypted_password: "$2a$10$CcYUeV5yayJYaJ0cfcKGVOo7FtxxHpuCFUw.GRcAxAO...", remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, failed_attempts: 0, locked_at: nil, created_at: "2016-03-28 05:15:14", updated_at: "2016-03-28 05:15:14">, #<Admin id: 10, email: "ervin.gleichner@cremin.org", encrypted_password: "$2a$10$8zSpB.qRVkCJSJRPP.630uOGyMOFQRHGKpB6h58COsx...", remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, failed_attempts: 0, locked_at: nil, created_at: "2016-03-28 05:15:15", updated_at: "2016-03-28 05:15:15">, #<Admin id: 15, email: "lonzo.dickens@johnscole.name", encrypted_password: "$2a$10$BfFV7CZmofSJC8ObT8f9OuE.ntSXNvtstd3MUuEolyu...", remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, failed_attempts: 0, locked_at: nil, created_at: "2016-03-28 05:15:15", updated_at: "2016-03-28 05:15:15">]>, @adapter_opts={:adapter=>:json_api}, @serializer_opts={}>

Kosmin commented 8 years ago

I've used @hut8's approach, but changed it for rails 4.1 and active_model_serializers version 0.10.rc3, to solve my problem. My problem was that I wanted to render a view using the html format and serialize a bunch of paginated objects using the json_api to initialize a client-side JS framework. Without the serialization context, I get undefined method 'query_parameters' for nil:NilClass

Since I'm not doing this within the context of a render, but rather a helper method, and I'd rather stay away from rendering inside a variable (since that seems like a hack which does more than I want), this is what I ended up doing

  def serialized_objects(objects)
    @serialized_objects =
      ActiveModel::SerializableResource.new(objects, {}).as_json(serialization_context_options)
  end

  def serialization_context_options
    {
      serialization_context: Struct.new(:request_url, :query_parameters).new(
        request.path,
        env['rack.request.query_hash']
      )
    }
  end
onomated commented 8 years ago

Is Grape support complete? I've followed this thread and updated to the latest 0.10.0 release and getting the following error after following the Grape Support documentation

"status":500
    "title":"Internal Server Error"
    "detail":"key not found: :serialization_context"
    "meta":{"error_class":"KeyError"
    "backtrace":
    "~/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/active_model_serializers-0.10.0/lib/active_model_serializers/adapter/json_api/pagination_links.rb:12:in `fetch'"
    "~/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/active_model_serializers-0.10.0/lib/active_model_serializers/adapter/json_api/pagination_links.rb:12:in `initialize'"
    "~/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/active_model_serializers-0.10.0/lib/active_model_serializers/adapter/json_api.rb:506:in `new'"
    "~/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/active_model_serializers-0.10.0/lib/active_model_serializers/adapter/json_api.rb:506:in `pagination_links_for'"
    "~/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/active_model_serializers-0.10.0/lib/active_model_serializers/adapter/json_api.rb:131:in `success_document'"
    "~/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/active_model_serializers-0.10.0/lib/active_model_serializers/adapter/json_api.rb:48:in `serializable_hash'"
    "~/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/active_model_serializers-0.10.0/lib/active_model_serializers/adapter/base.rb:29:in `as_json'"
    "~/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/active_model_serializers-0.10.0/lib/active_model_serializers/serializable_resource.rb:8:in `to_json'"
    "~/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/active_model_serializers-0.10.0/lib/active_model_serializers/serializable_resource.rb:8:in `to_json'"
    "~/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/active_model_serializers-0.10.0/lib/active_model_serializers/logging.rb:6
bf4 commented 8 years ago

That's a bug. Please open a new issue

B mobile phone

On May 29, 2016, at 9:36 AM, Onome notifications@github.com wrote:

Is Grape support complete? I've followed this thread and updated to the latest 0.10.0 release and getting the following error after following the Grape Support documentation

"status":500 "title":"Internal Server Error" "detail":"key not found: :serialization_context" "meta":{"error_class":"KeyError" "backtrace": "~/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/active_model_serializers-0.10.0/lib/active_model_serializers/adapter/json_api/pagination_links.rb:12:in fetch'" "~/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/active_model_serializers-0.10.0/lib/active_model_serializers/adapter/json_api/pagination_links.rb:12:ininitialize'" "~/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/active_model_serializers-0.10.0/lib/active_model_serializers/adapter/json_api.rb:506:in new'" "~/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/active_model_serializers-0.10.0/lib/active_model_serializers/adapter/json_api.rb:506:inpagination_links_for'" "~/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/active_model_serializers-0.10.0/lib/active_model_serializers/adapter/json_api.rb:131:in success_document'" "~/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/active_model_serializers-0.10.0/lib/active_model_serializers/adapter/json_api.rb:48:inserializable_hash'" "~/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/active_model_serializers-0.10.0/lib/active_model_serializers/adapter/base.rb:29:in as_json'" "~/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/active_model_serializers-0.10.0/lib/active_model_serializers/serializable_resource.rb:8:into_json'" "~/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/active_model_serializers-0.10.0/lib/active_model_serializers/serializable_resource.rb:8:in `to_json'" "~/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/active_model_serializers-0.10.0/lib/active_model_serializers/logging.rb:6 — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.