rmosolgo / graphiql-rails

Mount the GraphiQL query editor in a Rails app
MIT License
447 stars 135 forks source link

uninitialized constant Resolvers::MyResolver::MyModel - model not loaded in some cases? #66

Closed shaneardell closed 5 years ago

shaneardell commented 5 years ago

I decided to break my resolver functions out into actual Resolver classes (in an app/graphql/resolvers directory), mainly because my app is already pretty big and having all of the field definitions and resolver functions in my QueryType class was getting unruly and the problem was only going to get worse as our product grows.

All my request specs pass just like before I broke them out, and it all seems to be working fine on our dev app on Heroku, both querying GraphQL directly and when using the GraphiQL playground.

However, I noticed that I'm getting this error when I try to play with it in GraphiQL on local, but only in some cases, and it seems not consistently:

uninitialized constant Resolvers::MyResolver::MyModel

An example:

A Recipient belongs to an Organization

My base Resolver:

class Resolvers::Base < GraphQL::Schema::Resolver
  def authorize_resource(resource, action)
    return if Pundit.policy(context[:current_user], resource).send("#{action}?")

    raise GraphQL::ExecutionError, "Unauthorized: #{action} #{resource}"
  end
end

A problematic Resolver:

class Resolvers::Questions < Resolvers::Base
  type [QuestionType], null: false

  argument :organization_uuid, ID, required: false

  def resolve(organization_uuid: nil)
    user         = context[:current_user]
    organization = Organization.friendly.find(organization_uuid) if organization_uuid

    if organization.present?
      authorize_resource(organization, 'show')
      organization.questions
    else
      Question.where(organization_uuid: user.organization_uuids)
    end
  end
end

When I run the query:

query {
  questions(organizationUuid: "14afb4cc-3ab8-11e9-b185-186590df942f") {
    uuid
  }
}

It works. However, when I run it without scoping it to a specific organization, ie:

query {
  questions {
    uuid
  }
}

I get the error uninitialized constant Resolvers::Questions::Question.

Why would the Organization model be loaded just fine but not the Question model? It seems like it's looking for the model inside the existing class in the Question case, but looking outside of it (and finding it succesfully) in the Organization case. Some Resolvers seem to work just fine with zero problems.

I tried tweaking the autoload_paths in config/application.rb but haven't had any success yet. I read that Rails handles autoloading different in production vs local and that messing with it is risky, anyway. That said, I have my types folder added. When I tried added my resolvers folder, there were all sorts of errors.

Here is my autoload paths list:

.../app/graphql/types
.../app/assets
.../app/channels
.../app/controllers
.../app/controllers/concerns
.../app/graphql
.../app/helpers
.../app/javascript
.../app/jobs
.../app/mailers
.../app/models
.../app/models/concerns
.../app/observers
.../app/policies

Any help is appreciated.

rmosolgo commented 5 years ago

Hmmm, tricky! Some suggestions:

shaneardell commented 5 years ago

The full server output:

Started POST "/graphql" for 127.0.0.1 at 2019-02-27 13:48:01 -0700
Processing by GraphqlsController#create as */*
  Parameters: {"query"=>"query {\n  questions {\n    uuid\n  }\n}\n", "variables"=>nil, "graphql"=>{"query"=>"query {\n  questions {\n    uuid\n  }\n}\n", "variables"=>nil}}
  AuthToken Load (1.4ms)  SELECT  "auth_tokens".* FROM "auth_tokens" WHERE "auth_tokens"."expired_at" IS NULL AND "auth_tokens"."token" = $1 LIMIT $2  [["token", "**********"], ["LIMIT", 1]]
  ↳ app/controllers/application_controller.rb:35
  User Load (0.5ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2  [["id", 4], ["LIMIT", 1]]
  ↳ app/controllers/application_controller.rb:42
   (0.1ms)  BEGIN
  ↳ app/controllers/application_controller.rb:42
  AuthToken Update (0.3ms)  UPDATE "auth_tokens" SET "last_used_at" = $1, "updated_at" = $2 WHERE "auth_tokens"."id" = $3  [["last_used_at", "2019-02-27 20:48:01.568622"], ["updated_at", "2019-02-27 20:48:01.571236"], ["id", 1]]
  ↳ app/controllers/application_controller.rb:42
   (0.3ms)  COMMIT
  ↳ app/controllers/application_controller.rb:42
Completed 500 Internal Server Error in 24ms (ActiveRecord: 6.2ms)

NameError (uninitialized constant Resolvers::Questions::Question):

app/graphql/resolvers/questions.rb:16:in `resolve'
app/controllers/graphqls_controller.rb:31:in `create'

I'm not seeing anything that points to a preceding error.

Adding the :: prefix does work, but feels weird.

The Question class is defined as a Rails model (app/models/question.rb). The core of the app can be thought of like a versioned question bank.

class Question < ApplicationRecord
  include Trackable
  include Identifiable

  belongs_to :organization
  has_many   :question_versions

  accepts_nested_attributes_for :question_versions

  validates :name, presence: true

  enum field_type: [
    :short_text,
    :paragraph_text,
    :number,
    :date,
    :time,
    :single_select,
    :multi_select,
    :likert,
    :photo,
    :audio,
    :video,
  ]

  def latest_version
    question_versions.order(:version_number).last
  end
end
rmosolgo commented 5 years ago

Thanks for sharing! I was hoping to see a smoking gun, but nothin' 😖

I guess since the adding :: works, we can be sure that the file does load correctly.

Sorry, I don't really have any other suggestion :(

If you don't like adding the explicit ::, the other option is to manually load the file using require_dependency at the top of the file, eg

require_dependency "question"

That will make sure Rails loads the file and can reload it as needed.

Sorry, I don't have any other ideas how graphiql-rails could be involved here!

shaneardell commented 5 years ago

Yeah, it occurs to me I probably should have opened the issue under the graphql-ruby project instead.

My gut says it has everything to do with Rails' autoloading, especially since it's behaving differently in both prod and test environments vs local.

require_dependency 'question' works and feels a little better than the :: prefix.

I'll go with that for now. Thanks so much @rmosolgo for looking into it.