The goal of this tutorial is to show you how, with Ruby, we can turn this:
if ($(‘a‘).value == ‘a‘) {
doThis(true, ‘maybe‘);
andThis($(‘a‘).innerHTML);
}
var MyClass = Class.create();
MyClass.prototype = {
myMethod: function(arg1, arg2) {
cool();
},
anotherOne: function() {
say(‘hi‘);
},
};
Into Ruby code, with a 100 lines Ruby script.
Go grab a beer / coffee / Guru / water / Asian pear / Pad Thai and get ready !
# First we build a base Page class that will handle the common statement and hold the output.
# This is pretty simple, as you can see, we only output text.
class Page
def initialize
@output = ‘‘
end
def if(condition)
self << "if (#{condition}) {"
yield self
self << "}"
end
def def(method, *args)
self << "#{method}: function(#{args * ‘, ‘}) {"
yield self
self << "},"
end
def class(name, &block)
self << "var #{name} = Class.create();"
self << "#{name}.prototype = {"
yield self
self << "};"
end
def <<(output)
@output << output << "\n"
end
def to_s
@output
end
end
page = Page.new
# Using those methods looks pretty cool
# but we still need to write some js code.
page.if "$(’a').value == ‘a’" do
page << "doThis(true, ‘maybe’);"
page << "andThis($(’a').innerHTML);"
end
page.class :MyClass do
page.def ‘myMethod‘, :arg1, :arg2 do
page << "cool();"
end
page.def ‘anotherOne‘ do
page << "say(’hi’);"
end
end
puts page
# Outputs
if ($(‘a‘).value == ‘a‘) {
doThis(true, ‘maybe‘);
andThis($(‘a‘).innerHTML);
}
var MyClass = Class.create();
MyClass.prototype = {
myMethod: function(arg1, arg2) {
cool();
},
anotherOne: function() {
say(‘hi‘);
},
};
# Iteration 2 - Make the outputed code beautiful
# Lets add some helper methods to all the objects
# so we can make beautiful Ruby code output as
# beautiful Javascript.
class Object
# This method will allow to use Ruby naming convention
# to declare Javascript methods.
# Converting my_method to myMethod
def to_js_method
to_s.gsub(/_(\w)/) { $1.upcase }
end
# This one will convert an object to it’s Javascript
# equivalent. This is pretty basic here. Wrap string in ‘
# or outputing if any other type.
def to_js_arg
self.is_a?(String) ? "‘#{self}‘" : self
end
end
class Page
def initialize
@output = ‘‘
@indent = 0
end
# We make the outputed code a little more pretty
# by indenting it! See the indented method bellow.
def if(condition)
self << "if (#{condition}) {"
indented { yield self }
self << "}"
end
…
# If an unexisting method is called, output it
# as a Javascript method call.
def method_missing(method, *args)
self << "#{method.to_js_method}(#{args.collect { |arg| arg.to_js_arg } * ‘, ‘});"
end
def <<(output)
@output << ‘ ‘ * @indent << output << "\n"
end
private
def indented
@indent += 1
yield
@indent -= 1
end
end
page = Page.new
page.if "$(’a').value == ‘a’" do
# As you can see, we use the method_missing magic
# to create method calls
page.do_this true, ‘maybe‘
page.and_this "$(’a').innerHTML"
end
page.class :MyClass do
page.def :my_method, :arg1, :arg2 do
page.cool
end
page.def :another_one do
page.say ‘hi‘
end
end
puts page
# Nice camelized method names, clearly indented = sweet!
if ($(‘a‘).value == ‘a‘) {
doThis(true, ‘maybe‘);
andThis(‘$(‘a‘).innerHTML‘);
}
var MyClass = Class.create();
MyClass.prototype = {
myMethod: function(arg1, arg2) {
cool();
},
anotherOne: function() {
say(‘hi‘);
},
};
# Iteration 3 - converting Ruby expressions to Javascript
# Now lets take care of that if statement.
# In Ruby, even operators are methods. We can take advantage of this
# and override those and output what we want!
# This module will override all comparison operators
# and output a string representation of the expression.
# So calling a == ‘a’ will return the string "a == ‘a’"
module JSComparable
def self.included(base)
base.class_eval do
%w(== < > <= >=).each do |operator|
define_method operator do |other|
[self.to_js_arg, operator, other.to_js_arg] * ‘ ‘
end
end
end
end
end
# We need to defined a simple class in which we will
# override those operators.
class Element
attr_reader :id
include JSComparable
def initialize(id)
@id = id
end
def to_s
"$(’#{id}‘)"
end
end
# Since we’re lazy, we’d like to type E[:a] ratter then Element.new(:a)
module E
def self.[](id)
Element.new id
end
end
# All this allows us to rewrite this:
page.if "$(’a') == ‘a’" do
# into this:
page.if E['a'] == ‘a‘ do
# Pure Ruby, how cool ?
###############
# Iteration 4 #
###############
# Now one line is still pretty annoying:
# page.and_this "$(’a').innerHTML"
# To turn this into Ruby code we can use a little
# method_missing magic again.
# We’ll create a small class that will handle
# the calls to methods on element.
# When rendered, parent.method is outputed.
class MethodProxy
include JSComparable
def initialize(parent, name)
@parent = parent
@name = name
end
def to_s
[@parent, @name.to_js_method] * ‘.‘
end
end
# Add a simple hook into Element
# so any unknow method calls are rendered as
# Javascript method calls.
class Element
…
def method_missing(method, *args)
MethodProxy.new(self, method)
end
end
# All this allows us to come to the almost perfect:
page.if E['a'].value == ‘a‘ do
page.do_this true, ‘maybe‘
page.and_this E[:a].innerHTML
end
page.class :MyClass do
page.def :my_method, :arg1, :arg2 do
page.cool
end
page.def :another_one do
page.say ‘hi‘
end
end
# Iteration 5 - Final touch, making this a real language !
# The ‘page.’ thing is pretty annoying I know!
# What’s the simpless thing we could do to remove this ?
page = Page.new
code = File.read(ARGV[0])
code.gsub! /(if|class|def) (.*)$/, ‘page.\1 \2 do‘
eval code
puts page
# Now place this in a seperate file:
if E[:a].value == ‘a‘
page.do_this(true, ‘maybe‘)
page.and_this(E[:a].innerHTML)
end
class :MyClass
def :my_method, :arg1, :arg2
page.cool
end
def :another_one
page.say(‘hi‘)
end
end
# And run with: ruby js.rb javascript.rjs
# and you’ll get:
if ($(‘a‘).value == ‘a‘) {
doThis(true, ‘maybe‘);
andThis($(‘a‘).innerHTML);
}
var MyClass = Class.create();
MyClass.prototype = {
myMethod: function(arg1, arg2) {
cool();
},
anotherOne: function() {
say(‘hi‘);
},
};
If you’re serious about this, I’d suggest you take a look at ParseTree which allows you to explore the parse tree of some Ruby code.
I hope this helps anyone understand better some of Ruby meta-programming capability, beauty and power!
Ruby, I love you!
Amen
Get the full script here (rename it to js.rb)









