I considered making a post last week, to keep up my at-least-one-posting-per-week schedule. However, I had dug myself deep into Rails' generator script and the built-in generators and resolved myself to finally get them running. There were a number of more complicated issues to solve, but it felt like I was very close to having it working. Any of you all-night hacker types know the feeling; success is just around the corner...maybe this is the last bug...maybe this run will complete without errors.
I can now announce that as of this evening, all the built-in Rails generators appear to be running and generating correctly using JRuby.
This has certainly been a hard-fought battle, and the last week had some big fixes:
- As mentioned in Pickaxe, Object does not actually define any instance methods; instead, it mixes them all in from Kernel. However, the original design of JRuby had followed Pickaxe in form rather than substance, defining those methods on Object. While this did not typically affect the functioning of normal Ruby code, it did break one library in particular: delegate. DelegateClass, in delegate, uses Kernel's list of public instance methods to select which methods on the target class are to be delegated. Rails uses it internally for, among other things, delegating some behavior for generator commands to a generator base class. Fixing the issue meant redefining the Object instance methods as Kernel module methods...a fairly major change, but one that does not appear to have caused any other regressions.
- My recent addition of binding support had a small flaw. The current chain of method calls in JRuby to do an eval is long and winding (much longer than I would like), and one link in that chain I did not inspect caused two issues: evaluating with a binding did not correctly set "self", and completion of that eval did not correctly reset it. In lieu of cleaning up the eval chain (which I commit myself to eventually do), I made a few modifications so "self" would work correctly.
- Enumerable#collect should work without a block; this is not documented in Pickaxe and finding this issue from deep within the bowels of 'generate' was a painful chore. This is a perfect example of a miniscule bug that causes massive trouble; the fix was less than a line of code, but the bug prevented 'generate' from correctly mapping and executing any actions. And why would you want to collect without a block? Answer: if you only have "each" defined and wish to turn your Enumerable into a simple array.
- JRuby's Module code inexplicably defined a singleton "new" method. This prevented Module subclasses from defining their own initializers that call "super". I'm still not sure why this was there, but it has been removed.
- Module#ancestors failed to include singleton classes.
- Java does not support the concept of a process-wide "current directory" as Ruby does. In order to fake this behavior, JRuby originally modified the system property "user.dir" to point at JRuby's new current directory. This was not only a dangerous thing to do, but was also not sufficient to make current directories work correctly. It also would drastically affect all other code running in the same JVM that depended on "user.dir" being correct. My modification was to introduce into JRuby a runtime-wide "current directory". In addition, I fixed a problem in Dir.chdir where failures in the provided block prevented chdir from resetting the dir back to its original location.
- Dir.mkdir dir not handle multiple levels of dir creation. The simple fix was to use Java's File#mkdirs instead of File#mkdir. How delicious...an easy bug.
- IO#read returned nil at EOF, instead of the correct "".
- File#join did not clean up multiple dir separators in a row, resulting in invalid paths like "app//models".
At any rate, you're here to read about the generators.
Part of what makes Rails so agile and powerful is its beautifully simplistic code generation capability. By using the "generate" script, you can generate perhaps 90% of a working web application. With the 1.0 release, there are generators built in to create models, controllers, mailers, plugins, web services, and database and session migration code. There are also third-party generators for quickly generating other bits and pieces of a typical web app.
The generator code is fairly extensive, but unsurprisingly it does not exercise all of Rails' code. It does, however, represent the typical "first step" into the Rails world, and so I set out to get it running in JRuby. After many tests and fixes, documented in my other blog entries and immortalized in CVS, generators now work.
A few test runs to demonstrate:
C:\rails>jruby script\generate scaffold "myapp/Account" open close balance
C:\rails>jruby script\generate web_service User add edit list remove
C:\rails>jruby script\generate plugin SiteMinderAuthentication
And so on. It's a pretty big milestone to finally have these generators working, and it means that one more step in the Rails development process now works under JRuby. I'm very pleased.
There are a few caveats (of course), but all told they're fairly minor. Rest assured they'll be remedied forthwith:
- Among other block arg tricks, specifying an index into an array or hash as a block arg is still nonfunctional. This will require interpreter work and possible parser changes.
- There are a couple warnings that display while running 'generator', but they are safely ignored. I believe they are overzealous warnings within the parser, left over from Ruby 1.6.
- I can neither confirm nor deny that the generated code and content is correct; however, it looks correct to my untrained eye.
- Not all the above fixes are committed; not all fixes committed are guaranteed not to cause regressions.
- I'm no Rails expert, despite swimming in the deepest parts of its ocean. I will be putting my Pragmatic 'Rails' book to heavier and heavier use now that we're finally putting JRuby on Rails.
So there you have it. Great progress has been and is being made, and I'm having fun making it happen. Hopefully Rails actually running is coming very soon...stay tuned.