Rails, custom exceptions - ruby-on-rails

I'm following instructions at http://ariejan.net/2011/10/14/rails-3-customized-exception-handling/ and have hit a road block.
I am relatively new to rails, so I'm not sure what I've done right/not-so-right.
The first step was to create class
MyApp::ProfileNotFoundError < StandardError
end
So I went to app/models and created profile_not_found.rb which contains the following, where (APP) is the name of my app as defined by Rails.application.class.parent_name, but I have hidden from this post for security/privacy.
(APP)::ProfileNotFoundError < StandardError
end
In app/controllers/application_controller.rb I added
rescue_from (APP)::ProfileNotFoundError, :with => :profile_not_found
and in my login controller I added
raise (APP)::ProfileNotFoundError if #profile.nil?
However, when I try to test the code, I get a Routing Error stating
uninitialized constant (APP)::BlankUsernameError
In my opinion, this suggests that I did something wrong pertaining to the class creation, but the tutorial is so vague I can't figure it out. Any pointers?
I'm running Rails 3.0.20 & Ruby 1.8.7 on Ubuntu 12.04.2 x86_64

Do you have the class keyword in your class definition?
class MyApp::ProfileNotFoundError < StandardError
end
Secondly, you'll have to require your exceptions where you're using it. This is probably the problem you're encountering with the uninitialized constant error. To do this you will probably have to wrap it in a module:
module Exceptions
class MyApp::ProfileNotFoundError < StandardError
end
end
Also, you should put your error classes in a different directory than /models. This directory should be explicitly for your models. Maybe make one like /errors.

Related

uninitialised constant after upgrade of Gemfile

I've just updated my Gemfile.
At the beginning I thought problem came from Zeitwerk (from 2.4.2 to 2.5.4) but I've downgraded it and I still have an issue on my spec. I've isolated that the problem does not come from RSpec and dependencies.
Actually, RSpec does not found a class which is defined within another file and does not match the file name/class name.
Important point: Filter::MyStandardError is found.
# app/services/filter/my_standard_error.rb
module Filter
class MyStandardError < StandardError; end
class MySpecificError < MyStandardError; end
# ...
end
# app/services/filter/my_tested_service.rb
module Filter
class MyTestedService
def initialize
raise ::Filter::MySpecificError
end
end
end
RSpec.describe Filter::MyTestedService do
subject { described_class.new }
it 'raises an error'
expect{subject}.to raise_error(::Filter::MySpecificError)
end
end
And I got the error:
NameError:
uninitialized constant Filter::MySpecificError
I got the Changelog but breaking changes are not used on my configuration.
Does anybody have an idea for this one?
You do not need to add app/services to the autoload paths, that is done automatically by Rails. I'd suggest to remove that configuration to keep things simple/idiomatic.
The implementation of app/services/filter.rb should not be needed. Your application is doing something that is not right, we just need to find it.
Could you please delete app/services/filter.rb, throw Rails.autoloaders.log! in config/application.rb, trigger the error, and share the traces?
After reading one-file-one-constant-at-the-same-top-level
I found this to fix my issue
# app/services/filter.rb
class Filter
class MyStandardError < StandardError; end
class MySpecificError < MyStandardError; end
end
# app/services/filter/my_tested_service.rb
class Filter
class MyTestedService
def initialize
raise ::Filter::MySpecificError
end
end
end
I still don't know why it was working before..
You cannot define two constants at the same level in the same file. It is one constant, one file. This has not changed in Zeitwerk upgrades. You need one file for the standard error, and another file for the specific error.

How to properly extend ApplicationRecord in Rails 6 with Zeitwerk

Consider a Rails 6 application that has app/models/application_record.rb. This Rails 6 application is using Zeitwerk loader.
class ApplicationRecord
end
If I want to add functionality to ApplicationRecord via a module:
# app/models/concerns/fancy_methods.rb
module FancyMethods
def fancy_pants
puts "I'm wearing fancy pants"
end
end
and do the following:
class ApplicationRecord
include FancyMethods
end
I will get a deprecation warning or error:
DEPRECATION WARNING: Initialization autoloaded the constant FancyMethods.
Being able to do this is deprecated. Autoloading during initialization is going
to be an error condition in future versions of Rails.
Reloading does not reboot the application, and therefore code executed during
initialization does not run again. So, if you reload FancyMethods, for example,
the expected changes wont be reflected in that stale Module object.
This autoloaded constant has been unloaded.
Please, check the "Autoloading and Reloading Constants" guide for solutions.
(called from <top (required)> at /Users/peter/work/recognize/config/environment.rb:5)
I've read lots of articles including the Rails autoload docs, but nothing really addresses this minimal but common case of extending ApplicationRecord. Yes, I could wrap ApplicationRecord in a .to_prepare block like:
Rails.configuration.to_prepare do
class ApplicationRecord < ActiveRecord::Base
include FancyMethods
end
end
But this seems like a code smell and could cause other unexpected problems now or down the line.
Figured it out! The issue was there was an explicit require in an initializer that loaded ApplicationRecord.
# config/initializers/setup_other_fancy_thing.rb
require 'application_record'
module OtherFancyThing
def also_fancy
puts 'also fancy'
end
end
ApplicationRecord.send(:include, OtherFancyThing)
The way I debugged this was that I:
Created a new Rails app of the same version and could not reproduce the error
I copied the default application.rb and development.rb and still got the error
Moved the entire config/initializers directory to a temp directory and it made the warning go away! So, I knew it had to be one of the initializers. From there it was just a matter of dividing and conquering until I found the offending initializer.
Great! So, basically, you do not need to define the module and include it that way. Idiomatically, you define the module in its own file, normal:
# app/models/other_fancy_thing.rb
module OtherFancyThing
def also_fancy
puts 'also fancy'
end
end
and then
# app/models/application_record.rb
class ApplicationRecord
include FancyMethods
end
Whenever ApplicationRecord is loaded, for example as a side-effect or loading some regular model, it will load FancyMethods just fine.

