0

When I make a request to a custom route in my rails project using jsonapi-resources, I get:

NoMethodError: undefined method `each_key' for nil:NilClass

  0) ArticlesController when requesting latest_article succeeds
     Failure/Error: serializer.serialize_to_hash(Article.latest)

     NoMethodError:
       undefined method `each_key' for nil:NilClass
     # /Users/me/.rbenv/versions/2.7.3/gemsets/blog-api/bundler/gems/jsonapi-resources-bf4b4cd7d79d/lib/jsonapi/resource_set.rb:37:in `populate!'
     # /Users/me/.rbenv/versions/2.7.3/gemsets/blog-api/bundler/gems/jsonapi-resources-bf4b4cd7d79d/lib/jsonapi/resource_serializer.rb:49:in `serialize_to_hash'
     # ./app/controllers/articles_controller.rb:4:in `latest'
     # /Users/me/.rbenv/versions/2.7.3/gemsets/blog-api/gems/actiontext-6.1.4.4/lib/action_text/rendering.rb:20:in `with_renderer'
     # /Users/me/.rbenv/versions/2.7.3/gemsets/blog-api/gems/actiontext-6.1.4.4/lib/action_text/engine.rb:59:in `block (4 levels) in <class:Engine>'
     # /Users/me/.rbenv/versions/2.7.3/gemsets/blog-api/gems/rack-2.2.3/lib/rack/etag.rb:27:in `call'
     # /Users/me/.rbenv/versions/2.7.3/gemsets/blog-api/gems/rack-2.2.3/lib/rack/conditional_get.rb:27:in `call'
     # /Users/me/.rbenv/versions/2.7.3/gemsets/blog-api/gems/rack-2.2.3/lib/rack/head.rb:12:in `call'
     # /Users/me/.rbenv/versions/2.7.3/gemsets/blog-api/gems/railties-6.1.4.4/lib/rails/rack/logger.rb:37:in `call_app'
     # /Users/me/.rbenv/versions/2.7.3/gemsets/blog-api/gems/railties-6.1.4.4/lib/rails/rack/logger.rb:26:in `block in call'
     # /Users/me/.rbenv/versions/2.7.3/gemsets/blog-api/gems/railties-6.1.4.4/lib/rails/rack/logger.rb:26:in `call'
     # /Users/me/.rbenv/versions/2.7.3/gemsets/blog-api/gems/rack-2.2.3/lib/rack/runtime.rb:22:in `call'
     # /Users/me/.rbenv/versions/2.7.3/gemsets/blog-api/gems/rack-2.2.3/lib/rack/sendfile.rb:110:in `call'
     # /Users/me/.rbenv/versions/2.7.3/gemsets/blog-api/gems/railties-6.1.4.4/lib/rails/engine.rb:539:in `call'
     # /Users/me/.rbenv/versions/2.7.3/gemsets/blog-api/gems/rack-test-1.1.0/lib/rack/mock_session.rb:29:in `request'
     # /Users/me/.rbenv/versions/2.7.3/gemsets/blog-api/gems/rack-test-1.1.0/lib/rack/test.rb:266:in `process_request'
     # /Users/me/.rbenv/versions/2.7.3/gemsets/blog-api/gems/rack-test-1.1.0/lib/rack/test.rb:119:in `request'
     # ./spec/requests/articles_controller_spec.rb:22:in `block (3 levels) in <top (required)>'

What am I doing wrong here? And more importantly, how do I fix it?

For the purposes of this question, I have a Rails project with Article and Comment models. I use jsonapi-resources to generate all of the CRUD routes:

Rails.application.routes.draw do
  jsonapi_resources :articles do
    collection do
      get :latest
    end
  end
  jsonapi_resources :comments
end

I added a custom articles/latest route to return the most recent article. I'm using JSONAPI::ResourceSerializer to serialize the Article object following the serializer documentation

class ArticlesController < JSONAPI::ResourceController
  def latest
    serializer = JSONAPI::ResourceSerializer.new(ArticleResource)
    serializer.serialize_to_hash(Article.latest)
  end
end

I wrote a spec to duplicate the error:

describe 'ArticlesController' do
  let!(:articles) { create_list(:article, 10)}

  context 'when requesting articles' do
    it 'succeeds' do
      get articles_path
      expect(response).to have_http_status(:ok)
    end
  end

  context 'when requesting latest_article' do
    it 'succeeds' do
      get latest_articles_path
      expect(response).to have_http_status(:ok)
    end
  end
end

For anyone that made it this far, I have a complete MCVE project on my GitHub.

From what I can tell from debugging, serializer.serialize_to_hash creates a JSONAPI::ResourceSet with resource_set = JSONAPI::ResourceSet.new(source, include_related, options).

Here is JSONAPI::initialize:

    def initialize(source, include_related = nil, options = nil)
      @populated = false
      tree = if source.is_a?(JSONAPI::ResourceTree)
               source
             elsif source.class < JSONAPI::BasicResource
               JSONAPI::PrimaryResourceTree.new(resource: source, include_related: include_related, options: options)
             elsif source.is_a?(Array)
               JSONAPI::PrimaryResourceTree.new(resources: source, include_related: include_related, options: options)
             end

      if tree
        @resource_klasses = flatten_resource_tree(tree)
      end
    end

I don't understand the cases in the if...elsif chain, but none of the conditions is true for me, so tree is nil and so @resource_classes is also nil.

After this, serialize_to_hash calls resource_set.populate! which in turn calls @resource_klasses.each_key, but @resource_classes is nil.

My current assumption is that I'm doing something wrong that causes ResourceSet.initialize to skip initializing @resource_klasses. But I'm not sure how to fix it. Any ideas or suggestions?

Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268
  • 1
    @engineersmnky Thanks for your time to look at this. However, I am unable to confirm. When I add `puts Article.latest` to `ArticlesController.latest` and run `'when requesting latest_article'`, I get `#` which isn't `nil`. – Code-Apprentice Feb 08 '22 at 07:44
  • @engineersmnky I've added some more details that I found while debugging. – Code-Apprentice Feb 08 '22 at 07:59

1 Answers1

1

The problem here is that serialize_to_hash does not accept an ActiveRecord object as an argument. Instead, I need to pass it a resource such as with the following:

    serializer.serialize_to_hash(ArticleResource.new(Article.latest, {}))

I feel like jsonapi-resources could give a meaningful error message here rather than blowing up with NoMethodError.

Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268