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 / 2

generates

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.5

Strings

  • 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"
end

outputs

strings and symbols are the same

Arrays

  • 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
end

JSON

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 }
end

Surround 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
< 10

Uncaught 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
    callback

Opal 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"]]
end
Express.new do
  get '/' do |req, res|
    res.send 200, 'hulla!'
  end
end.listen 3000

Express.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::Application

config.ru

require 'opal'

puts "wow, running ruby!"

opal/application.rb

(transpiled)

$ bundle exec rackup

Running 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>'
end

app/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
  end

app/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