Friday, December 21, 2007

JRuby 1.0.3, Rubygems 0.9.5 TypeError

Here's that error again: After updating Rubygems to 0.9.5 (yeah, I know 1.0.0 just came out, but that one does some weird stuff with bin script filenames that I'd rather not deal with right now), trying to install rails gives:

ERROR: While executing gem ... (TypeError)
can't convert nil into Array


Running gem with the -V and --backtrace options makes it seem that the problem comes up from checking a default Ruby version requirement. Gem::Requirement.default creates a new Gem::Requirement with version GTE 0, and when it compares that version 0 to the Ruby version 1.8.5, the 0 doesn't properly get turned into an array [0] and gets left as nil, which when compared to an Array causes the above TypeError. Patching Rubygems to make it compare to_ints to to_ints rather than @ints to #ints works around the problem, but doesn't address the real issue: why does the same code work in MRI but not JRuby?

So I fired up jirb and started checking it out, and found that Gem::Version#normalize and to_ints weren't being called when comparing to the version requirement from the gemspec. Comparing to the Gem::Requirement.default worked fine.

According to the response I got from Eric on the Rubygems patch I submitted, @ints gets set from Version#initialize, #yaml_initialize, and #marshal_load. So why isn't it getting set for the default in this case? All of those constructor methods call self.version=, and #version= calls normalize to turn the version string into an array. Initialize seemed to work, but a gemspec uses yaml, and I found that yaml_initialize isn't being called!


class Foo
def initialize
@foo=1
end
def yaml_initialize tag, val
@foo=5
end
end
foo=Foo.new
YAML.load YAML.dump(foo) # #


Doh! yaml_initialize is definitely NOT being called. So I submitted JRUBY-1786, and hopefully Ola Bini or someone else will know how to fix it in the java code. In the meantime, my rubygems/version.rb workaround still does the trick.

Wednesday, December 19, 2007

JRuby 1.0.3, Rails 2.0.2, Rubygems 0.9.5

So here's the deal... The easiest way to install Rails is through Rubygems, which automatically creates the "rails command" that invokes the rails application skeleton generator, in your ruby bin directory, and sticks rails in the rubygems load path so that you can require 'rubygems' and then require 'activerecord' etc.

Problems start to pop up when you try installing Rails 2.0 (the current, production-ready Rails) in JRuby. As I mentioned earlier, the new gems were created with Rubygems 0.9.5 rather than Rubygems 0.9.4 which ships in the JRuby binary package. In C-Ruby, you can easily upgrade Rubygems by running gem update --system. That runs gem's update_command.rb, which pulls down the latest rubygems-update-XXX.gem file, unpacks it, and then runs this little ditty:


Dir.chdir update_dir do
say "Installing RubyGems #{version_string}"
setup_cmd = "#{Gem.ruby} setup.rb #{args.join ' '}"

# Make sure old rubygems isn't loaded
if Gem.win_platform? then
system "set RUBYOPT= & #{setup_cmd}"
else
system "RUBYOPT=\"\" #{setup_cmd}"
end
end


Did you catch that? UnSet the environment variable RUBYOPT, and re-run the ruby command in the install directory, running setup.rb. Like I said, it works fine in C-Ruby, but here's how Gem.ruby is defined in Rubygems 0.9.4:


# Return the Ruby command to use to execute the Ruby interpreter.
def ruby
"ruby"
end


And here it is in 0.9.5:

# Return the Ruby command to use to execute the Ruby interpreter.
def ruby
if @ruby.nil? then
@ruby = File.join(Config::CONFIG['bindir'],
Config::CONFIG['ruby_install_name'])
@ruby << Config::CONFIG['EXEEXT']
end

@ruby
end


Which is correct. The old version just says, "the ruby interpreter command is 'ruby', and that's that." Which doesn't do a lick of good if ruby isn't in your path, and more importantly, if the command is jruby and not ruby! So running gem update --system won't do any good in JRuby. We can get around that by pulling down the gem and running setup.rb manually with JRuby:

bin\jruby -S gem install rubygems-update --no-rdoc --no-ri
cd lib\ruby\gems\1.8\gems\rubygems-update-0.9.5
..\..\..\..\..\..\bin\jruby setup.rb --no-rdoc --no-ri
cd ..\..\..\..\..\..


Now, there's one more issue with Rubygems to fix before we can get Rails installed. Fire up this bit of code in bin\jirb:

fname='lib/ruby/site_ruby/1.8/rubygems/version.rb'
lines = File.readlines fname
lines[126]


