I have several small classes that are in a single file in /app/models, similar to:
# /app/models/little_class.rb
class LittleClass; ...do stuff; end;
class AnotherLittleClass; ...do stuff; end;
Rails only seems geared to autoload classes in files reflecting the class name. So referencing AnotherLittleClass outside of the file raises "unitialized constant" errors as below until LittleClass is referenced:
irb(main):001:0> AnotherLittleClass
NameError: uninitialized constant AnotherLittleClass
irb(main):02:0> LittleClass
=> LittleClass
irb(main):03:0> AnotherLittleClass
=> LittleClass2
It would be a pain and messy to split them into individual files. Is there a way to autoload these classes, so referencing AnotherLittleClass without LittleClass doesnt raise an error?
You could put them into a module and use them within this namespace SomeLittleClasses::LittleClass.do_something
# /app/models/some_little_classes.rb
module SomeLittleClasses
class LittleClass
def self.do_something
"Hello World!"
end
end
class AnotherLittleClass
def self.do_something
"Hello World!"
end
end
end
Try this trick:
1.9.2p312 :001 > AnotherLittleClass.new
# => NameError: uninitialized constant AnotherLittleClass
1.9.2p312 :002 > autoload :AnotherLittleClass, File.dirname(__FILE__) + "/app/models/little_class.rb"
# => nil
1.9.2p312 :003 > AnotherLittleClass.new
# => #<AnotherLittleClass:0xa687d24>
These are your choices, as I see it:
split your file up into one file per class, put them in a dir named according to the rails convention (SomeClass => some_class.rb) and in a startup file (say, create a file in config/initializers), call:
autoload_paths Rails.application.config.root + "/path/to/lib"
add something like this to a startup file:
%W[
Class1 Class2
Class3 Class4 Class4
].map(&:to_sym).each dp |klass|
autoload klass,Rails.application.config.root + "/path/to/lib/file"
end
This of course will have to be updated each time a new class is added to the file.
Move all of the classes into a module/class namespace and call autoload to add it as above
just load the whole file up-front in a startup file with require. Ask yourself: does the extra effort warrant delaying the load of this file?
The following file app/models/statistic.rb is given :
class Statistic
# some model code here
end
class UsersStatistic < Statistic; end
class CommentsStatistic < Statistic; end
class ConnectionsStatistic < Statistic; end
Create a file config/initializers/autoload_classes.rb and add the following code:
# Autoloading subclasses that are in the same file
# This is the normal way to load single classes
#
# autoload :UsersStatistic, 'statistic'
# autoload :CommentsStatistic, 'statistic'
# autoload :ConnectionsStatistic, 'statistic'
# This is the most dynamic way for production and development environment.
Statistic.subclasses.each do |klass|
autoload klass.to_s.to_sym, 'statistic'
end
# This does the same but loads all subclasses automatically.
# Useful only in production environment because every file-change
# needs a restart of the rails server.
#
# Statistic.subclasses
Related
I have some code i've inherited and am in the process of upgrading it to Rails 3.1. I'm suuuuper close to done but I got a bug.
In Rails Console I run User.first and I get this error
undefined local variable or method `acts_as_userstamp' for #<Class:0x000000046bef50>
Now acts_as_userstamp is a method located on line two inside my User model
class User < ActiveRecord::Base
#TODO /lib is not loading??? or is it??? why this method not work in browser?
acts_as_userstamp
And is defined in a file called app/lib/model_modifications.rb.
Now I recently discovered that my app/lib folder was not being autoloaded in my application.rb file and I think that's been fixed...or has it? Is this file correct? Or no?
require File.expand_path('../boot', __FILE__)
require 'rails/all'
# evil outdated soap middleware, TODO: kill it with fire
# Does this have to be loaded BEFORE the first line???
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', "vendor", "soap4r"))
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', "vendor", "plugins", "soap4r-middleware", "lib"))
# evil outdated soap middleware, TODO: kill it with fire
require 'soap4r-middleware'
require File.join(File.dirname(__FILE__), '..', 'app', 'lib', 'soap.rb')
if defined?(Bundler)
# If you precompile assets before deploying to production, use this line
Bundler.require *Rails.groups(:assets => %w(development test))
# If you want your assets lazily compiled in production, use this line
# Bundler.require(:default, :assets, Rails.env)
end
module MyappDev
class Application < Rails::Application
# startup the lib directory goodies <-- IS THIS CORRECT???
# config.autoload_paths << "#{Rails.root}/lib"
# config.autoload_paths += %W( lib/ )
config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir["#{config.root}/lib/**/"]
# Configure the default encoding used in templates for Ruby 1.9.
config.encoding = "utf-8"
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters += [:password]
config.middleware.use MyAPIMiddleware
end
end
I'm trying to debug this file as I post this now. Here is a peak at it's internal structure...(i've just included the overall structure for the sake of brevity)
app/lib/model_modificatons.rb
class Bignum
...
end
class Fixnum
...
end
class ProcessorDaemon
...
end
module ActiveRecord
module UserMonitor
...
end
module MyLogger
...
end
end
class Object
...
end
class Struct
...
end
class String
...
end
class Fixnum
...
end
class OpenStruct
...
end
class ActiveRecord::Base
def self.visible_columns
...
end
...
def self.acts_as_userstamp
logger.info "HI fonso - acts_as_userstamp is called"
include ActiveRecord::UserMonitor
end
...
protected
def self.range_math(*ranges)
...
end
end
class Array
...
end
class DB
...
end
If you can spot a problem with the overall structure or anywhere else please let me know.
So why is this method not found? I'm trying to debug it as I'm posting this and I'm getting nothing.
I suspect the file app/lib/model_modifications.rb is not being loading. That nothing in the /lib directory is being loaded..but how do I confirm this?
Thank you for reading this far, I hope I've not rambled on too much.
autoload_path configuration does not load all the given files on the boot but defines folders where rails will be searching for defined constants.
When your application is loaded, most of the constants in your application are not there. Rails have a "clever" way of delaying loading the files by using a constant_missing method on Module. Basically, when Ruby encounters a constant in the code and fails to resolve it, it executes said method. THe sntandard implementation of this method is to raise UndefinedConstant exception, but rails overrides it to search all of its autoload_paths for a file with a name matching the missing constant, require it and then check again if the missing constant is now present.
So, in your code everything works as expected and you need to load this extension file manually. If you want to have some code that executes on the application boot, put your file within config/initializers folder.
Aside: Try avoiding monkey patching whenever possible. It might be looking clever, but adding more methods to already overpopulated classes will not make them easier to use.
I have a app/extensions folder where my custom exceptions reside and where I extend some of the Ruby/Rails classes. Currently there are two files: exceptions.rb and float.rb.
The folder is specified in the ActiveSupport::Dependencies.autoload_paths:
/Users/mityakoval/rails/efo/app/extensions/**
/Users/mityakoval/rails/efo/app/assets
/Users/mityakoval/rails/efo/app/channels
/Users/mityakoval/rails/efo/app/controllers
/Users/mityakoval/rails/efo/app/controllers/concerns
/Users/mityakoval/rails/efo/app/extensions
/Users/mityakoval/rails/efo/app/helpers
/Users/mityakoval/rails/efo/app/jobs
/Users/mityakoval/rails/efo/app/mailers
/Users/mityakoval/rails/efo/app/models
/Users/mityakoval/rails/efo/app/models/concerns
/Users/mityakoval/rails/efo/app/template.xlsx
/Users/mityakoval/.rvm/gems/ruby-2.4.1#web_app/gems/font-awesome-rails-4.7.0.2/app/assets
/Users/mityakoval/.rvm/gems/ruby-2.4.1#web_app/gems/font-awesome-rails-4.7.0.2/app/helpers
/Users/mityakoval/rails/efo/test/mailers/previews
The reason for it to be listed there twice is that it should be automatically loaded since it was placed under app directory and I have also manually added it to the autoload_paths in application.rb:
config.autoload_paths << File.join(Rails.root, 'app', 'extensions/**')
The strange thing is that my exceptions.rb is successfully loaded at all times, but the float.rb isn't unless eager loading is enabled.
Answers to this question say that it might be related to Spring (which I tend to believe), so I've added the folder to spring.rb:
%w(
.ruby-version
.rbenv-vars
tmp/restart.txt
tmp/caching-dev.txt
config/application.yml
app/extensions
).each { |path| Spring.watch(path) }
I've restarted Spring and the Rails server multiple times and nothing worked. Does anyone have any suggestions?
Ruby version: 2.4.1
Rails version: 5.1.5
EDIT
/Users/mityakoval/rails/efo/app/extensions/float.rb:
class Float
def comma_sep
self.to_s.gsub('.', ',')
end
end
rails console:
irb> num = 29.1
irb> num.comma_sep
NoMethodError: undefined method `comma_sep' for 29.1:Float
from (irb):2
A better way to monkeypatch a core class is by creating a module and including it in the class to be patched in an initializer:
# /lib/core_extensions/comma_seperated.rb
module CoreExtensions
module CommaSeperated
def comma_sep
self.to_s.gsub('.', ',')
end
end
end
# /app/initializers/core_extensions.rb
require Rails.root.join('lib', 'core_extensions', 'comma_seperated')
# or to require all files in dir:
Dir.glob(Rails.root.join('lib', 'core_extensions', '*.rb')).each do |f|
require f
end
Float.include CoreExtensions::CommaSeperated
Note that here we are not using the Rails autoloader at all and explicitly requiring the patch. Also note that we are placing the files in /lib not /app. Any files that are not application specific should be placed /lib.
Placing the monkey-patch in a module lets you test the code by including it in an arbitrary class.
class DummyFloat
include CoreExtensions::CommaSeperated
def initialize(value)
#value = value
end
def to_s
#value.to_s
end
end
RSpec.describe CoreExtensions::CommaSeperated do
subject { DummyFloat.new(1.01) }
it "produces a comma seperated string" do
expect(subject.comma_sep).to eq "1,01"
end
end
This also provides a much better stacktrace and makes it much easier to turn the monkey patch off and on.
But in this case I would argue that you don't need it in the first place - Rails has plenty of helpers to humanize and localize numbers in ActionView::Helpers::NumberHelper. NumberHelper also correctly provides helper methods instead of monkeypatching a core Ruby class which is generally best avoided.
See:
3 Ways to Monkey-Patch Without Making a Mess
I am attempting to create a permissions structure for users in my application. I created a permissions.rb file in the lib/ directory in my rails application.
When I try to include Permissions in my user model I am getting this error.
This is what I have in the user model.
class User < ApplicationRecord
include Permissions
...
end
How can I include this file and its methods without getting this error?
To include modules under lib folder you will need to add your lib folder in autoload_path in your application.rb
config.autoload_paths += %W( #{config.root}/lib/)
add this line in your application.rb.
I think it would be good if you use autoload file when application start then it would like to on the application.rb
config.autoload_paths << Rails.root.join('lib')
Or you can use user.rb
class User < ApplicationRecord
load File.join(Rails.root, 'lib', 'permissions.rb')
end
The module would look like this, always make sure the naming conventions is right like if run module name on the console with underscore then he would give your file name, see the below if your module name is Permissions then
Loading development environment (Rails 5.1.4)
2.3.4 :001 > "Permissions".underscore
=> "permissions"
your file name is permissions.rb
#=> permissions.rb
module Permissions
...
def self.method #=> method name instead of the method
#=> code staff here
end
or
def method #=> method name instead of the method
#=> code staff here
end
...
end
Hope it helps
I'm running into quite a few errors around how to require files property. Hoping for some insight.
There are files as so:
app/models
model.rb
app/workers
parent_worker.rb
app/workers/directory_1
directory_worker.rb
foo_worker.rb
bar_worker.rb
class DirectoryWorker < ParentWorker
end
class FooWorker < DirectoryWorker
def method_called_by_model
end
end
When I call the method, method_called_by_model I get the following error:
NameError: uninitialized constant Model::FooWorker
I've added the following to application.rb, didn't add app/workers since it should be loaded automatically according to the documentation.
config.autoload_paths << "#{Rails.root}/app/workers/directory_1"
When I require_relative the worker files in the model I get the following error referring to the inherited class being unknown:
NameError: uninitialized constant DirectoryWorker
from project/app/workers/directory_1/FooWorker.rb:2:in `<top (required)>'
Any have any ideas what I can do?
You need to namespace those workers since they are inside a directory.
First remove the autoload call you added.
Here's how the files should be named and what they should look like inside.
# app/workers/parent_worker.rb
class ParentWorker
end
# app/workers/directory_1/directory_worker.rb
class Directory1::DirectoryWorker < ParentWorker
end
# app/workers/directory_1/foo_worker.rb
class Directory1::FooWorker < Directory1::DirectoryWorker
def method_called_by_model
end
end
# app/workers/directory_1/bar_worker.rb
class Directory1::BarWorker < Directory1::DirectoryWorker
end
I'm currently migrating an application in rails v2 to v3
In my lib/ i've some modules in subdirectories, for example,
i've the lib/search/host_search.rb
with a
module HostSearch
def do_search(args)
#...
end
end
then I need to use it in a controller named Discovery::HostController < ApplicationController :
def search_results
output = HostSearch.do_search(:search_string => #search_string,
:page => params[:page],
:user => #current_user)
#...
end
But have I get:
uninitialized constant Discovery::HostController::HostSearch
..I tried to put this lines in application.rb but it doesn't work..
config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir["#{config.root}/lib/**/"]
I found that moving the module to the lib folder or explicitly including the folder to load worked, in your case
config.autoload_paths += %W(#{config.root}/lib/search)
I think there's something syntaxical that we are missing. Another thing is that if you don't want to mess with the application.rb file, require the file, which if I remember, takes the file path from the lib folder eg: search/host_search <- check that.
I think if you put the HostSearch module under a search subdir, (ie in lib/search/host_search.rb), then you need to namespace it:
module Search
module HostSearch
end
end
If you don't want to namespace it, you can should move the file into the lib root: lib/host_search.rb.
See also: https://stackoverflow.com/a/19650564/514483