Over my time of using Rails I've struggled with the placement of code that does not explicitly belong to a controller, model or view. My Java background probably had a lot to do with my selection of the lib dir for all 'extra' code when I first started.
Then I learned about plugins and loved the auto require feature. No more requires in my environment.rb. Sweet, but I didn't feel everything qualified as a plugin, or at least to my definition of a plugin. So now I have some code in the plugin directory and some code in the lib dir without any rule on where to look for a particular type of code. Things are starting to get messy.
I wasn't stopping there. I decided to use helpers for their intent to house "view only" code. This cleaned up my lib directory some but now there are three places to look. You would think that would be enough. Oh no, there's more!
Rails 2.0 introduced config/initializers and I thought this was great, the answer to my dilemma of lib vs plugin. Code in the initializers dir is loaded automatically like the plugin and it wasn't "way out there" in vendor/plugins so it felt more like a part of the application. Initializers for everything!
...
Those of you who have been coding for a while realize that this is a bad idea. Nothing is for everything. So before I went on my mindless bulk move, I decided I needed to think about this some. This is what I have decided.
View code goes in the helper. That was easy.
Plugins should be reserved for generic functionality such as attachment_fu. For every project you write that needs file uploads you can use this same plugin. You don't have to alter the code on a per project basis. It's good to go. So, if you have multiple applications and you find yourself copying code over and over without modifications, it should probably be a plugin.
In one sense, I consider initializers to be plugins that may need some degree of customization. Project constants are something you use all the time, but they need to be specific for the project. Even though you may reuse a lot of the same constants, odds are some will be specific to the project. Don't separate out the ones that never change to a plugin. This leaves you with two places to look and will cause you headaches.
Another use for initializers is code that is used throughout your application, but it's not generic enough to be a plugin. A module that maintains your session is a perfect example.
This leaves the lib directory. This should be reserved for specialized functionality, something only a couple of controllers or models may use and would be "required" only where necessary. Specialized math or string methods come to mind.
Another, more common use of the lib directory would be to house modules that eliminate repetitive code. For example, say I have multiple models using the same find method like find_recent_posts_by_author. This could be applied to books, pages, comments, etc... I don't want to rewrite this find_recent_posts_by_author method in each of those models. So, I write it once in a module and include it in only the models that need that functionality.
Well, that's all for now, I'm still refining things. If you disagree or have another rule, I'd like to hear it.
thanks.
Thursday, March 20, 2008
Subscribe to:
Post Comments (Atom)

2 comments:
Why limit yourself to config/initializers, lib, or vendor plugins? I generally add new folders under the app directory (eg. app/presenters, app/managers, app/mailers, etc). If you add these paths to rail's load path, you get all the auto-loading love and you get very well organized code.
Good point. I didn't intend for this to imply these were the only places to put your code. I should have covered this capability and will probably update the post.
I do think mailers should have their own directory. I also think it would be cleaner to have an observers directory. Essentially, only put models in the models directory.
I'm struggling a bit with the presenters directory due to the existence of the helpers. I can see the argument for both usages.
I'm not sure what you have in your managers directory, but from your other examples I'm sure it make sense. :)
Post a Comment