Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Unable to autoload constant User" error when changed code in development

I have a problem with my rails application. After an Update from Rails 3 to 4. When I surf through the pages after starting the server in development mode everything is fine.

But after a single code change (even adding a space) every page request shows the following error.

Unable to autoload constant User, expected /path/to/my/rails-app/app/models/user.rb to define it

The file lives exactly there and defines the class:

class User < ActiveRecord::Base
  …

I tried many things with config.autoload_paths and config.eager_load_paths in application.rb but with no luck. Deactivating spring did not help either.

Developing an app and having to restart the server after every single change seems so 90s.

$ rails -v
Rails 4.2.4
$ ruby -v
ruby 2.1.7p400 (2015-08-18 revision 51632) [x86_64-linux]

Some relevant configs:

development.rb

MyApp::Application.configure do
    # Settings specified here will take precedence over those in config/application.rb

  # In the development environment your application's code is reloaded on
  # every request.  This slows down response time but is perfect for development
  # since you don't have to restart the webserver when you make code changes.
  config.cache_classes = false

  # Do not eager load code on boot. This avoids loading your whole application
  # just for the purpose of running a single test. If you are using a tool that
  # preloads Rails for running tests, you may have to set it to true.
  config.eager_load = false

  # Show full error reports and disable caching
  config.consider_all_requests_local       = true
  config.action_controller.perform_caching = false

  # Don't care if the mailer can't send
  config.action_mailer.raise_delivery_errors = false

  # Print deprecation notices to the Rails logger
  config.active_support.deprecation = :log

  # Only use best-standards-support built into browsers
  config.action_dispatch.best_standards_support = :builtin

  # Do not compress assets
  config.assets.compress = false

  # Expands the lines which load the assets
  config.assets.debug = true

  config.action_mailer.delivery_method = :test
  config.action_mailer.default_url_options = {
    host: 'localhost',
    port: 3000
  }

end

application.rb

module Serviceportal
  class Application < Rails::Application    
    # Enable the asset pipeline
    config.assets.enabled = true

    # Version of your assets, change this if you want to expire all your assets
    config.assets.version = '1.0'

    [… some asset precompile stuff …]

    # Configure the default encoding used in templates for Ruby 1.9.
    config.encoding = 'utf-8'

    # Custom directories with classes and modules you want to be autoloadable.
    config.autoload_paths += Dir["#{config.root}/app/mailers",
        "#{config.root}/app/controllers/concerns",
        "#{config.root}/app/models/concerns",
        "#{config.root}/app/decorators/concerns",
        "#{config.root}/lib",
        "#{config.root}/lib/shared"
    ]
    config.eager_load_paths += Dir["#{config.root}/app/mailers",
        "#{config.root}/app/controllers/concerns",
        "#{config.root}/app/models/concerns",
        "#{config.root}/app/decorators/concerns",
        "#{config.root}/lib",
        "#{config.root}/lib/shared"]

    # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
    # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
    config.time_zone = 'Berlin'

    # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
    # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
    config.i18n.default_locale = :de

    [… some SQL and active support stuff …]

    config.action_controller.include_all_helpers = false

    config.action_controller.action_on_unpermitted_parameters = :raise

    # Do not swallow errors in after_commit/after_rollback callbacks.
    config.active_record.raise_in_transactional_callbacks = true
  end
end

Edit: The error mostly shows up in lib/auth/user_proxy.rb in the following function. Maybe this helps to narrow the range of possible causes.

def self.usertype_allowed?(type)
  [ User, TempCustomer ].include? type.classify.safe_constantize rescue false
end

Edit 2: Stringify the class names in Edit 1 helped (thanks @Benjamin Sinclaire). But only leads to the next errors. I could also avoid using classes. But at the following error in app/controllers/concerns/security.rb there is nothing can change?

Unable to autoload constant User, expected /path/to/my/rails-app/app/models/user.rb to define it

code:

def set_current_user
  User.current = current_user
end

with current user saved in the Thread (code from /path/to/my/rails-app/app/models/user.rb

def self.current
  Thread.current['current_user']
end

def self.current=(user)
  Thread.current['current_user'] = user
end

Just to make it clear again: It works after server restart in development until I change some code somewhere.

like image 802
Sandro L Avatar asked Sep 23 '15 07:09

Sandro L


3 Answers

1 See if you have any multiple-level class or module declaration done one one line and change them to be declared in several lines.

Instead of

class Parent::Sub::Child
end

Do

module Parent
  module Sub
    class Child
    end
  end
end

2 Check your model association definitions, and ensure you are never using constant. Use string instead.

Instead of

belongs_to :manager, class_name: User

Do

belongs_to :manager, class_name: 'User'

3 Just saw your edit. Can you refactor like this?

# I assume `type` is a string or such, so we can compare classes
# names instead of constants, and get rid of `safe_constantize`
def self.usertype_allowed?(type)
  ['User', 'TempCustomer'].include? type.classify rescue false
end

4 Not a good idea to serialize an active record object in the Thread storage. Change it to store the user id instead, like this:

def set_current_user
  User.current = current_user.id
end

def self.current
  Thread.current['current_user_id']
end

def self.current=(user_id)
  Thread.current['current_user_id'] = user_id
end
like image 179
Benj Avatar answered Oct 17 '22 02:10

Benj


You don't need include app/models/concerns and app/controllers/concerns in your autoload/ eagerload paths as they are included by default in Rails 4: https://signalvnoise.com/posts/3372-put-chubby-models-on-a-diet-with-concerns

Also make sure that your concerns are defined as modules, extend ActiveSupport::Concern and with the appropriate file name

#taggable.rb
module Taggable
  extend ActiveSupport::Concern
end

Another cause of your problem might be that some modules/ classes in app/decorators/concerns, lib, lib/shared are using the User class which is not loaded yet or some of it's dependencies are not loaded so try adding require_relative path_to_user.rb at the top of those files

-----Edit-------

Try adding at the top of lib/auth/user_proxy.rb

require_dependency 'app/models/user'

This way you'll remove any ambiguity in autoloading the User class and you won't mess around with Rails autoloading see more here: http://guides.rubyonrails.org/autoloading_and_reloading_constants.html#require-dependency , http://guides.rubyonrails.org/autoloading_and_reloading_constants.html#common-gotchas

like image 29
bigsolom Avatar answered Oct 17 '22 03:10

bigsolom


Same problem but in an engine w/ namespaces. No issues in production or in development until a code-change / autoload.

The solution was to

  1. checking for double definitions (there were none)
  2. checking if the module nesting strictly follows rails conventions in the filesystem.

I've had myns under myengine/app/myns/subns/obj.rb but myns is being ignored as it is at the root of the app folder, so moving the myns folder into a subfolder myengine/app/lib/myns solved the issue.

Note: the rails error message was very explicit about the module nesting (while still pointing to the wrong .rb file in the filesystem) so look closely at the error. The error was 'Unable to autoload constant subns/obj.rb in .../myns/subns/obj.rb'. Rails suggesting the incorrect file-location (which exists) is misleading in this case.

like image 1
count0 Avatar answered Oct 17 '22 01:10

count0