I'm working on a rails app that is starting to have what seems (to me) to be a lot of models. There are 15 right now, but I'm thinking about adding 3-4 more to serve as "tag" like models (I need more functionality than Acts As Taggable offers).
So, the reason this bugs me a bit, is that 7 of the 15 models belong to a common parent. Several are belong_to, and a few are has_and_belongs_to_many. All the new models I'm contemplating would belong_to the same parent as well.
So, what I'm wondering is, what is the best "Railsy" way of organizing this kind of situation?
Instead of app/models being super crowded with 6 "first-class" models and 10+ children of one of these, should/can I start using sub folders in my app folder? ie: app/models/parent/child.rb?
I know this is kind of an open-ended question, but I would really appreciate advice as to the best way to handle a rails project with a proliferation of models.
Thanks!
You can do this, I always do :)
Just beware of something: if you create a folder which has the name of one your models, it will fail. Actually, Rails will think you want to extend it.
So in your model folder, prepend the name of your class with whatever fancy you want.
Example: if you want to put models related to users, put them in models/user_related/
You'll have to add this to your application.rb file:
config.autoload_paths += Dir["#{Rails.root.to_s}/app/models/*"].find_all { |f| File.stat(f).directory? }
This will autoload all folders included in modelsdirectory.
I think apneadiving's answer is good approach
Based on research with activesupport 3.0.11, there are some rules to follow when choosing a directory name however:
The name must never match a constant in your system, or LoadError's could occur
The name must be able to be converted to a valid constant name, or NameError's will occur.
Explanation of problem #1
Apneadiving's example of a directory name app/models/user_related works as long as a
constant UserRelated is never used in your code. Otherwise a LoadError could
potentially happen.
For example, assume there was a model called UserProfile and the first time
rails sees the constant is in the UserRelated module. Rails will first try to
load a UserRelated\:\:UserProfile constant and failing that a UserProfile
constant.
If the user_profile file is at app/models/user_related/user_profile.rb, this
matches the underscored path of UserRelated\:\:UserProfile and the file would
be loaded expecting to define the UserRelated::UserProfile constant. This
would raise the following error because it really defines the UserProfile
constant.
Expected app/models/user_related/user_profile.rb to define UserRelated::UserProfile (LoadError)
This happens in the active support dependency code.
Explanation of problem #2
Another caveat is the directory name must be able turned into a valid ruby
constant name (although to follow #1 the constant should be undefined). For
example, if the directory name were app/models/user.related this would result
in the following error inside the active_support dependency code:
wrong constant name User.related (NameError)
Related
This issue relates to a need to set a Rails config variable as the application boots, and the value of that variable needs to come from data in the database (which are then modified). So, I have an initializer with something like this:
require "#{Rails.root}/lib/modules/facet_altering.rb"
include FacetAltering
Rails.application.config.reject_subjects = FacetAltering.reject
The reject method is potentially slow and calls the Subject model (which includes some concerns).
If I try to require subject.rb, application_rb and the relevant concerns from app/models then I progress a bit further, but eventually get stuck on uninitialized constant Subject::MySpecialConcern.
There might be some better way to set the reject_subjects value; I'd prefer not to run FacetAltering.reject each time the value of reject_subjects is used, though this might be an easy 'fix' if no other solution arises (at the cost of slowing things down). Or, is there another way to access these classes as the application boots?
Edit: Following on from the comment below, this is in config/application.rb:
%W[#{Rails.root}/lib/modules #{Rails.root}/test/mailers/previews].each do |path|
config.eager_load_paths << path
end
This post offered a useful clue:
Rails Model no longer available in initializer after upgrade to Rails 7.0
So, putting my code in config/application.rb as follows did the trick:
config.after_initialize do
Rails.application.config.reject_subjects = FacetAltering.reject
end
Now to find the answer to RuntimeError: Foreign key violations found in your fixture data. Ensure you aren't referring to labels that don't exist on associations. and I might be able to complete this Rails 6 -> 7 upgrade!
When I try to upgrade to Mongoid ~7, it throws this error:
Unable to autoload constant User::AuditSession, expected ./testapp/app/models/user/audit_session.rb to define it
I indeed have this file, but it defines AuditSession, not User::AuditSession. I put all the user related models into this directory which worked in Mongoid ~6, but it seems to no longer work in Mongoid ~7.
Is there any workaround I can do to get the old behaviour? - My goal is to avoid moving all my model files or editing all my class names.
Per the Rails conventions, if the model is defined in user/audit_session.rb, the class name should be User::AuditSession.
A workaround is to preload the class so that it is loaded by the time it is needed. Depending on where the class is used from, this could be as simple as placing
require 'user/audit_session'
in the file that uses AuditSession.
Alternatively you could define a top-level audit_session.rb which includes the other file (i.e. just has the above include in it).
I am using namespacing in my Rails 5 app to try to keep resources organised.
I've been generating resources using the command line by adding the namespace folder to the generate command.
This makes a folder in the models folder for the main folder which the namespaced files are saved in.
I've since been reading posts from others that suggest namespacing models is not a good idea.
An example of what I currently have is:
class Stance::Assessment < ApplicationRecord
It seems to work alright so far.
What is the problem with namespacing models?
If it is a problem, does that mean I can't organise my models into folder groups, or does it mean that the model class doesnt need to be named wiht the "Stance::"?
There is a certain cost of complexity involved with "namespacing" your models. Ruby does not actually have true namespaces. Rather it has has modules which provide encapsulation.
Rails and ActiveRecord was designed around placing your application code in the Main object (the global object). While this might seem like a bad practice it is very simple and works well with the convention over configuration approach. It also allows a much simpler autoloading scheme and avoids the need to nest every single file in an additional folder.
Namespacing does have great organizational merits though and lets you avoid collisions. But there are a few minor aches in the backside:
table prefixes, having the generated table names like my_app_projects_tasks is really inconvenient when you need to write a custom join.
You need to override ActiveModel::Naming so that it does not look for paths like my_app_projects_tasks_path when using the polymorphic route helpers.
You need to explicitly set the class_name option when creating associations or override how ActiveRecord resolves constant names.
you can prefix your models, so instead of Stance::Assessment you would have StanceAssessment. not as clean as a namespace but it's pretty close to it
I've found that if I create a module, I have to create a "wrapper class" for it to work in certain circumstances (actually Resque-pool in my case, but I think it's relevant elsewhere too, even though it worked in regular Unicorn without needing it). By wrapper class, I mean if I create:
models/
posts/
selfie.rb
I will get a "Expected selfie.rb to define Posts::Selfie" unless I also create models/posts.rb containing "require posts/selfie.rb". (Based on https://stackoverflow.com/a/11001022/18706)
My question is, should this be done for every module under models, controllers, etc? And is there a name for this kind of class, or any reference info about it?
When you create a namespaced model with rails scaffolding, you get two files. For example, this scaffold:
rails generate model Staff::Location name:string address:string
Generates these files:
/app/models/staff.rb
module Staff
def self.table_name_prefix
"staff_"
end
...
/app/models/staff/location.rb
class Staff::Location < ActiveRecord::Base
...
I am running into problems when in development mode where rails unloads the Staff module and never reloads it. This causes several annoying bugs such as Location not able to access it's table due to the missing table_name_prefix. The problem seems to crop up when I don't access the models directly, such as through a polymorphic relationship.
I can't seem to get the module loaded on a consistent basis. Is this the best practice way to do namespaced models? If it is, what am I missing?
Although I wasn't able to reproduce the problem in Rails 3.2.2, I've run into something like this before. The generic way to hack around this problem in development mode is through an ActionDispatch callback. Add this to config/environments/development.rb:
MyApp::Application.configure do
ActionDispatch::Callbacks.before do
load Rails.root.join('app', 'models', 'staff.rb')
end
end
Anything you do in that block will be executed before each request, so make sure you're only doing it in development mode.† Otherwise, you're going to suffer a performance hit in production.
I logged a message inside the staff.rb file and within the Staff module itself, and both messages appeared in the log for each request.
† I tried using the to_prepare callback, since that seems to be the documented way to execute code before each request only when cache_classes is false. But that only seemed to execute after restarting the application. There's at least one other open Stack Overflow question regarding this, although he's using a slightly different syntax than I used. If you can get to_prepare to work, I'd suggest that instead of before.
About a year later, I have finally found the answer to this question. This answer is specifically for rails 3.1. I am not sure if it is a problem in rails 3.2.
The problem occurs when setting up a model. If scaffolding is used, no helper file is generated. This would normally be in /app/helpers/staff/location_helper.rb. There are two ways to setup this file:
module Staff::LocationHelper
...
end
module Staff
module LocationHelper
...
end
end
In rails 3.1, specifically for helpers, you must use the first solution. You do not have to use it for other modules that use a namespace in other parts of the rails project. In fact, some structures in ruby require the second solution.
If you use the second solution when declaring a helper, in certain cases the Staff module in the helper file will override the module in /app/models/staff.rb. It will silently replace it with the empty Staff module in the file. This does not happen 100% of the time because helpers are not always loaded.