Accessing namespaced class in gem vs Rails

I've worked with a couple of Ruby gems and also Rails. One thing I've never fully understood is why Rails required explicit class constant references for code defined in the /lib folder. In a ruby gem, I could create something like this:
lib/my_gem/custom_error.rb
module MyGem
class CustomError < StandardError
end
end
lib/my_gem/some_class.rb
module MyGem
class SomeClass
def initialize
raise CustomError
end
end
end
Whether it's an error, another class or whatever, as long as the calling class is in the same namespace as the referenced class, ruby would initialize the correct class CustomError in the case above. Moving to Rails, this is a different story and this code would result in an uninitialized constant error. In Rails I would have to raise MyGem::CustomError instead. Why is this the case? I assume it has something to do with autoloading. Is there a way around this or is this standard?

Rails and RSpec: Testing controllers with the same name in different namespace (module)

I have rails 4.1.16 API application that is tested using RSpec 3.4.0, and I experience problems with testing classes called the same name in a different module.
The structure is:
app/controllers/bar/notifications_controller.rb
class Bar::NotificationsController < ApiController
...
end
and controller with the same name in a different module:
app/controllers/foo/bar/notifications_controller.rb
module Foo
class Bar::NotificationsController < ApiController
...
end
end
The Foo is a new module and does not have tests yet.
After adding it, all the corresponding controller tests for the old Bar::NotificationsController started to fail.
The spec file:
spec/controllers/bar/notifications_controller_spec.rb
require 'spec_helper'
describe Bar::NotificationsController, type: :controller do
...
end
All the tests in that spec file fail with the same error:
RuntimeError:
#controller is nil: make sure you set it in your test's setup method.
The problem does not exist when I change the controller name in the Foo module:
app/controllers/foo/bar/foo_notifications_controller.rb
module Foo
class Bar::FooNotificationsController < ApiController
...
end
end
I already tried adding on top of the spec file require 'bar/notifications_controller' and using the class name as a string describe "Bar::NotificationsController, type: :controller but it did not solve the issue (the same error).
Why is this happening? What is the solution?
I want to believe there is a tiny thing I did not try yet and I don't have to pollute my code and the structure with nonsense names just to make the specs pass.
Many thanks in advance for your help!
In general, I've take to including all namespacing in the class definition. Something like:
app/controllers/foo/bar/notifications_controller.rb
class Foo::Bar::NotificationsController < ApiController
...
end
While, at first glance, this might look the same as:
app/controllers/foo/bar/notifications_controller.rb
module Foo
class Bar::NotificationsController < ApiController
...
end
end
These are, in fact, different. The difference is in how Rails handles autoloading of constants. I won't go into the details here because it's a longer topic and there are good articles/posts out in the web-o-sphere.
You can find good articles on how Rails handles autoloading like this one (or try Googling rails constant loading)
Also, as the article notes, Ruby constant loading operates differently than Rails loading. Good information on Ruby constant loading can be found here (or try Googling ruby constant loading).

Rails, production-env, "Object is not missing constant"

So I seems I was stupid and haven't checked running in production-env for a long time, and now that I'm trying to deploy, I'm getting this annoying error. Any ideas?
lib/history_tools.rb
module HistoryTools
def self.included(base)
base.has_many :history, :dependent => :destroy
History::TYPES.each do |htype|
base.has_many "history_#{htype}", :class_name => "History::#{htype.capitalize}"
end
end
# ... other minor things removed ...
end
app/models/user.rb
class User < InheritedResources::Base
include HistoryTools
end
config/environment.rb
# ... the usual stuff, then, at the very bottom:
require 'history_tools'
This gives the error:
activesupport-2.3.8/lib/active_support/dependencies.rb:417:in
`load_missing_constant':ArgumentError: Object is not missing
constant HistoryTools!
If I add an additional require 'history_tools' at the top of user.rb, it fixes that error, I believe, but then it fails at finding other things in #{RAILS_ROOT}/lib, that were required in the environment.rb in the same manner.
The kicker: this works perfectly in development mode. It only gives this error in production. Most of my googling seems to suggest that "not missing constant" errors relates to how Rails autoloads files, which should go away in production when nothing is unloaded. This seems to be the opposite of that behavior?
When I've gotten this error, it's because there's an error in an inner class/module that's inside the class/module mentioned in the error.
I can't say if this is a typo or the real code but:
class User < InheritedResources::Base
include HistoryTools
end
Should probably be
class User < ActiveRecord::Base
include HistoryTools
end
InheritedResources should be used for controllers, not models.
You shouldn't have to have the require 'history_tools' in the environment.rb. In that version of Rails all files in the lib folder should be auto-loaded.
ok, after many more hours of digging, it seems it wasn't even related to this stuff at all. It was an error about 3 more classes down the tree, that was failing for another strange reason, and the exception was apparently being caught by the internals of rails somewhere and just ignored.
That doesn't explain why it worked in development mode, unfortunately, but at least all my stuff works now. Thanks anyway!

Resources