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.
Related
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.
This might be silly, but I'm including a gem which represents all the models I need for my project. I want to add a method, to_custom_string to one of those models, Person.
I was attempting to do this via (following this example):
config/initializers/extensions/person.rb
Which contained something like:
class Person < ActiveRecord::Base
def to_custom_string
address.street.to_s
end
end
The Person class in the gem has a has_one :address association.
The problem I was experiencing was that this patch seems to override the Person class from the gem, instead of patching it. What's crazy is that this override behavior was only experienced via rake (all of the associations declared in the Person class from the gem are lost).
My rake task was something like:
namespace :convert
task :all_persons => :environment do
Person.where(:param => value).includes(:address).find_in_batches(:batch_size => 2000) do |persons|
persons.each do |person|
puts person.to_custom_string
end
end
end
end
calling bundle exec rake convert:all_persons gave me:
Association named 'address' was not found; perhaps you misspelled it?
But copying and pasting the code in the rake task into rails console worked fine.
My current solution is to copy the code for Person from the gem into my app/models directory, and have my to_custom_string method there, which I know is wrong.
Can someone please explain why a) irb preserved my Person associations, but rake did not, and b) how I can get rake to cooperate?
Thank you!
First of all instead of reopening the class I would create a Module and include it into the Person. So it would look like that
module CustomString
def to_custom_string
address.street.to_s
end
end
Person.send(:include, CustomString)
Also it seems like the Person model is not yet available at the point of running the initializer. You may want to put this in your application.rb if still doesn't work.
config.railties_order = [ModelEngine::Engine, :main_app, :all]
I guess the reason why it works in irb and not in rake is because they look up classes differently. Irb (which I believe you run by running rails console) loads all the classes at once therefore it loads the classes from engine, then it runs the initializer where you have the classes from engine already defined. I guess (though I'm not sure) Rake in development mode uses lazy loading of constants. So it doesn't load all the classes at the very beginning and only when it finds a constants that is undefined. Then it starts looking for a file that may define that constant. Since you put some Person in initializer it doesn't look up the engine's model at all cause at the point it sees Person it has the Person definition already. That's why the inclusion of module instead of reopening the class may help -> it enforces that it will lookup the Person constant from engine.
I think it will work as long as you just reopen the class, without inheriting from ActiveRecord::Base again. So, like this:
class Person
def custom_string
address.to_street.to_s
end
end
Edit:
You might also need to add a line like this before you reopen the class:
require_dependency ModelEngine::Engine.root.join('app', 'models', 'person').to_s
where ModelEngine::Engine is just the class for the engine that contains all your models.
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
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.
I have written a simple blog plugin (it's actually a rails engine). It is designed to be installed into an app that already has a user model set up.
In order to save me from having to open up my User model and manually inserting "has_many :posts", I'd like to write a rake task that automatically does this for me.
If I were to package my engine as a generator inside a gem, then the following would probably work:
def manifest
record do |m|
m.insert_into "app/models/user.rb", 'has_many :posts'
end
end
Can this kind of thing be done with from a rake task? I've look around and I can't find an answer... thanks in advance
Can you include a model file in your plugin that would open up the User class and add the "has_many :posts"?
class User < ActiveRecord::Base
has_many :posts
end
I think that would work because you can open Ruby classes at any time and from any file; so no matter if the project using your plugin has a user.rb file in his model folder, you file will also be loaded and the has_many will be added to the User class at runtime.
Hope it helps.
You can definitely access your model through a rake task. You have to be sure to pass it your environment though so that it knows about your models. For example,
desc"This will insert the Posts"
task(:insertPosts => :environment) do
#your code here
end
Is this a task where actually modifying the source is appropriate? Have you considered including a module? Please give further details of what you're trying to achieve for correct guidance.