Ruby 2.0 arrives with refined monkey patching, faster Rails startup

Monkeying around isn't such a bad thing.

Ruby 2.0 was released yesterday, giving the open source programming language a variety of new features and better-than-usual compatibility with the previous version.

One significant addition is "Refinements," an experimental feature that aims to change the way monkey patching is performed. Refinements allow developers to change built-in methods without affecting other code that uses them.

JRuby maintainer Charles Nutter describes Refinements as "a way to make class modifications that are only seen from within certain scopes," or to localize the monkey patches. He explains in a blog post:

In Ruby, all classes are mutable. Indeed, when you define a new class, you're really just creating an empty class and filling it with methods. The ability to mutate classes at runtime has been used (or abused) by many libraries and frameworks to decorate Ruby's core classes with additional (or replacement) behavior. For example, you might add a "camelize" method to String that knows how to convert under_score_names to camelCaseNames. This is lovingly called "monkey-patching" by the Ruby community.

Monkey-patching can be very useful, and many patterns in Ruby are built around the ability to modify classes. It can also cause problems if a library patches code in a way the user does not expect (or want), or if two libraries try to apply conflicting patches. Sometimes, you simply don't want patches to apply globally, and this is where refinements come in.

Refinements have been discussed as a feature for several years, sometimes under the name "selector namespaces". In essence, refinements are intended to allow monkey-patching only within certain limited scopes, like within a library that wants to use altered or enhanced versions of core Ruby types without affecting code outside the library.

The Ruby announcement from yesterday warns that Refinements isn't quite finished. "Please be aware that Refinements is still an experimental feature: we may change its specification in the future," the announcement says. "Despite that, we would like you to play with it and give us your thoughts. Your feedback will help to forge this interesting feature."

The new Ruby release is version 2.0.0, the first stable release in the Ruby 2.0 series. Besides Refinements, there are updates to the language's core features, built-in libraries, debugging support, and performance improvements. One performance improvement allows Ruby on Rails to start up very quickly. On Hacker News, one commenter reported a startup time of 6.1 seconds, down from 9.9 seconds in the previous version.

The announcement and this post by developer Marc-André Lafortune have rundowns of these and other new features. The language's maintainers say they took great care to ensure compatibility with version 1.9, saying, "It will be easier to migrate from 1.9 to 2.0 than it was from 1.8 to 1.9."

17 Reader Comments

What I also understand, but have not yet tested, is that forking ruby processes is now a bit less memory intensive, due to the way the GC has been changed. This means that your forked processes (like your unicorn server instances) share more memory with the main process, and only use memory for stuff a worker changes.

Especially if you run large sites, that can be a nice improvement! (For those that not already run JRuby that is ofcourse)

There are some fundamental things broken with Ruby 2.0 and the latest Rails version, 3.2.12. For instance, the method that Rails calls to duplicate objects in Ruby was changed to a private method in Ruby 2.0 causing many things to break. A patch was merged into Rails 3.2 master several months ago but an official release has not happened yet!

There are some fundamental things broken with Ruby 2.0 and the latest Rails version, 3.2.12. For instance, the method that Rails calls to duplicate objects in Ruby was changed to a private method in Ruby 2.0 causing many things to break. A patch was merged into Rails 3.2 master several months ago but an official release has not happened yet!

Yes, claims of backwards compatibility (and indeed, specifically Rails compatibility) were greatly exaggerated. The other major change to note that may hurt library compatibility is that respond_to? now returns false for private/protected methods, unless an additional parameter is passed.

There are some fundamental things broken with Ruby 2.0 and the latest Rails version, 3.2.12. For instance, the method that Rails calls to duplicate objects in Ruby was changed to a private method in Ruby 2.0 causing many things to break. A patch was merged into Rails 3.2 master several months ago but an official release has not happened yet!

Yes, claims of backwards compatibility (and indeed, specifically Rails compatibility) were greatly exaggerated. The other major change to note that may hurt library compatibility is that respond_to? now returns false for private/protected methods, unless an additional parameter is passed.

Ya, that's actually how this Rails 3 bug happens. Rails does activerecord_instance.initialize_dup but that's now a private method. It needs to be activerecord_instance.send :initialze_dup. It was fixed seven months ago but a bugfix release hasn't happened yet (https://github.com/rails/rails/issues/9417)

