This is somewhat old news, but I don’t think it has received the attention it deserves. As of Rails 2.2, ActiveRecord associations and attributes will now behave properly with regard to access control. You can view the Rails tickets, with patches, here and here.
Take, for example, this schema:
mysql> desc accounts; +----------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +----------------+--------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | balance | int(11) | NO | | 0 | | +----------------+--------------+------+-----+---------+----------------+
used by this model:
class Account < ActiveRecord::Base
def deposit(amount)
do_state_and_federally_mandated_things
balance += amount
end
def withdraw(amount)
if sufficient_funds?(amount)
do_state_and_federally_mandated_things
balance -= amount
end
end
private :balance=
private
def do_state_and_federally_mandated_things
...
end
end
You most likely don’t want someone coming along and modifying the balance attribute directly, either intentionally or inadvertently. However, prior to Rails 2.2, ActiveRecord ignores the privacy declaration for #balance=, so you must execute horrid machinations in order to protect it:
class Account < ActiveRecord::Base
def balance=(amount)
raise "I'm private!"
end
def deposit(amount)
do_state_and_federally_mandated_things
write_attribute(:balance, balance + amount)
end
...
As of Rails 2.2, ActiveRecord will respect private accessors for database column attributes.
Along the same vein, if we add the following:
class User < ActiveRecord::Base
has_one :account
end
Now we can call methods on the proxy returned by calling User#account, just as if we were calling methods on an account instance; any methods:
johnny_taxpayer = User.first
johnny_taxpayer.account.withdraw(700_000_000_000)
johnny_taxpayer.account.do_state_and_federally_mandated_things
Who knows what scary things #do_state_and_federally_mandated_things does? This will, frighteningly, run just fine prior to Rails 2.2. But, no more.
Now, a number of people have considered this change and asked “why bother?” Ruby allows access to private methods via #send, so they’re not really private, right?
This argument leads down the dark path. Like it or not, cheat around it as you may, access control is an important aspect of object oriented programming. If nothing else, the private keyword is my way of saying “hic sunt dracones,” or “hands off!” It’s also a way of saying “this method may or may not exist in the future.” As a class designer I have every right to refactor that private method entirely away, thus breaking any code that calls it; including, significantly, test code.
More fundamentally, object interactions should be via interfaces. If code makes calls to an object’s private methods, that code has now tied itself to the object’s implementation. Coupling ensues, duck-typing breaks down, anarchy reigns.
So, this begs the question, why does #send ignore access control?. Why should two forms of sending a message to an object differ in their access control semantics? Sometimes I’m forced to use #send:
method_name = extract_method_name_from_the_aether
some_object.send(method_name)
In order to make this code correct with regard to access control I have to add cruft:
method_name = extract_method_name_from_the_aether
some_object.send(method_name) if some_object.respond_to?(method_name)
Now, this is not to say that I don’t think Ruby should provide the ability to call private methods, or generally dig around in an object’s internals. This comes in handy in some instances (although I find many of these instances are short-term solutions that should get refactored away). But, in an ideal world I think the sender should explicitly specify when a message should ignore access control:
potentially_private = extract_method_name_from_the_aether
some_object.send_without_restriction(potentially_private)
This makes the syntax somewhat more ugly, but ugly syntax suits an ugly operation.
At one point, in Ruby 1.9, #send was changed to not call private methods any more. However, the change apparently broke too much code, so it was reverted.
December 2, 2008 at 5:20 pm
I agree that private methods are great for signaling the _intent_ of the class designer. As to their enforcement, I’m a believer in [Gentlemen's Agreements](http://therealadam.com/archive/2008/11/20/gentlemens-agreements/).
December 4, 2008 at 1:07 am
@Another Adam,
Keep in mind I said I have no problem with Ruby providing mechanisms for circumventing access control. What I don’t like is that in cases where I have to use #send rather than a direct method invocation Ruby gives me no choice to respect access control.
In other terms, #send violates cohesion; it serves dual purposes. On one hand, it allows the sending of arbitrary messages. On the other, it allows calling private methods. When I want one behavior I get both, whether I like it or not.
December 4, 2008 at 6:56 am
I believe we are in violent agreement.
December 4, 2008 at 9:18 pm
Wouldn’t “send!” fit the semantic bill for “send_without_restriction”? Bang means “dangerous!”
December 6, 2008 at 6:16 pm
@Alex, I quite like it. Make it so.
December 6, 2008 at 10:09 pm
If you’re not on Google Reader you’re missing a lively comment (“Note”) discussion between me and Steve Conover. Excerpts:
Alex: “The fact that it took so long is more evidence that Rails designers disdain OO.”
Steve: “Eh, access control is overrated. If I want something to not be called by others I’ve taken to the _method convention. You don’t accidentally send to it, and it’s obvious from looking at it what’s intended.
And even then, if you have small classes, private vs public isn’t much of an issue. I’m unable to relate or understand the people who want double secret lockdown on private methods – if you get a big enough sledgehammer you can get around that stuff (even in Java – without a locked-down SecurityManager everything is accessible one way or another)”
Alex: “Classic Conover comment, only this time it’s about code, not politics. (Are OO programmers the new hippies?) Note the classic fallacies:
* Simultaneously mocking and agreeing with the author: “Eh, access control is overrated. If I want something to not be called by others I’ve taken to the _method convention.” So, apparently it’s worthwhile enough to do sometimes, but not with a plain keyword (“private”) that’s already in the language, but only with a sneaky “convention” (aka “hack”) that already has 8 or 10 different ambiguous interpretations. Why admit that something is useful when you can both use it *and* feel superior to those who use it?
* Missing the point: “And even then, if you have small classes, private vs public isn’t much of an issue.” Adam’s article listed several reasons why, even for small classes, access control *is* an issue. (Documentation, future API support, test refactoring…) And the “much of” language implies that the author is hysterically overreacting, while allowing wiggle room so that when *Steve* does it it’s only during those rare *justifiable* cases.
Straw man: “I’m unable to relate or understand the people who want double secret lockdown on private methods.” These “people” clearly don’t include the article’s author, who in fact explicitly said private access “comes in handy” and closes the article with a reasonable proposal for how to do it in a clean and self-documenting way.
Remember, Adam, you might be right, but it doesn’t count if you’re *inadvertently* right.”
December 7, 2008 at 5:36 pm