Rails models in subfolders and relationships - ruby-on-rails

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.

Related

Autoloading Rails models that don't follow the default file name structure

THE SHORT VERSION: How do I tell Rails to autoload an ActiveRecord class called ClassName that isn't located in the default location (app/models/class_name.rb). E.g. what if it's in app/models/subdirectory/class_name.rb and I don't want to rename the class to Subdirectory::ClassName?
THE LONGER VERSION:
I know that, by default in Rails, my class names have to follow a specific structure for Rails to be able to autoload them.
E.g. If my class is called Person, if I put it in app/models/person.rb, Rails can load it fine, but if I put it in e.g. app/models/person_class.rb, it can't. If I namespace it, e.g. Humanity::Person, I need to put it in the right folder app/model/humanity/person.
(Plus I can put classes in lib but I'll leave aside that detail for now)
So far so good. But what happens when I have a ton of ActiveRecord classes clogging up my app/models folder and I want to logically organise them into directory, but don't want to rename or namespace the actual classes? How can I tell Rails to autoload this classes?
Or is there a good reason why I shouldn't do this?
Add this line to config/application.rb
config.autoload_paths += Dir[Rails.root.join('app', 'models', '{**/}')]
Then you can use your model with their name as it is without name-spacing.

Rails 4: organize rails models in sub path without namespacing models?

Would it be possible to have something like this?
app/models/
app/models/users/user.rb
app/models/users/education.rb
The goal is to organize the /app/models folder better, but without having to namespace the models.
An unanswered question for Rails 3 is here:
Rails 3.2.9 and models in subfolders.
Specifying table_name with namespaces seems to work (see Rails 4 model subfolder), but I want to do this without a namespace.
By default, Rails doesn't add subfolders of the models directory to the autoload path. Which is why it can only find namespaced models -- the namespace illuminates the subdirectory to look in.
To add all subfolders of app/models to the autoload path, add the following to config/application.rb:
config.autoload_paths += Dir[Rails.root.join("app", "models", "{*/}")]
Or, if you have a more complex app/models directory, the above method of globing together all subfolders of app/models may not work properly. In which case, you can get around this by being a little more explicit and only adding the subfolders that you specify:
config.autoload_paths += Rails.root.join("app", "models", "<my_subfolder_name1>")
config.autoload_paths += Rails.root.join("app", "models", "<my_subfolder_name2>")
UPDATE for Rails 4.1+
As of Rails 4.1, the app generator doesn't include config.autoload_paths by default. So, note that the above really does belong in config/application.rb.
UPDATE
Fixed autoload path examples in the above code to use {*/} instead of {**}. Be sure to read muichkine's comment for details on this.

Organizing models/controllers and classes in rails in subfolders

I am working in a Rails project in which i have used the below names for model/controller and class files
/app/models/friends/friend.rb
/app/controllers/friends/friends_controller.rb
/lib/classes/friends/friend.rb
I tried to add all the models, controllers and class files in autoload path in application.rb.
But i am facing issues since the class names are same.
How should i handle this? and organize files in such a way that files are organized with name spaces.
Thanks,
Balan
A much better approach would be to use Rails Engines & divide your app in isolated modules.
rails plugin new friends --full --mountable --dummy-path spec/dummy
the above command will generate a full mountable engine with isolated namespace, meaning that all the controllers and models from this engine will be isolated within the namespace of the engine. For instance, the Post model later will be called Friends::Post, and not simply Post. to mount this app inside your main rails app, you need do two things:
Add entry to Gemfile
gem 'friends', path: "/path/to/friends/engine"
And then add route to config/routes.rb
mount Friends::Engine, at: "/friends"
For more information on this approch, checkout:
Rails Guide to Engines
Taming Rails Apps with Engines
RailsCast #277 Mountable Engines
The class names are same but path's are different, and you don't need to add classes to autoload except /lib/classes/friends/friend.rb
Did you tried the following way:
# app/models/friends/friend.rb
class Friends::Friends
#...
end
# Friends::Friends.new
# app/controllers/friends/friends_controller.rb
class Friends::FriendsController < ApplicationController
#...
end
# lib/classes/friends/friend.rb
module Classes
module Friends
class Friends
#...
end
end
end
# Classes::Friends::Friends.new
To add lib files to autoload add following to your applicaion.rb
config.autoload_paths += %W(#{config.root}/lib)

Is it a bad idea do divide the models into directories?

I have a over 100 models in my rails application, and just for organization, I'm dividing them into folders, all still under the main model folder, just to make it simpler to navigate on the project and see files that are related.
Is this a bad idea? What is the rails way to do this?
No, it's not a bad idea. Many people do it and I couldn't live without it in large applications.
There are two ways of doing it:
The first is to just move your models. You will, however, have to tell Rails to load the wayward models (as it won't know where they are). Something like this should do the trick:
# In config/application.rb
module YourApp
class Application < Rails::Application
# Other config options
config.autoload_paths << Dir["#{Rails.root}/app/models/*"]
end
end
The first way is easy, but is not really the best way. The second way involves namespacing your models with groups they're in. This means that instead of having User and UserGroup and UserPermissions, you have User, User::Group and User::Permission.
To use this, generate a model like this: rails generate model User::Group. Rails will automatically create all of the folders for you. An added benefit is that with this approach, you won't have to spell out the full model name for associations within a namespace:
class User < ActiveRecord::Base
belongs_to :group # Rails will detect User::Group as it's in the same namespace
end
class User::Group < ActiveRecord::Base
has_many :users
end
You can specify however many levels of namespacing as you want, so User::Group::Permission would be possible.
For 100 models, it's practically a requirement. 100 models is noisy in one directory.
Try this to get an idea of the Rails Way (tm)
rails new MultiDirectoryExample
cd MultiDirectoryExample
rails generate scaffold User::Photo description:string
Watch the script output and view the generated files.

Autoload path and STI inheritance

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.

Resources