Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Leverages Google's V8 JavaScript library to interface Ruby code with JavaScript code.

branch: master

Fetching latest commit…

Octocat-spinner-32-eaf2f5

Cannot retrieve the latest commit at this time

Octocat-spinner-32 ext
Octocat-spinner-32 lib
Octocat-spinner-32 spec
Octocat-spinner-32 .document
Octocat-spinner-32 .gitignore
Octocat-spinner-32 LICENSE
Octocat-spinner-32 README.rdoc
Octocat-spinner-32 Rakefile
Octocat-spinner-32 VERSION
Octocat-spinner-32 tomato.gemspec
README.rdoc

tomato

Leverages Google's V8 JavaScript library to interface Ruby code with JavaScript code.

IMPORTANT

I've left the version number in the 0.0.x range because it's WAAAY too early for a proper release. This is some pretty cool code IMO, but keep in mind if you want to check it out that it's still development code (not even pre-Alpha). I do hope to release it as a real gem after it's reached a sufficient level of functionality.

Examples

Instantiation

require 'tomato'
tomato = Tomato.new

Running JavaScript code

When JS code is executed, it'll do its thing internally and then return the result:

tomato.run("(1+1);") # => 2

Bindings

You can bind Ruby methods to JavaScript, as well. You can bind an instance method of Tomato like so:

tomato.bind_method(:inspect)
tomato.run("inspect();")  #=> "#<Tomato>"

Or, perhaps more usefully, you can bind an instance method of some other object:

tomato.bind_method(:inspect, 5)
tomato.run("inspect();")  #=> "5"

It's also easy to bind methods to arbitrary JavaScript objects. If a JS object in the chain doesn't exist, Tomato will silently generate it for you on-the-fly:

def say(something)
  puts something
end

tomato.bind_method(:say, self, :to => "person.mouth")

# Tomato generated both the "person" and "mouth" objects for us:
tomato.run "person.mouth.say('Hello there');"

# Produces:
#
# Hello there
#  => nil

puts tomato.run("this")

# Produces:
#
# {"person":{"mouth":{}}}
#  => nil

In the spirit of true Ruby dynamicism, (is that a word?), you can feel free to re-bind new methods over old ones:

tomato.bind_method(:say)
tomato.run("say('something');")
#=> error! say is not an instance method of Tomato

tomato.bind_method(:say, self)
def say(something); puts something; end
tomato.run("say('something');")
#=> something

You can also easily bind an entire object to JavaScript:

# By default objects are mapped to 'ruby.[object_class_name]'
tomato.bind_object(Time.now)
tomato.run("ruby.time.to_s();")
#=> "2010-06-25 18:12:23 -0400"

# Or give it an object name or chain of names:
tomato.bind_object(Time.now, "time.current")
tomato.run("time.current.to_s();")
#=> "2010-06-25 18:12:23 -0400"

Even better, you can also bind a whole class and instantiate it from within JavaScript. If you return the object to Ruby, it'll be seamlessly converted into the corresponding Ruby object.

class Person
  def initialize(name)
    @name = name
  end
  attr_accessor :name, :age
end

tomato.bind_object(Person, "world.Person")
tomato.run <<-end_js
  var colin = new world.Person("Colin");
  colin.age = 25;
  colin;
end_js

#=> #<Person:0x00000100dbbc50 @name="Colin", @age=25>

Error Handling

When JS code encounters an error of some kind, it'll get raised in Ruby:

tomato.run("throw 'error';")

# Produces:
# 
# Tomato::Error: (dynamic):error
# throw 'error';
# ^
#
#     from (irb):3:in `run'
#	    from (irb):3

You can also catch Ruby errors in JavaScript:

tomato.bind_method(:raise_err, self)
def raise_err
  raise ArgumentError, "Not what I meant!"
end
tomato.run("try { raise_err(); } catch(e) {}");

# Produces:
#
# Nothing, because the error was caught.

Or let them bubble up to Ruby:

tomato.bind_method(:raise_err, self)
def raise_err
  raise ArgumentError, "Not what I meant!"
end
begin
  tomato.run("raise_err();");
rescue ArgumentError => err
  puts "Error: #{err.message}"
end

# Produces:
# 
# Error: Not what I meant!
#  => nil

Object Types

Some objects in Ruby don't exist in JS. So far the ones I've implemented are Hashes and Symbols.

Symbols

A Symbol is converted to an object in JS with a “symbol” attribute and a “toString()” method. That same object, if returned to Ruby, is converted back to a Symbol. Examples:

def fetch_a_symbol
  :a_symbol
end

t = Tomato.new
t.bind_method(:fetch_a_symbol, self)
t.run("fetch_a_symbol()")            #=> :a_symbol 
t.run("fetch_a_symbol().symbol")     #=> "a_symbol"
t.run("fetch_a_symbol().toString()") #=> "a_symbol"

Hashes

A Hash is converted to an object of some form or another. It's not an Array. I haven't added much to the JS side of this object yet, so I don't know if it's even usable in JS. But it does, at least, return to Ruby as a Hash. So you can have a method that returns a Hash, call the method from JS, return that object to Ruby, and get the hash. Dunno if that's useful, but it's an implementation detail that had to be looked at regardless. Hashes still have a lot of development ahead of them.

Requirements

* Ruby. Tested:
  * 1.8.7 p174 - pass
  * 1.9.1 p378 - pass
    * This is the version I developed Tomato against.
  * 1.9.2 preview3 - pass
* Python 2.4+. This is used to compile the bundled V8.

Note: I've not tested it on Windows, but if you can satisfy the above requirements and have a C++ compiler (gcc recommended), it should work. Make sure Python is in your path.

Installation

To install the latest and greatest prerelease:

gem install tomato --pre

To install the current stable release:

gem install tomato

That should be all there is to it. The package is pretty large as gems go, so be patient.

Performance

ruby-1.9.1-p378 > javascript = <<-end_js
ruby-1.9.1-p378">   var str = "";
ruby-1.9.1-p378">   for (var i = 0; i < 1000000; i++)
ruby-1.9.1-p378">     str = 'a';
ruby-1.9.1-p378"> end_js
ruby-1.9.1-p378 > require 'benchmark'
ruby-1.9.1-p378 > require 'tomato'
ruby-1.9.1-p378 > t = Tomato.new
ruby-1.9.1-p378 >
ruby-1.9.1-p378 > # Setup complete, let's start the test
ruby-1.9.1-p378 > puts Benchmark.measure { for i in 1..1000000; str = 'a'; end }
ruby-1.9.1-p378 >  0.350000   0.000000   0.350000 (  0.358106)
ruby-1.9.1-p378 >
ruby-1.9.1-p378 > puts Benchmark.measure { t.run(javascript) }
ruby-1.9.1-p378 >  0.010000   0.000000   0.010000 (  0.012603)

'Nuff said.

Since V8's JavaScript code is compiled into byte code, this can have a positive impact on performance in some applications. Just translate your code to JavaScript and let Tomato compile it into byte code.

Note on Patches/Pull Requests

  • Fork the project.

  • Make your feature addition or bug fix.

  • Add tests for it. This is important so I don't break it in a future version unintentionally.

  • Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)

  • Send me a pull request. Bonus points for topic branches.

Copyright

Copyright © 2010 Colin MacKenzie IV. See LICENSE for details.

Something went wrong with that request. Please try again.