I am trying to create custom exceptions in rails, but I 've problem with my designed solution.
Here what I've done so far:
-Create in the app/ folder a folder named errors/ with a file exceptions.rb in it.
app/errors/exceptions.rb:
module Exceptions
class AppError < StandardError; end
end
In one of my controllers, tried to raise it:
raise Exceptions::AppError.new("User is not authorized")
But when I call the controller's action, here is what I get:
NameError (uninitialized constant Exceptions::AppError
Did you mean? TypeError
KeyError
IOError
EOFError
Did you mean? TypeError
KeyError
IOError
EOFError
):
I think I don't have fully understood how to create new directories and files, and use them.
I've read that everything created in the app dir, is eager loaded, so I can't understand where is the problem.
Short version: this is about rails' automated code loading - the fact that in this case the files contain exceptions doesn't matter (see guide on the subject for more details)
Rails would try to load this from exceptions/app_error.rb, in any of the files that are on its auto load path. Because you file naming doesn't match this, it can't find the definition and you get a NameError.
If you don't care about code reloading (and you might not for this sort of content) then you can keep the files as they are but require them in an initialiser (ensure that app/errors is in the load path):
require 'exceptions'
If not then you'll have to rearrange your files to match. If you add app/errors to rails' autoload path and keep the files as is, then it should work. If you don't want to change the autoload path then you'd have to mode it to somewhere in the autoload path and ensure the nesting of modules reflects the organization on disk.
Personally I'd probably stick these in lib and require them with an initialiser
Related
There are probably hundreds of these questions on here but I haven't been able to get one to work yet. I'm using Rails 6 and I always have trouble writing custom modules.
I have a file for creating email tokens: lib/account_management/email_token.rb
module AccountManagement
module EmailToken
def create_email_token(user)
....
end
end
end
Then in my controller I have tried all kinds of things:
require 'account_management/email_token'
include AccountManagement::EmailToken
Calling it with: create_and_send_email_token(user)
At one point I added config.eager_load_paths << Rails.root.join('lib/account_management/') but I didn't think you still had to do that.
Whenever I try to call the controller action I get one of a few error messages:
*** NameError Exception: uninitialized constant Accounts::SessionsController::EmailToken
#this happens if I try to manually send through the rails console
(although inside of a byebug I can call require 'account_management/email_token' and it returns
true.
Most commonly I get:
NoMethodError (undefined method `create_email_token' for #<Accounts::SessionsController:0x00007ffe8dea21d8>
Did you mean? create_email):
# this is the name of the controller method and is unrleated.
The simplest way to solve this is by placing your files in app/lib/account_management/email_token.rb. Rails already autoloads any subdirectory of the app folder*.
/lib has not been on the autoload paths since Rails 3. If you want to add it to the autoload paths you need to add /lib not /lib/account_management to the autoload/eager loading paths. In Zeitwerk terms this adds a root where Zeitwerk will index and resolve constants from.
config.autoload_paths += config.root.join('lib')
config.eager_load_paths += config.root.join('lib')
Note that eager_load_paths is only used when eager loading is turned on. Its turned off by default in development to enable code reloading.
/lib is added to $LOAD_PATHso you can also manually require the file with:
require 'account_management/email_token'
See:
Autoloading and Reloading Constants (Zeitwerk Mode)
Rails #37835
I've defined a few Exception classes in my app/models/core/exceptions.rb file:
class Core::Exception < Exception end
class Core::UserNotFoundException < Core::Exception end
...
Then added /config/initializers/require.rb file so Rails can find classes with names which don't meet file names:
require "#{Rails.root}/app/models/core/exceptions.rb"
When I start the app (development mode), everything works fine until I change anything to any .rb file. Then when I refresh browser page I get error "Uninitialzed constant Core::Exception". So every time I make any modification to the source code (except the views) I have to restart 'rails server'.
Any idea on why when I refresh a page my 'require' are no longer loaded? How to fix this?
Creating a core.rb file should help.
Just place the Core module definition there
# /app/models/core.rb
module Core
end
I believe the problem is the Core module. When you hit
class Core::Exception ...
rails needs to find the module or class named Core. Rails will create a module called Core automatically if it finds files with paths off the form 'core/foo.rb' in its autoload path (see the algorithm pseudo code in the guide on autoloading).
Because this Core module is autoloaded, code reloading will trigger its removal. A new Core module may be created, but this will no longer have the classes you had added to it.
One option would be to not fight Rails's code organisation and put those classes in individual files.
An alternative may be to change how you create those classes to
module Core
class Exception < ::Exception; end
end
Which means that the Core module won't be autoloaded and thus shouldn't be eligible for removal.
I'd like to create a general purpose string manipulation class that can be used across Models, Views, and Controllers in my Rails application.
Right now, I'm attempting to put a Module in my lib directory and I'm just trying to access the function in rails console to test it. I've tried a lot of the techniques from similar questions, but I can't get it to work.
In my lib/filenames.rb file:
module Filenames
def sanitize_filename(filename)
# Replace any non-letter or non-number character with a space
filename.gsub!(/[^A-Za-z0-9]+/, ' ')
#remove spaces from beginning and end
filename.strip!
#replaces spaces with hyphens
filename.gsub!(/\ +/, '-')
end
module_function :sanitize_filename
end
When I try to call sanitize_filename("some string"), I get a no method error. When I try to call Filenames.sanitize_filename("some string"), I get an uninitilized constant error. And when I try to include '/lib/filenames' I get a load error.
Is this the most conventional way to create a method that I can access anywhere? Should I create a class instead?
How can I get it working? :)
Thanks!
For a really great answer, look at Yehuda Katz' answer referenced in the comment to your question (and really, do look at that).
The short answer in this case is that you probably are not loading your file. See the link that RyanWilcox gave you. You can check this by putting a syntax error in your file - if the syntax error is not raised when starting your app (server or console), you know the file is not being loaded.
If you think you are loading it, please post the code you are using to load it. Again, see the link RyanWilcox gave you for details. It includes this code, which goes into one of your environment config files:
# Autoload lib/ folder including all subdirectories
config.autoload_paths += Dir["#{config.root}/lib/**/"]
But really, read Yehuda's answer.
I'm developing a gem/engine. The way I do this is by bundling it in a test RailsApp from source:
# Gemfile
gem 'my-engine', path: '../local/path/to/gem'
This works fine so far.
But, after I change a file in my gem (add a space or a break for example) the Engine is unloaded. Causing the following error:
uninitialized constant My::Engine
This error is thrown by the file that does the first call to My::Engine. ( I need to call that to get the root: My::Engine.root ) If I delete that line, there are no error thrown, but just an empty page is rendered, and this is happening because all my SQL's change and no content is loaded from the database. I think this is because the files in the lib dir are unloaded, because in these files I dynamically create active-record models..
I already checked out the autoload_paths and watchable_dirs:
# engine.rb
module My
class Engine < Rails::Engine
engine_name 'my-engine'
initializer "my-engine.load_config" do |app|
app.config.autoload_paths += %W(#{Engine.root}/lib)
app.config.watchable_dirs["#{Engine.root}/lib"] = [:rb]
end
end
end
I'm not sure if I'm implementing these the right way, but they don't seem to solve my problems the way I'm using them.
I think you may need to require 'my/engine' before calling My::Engine.root, or change the order of your requires so that 'my/engine' is required prior to the file that makes a call to My::Engine.
I'm having a problem with a module name and the folder structure.
I have a model defined as
module API
module RESTv2
class User
end
end
end
The folder structure looks like
models/api/restv2/user.rb
When trying to access the class, I get an uninitialized constant error. However, if I change the module name to REST and the folder to /rest, I don't get the error.
I assume the problem has to do with the naming of the folder, and I've tried all different combos of /rest_v_2, /rest_v2, /restv_2, etc.
Any suggestions?
Rails uses the 'underscore' method on a module or class name to try and figure out what file to load when it comes across a constant it doesn't know yet. When you run your module through this method, it doesn't seem to give the most intuitive result:
"RESTv2".underscore
# => "res_tv2"
I'm not sure why underscore makes this choice, but I bet renaming your module dir to the above would fix your issue (though I think I'd prefer just renaming it to "RestV2 or RESTV2 so the directory name is sane).
You'll need to configure Rails to autoload in the subdirectories of the app/model directory. Put this in your config/application.rb:
config.autoload_paths += Dir["#{config.root}/app/models/**/"]
Then you should be able to autoload those files.
Also, your likely filename will have to be app/model/api/res_tv2/user.rb, as Rails uses String.underscore to determine the filename. I'd just call it API::V2::User to avoid headaches, unless you have more than one type of API.