OpalRB

gem 'opal'
require 'opal'
Opal::Builder.build('opal') + Opal.compile('puts "hello"')Brought to you by

Adam Beynon
creator - first commit - 9/9/2010
Elia Schlito
Lives in England
active committer - avg > 1 commit / day
Lives in Italy
Opal's relationship to JS
As native as possible
Native code embedding
a = `3 + 5`class Array
def *(other)
# coercion code removed here
%x{
var result = [];
for (var i = 0; i < other; i++) {
result = result.concat(self);
}
return result;
}
end
end
one liners backtick
multi liners %x
Numbers
- JS Numbers
- Class: Numeric
- +,-,*,/ are all implemented as method calls
- operations are done as floating point
puts(`3+5`)
puts(3-5)generates
self.$puts(3+5);
return self.$puts((3)['$-'](5));
puts 1 / 2generates
class Numeric
def /(other)
%x{
if (other._isNumber) {
return self / other;
}
else {
return #{send_coerced :/, other};
}
}
end
end
calls
self.$puts((1)['$/'](2))results in
0.5Strings
- JS Strings
- Immutable - all string mutation methods not defined
- + implemented as a method call
a = "abc" + "def"
b = `"abc" + "def"`a = "abc"['$+']("def");
b = "abc" + "def";
generates
Symbols
- JS Strings
- Acts like HashWithIndiffentAccess in Hashes
if :string == "string" then
puts "strings and symbols are the same"
else
puts "are not"
endoutputs
strings and symbols are the sameArrays
- JS Arrays
- + implemented as method call
Hash
- Implemented as complex JS object
- Opal.hash(js_object) to create ruby hash
a = {a: 1, b: 2}{
keys: ["a", "b"],
map: {
"a": 1,
"b": 2
}
}generates
a = $hash2(["a", "b"], {"a": 1, "b": 2});creates an object shaped like
Regexp
- JS Regexp
- \a (bell)
- \e (escape)
- \A (start of string)
- \Z (end of string, before final line break)
- \z (end of string)
- Forward references \1 through \9
- Backreferences to failed groups also fail
- (?>regex) (atomic group)
- \G (start of match attempt)
- (?#comment)
- Free-spacing syntax supported
- Character class is a single token
- # starts a comment
- [:alpha:] POSIX character class
- (?i) (case insensitive) (JavaScript supports /i only)
- (?s) (dot matches newlines) (?m)
- (?m) (^ and $ match at line breaks) (/m only in JavaScript)
- (?x) (free-spacing mode)
- (?-ismxn) (turn off mode modifiers)
- (?ismxn:group) (mode modifiers local to group)
Supported by Ruby and not JS
- \cA through \cZ (control character)
- \ca through \cz (control character)
- \u0000 through \uFFFF (Unicode character)
Supported by JS and not Ruby
Boolean
- JS Boolean object
- Ruby class Boolean (not FalseClass and TrueClass)
Time
- JS Date object
Block, Proc, Lambda
- Lambda acts like a Proc
var array = [1,2,3]
// _p stands for proc
array.$each_with_index._p = function(number, index){ console.log(number, index) }
array.$each_with_index.call(array)
Calling Ruby method with a block
var array = [1,2,3]
// _p stands for proc
Opal.block_send(array, 'each_with_index', function(number, index){ console.log(number, index) })Or
to_n
class Toon
def set_window_value
@complex_value = {a: [1,2,3], b: {c: 1}}
`window.value = #{@complex_value.to_n}`
end
endJSON
class Jsonify
def initialize
@a = JSON.parse('{"a": 1, "b": [1,2,3]}')
puts @a.inspect
puts @a.to_json
end
end
Jsonify.new{"a"=>1, "b"=>[1, 2, 3]}
{"a":1, "b":[1, 2, 3]}outputs
Debugging Opal
Generated JS
class Auto
def initialize(color, x_pos, y_pos)
@color = color
@x_pos = x_pos
@y_pos = y_pos
end
def drive(x_velocity, y_velocity)
@x_pos += x_velocity
@y_pos += y_velocity
end
end/* Generated by Opal 0.6.2 */
(function($opal) {
var self = $opal.top, $scope = $opal, nil = $opal.nil, $breaker = $opal.breaker, $slice = $opal.slice, $klass = $opal.klass;
$opal.add_stubs(['$+']);
return (function($base, $super) {
function $Auto(){};
var self = $Auto = $klass($base, $super, 'Auto', $Auto);
var def = self._proto, $scope = self._scope;
def.x_pos = def.y_pos = nil;
def.$initialize = function(color, x_pos, y_pos) {
var self = this;
self.color = color;
self.x_pos = x_pos;
return self.y_pos = y_pos;
};
return (def.$drive = function(x_velocity, y_velocity) {
var self = this;
self.x_pos = self.x_pos['$+'](x_velocity);
return self.y_pos = self.y_pos['$+'](y_velocity);
}, nil) && 'drive';
})(self, null)
})(Opal);Ruby Exceptions
def h
raise "errors here"
end
def g
h
end
def f
g
end
begin
f
rescue Exception => e
puts e.message
e.backtrace.each { |b| puts b }
endSurround all top level calls with rescue
errors here
Error: errors here
at r.$new (http://opalrb.org/javascripts/try.js:5:19603)
at n.h.$raise (http://opalrb.org/javascripts/try.js:5:14975)
at n.$opal.Object._proto.$h (eval at <anonymous> (http://opalrb.org/javascripts/try.js:18:31863), <anonymous>:9:17)
at n.$opal.Object._proto.$g (eval at <anonymous> (http://opalrb.org/javascripts/try.js:18:31863), <anonymous>:14:17)
at n.$opal.Object._proto.$f (eval at <anonymous> (http://opalrb.org/javascripts/try.js:18:31863), <anonymous>:19:17)
at eval (eval at <anonymous> (http://opalrb.org/javascripts/try.js:18:31863), <anonymous>:22:15)
at $TryOpal.eval (eval at <anonymous> (http://opalrb.org/javascripts/try.js:18:31863), <anonymous>:30:3)
at $TryOpal.def.$eval_code (http://opalrb.org/javascripts/try.js:18:31858)
at $TryOpal.def.$run_code (http://opalrb.org/javascripts/try.js:18:31752)
at e._p.o (http://opalrb.org/javascripts/try.js:18:31173)Interacting in JS console
> auto = Opal.Auto.$new('red', 0, 0)
< $Auto {_id: 4544, color: "red", x_pos: 0, y_pos: 0, constructor: function…}
> auto.x_pos
< 0
> auto.color
< "red"
> auto.$drive(1, 10)
< 10
> auto.x_pos
< 1
> auto.y_pos
< 10Uncaught Type Error
> Uncaught TypeError: undefined is not a function application:50
def.$update_every_succeeded application:50
$a._p.TMP_7 action_syncer.js?body=1:167
Opal.$yieldX runtime.js?body=1:602
def.$call.TMP_2 proc.js?body=1:37
def.$succeed http.js?body=1:143
settings.success http.js?body=1:86
fire jquery.js?body=1:3075
self.fireWith jquery.js?body=1:3187
done jquery.js?body=1:8254
callbackOpal Integrated
- Nodejs
- Sinatra
- Rails (lite)
- Rails (heavy)
Nodejs
- npm install -g opal
- see opal/opal-node
# app.rb
require 'http/server'
HTTP::Server.start 3000 do
[200, {'Content-Type' => 'text/plain'}, ["Hello World!\n"]]
endExpress.new do
get '/' do |req, res|
res.send 200, 'hulla!'
end
end.listen 3000Express.js
A simple Express.js wrapper example can be found in examples/express-wrapper.rb
Sinatra
gem 'sinatra'
gem 'opal'Gemfile
require 'sinatra'
require 'opal'
map '/assets' do
env = Opal::Environment.new
env.append_path 'opal'
run env
end
get '/' do
<<-HTML
<!doctype html>
<html>
<head>
<script src="/assets/application.js"></script>
</head>
</html>
HTML
end
run Sinatra::Applicationconfig.ru
require 'opal'
puts "wow, running ruby!"opal/application.rb
(transpiled)
$ bundle exec rackupRunning it
Rails (lite) - part 1
gem 'opal-rails'add to Gemfile
//= require opal
//= require opal_ujs
//= require turbolinks
//= require_tree .
app/assets/javascripts/application.js
puts "G'day world!" # check the console!
# Dom manipulation
require 'opal-jquery'
Document.ready? do
Element.find('body > header').html = '<h1>Hi there!</h1>'
endapp/assets/javascripts/greeter.js.rb
Rails (lite) - part 2
Server side templating
%article.post
%h1.title= post.title
.body= post.body
%a#show-comments Display Comments!
.comments(style="display:none;")
- post.comments.each do |comment|
.comment= comment.body
:opal
Document.ready? do
Element.find('#show-comments').on :click do |click|
click.prevent_default
click.current_target.hide
Element.find('.comments').effect(:fade_in)
end
endapp/views/posts/show.html.haml
Gemfile
gem 'haml-rails'Rails (lite) - part 3
<h1>Hello from <%= self.name %> using ERB</h1>
ERB
Haml
app/assets/javascripts/hello.opalerb
app/assets/javascripts/hello.haml
Client side templating
%h1= "Hello from #{self.name} using HAML"
require 'opal'
require 'erbtest'
require 'hamltest'
Document.ready? do
Element.find("#erbtest").html = Template["erbtest"].render(Struct.new(:name).new('opal'))
Element.find("#hamltest").html = Template["hamltest"].render(Struct.new(:name).new('opal'))
end
app/assets/javascripts/show_template.js.rb
Rspec
- Run rspec in browser
- Run rspec with phantomjs
Rails (heavy)
Share everything

Sharing views causes cascade
views => controllers => models, routes

opal-activerecord
- https://github.com/boberetezeke/opal-activerecord
- In memory or localStorage backing
opal-actionpack
- https://github.com/boberetezeke/opal-actionpack
- creates controller, runs action for route
- renders view
- runs client controller
- supports going to new routes without server
Sample App
- Coming soon
Twitter: @boberetezeke
Github: boberetezeke
My Info
Name: Steve Tuckner
OpalRB
By boberetezeke
OpalRB
- 1,310
