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.