Random "Uninitialized Constant" errors in staging/production - ruby-on-rails

Like many Rails applications, we have a few classes we define in the lib/ folder. We make sure to let Rails know about them in the config/application.rb file like so
config.autoload_paths += Dir[Rails.root.join('lib'), Rails.root.join('lib', '**')]
And that has worked perfectly up until now with no problems. However recently we have been getting random occurrences of "Uninitialied Constant" errors in staging/production. Most of the times things work fine, it's just every once in a while that things break down. Opening a Rails console and the classes are always there.
I tried to better understand how Rails does the autoloading magic, so I read this great article: http://urbanautomaton.com/blog/2013/08/27/rails-autoloading-hell/, but nothing in it helped me.
I also tried a few things that I thought might work, like requiring all the files individually in an initializer, but nothing worked.
I'm using Ruby 2.1.1, Rails 3.2.19, Phusion Passenger 4.x. Anything else that might help? How might Constants become undefined or inaccessible from somewhere? Maybe a gem is messing with the way Rails modifies constant lookup...
Update: I have not enabled threadsafe mode

Related

There is a programmatical way to detect Zeitwerk::NameError when upgrading to Rails 6?

I am currently migrating an old Rails application to Rails 6.
It seems that some files in the project are not consistent with the classes defined in it.
I don't see this error when running the tests of the application, but after deploy I receive errors like:
Zeitwerk::NameError: expected file /app/my?_/app/lib/multi_io.rb to define constant MultiIo, but didn't
In previous SO questions I could find suggestions to:
Rails 6: Zeitwerk::NameError doesn't load class from module set config.autoloader = :classic
How to ignore a folder in Zeitwerk for Rails 6? ignore a given path
Autoloading and Zeitwerk Mode in Rails 6 tweak config.autoload_path
I am not looking for a workaround so I corrected the file raising the exception.
I would like to find a programmatical way to detect all inconsistent path/class names without running the application in production mode.
So far, three options came to my mind:
A rake task to validate the autoloading (which is not provided by the gem). Does it exists? Would you have a snippet to run from the rails console in development env instead?
A rubocop cop or another static code analyser. There is any?
Force the eager load in the test environment
What it the suggested approach to validate my code without requiring several iterations/deployments?
thank you very much in advance
You can check the autoload compatibility of a project with the zeitwerk::check task:
$ bin/rails zeitwerk:check
Hold on, I am eager loading the application.
All is good!
It exercises all the autoload paths and will flag issues such as those caused by the change in constant name inference (such as those with acronyms). The task doesn't appear to be widely advertised or documented except in the upgrade guide. But, I actually use it sometimes to just flush out constant naming issues in the project generally (i.e., where the class doesn't match the file).
#rmlockerd Described one of the best tools bin/rails zeitwerk:check for this in his answer here.
The other articles I found informative were:
Understanding Zeitwerk in Rails
9 Tips & Tricks To Help You Enable The Zeitwerk Autoloader In Your
Rails App
Rails doc on autoloading
Rails doc on upgrading from Rails 5.2 to 6.0
The process I took was to run bin/rails zeitwerk:check fix an error, run it again until I saw "All is good". After that worked through errors reported by our spec tests.
One general problem to fix is if a folder is not within the Rails default file structure, Zeitwerk will not be able to find it when autoloading. We had a folder named "services" (app/services) that needed to be added to the autoload path within the application.rb file like:
config.autoload_paths += %W(#{config.root}/app/services/)
Error:
Expected file path/file_name.rb to define constant FileName
Fix:
remove any require or require_relative in other files referencing where the class is defined (Zeitwerk doesn't need them).
Error (class is defined within a module of a sub directory):
NameError: uninitialized constant SubFile::ClassName
Fix:
Detailed in another S/O answer. Whenever method is used within sub folders structure the call as Folder::SubDirectory::Class.method_call
Goodluck!

Ruby on Rails - using modules from a different directory

This is a very basic question, but I can't find exactly the answer I need.
I have the following code in trunk/app/models/parsers/my_file.rb in my dev environment:
def initialize
...
#logger = Utils::SingletonLogger.get_logger
#logger.debug("Instantiating my_file object")
end
It runs fine.
But when I deploy it to the test environment (all code should be identical, but I'm not sure where to start looking for differences if there are any), Rails complains that it can't find Parsers::MyFile::Utils. The Utils module I want to use is in app/lib/my_utils.rb. This makes me think that Rails is creating some sort of namespace for the code in the parsers sub-directory, and only looking there for the Utils module, but I haven't been able to figure out how to make it work for me. Is there some main, application level prefix I can use to specify to look outside of the current directory structure?
I've tried adding require 'my_utils' and require_relative '../../../lib/my_utils.rb'. The former can't find the file, the latter just throws the same error as when I don't have any require at all. I'm not sure if I should have to require this or not.
In any case, I clearly don't quite understand how to refer to code in modules in a different directory, I don't understand when/why rails needs an explicit path at some times/environments but not others, and I don't know how to make Rails look outside of the current file for code. Any help with any of these would be appreciated.
Oh, I'm using Ruby 1.9.3, and rails 3.2.1.
Edit: It just started working, without any changes to the application.rb or environment files. It doesn't even have a require in the current version. Is there any obvious reason for it not to work at first, then to work after another server restart? (I'm pretty sure I restarted it after the code went in - I don't think I just forgot to do that before.)
Anyway, thanks for your help - I really do appreciate it.
You can manually add directories you want to include in application.rb. Might want to make sure in your application.rb or test.rb config files you have this autoload_paths in there, yours might be specific to your development.rb file.
config.autoload_paths += %W(#{config.root}/lib/)

How to get rid of postgresql but still use ActiveRecord::RecordNotFound on heroku when using mongohq?

Edit:
How to use ActiveRecord::RecordNotFound exception to make Rails respond with the standard 404? The basic approach suggested in many places is to modify the config/application.rb to not require "rails/all", but instead check out the file: railties/lib/rails/all.rb and pick and choose what you want/need. However, if you just leave out the part a about active_record, then you can't use ActiveRecord::RecordNotFound (and apparently using fixtures might have problems too, I wouldn't know as I don't use them). How to solve this problem?
The old explanation of the problem is not very relevant but, I'm leaving it here for reference and context for the comments below..
Old explanation:
So here's the deal. We have a Rails app that uses MongoHQ as it's database. Locally thing's run smoothly without any problems, but on production and staging environments it seems that Heroku insists that we must have postgresql database and gems setup/installed. I've tried to delete the addons and related config variables, but it just wont do. This is rather annoying as now the free postgresql causes problems because if we crank up the dynos it will very quickly run out of connections (20 connection limit). However, upgrading to a more production level tier of postgresql seems like a very silly thing to do when the database has 0 tables. How are everyone else using MongoHQ managing this? I can't seem to be able to find any anything about it though I would have thought it to be an apparent issue that everyone using MongoHQ and Heroku would face?
The key idea is that we can still require "activer_record", but lets just leave out the corresponding railtie. This is what I ended up putting into my config/application.rb
require "rails"
frameworks = %w(action_controller action_mailer active_resource rails/test_unit)
frameworks.each { |framework| require "#{framework}/railtie" }
# For errors like ActiveRecord::RecordNotFound
require "active_record"
Credits for the idea goes to mr. Benjamin Oakes:
http://www.benjaminoakes.com/2011/09/15/activerecordconnectionnotestablished-in-rails-3-1-on-heroku/

Two almost identical Rails projects, missing gem helper file in one but not the other

I have two rails projects under the same parent within my home directory with the same environment. Both use the authorize-net (1.5.2) gem. I'm using ruby 1.8.7 (2012-02-08 patchlevel 358) [i686-darwin11.4.0], Rails 2.3.14, and rvm 1.14.3 on Mac OS X 10.8.1. Both have config.gem 'authorize-net', :lib => 'authorize_net in their config/environment.rb, both have helper :authorize_net at the top of their relevant controllers, both have helper :all at the top of their application_controller.rb. Yet one works fine and the other gets a MissingSourceFile (Missing helper file helpers/authorize_net_helper.rb) error. The helper file is in fact located at ~/.rvm/gems/ruby-1.8.7-p358#global/gems/authorize-net-1.5.2/lib/app/helpers/authorize_net_helper.rb
I cannot for the life of me figure out why one works but not the other. Can anyone point me in the right direction? How can I figure out what path Rails is using to find this helper in each case?
One thing I've learned over the years of using the StackExchange universe is that when I have a weird problem and don't get an answer within a couple of days, it's something stupid I did that no one else did or probably ever will do. I imagine this is one of those things, but I'll never know because the problem has gone away and I don't know why.
Realizing that the working project came from authorize.net's example and the non-working one didn't, I discovered that the authorize-net gem had a generator that I had not used. So I went thru the generator code and realized I was missing an initializer that I was sure I had already created. This initializer just loads some constants (api_login_id and api_transaction_key) from a YML file and has nothing having to do with paths, but I created it anyway before realizing I had in fact created it before but put it in controllers instead of config/initializers. Anyway, when I restarted WEBrick, everything worked, no path issues. Now I have restarted WEBrick MANY times before, including just before posting this question. But this time the problem went away.
I HATE when this happens.

Proper way to use a Rack middleware from a gem in a Rails project with Bundler

I've got a Rails project that's setup using Bundler. One of my bundled gems provides a Rack middleware that I'd like to use in my Rails app (but only in the 'production' Rails environment).
If I just put something like this in config/environments/production.rb, I get an unknown constant error:
config.middleware.use ::Rack::MyMiddleware
... presumably because Bundler.require has not yet been called at this point, and none of my bundled gems are available.
I have found a few ways of working around this, but none seem great, and I'm wondering if there's a more standard solution that I'm missing:
Explicitly require 'my_middleware_gem' in config/environments/production.rb
Wrap the config.middleware.use call in an after_initialize block, ensuring that Bundler has a chance to do its thing before I try to reference the constant.
Use a string ("::Rack::MyMiddleware") instead of the bare class constant. This doesn't seem to work with Unicorn for some reason, but does work with some other servers (with Unicorn it ends up trying to call "::Rack::MyMiddleware".new, which of course fails).
Am I missing some better approach? At what point in the initialization process is it expected that bundled gems should be available?
Copying the answer from the comments in order to remove this question from the "Unanswered" filter:
matt suggested:
I think using the after_initialize block is the right way in this case.
grumbler confirmed:
Yeah, that's what I ended up going with. Thanks! Regarding the unicorn issue alluded to in the original question, turns out I was hitting this problem: http://davidvollbracht.com/blog/headachenewunicorn-capistrano-bundler-usr2

Resources