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...
Articulated 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 :)
It'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.
True, 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.
Indeed. 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.
Am 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?
Well... 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."
This is basically how ActiveSupport's "activesupport/ruby/shim" functionality works. It adds pure-ruby versions of features added in Ruby 1.9 only if they aren't already defined.
I love reading articles like this. 8 years or so ago Ruby replaced Perl for me as my 'go to' language for day-to-day problems. I haven't looked back since.
What other people call 'magic', I call not understanding the language and execution environment.
For long-running server-side stuff Ruby would not be my first choice, but for eloquence and scripting capabilities... accept no substitute.
You're partly right. Fixnums don't have a metaclass (or virtualclass) because they're actually "immediate". So you can't say 123.extend(Something) because 123 isn't really a full Object.
That's just to say that there aren't multiple "2"s. But 2 itself is still an object, pointing at its Fixnum class, which has all the normal Class and Object semantics.
Yeah. Conceptually, you can think of the difference as whether or not there can be two copies of something. There can only be one copy of a Symbol, Fixnum, true, false, and nil. This means that true == true, and true.dup #=> error.
try was originally defined as:
In Rails it is defined as: The following code will exhibit different behavior depending on which version of try you're using: For example: 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...