Heavy frameworks (e.g. Rails), in general, are subject to these frustrating problems: holding their adherents hostage until a bug is fixed.

I wrote Ruby for approx. five years. Several years ago I made two wonderful decisions: use only lightweight frameworks and write Python instead. I still think Ruby's syntax is superior, but overall Python wins out.

I'm not a Ruby developer, so excuse my ignorance, but why can't you just make a copy of a class object and assign it to a variable in the appropriate lexical scope. Then you can monkey patch away without breaking the other clients of that class. Classic GoF prototype pattern. What am I missing? I know that Ruby classes are just objects that instantiate objects.

I'm not a Ruby developer, so excuse my ignorance, but why can't you just make a copy of a class object and assign it to a variable in the appropriate lexical scope. Then you can monkey patch away without breaking the other clients of that class. Classic GoF prototype pattern. What am I missing? I know that Ruby classes are just objects that instantiate objects.

I'm not a Ruby developer, so excuse my ignorance, but why can't you just make a copy of a class object and assign it to a variable in the appropriate lexical scope. Then you can monkey patch away without breaking the other clients of that class. Classic GoF prototype pattern. What am I missing? I know that Ruby classes are just objects that instantiate objects.

Monkey patching re-opens a class for modification. You can add new methods or change existing ones. This allows you to add to or change a class in place without affecting or needing to do anything to any of the methods you don't care about. Prior to Ruby 2.0, it changed the behavior of that class/method everywhere. That means a change you make, might have a negative impact on any gems you require (directly or indirectly).

With Refinements, it allows to you say: "I'm going to change the way this method/class works here, and only here.". Nothing else has to deal with any unexpected behavior changes.

I can see this being most beneficial in relation to gems. You can monkey patch a class for behavior you need in your gem, but without it affecting anybody else's code. Similarly, monkey patching a base class in your application shouldn't affect any gems you install.

The Ruby developers have a weird way to manage their version numbers.The 1.8-->1.9 changes were more significant than the 1.9-->2.0 changes, so 2.0 should have been named 1.10, or even better: given the fact that each of these changes makes backward incompatible changes, upgrading the major version number each time would be sensible too.It's a small detail but it doesn't give trust in Ruby developers if they're not even able to manage their version number properly, how could they manage a language properly?

It's a small detail but it doesn't give trust in Ruby developers if they're not even able to manage their version number properly, how could they manage a language properly?

Because the two skills have nothing whatsoever to do with each other?

That's not true: the common part is "communication skill", a very important skill both for making a language or managing a project.

However, there are no parts of a language which are required to operate based on what the viewer assumes they mean, as opposed to what the language definition says they mean. It's just a version number. There is a hard limit on how much information you can encode in it. Specifically, that information is "this is a different version".

The Ruby developers have a weird way to manage their version numbers.The 1.8-->1.9 changes were more significant than the 1.9-->2.0 changes, so 2.0 should have been named 1.10, or even better: given the fact that each of these changes makes backward incompatible changes, upgrading the major version number each time would be sensible too.It's a small detail but it doesn't give trust in Ruby developers if they're not even able to manage their version number properly, how could they manage a language properly?

It's just a version number. There is a hard limit on how much information you can encode in it. Specifically, that information is "this is a different version".

You can do a lot better than that with a three-part version number like Ruby's. Semantic Versioning is one such approach, which says roughly (I'm summarising brutally, full description here):

1. until version 1.0.0, all bets are off - the public interface is not stable and may change at any time.2. post 1.0.0, the following rules apply:3. The patch version (last number) is incremented for purely backwards-compatible bug fixes or internal changes4. The minor version (middle number) is incremented if new, backwards-compatible behaviour is introduced5. The major version (first number) is incremented for *any* backwards-incompatible changes to the public API.

This sort of approach makes a huge difference to the manageability of a project's dependencies, and to the management of libraries. Whether there's much to be gained by applying this sort of approach to a project like Ruby (as opposed to, for example, the maintenance of individual gems) is debatable, but version numbers can certainly express much more than simple difference.

We've started to move away from ruby because the must-have gems are never maintained across multiple major revisions of the core. The Mysql gem was abandoned with ruby 1.8 and a new gem project had to be started for 1.9 with different syntax. I expect 2.0 to be no different.

Ruby doesn't provide enough functionality in of itself to write meaningful applications, you need gems. So they can't keep changing the language so drastically that the gem library becomes useless with every major release.