Ruby On Rails IntelliSense
The last part of my current IntelliSense project has been to add Rails IntelliSense to Ruby In Steel. You might think that this is a no-brainer – after all, if Ruby can be ‘IntelliSensed’ then surely Rails can? Well, no. It’s a bit trickier than that - as I found out...
The first problem is that Rails files are not Ruby programs. If you directly run them, they will fail. This is because Rails silently does a whole load of ‘requires’ just before it loads the file in question. Rails works out which files it needs to require by looking at the file name. But those ’requires’ are not actually in the source code so the program will fail if you try to run it in the normal Ruby way.
But that’s not all. If you try and naively parse a Rails file that, say, refers to ActiveRecord, you’ll find that it takes forever. That’s not just because the Rails ActiveRecord is big and complicated (it is), but also because it requires just about all of the Ruby library. I certainly didn’t know that Rails required the Fox UI library (I think it was that). But something inside something that Rails called (‘yaml’ probably) did – and so the Fox UI was hauled in and parsed. Unlike most Ruby programmers, due to the fact that I am debugging the source code inside Visual Studio, I can see what’s being done by the Ruby sub-systems and it’s often neither logical nor efficient.
So a straight parse of a Rails program didn’t work too well. To get round the problem, I’ve introduced a ‘librarian’. This pre-parses a Rails file and spits out the bits that are of interest to IntelliSense. The system can then incorporate this stripped-down Ruby into its main database and get a good quick parse time. It works a bit like metadata in the .NET framework: a C# program doesn’t parse all of the .NET library whenever you type using System.IO;, say. It just looks in the metadata.
Now that idea turns out to work well. So well that we’ll be introducing the Ruby Librarian as a tool for end users at a later date. Then you will be able to build your own Ruby IntelliSense libraries. But time constraints being what they are, the Librarian won’t make it into our first release so, for now, it is just something that we are using internally.
A First Look At Rails IntelliSense
So what does Rails IntelliSense look like? Here’s a couple of screen-shots. First, is a list of the methods available via Ctrl-Space. You can see many ActiveRecord methods like render in the list. The second screen shot shows something which I find to be particularly useful – the ‘hover’ tooltips over a method or a variable (here the Rails method render). It turns out that this is excellent for debugging IntelliSense! The thing to note here is that this IntelliSense is the real thing – it’s not just a set of methods added to a list and labelled ‘Rails IntelliSense’. The methods are derived from the actual Rails source code by parsing it – and, as proof, I found a good few errors in my IntelliSense by doing just that. Additionally, you can ‘drill into’ the Rails code using the scoping operator ::, so that ActiveRecord::Base resolves the methods of the Base class in the module ActiveRecord correctly.
So Where’s The Catch...?
But let’s not get too carried away. I reckon that the current Ruby In Steel IntelliSense system should deal reasonably well with about 70%-80% of an average program that wasn’t written with IntelliSense in mind. But if you do write with IntelliSense in mind, you can get close to 95% or greater: in short, if you help IntelliSense, it will help you.
Rails is a good example of an IntelliSense ‘unfriendly’ system. I can say (with some feeling) that, from an IntelliSense perspective, it’s a pig. Here’s an example of what I mean. Rails dynamically ‘extends’ the methods available to a class like this:
In this particular case, the methods of ActiveRecord::Associations::ClassMethods are being added to ActiveRecord::Base. But from an IntelliSense view there’s a problem: IntelliSense has no idea of what the object base is. It’s an input parameter with no associated type information. So as things stand, IntelliSense gives up. I’ve got round this by manually adding an include statement into the IntelliSense ‘stubs’ and this seems to work rather well.
Here’s the code actually working – you can see the has_many method in the Ruby Explorer on the right with a little arrow symbol indicating that it has been included explicity rather than being extended: The future iterations of IntelliSense will get closer to 100% (and a lot faster and more knowledgeable about Rails ), but it’s never going to be like C#, say, where you always know a priori what the type of each and every variable is.
One final thought on the matter. I’ve noticed that using IntelliSense alters the way I write Ruby programs. Now, I’m not a Ruby guru – I write Ruby programs to test my C# and Visual Studio code rather than writing, say, entire interactive web sites in Ruby. Ruby has always struck me as rather like a jelly-fish – very flexible, but no ’backbone’. On the other hand, writing in C# is like wearing a straightjacket: too much ‘backbone’. But I’ve found that if I write Ruby with IntelliSense in mind – that is, not doing anything too outlandish and specifying the types of the variables and methods as I go along, I can get a very reasonable and flexible compromise – a well structured Ruby program that is documented and that has fewer potential unexploded bombs hidden in the code.
This goes to the core of what an IDE gives to a language like Ruby. For many programmers knocking out ‘scripts’ an IDE won’t do a great deal. Nice colors, but so what? But if you are interested in producing a maintainable system then an IDE with IntelliSense is a good investment. To prove my point, just try working in an editor without a ‘Goto Definition’ facility – I did for a few years with Perl. And I would have killed for something like Visual Studio where it’s standard. Especially at 1am in the morning with the users breathing down my neck!