Autoload path and STI inheritance - ruby-on-rails

I have two models User and Manager. I use STI to inherit Manager from User model.
app/models/user.rb
class User < ActiveRecord::Base
end
custom_lib/models/manager.rb
class Manager < User
end
I have added the custom models to load path as follows:
config/environment.rb
config.autoload_path += File.join(RAILS_ROOT, "custom_lib", "models")
Every thing works as expected in development mode. In the production mode I get the following error:
The single-table inheritance mechanism failed to locate the subclass: Manager
For some reason rails is not loading the inherited classes.
To work around this issue I explicitly require the classes in an initializer.
config/initializers/custom_models.rb
Dir[File.join(RAILS_ROOT, "custom_lib", "models", "*.rb")].each do |file_name|
require(File.join(File.dirname(file_name), File.basename(file_name, ".rb")))
end
I prefer to use autoload_path. I am wondering if anybody else has seen this behavior.
I am on Ruby 1.8.7, Rails 2.3.9, Ubuntu
Edit 1
I am aware that everything works if all the models are residing in app/models directory. In my application generated models are residing in custom location, hence this requirement.

Keep your manager.rb file in your app/models directory where your user.rb file is. That's where rails looks for models. There's no benefit to hiding the manager model in a different directory.
Once you do this, you can also get rid of mucking with the load path.

Related

Zeitwerk and Modules Nested in Classes

I'm having some trouble switching from the classic autoloader to Zeitwerk with a Rails app that's seen the light of day back in Rails 3 days – so there's some crust there.
Some model code has been extracted to modules and these modules are nested in the model class (which acts as the namespace):
# app/models/donation
class Donation < ApplicationRecord
(...)
end
# app/models/donation/download
class Donation
module Download
def csv
(...)
end
end
end
The modules are then used on the fly when needed:
donation = Donation.find(...)
donation.extend(Donation::Download).csv
Since the subdirs in app/models are not added by default, it's done explicitly in application.rb:
Dir[
"#{config.root}/app/models/*/"
].then do |paths|
config.autoload_paths += paths
config.eager_load_paths += paths
end
The eager_load_paths are required by Zeitwerk (as per the Rails guides), however, Zeitwerk doesn't seem to like this constellation:
% rails zeitwerk:check
Hold on, I am eager loading the application.
expected file app/models/donation/download.rb to define constant Download
Strange, because Download is defined there. Any idea what's going on here and how best to refactor things to work with Zeitwerk?
Thanks for your hints!
Hmmm, that should work out of the box, looks like a regular setup to me.
Since app/models is in the autoload paths, Donation and Donation::Download are going to be autoloaded just fine, no custom configuration is needed.
If they do not, the app has to be doing something funky. We could debug it.

Make Rails autoloading/reloading follow dynamic includes

Context
I want to add some admin specific code to all models via concerns that are automatically included. I'm using the naming convention MyModel => MyModelAdmin and they're in the standard Rails directory app/models/concerns/my_model_admin.rb. Then I can glob over all of them and do MyModel.include(MyModelAdmin).
Issue
Dynamic includes work fine, but when changing the concern in development Rails doesn't reload it properly. In fact the concern seems to get removed.
Reproduction
app/models/my_model.rb
class MyModel
end
app/models/concerns/my_model_admin.rb
module MyModelAdmin
extend ActiveSupport::Concern
def say
"moo"
end
end
config/initializers/.rb
MyModel.include(MyModelAdmin)
So far so good, MyModel.new.say == "moo".
But now change say to "baa" and you get NoMethodError undefined method 'say'.
Notes
I tried a number of things that didn't help:
require_dependency
Model.class_eval "include ModelAdministration"
config.autoload_paths
Using another explicit concern in ApplicationModel with an included hook that includes the specific concern in each model.
ActiveSupport.on_load only triggered on Base not each model.
Does this mean Rails can only autoload using static scope? I guess Rails sees the concern change, knows the model has it included, reloads the model but the static model definition doesn't have the include so the concern goes missing and stops being tracked. Is there a way to force Rails to track dynamically included modules?

Rails models in subfolders and relationships

