I was really shocked yesterday to discover that there’s literally no baked-in way to declare an ActiveRecord attribute as private or protected. Sometimes I don’t want publicly-accessible ActiveRecord attribute methods. Sorry, but not every field in my database should have a corresponding public mutator.
Consider an IP address field on a User model, that’s used to record the last IP the user logged in from. This is something that, although clients outside of the model should be able to access the value, they certainly shouldn’t be able to set it. Only the model itself should be able to update that field in the database. It’s private!
As another example, consider the case where a database field is just a cache of calculated relationship data. That cached value should never be set directly via a public instance method. One example is an average_rating as calculated from a bunch of user ratings on an article. Why would you want to expose a mechanism to set this directly? It should only be set through some sort of recalculate_average_rating public method that lives on your article model and fires as an after_save filter on a new rating object.
It turns out that counter_cache itself suffers from this same problem. Ticket #6896 in the Rails Trac points out this exact issue and proposes a solution in the form of attr_readonly. This is a solid solution, and would go a long ways towards enforcing proper encapsulation in AR.
I ranted about this yesterday in freenode and got yelled at a little bit. Yes, I was probably being a bit obnoxious, but I also don’t want the importance of this to be overlooked. The bottom line is that, like it or not, people are largely stupid. Programmers are not excluded from this. Even Ruby programmers, who admittedly are more self-conscious than most. If you give someone the opportunity to do something stupid, be it at the user level in your app, within your API, or right within the rest of your code base, they’ll do it. If we’re writing for an audience > 1, we should be writing code like we design user interfaces — the public methods available on our classes shouldn’t include things that give our audience permission to twiddle with the guts of that thing. This is a cornerstone of encapsulation and good OO design.
So how do you feel about all this? Is it not A Big Deal to you? To me, it’s a significant limiting factor when it comes to organizing large code bases amongst multiple programmers. If you’re the only guy working on a hobby app, then fine, I guess you can police yourself. Go somewhere and do your thing. But if you’re part of a larger team, or hope to build the foundation for something that can grow into a real enterprise-class app, then proper encapsulation is mandatory.
I really hope that patch gets committed, or that someone puts together a plugin that delivers the same functionality. In the meantime, sure, there are some things you can do that are better than nothing at all. Try declaring a public mutator in your class to override the AR attribute method of the same name and raise NoMethodError, or write a before_save filter that loads up the model into another variable and replaces the current field with it’s previous value. But just describing those “solutions” makes me feel somewhat ill. Rails is such a clean, elegant platform, and it needs a clean, elegant way to provide non-public methods for AR attributes. Let’s get that patch committed :-).