4 ms·
Unfortunately try illustrates the exact problem with open classes so many non-Rubyists are afraid of: collisions. try was originally defined as: class Objec
by defunkt 17y ago
Unfortunately try illustrates the exact problem with open classes so many non-Rubyists are afraid of: collisions.
try was originally defined as:
class Object
def try(method)
send method if respond_to? method
end
end
In Rails it is defined as:
class Object
alias_method :try, :__send__
end
class NilClass
def try(*args)
nil
end
end
The following code will exhibit different behavior depending on which version of try you're using:
[].try :upcase
For example:
# original try
>> [].try :upcase
=> nil
# rails try
>> [].try :upcase
NoMethodError: undefined method `upcase' for []:Array
If Rails uses try internally, and a Rails plugin you've loaded depends on the original version of try, what now? They both behave differently and, presumably, code using them depends on their specific behavior.
Also don't forget the dozen or so other versions of try people have added to their own plugins and apps...
- dylanz 17y agoArticulated perfectly! Any time I'm working on a Rails project, and run into odd behaviours, the first thing I'll do is check to see if I'm actually using a Ruby or a Rails method. Most often than not, I'm expecting a "Ruby" way of doing things, but find out I'm actually using the "Rails" version, which does something completely different. If I run into any in the next few hours, I'll post them here :)
- carbon8 17y agoIt's not really a Ruby vs Rails way of doing things. The Rails implementation is arguably more in line with the standard Ruby behavior since it throws a NoMethodError for everything other than Nil objects.
- defunkt 17y agoI would argue that, no, there isn't "standard Ruby behavior" in this instance. It's just a method.
- carbon8 17y agoTrue, they are all just methods, but it's basically just an altered version of Object#send, and the current Rails implementation only significantly differs from it in the specific situation it's intended for. I realize there are a range of preferences regarding ways to tackle the problem this is intended to solve, and I'm relatively agnostic about the overall issue, but if the implementation is basically a #send that guards against nil, then it would be unexpected for it to deviate remarkably from #send when dealing with non-nil objects.
- steveklabnik 17y agoThis was supposed to be a really simple example. One could always write the code such that try only gets defined if there's no existing definition.
- defunkt 17y agoAgain: you are using two libraries (Rails and plugin X) that depend on their own versions of try, which they include. One of the libraries is going to break.
- wycats 17y agoIndeed. It's probably wise to use namespaced internal methods inside of libraries or frameworks. However, Rails has historically pioneered new language features that eventually make their way into the language itself (Object#tap, Enumerable#group_by, etc. etc.). In Rails 3, we've made it reasonable to pull in just small pieces of the ActiveSupport library, making it more likely that users will reuse functionality from there rather than reinvent the wheel. Of course, it's not perfect.
- simonw 17y agoAm I right in thinking that once a piece of ActiveSupport is pulled in anywhere in the codebase it will affect ALL Ruby objects, rather than having its effect scoped to just the module that included it?
- wycats 17y agoYep. You'd be modifying the String class itself, not just Strings that find their way into some file.
- steveklabnik 17y agoWell... yes. You're right. If you're depending on one behavior rather than the other, then totally. I guess I was thinking about it more in terms of two different implementations of the same thing. "If we don't have a quicksort defined yet, make one."
- xenophanes 17y agoAnother difference: the original version doesn't take multiple arguments, but the new one does.