I organized some of my rails models in folders which I am autoloading with
config.autoload_paths += Dir[Rails.root.join('app', 'models', '{**}')]
I can use all models directly(e.g Image.first.file_name) but when I try to access them through relationships, e.g. #housing.images.each do... with has_many: images I get the following error
Unable to autoload constant Housing::HousingImage, expected /path/app/models/housing/image.rb to define it
How do i get rails to use my models for the relationship methods?
I'm running ruby 2.2 and rails 4.2
Rails automatically loads models from subfolders but does expect them to have namespace.
/app/models/user.rb
class User
end
/app/models/something/user.rb
class Something::User
end
If you do not properly namespace your models in subfolders it will mess up Rails autoloader and cause errors like you see.
Remove this
config.autoload_paths += Dir[Rails.root.join('app', 'models', '{**}')]
And add the proper namespaces to your models and everything will work fine.
You can easily use namespaced models in your relationships like this:
class User
has_many :photos, class_name: 'Something::Photo'
end
user.photos (will be instances of Something::Photo)
If you do not want to use the namespacing but split up your models for other reason, you can do that at the top-level and use other folders next to models.
By default rails loads all the folders in apps, so you could just make a folder "models2" or whatever you want to call it next to "models".
This will not have any effect on the functionality of the rails class loading.
Given your example you could then do:
/app
/controllers
/models
for all your normal models
/housing
for your "housing" models
Like this you can directly access them at the top level namespace, no class_name settings or anything needed.

Rails include module in model trouble

I have module in /lib/models/scopes.rb
module Models
module Scopes
extend ActiveSupport::Concern
...
end
end
I'm trying to include it from model:
class User < ActiveRecord::Base
include Models::Scopes
end
And getting error:
NameError: uninitialized constant User::Models
How to solve this trouble? Maybe it`s wrong to keep this types of files in /lib?
Environment:
Rails v3.1
Ruby v1.9.3
Rails doesn't require files in the lib directory automatically, but you can add to the autoloaded paths in config/application.rb:
config.autoload_paths += %W(#{config.root}/lib)
Restart the server to pick up the new settings.
This will now load the file automatically when the module name is first used. In development mode, you might want to reload the module after every change in order to see the changes without restarting the server. To do that, add it as an eager load path instead:
config.eager_load_paths += %W(#{config.root}/lib)
The scope shouldn't be a problem as long as you don't have a Models class or module within User or anywhere else.
when you define your class, you're "opening" a new scope. So when you do Models::Scopes, ruby is looking for User::Models::Scopes. You can fix this by using ::Models::Scopes, the :: telling ruby to look in the global scope.
FYI: I'm not sure about the terms I used or even if my train of thought if correct; but the solution should be good anyway. I'd think Ruby would try for ::Models::Scope after failing to find User::Models::Scope, but it doesn't.. Maybe there is a User::Models scope defined somewhere? Anyway, as you can see, I'm not yet familiar with those. You might want to dig on the subject if that interests you

Loading ActiveRecord models in the proper order outside of a rails app

How do I load/require my activerecord models in the proper order outside of a rails app. I have many STI models and I am getting an uninitialized constant exception.
$:.push File.expand_path("../../../app/models", __FILE__)
require "active_record"
Dir["#{File.expand_path('../../../app/models', __FILE__)}/*.rb"].each do |path|
require "#{File.basename(path, '.rb')}"
end
I have a lot of jobs that I need to run with resque and I would rather not have my rails app load everytime and be deployed to all of the worker machines
EDIT: One point to clarify as well. There are two projects a Rails project and a project that is a rails engine which contains my models. I dont load the rails engine itself with my resque jobs I just use the snippet above in a separate class to load active record on the models. This always worked until I added some STI models which because of the naming caused the children to attempt to be loaded before the parent. The rails engine project loads just fine in the rails project no issues there this is just because I am trying to use active record outside of a rails project.
A very simple solution if you don't want to autoload is to require the base class in the children classes. Explicitly requiring dependencies is a good thing. :)
app/models/profile.rb
class Profile < ActiveRecord::Base
end
app/models/student.rb
require 'models/profile'
class Student < Profile
end
app/models/teacher.rb
require 'models/profile'
class Teacher < Profile
end
Models will be autoloaded on their first mention. So just name them somewhere in a proper order (say, in config/initializers/load_order.rb):
Product
LineItem
Cart
and check if it helps.
I fixed my issue. There may be a better way but this does it for me.
basedir = File.expand_path('../../../app/models', __FILE__)
Dir["#{basedir}/*.rb"].each do |path|
name = "#{File.basename(path, '.rb')}"
autoload name.classify.to_sym, "#{basedir}/#{name}"
end

Resources