Quick experiments with JRuby (called from Java)

Posted on September 10, 2008

I keep this file around when I need a quick “so how does that work in JRuby when I call it from Java” kind of thing:

package com.banshee.radoop;

import javax.script.ScriptException;

import com.sun.script.jruby.JRubyScriptEngine;

public class Jrt {
  public static void main(String[] args) {
    JRubyScriptEngine j = new JRubyScriptEngine();
    try {
      Object o = j.eval("class Foo; def initialize; puts \"blarg\"; end; end; Foo.new");
      System.out.println(o);
      System.out.println(o.getClass());
    } catch (ScriptException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}

Just change the string in the eval statement to whatever it is you’re playing with.

You’ll need the jruby.jar file from jruby (on my machine, /home/james/jruby/lib/jruby.jar) and the JRuby script engine jar.

This particular case spits out:

blarg
#<Foo:0x61ec49>
class org.jruby.RubyObject

A gem-install command that installs the current gems

Posted on September 06, 2008

Here’s a one-liner to get the current gems, in the form of a gem-install command:

gem list | cut -f 1 -d ' ' | grep -v -e '\*\*\*' | xargs echo sudo gem install

RailsConf 2008 Friday - Silverline, GemStone

Posted on May 30, 2008

The most interesting thing I saw yesterday was John Lam and Jimmy Schementi’s presentation on IronRuby. They’ve got Ruby running on the client in Silverlight - they’re calling the combination Silverline. Assuming that it’s going to work just fine in Moonlight, this is hot - no more switching back and forth between Ruby and Javascript.

Avi Bryant’s work on Ruby with the GemStone VM was also interesting. If the performance numbers GemStone talked about turn out to be true in the final version, this could be a very fast Ruby. They’ve got some interesting technology around distributed objects (think of a cluster of machines as being a single, shared object space, with transactions on changes to your objects), but it’s a whole different way of looking at the world. Worth watching. Talking to some of their people at their party, some of them get that moving to an open source model is going to be a requirement for them to be taken seriously - hopefully they’ll solve their issues of how to change their business model.

The (paper) message boards downstairs are covered with companies looking for Ruby people. It’s a great time to be looking for work. My favorite was the one from square circle triangle - it’s a faux ticket to Australia.

Rails and HTTP status codes

Posted on December 16, 2007

Status codes

Curious about how Rails deals with HTTP status codes? Take a look at statuscodes.rb (on my system, it's in /usr/local/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/actioncontroller/status_codes.rb). When you do a

render :status => :reset_content

It's just looking up the string "reset content" in it's hash of codes-to-error messages. The error message strings are converted into stringswithunderscores, so "Reset Content" ends up as match for :reset_content.

Exceptions

In rescue.rb (on my system, /usr/local/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb), you'll see where some of the Rails exceptions are turned into status codes for you.

This is where you'll see things like ActiveRecord::RecordNotFound turning into a :not_found, which finally turns into a 404.

Controller status codes

In addition to the status codes for exceptions, Rails uses these status codes when it generates a controller for you:

  • :created (201) when #create is successful
  • :unprocessable_entity (422) when #create or #update fails

Sucessfull methods return :ok (200) otherwise.

The Rails code

status_codes.rb

module ActionController
  module StatusCodes #:nodoc:
    # Defines the standard HTTP status codes, by integer, with their
    # corresponding default message texts.
    # Source: http://www.iana.org/assignments/http-status-codes
    STATUS_CODES = {
      100 => "Continue",
      101 => "Switching Protocols",
      102 => "Processing",

      200 => "OK",
      201 => "Created",
      202 => "Accepted",
      203 => "Non-Authoritative Information",
      204 => "No Content",
      205 => "Reset Content",
      206 => "Partial Content",
      207 => "Multi-Status",
      226 => "IM Used",

      300 => "Multiple Choices",
      301 => "Moved Permanently",
      302 => "Found",
      303 => "See Other",
      304 => "Not Modified",
      305 => "Use Proxy",
      307 => "Temporary Redirect",

      400 => "Bad Request",
      401 => "Unauthorized",
      402 => "Payment Required",
      403 => "Forbidden",
      404 => "Not Found",
      405 => "Method Not Allowed",
      406 => "Not Acceptable",
      407 => "Proxy Authentication Required",
      408 => "Request Timeout",
      409 => "Conflict",
      410 => "Gone",
      411 => "Length Required",
      412 => "Precondition Failed",
      413 => "Request Entity Too Large",
      414 => "Request-URI Too Long",
      415 => "Unsupported Media Type",
      416 => "Requested Range Not Satisfiable",
      417 => "Expectation Failed",
      422 => "Unprocessable Entity",
      423 => "Locked",
      424 => "Failed Dependency",
      426 => "Upgrade Required",

      500 => "Internal Server Error",
      501 => "Not Implemented",
      502 => "Bad Gateway",
      503 => "Service Unavailable",
      504 => "Gateway Timeout",
      505 => "HTTP Version Not Supported",
      507 => "Insufficient Storage",
      510 => "Not Extended"
    }

    # Provides a symbol-to-fixnum lookup for converting a symbol (like
    # :created or :not_implemented) into its corresponding HTTP status
    # code (like 200 or 501).
    SYMBOL_TO_STATUS_CODE = STATUS_CODES.inject({}) do |hash, (code, message)|
      hash[message.gsub(/ /, "").underscore.to_sym] = code
      hash
    end

    # Given a status parameter, determine whether it needs to be converted
    # to a string. If it is a fixnum, use the STATUS_CODES hash to lookup
    # the default message. If it is a symbol, use the SYMBOL_TO_STATUS_CODE
    # hash to convert it.
    def interpret_status(status)
      case status
      when Fixnum then
        "#{status} #{STATUS_CODES[status]}".strip
      when Symbol then
        interpret_status(SYMBOL_TO_STATUS_CODE[status] ||
          "500 Unknown Status #{status.inspect}")
      else
        status.to_s
      end
    end
    private :interpret_status

  end
end

rescue.rb (excerpt)

DEFAULT_RESCUE_RESPONSE = :internal_server_error
DEFAULT_RESCUE_RESPONSES = {
  'ActionController::RoutingError'             => :not_found,
  'ActionController::UnknownAction'            => :not_found,
  'ActiveRecord::RecordNotFound'               => :not_found,
  'ActiveRecord::StaleObjectError'             => :conflict,
  'ActiveRecord::RecordInvalid'                => :unprocessable_entity,
  'ActiveRecord::RecordNotSaved'               => :unprocessable_entity,
  'ActionController::MethodNotAllowed'         => :method_not_allowed,
  'ActionController::NotImplemented'           => :not_implemented,
  'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
}

DEFAULT_RESCUE_TEMPLATE = 'diagnostics'
DEFAULT_RESCUE_TEMPLATES = {
  'ActionController::MissingTemplate' => 'missing_template',
  'ActionController::RoutingError'    => 'routing_error',
  'ActionController::UnknownAction'   => 'unknown_action',
  'ActionView::TemplateError'         => 'template_error'
}

Automatically download the latest NetBeans releases

Posted on November 10, 2007
Here's what I use to grab the latest daily builds. You'll want to tweak it for directory names etc.
bld=`wget -q -O - 'http://deadlock.netbeans.org/hudson/job/ruby/lastSuccessfulBuild/buildNumber'`
wget -P /home/james/Desktop "http://deadlock.netbeans.org/hudson/job/ruby/lastSuccessfulBuild/artifact/ruby/rubyide/dist/netbeans-rubyide-hudson-$bld.zip"
cd /home/james/Desktop
rm -rf nbrubyide
unzip "netbeans-rubyide-hudson-$bld.zip"
newdir=nb$bld
mkdir $newdir
mv nbrubyide/* $newdir

406 errors when you're testing xml responses

Posted on November 05, 2007

Seeing errors that look something like this?

  1) Failure:
test_should_get_index(SoundCollectionsControllerTest) [C:/InstantRails/rails_apps/rfs/test/functional/sound_collections_controller_test.rb:18]: 
Expected response to be a <:success>, but was <406>

Make sure your test isn't specifying :xml:

get :index, :format => :xml  # BAD
get :index, :format => 'xml' # GOOD

Filter your stack trace to only see your code

Posted on July 21, 2007

Most of the time, I don't want to see the whole stack trace including the environment - I just want to see my code. So, I tossed this in my test:

module Test
  module Unit
    module Util
      module BacktraceFilter
        def filter_backtrace_with_only_my_code(backtrace, prefix=nil)
          result = filter_backtrace_without_only_my_code backtrace, prefix
          result.reject! {|x| x =~ /ruby.lib.ruby/}
          result.reject! {|x| x =~ /vendor.plugins.mocha/}
          result
        end
        alias_method_chain :filter_backtrace, :only_my_code
      end
    end
  end
end

You'll probably want to play around with what gets filtered in and out.

I think of this sort of thing as more of a debugger macro than actual code; it gets changed constantly depending on what I'm doing.

Ruby on Rails IDEs: NetBeans and Aptana

Posted on July 05, 2007

Both NetBeans and Aptana (Aptana has taken over RadRails) are useful, and both have their drawbacks. I usually have both open at the same time, editing the same files.

Aptana/RadRails

  • Subversion support is much better than NetBeans.
  • The test runner system is very useful, and NetBeans doesn't have anything like it.

NetBeans:

  • subversion support on windows is poor (they depend on using external tools, and they don't work with cygwin svn)
  • better code completion
  • much better debugging
  • the NetBeans team responds to bug reports very quickly

I use NetBeans for the debugger, and Aptana when I just want debug printfs.

I don't really have much hope for anyone's code completion in Ruby for a while. Too many things are dynamic; the chances of doing full code completion without a running application are close to zero. Seems like you're going to need very tight integration between the IDE and a running process to make this work.

You want to be running the nightly (hourly, sometimes) builds for NetBeans. Today, you'd want netbeans-rubyide-hudson-2749.zip from http://deadlock.netbeans.org/hudson/job/ruby/

http://wiki.netbeans.org/wiki/view/Ruby

Standard Rails things that I always forget

Posted on July 01, 2007

Testing redirect_to :back

You need to set request.env["HTTP_REFERER"] :

back_url = 'http://test.host/last/page/visited' 
request.env["HTTP_REFERER"] = back_url
assert_redirected_to back_url

helper_method

In a controller, do this to turn a controller method into a method that you can call from a view:

def foo
  bar
end

helper_method :foo

Plurals for scaffolds and controllers

By hand:

script/generate model Foo
script/generate controller Foos

But

script/generate scaffold Foo

Builds

script/generate model Foo
script/generate controller Foos

How to use class methods to simplify your models

Posted on June 14, 2007

The short answer

Use class methods, and call them on your collection objects.

Given Customer has_many Orders, and Orders has_one Payments, add class methods to your Order model:

class Order < ActiveRecord::Base
  has_one :payment
  belongs_to :customer

  def self.paid
    find :all, :include => :payment, :conditions => ['payments.order_id']
  end

  # Returns the unpaid orders.  See <tt>paid</tt> for more information.
  def self.unpaid
    find :all, :include => :payment, :conditions => ['payments.order_id is null']
  end
end

You can call these class methods through your collection methods:

c = Customer.find(12)
c.orders # This is the collection method added by has_many :orders
c.orders.paid 
c.orders.unpaid

The long answer

# Our simple example is for a Customer
# that has_many Orders, and each Order
# has_one Payment.
class Customer < ActiveRecord::Base
  # See <tt>Customer::paid</tt> for how to get
  # all of the paid and unpayed orders.
  has_many :orders
end

class Order < ActiveRecord::Base
  has_one :payment
  belongs_to :customer

  # Returns all the paid orders.  A paid order
  # is any order that has a corresponding Payment
  # object.
  #
  # You can use this after a collection method.  In
  # this example, Customer has_many Orders, and Orders
  # has_one Payment, so you can do:
  #   
  #   c = Customer.find(12)
  #   c.orders # This is the collection method added by has_many :orders
  #   c.orders.paid 
  #   c.orders.unpaid
  #
  # The key thing to notice here is that this isn't a 
  # regular method.  It's a method of the class itself,
  # and Rails lets you call these class methods on the 
  # collection object.
  #
  # Here's another way to think about it: collection objects
  # don't return a set of objects.  This line:
  #
  #   c.orders
  # 
  # doesn't return an array of orders.  What it returns is
  # an "association proxy."  The proxy knows how you've called
  # it, and if you call it by itself, it'll just return
  # an array of the objects you're looking for.
  #
  # If you call it with something else attached to it, like:
  # 
  #   c.order.paid
  # 
  # what happens is a little different.  
  # 
  # <tt>c.order</tt> returns
  # the association proxy, and then the association proxy is
  # sent the :paid message.  The association proxy doesn't have
  # a method called :paid defined, so #method_missing is called.
  # The association proxy does know what kind of objects it holds
  # (Orders) so it asks the Order class if the Order class can
  # respond to the :paid message.  Order::paid does exist, since
  # we defined it as a method on the Order class.  The association
  # proxy starts to build a query using #with_scope, so the 
  # class method that looks like it would just return every unpaid
  # order only returns the unpaid orders that are in the right scope.
  # In this case, the scope is Orders that have the right
  # customer_id.
  def self.paid
    find :all, :include => :payment, :conditions => ['payments.order_id']
  end

  # Returns the unpaid orders.  See <tt>paid</tt> for more information.
  def self.unpaid
    find :all, :include => :payment, :conditions => ['payments.order_id is null']
  end
end

# In a real payment, you'd want to include more information.
# For this example, the fact that a payment exists means
# that the Order is paid.
class Payment < ActiveRecord::Base
  belongs_to :order
end

The trick is that methods on the class itself can be used in finders.

Links for Capistrano, rake, deployment, etc.

Posted on May 28, 2007

Steve Odom’s Capistrano rake tasks
Adam Greene’s s3.rake Building rubyforge projects Bowtie Tool for building configuration files, including Monit

Using xml with assert_select

Posted on May 25, 2007

Jamis Buck had a useful article about using xml with assert_tag, and so did Peter Marklund, but I wanted something slightly different.

Normally assert_select uses the @response.body string to test against, but it has another option. It'll take an HTML::Document as its first argument, so you can pass it any xml you like, from any source.

I just added a small helper to test_helper.rb called with_xml.

def with_xml xml, &block
  doc = HTML::Document.new(xml, false, true)
  assert_select doc.root, ':root', &block
end

And here's how to use it:

def test_foo
  xml =<<SNRK
<foo>
  <bar>
  </bar>
</foo>
SNRK
  with_xml xml do
    assert_select 'foo:root bar'
  end
end

** (two asterisks) in globs

Posted on May 25, 2007

Never realized before that two asterisks had special meaning in a file glob.

They mean “give me all directories recursively, including this directory.”

See the Dir documentation for the full details. And notice that it’s not documented everywhere you might expect; Pathname’s documentation doesn’t talk about it, for example.