Project

tomato

0.0
No commit activity in last 3 years
No release in over 3 years
Leverages Google's V8 JavaScript library to interface Ruby code with JavaScript code.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

>= 1.3.0

Runtime

>= 1.2.0
 Project Readme

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 © 2010 Colin MacKenzie IV. See LICENSE for details.