This is converting Ruby code to Javascript code using a Ruby program right?
So can you convert Ruby on Rails into Rhino on Rails?
Another thourght - can the program convert itself, run the result on Rhino and still work?
A sort of “eat your own dog food”.
That’s right Treblid, it converts Ruby code to JS using a Ruby script
But it would require a lot more work to convert a RoR app. I haven’t implemented the whole js language here and I beleive Rails and Rhino are not mapped one to one.
What do you mean by “convert itself”? That sounds like black magic, end of the world kind of stuff! I’m scared!
I think the answer is no as you “haven’t implemented the whole js language” (should that be Ruby language?). Now that would be REALLY impressive…
I’m not that familiar with Ruby but say you run it as follows,
foo bar.rb
and this produces bar.js, then try
foo foo.rb
to get foo.js
Then in theory you would have a javascript program that converts Ruby to javascript. To run the javascript program you would need Rhino (from Mozilla it runs JS on the java Virtual Machine).
If it was this capable, as Rails is written in Ruby, you could in theory create a JS Framework by converting Rails’s Ruby programs.
With JRuby, Ruby runs on the Java VM.
I don’t know if it supports Rails already but it will in a near future I’m sure.
With prototype trying so hard to bring rubyishness to javascript, I almost think ruby-to-js conversion would be possible.
Before rails, I was working with Google Web Toolkit quite a bit. With a code converter, a library like that might be possible on top of rails.
Anybody want to give it a try with me?
I think the problem with code converter is that you always end up needing to fix that tiny little details that requires you to fall down to the actual language. And I don’t find Javascript that bad, I fact, I like javascript! Specially with prototype!
But what would be cool is adding if statement to Rails RJS template, now that would be awesome!
page.if page[:block].visible do
page[:block].update ‘This is visible’
end
Thx for the comment James!