Search code examples
ruby-on-railsrubyactive-model-serializersrails-api

Can't access V1::JobSerializer inside of V1::JobsController


I am using ActiveModelSerializers gem with my Rails API project.

I created a JobsController inside of app/controllers/v1/jobs_controller.rb because of API versioning.

I also created a JobSerializer inside of app/serializers/v1/job_serializer.rb also because of API versioning.

When I try to access V1::JobSerializer inside of the controller like this:

  def today_jobs 
    todays_jobs = Job.where(created_at: Time.zone.now.beginning_of_day..Time.zone.now.end_of_day).all.order('created_at DESC').page(params[:page] ? params[:page].to_i : 1).per(10)

    render json: {objects: ActiveModel::Serializer::CollectionSerializer.new(todays_jobs, each_serializer: V1::JobSerializer), meta: pagination_meta(todays_jobs)}
  end

Don't mind the pagination, this part is important:

objects: ActiveModel::Serializer::CollectionSerializer.new(todays_jobs, each_serializer: V1::JobSerializer)

When I try to return this it says uncaught throw :no_serializer because I think it doesn't know what V1::JobSerializer is.

Just to make sure: jobs_controller.rb is defined like this:

class V1::JobsController < ApplicationController
end

and job_serializer.rb is defined like this:

class V1::JobSerializer < ActiveModel::Serializer
end

What should I do to be able to access V1::JobSerializer inside of my jobs controller?


Solution

  • The scope resolution operator :: should never be used when declaring nested classes / modules. Always use explicit nesting:

    # Bad:
    class V1::JobsController < ApplicationController
      puts JobSerializer.inspect # missing constant error
    end
    
    class V1::JobSerializer < ActiveModel::Serializer
    end
    
    # Good:
    module V1
      class JobsController < ApplicationController
        puts JobSerializer.inspect # resolves to V1::JobSerializer 
      end
    end
    
    module V1
      class JobSerializer < ActiveModel::Serializer
      end
    end
    

    Why? Because when you use the scope resolution operator the module nesting is resolved to the place of definition. And this can lead to extremely suprising constant lookup:

    A = "I'm in the main scope"
    
    module B
      A = "I'm in B"
      D = "Hello"
    end
    
    class B::C 
      puts A # outputs "I'm in the main scope"
      puts D # Missing constant error
    end
    

    When you use explicit nesting you actually reopen the module/class and set the correct module nesting so that constants are resolved in the same module:

    module B
      class C
        puts A # outputs "I'm in B"
        puts D # outputs "Hello"
      end
    end