You should get back: " @ints <=> other.ints\n", and ints is an attr_accessor. Makes sense, but if you just try installing rails at this point, there's an issue where checking against version 0 does not properly initialize the ints array, which is just a major/minor version number split on dots. What we need to check is to_ints, which splits the version number and returns @ints, so try this:

lines[126].gsub! /@ints|(\.)ints/, "\\1to_ints"
File.open(fname,'w'){|f| lines.each{|line| f.write line } }


Now, you can run bin\jruby -S gem install rails --no-rdoc --no-ri and be good to go. Note that Rubygems 0.9.5 has -y (--include-dependencies) as the default option, and will nag you if you specify it.

Cheers!

More Rails2.0.2 : C-Ruby Win32

In my never-ending quest to figure out what's the deal with Rails2.0 and JRuby, I'm trying the new rails out in Win32 C-Ruby with a clean install.

Ruby-186-25 One-Click Windows Installer, installed to c:\rubytest


echo %PATH%
c:\rubytest\bin;...

gemwhich rake
c:/rubytest/lib/ruby/gems/1.8/gems/rake-0.7.2/lib/rake.rb


So far, so good. Now, let's update rubygems and get rails.


gem update --system (gem update DASH_DASHsystem)

gem --version
0.9.5

gem install rails
Successfully installed rake-0.7.3
Successfully installed activesupport-2.0.2
Successfully installed activerecord-2.0.2
Successfully installed actionpack-2.0.2
Successfully installed actionmailer-2.0.2
Successfully installed activeresource-2.0.2
Successfully installed rails-2.0.2
7 gems installed

rails testapp
cd testapp
ruby script/generate scaffold blah name:string
gem install sqlite3-ruby


(download and install sqlite3.dll to c:\rubytest\bin)

rake db:migrate
(in C:/rubytest/testapp)
== 1 CreateBlahs: migrating ===============
-- create_table(:blahs)
- 0.0620s
== 1 CreateBlahs: migrated (0.0620s) ======

ruby script/server

(in another console)

irb
require 'active_resource'
class Blah < ActiveResource::Base
self.site='http://localhost:3000/'
end
b=Blah.create :name => 'Bob'

=> #<Blah:0x335287c @prefix_options={}, @attributes={"name"=>"Bob", "updated_at"=>Wed Dec 19 14:58:45 UTC 2007, "id"=>1, "created_at"=>Wed Dec 19 14:58:45 UTC 2007}>


So that sets up a WEBrick server with a resource scaffold, routed as /blahs, and accesses it remotely using ActiveResource to post to /blahs.xml and return the new Blah object. You can also fire up the web browser and go to http://localhost:3000/blahs to see the effect.

So on a whim, I figured I'd retry installing rails with JRuby 1.0.3, but I forgot that I still had Rubygems 0.9.4 on that install, and never successfully got it upgraded, so that'll be the focus of my next post.

Tuesday, December 18, 2007

JRuby 1.0.3 on Rails 2.0.2 : Too sexy for your migrations

Wow, what a week. First we get a new stable JRuby release, quickly followed by the big whopper Rails 2.0! And with an interesting twist: in the young Rails 2.0.2 update, SQLite is now the default database instead of MySQL, which means you can roll out much faster and more simply in a normal C Ruby environment. In JRuby however, the results might be unpredictable. As far as I can tell, JRuby doesn't support SQLite, but I hoped that may have changed in the meantime.

The short of it is, it hasn't changed. Even in 1.0.3, you won't succeed with jruby -S gem install sqlite3-ruby. It stinks, but oh well. You can get around it easily enough by installing activerecord-jdbc and Derby, but I was hoping for a simple "rails and roll" scenario. You can also get back to using mysql as the database with rails myapp -d mysql.

There's also the matter of getting Rails2.0.2 installed in the first place. A bunch of people reported problems with gem, which boiled down to a version incompatibility between Rubygems 0.9.4 (which most people with the problem had, and which ships with JRuby) and 0.9.5 (with which the Rails gems were built). And running gem update --system from JRuby doesn't give the expected result, because the update script attempts to exec "ruby" at some point. So now I'm not even sure if my existing C-Ruby install is stable or not, since it might have run the update there. We'll see once it comes time for me to update any production Rails code... Anyway, I had to check rel_2-0-2 out of SVN to get Rails installed. But once I did that I was able to run the rails generator out of railties with -d mysql, generate a scaffold resource, and migrate it.