Is That a Ruby In Your Browsers?



Is That a Ruby In Your Browsers?

0 1


ruby-in-your-browsers

Slides for a talk about Opal

On Github bspaulding / ruby-in-your-browsers

Is That a Ruby In Your Browsers?

An Introduction To Opal

  • Welcome
  • Introduce yourself

PSA for Examples

Ruby code is compiled and run live, and the JavaScript is verbose!

As such, they are best viewed in a browser

So if you are reading a static slide, go here:

bspaulding.github.io/ruby-in-your-browsers

What Is Opal?

Ruby to JavaScript Transpiler

AKA a "source to source" compiler

puts "Hello, World"

/* Generated by Opal 0.7.0.beta2 */
(function(Opal) {
  Opal.dynamic_require_severity = "error";
  var self = Opal.top, $scope = Opal, nil = Opal.nil, $breaker = Opal.breaker, $slice = Opal.slice;

  Opal.add_stubs(['$puts']);
  return self.$puts("Hello, World")
})(Opal);

What Is Opal?

JavaScript bridge

Browser APIs, NodeJS Support

puts %x{document.title}
%x{
  console.log(#{ RUBY_ENGINE_VERSION })
}

/* Generated by Opal 0.7.0.beta2 */
(function(Opal) {
  Opal.dynamic_require_severity = "error";
  var self = Opal.top, $scope = Opal, nil = Opal.nil, $breaker = Opal.breaker, $slice = Opal.slice;

  Opal.add_stubs(['$puts']);
  self.$puts(document.title);
  
  console.log($scope.get('RUBY_ENGINE_VERSION'))
;
})(Opal);

What Is Opal?

JavaScript bridge

require 'native'
win = Native(`window`)
puts win.location.href
# win.alert('+1 for native bridging!')

/* Generated by Opal 0.7.0.beta2 */
(function(Opal) {
  Opal.dynamic_require_severity = "error";
  var self = Opal.top, $scope = Opal, nil = Opal.nil, $breaker = Opal.breaker, $slice = Opal.slice, win = nil;

  Opal.add_stubs(['$require', '$Native', '$puts', '$href', '$location']);
  self.$require("native");
  win = self.$Native(window);
  return self.$puts(win.$location().$href());
})(Opal);

What Is Opal?

"a gemstone consisting of hydrated silica, typically semitransparent and showing varying colors against a pale or dark background"

New Oxford American Dictionary

What Is Opal?

National Gemstone of Australia

Wikipedia

http://www.patrickmclaurin.com/wordpress/wp-content/uploads/2013/06/get_on_with_it.png

Example: Fibonacci

def fib(n)
  n <= 1 ? 1 : fib(n - 1) + fib(n - 2)
end

puts (1..10).
  map {|n| fib n }.
  map(&:to_s).
  join(", ")
/* Generated by Opal 0.7.0.beta2 */
(function(Opal) {
  Opal.dynamic_require_severity = "error";
  var $a, $b, $c, $d, TMP_1, self = Opal.top, $scope = Opal, nil = Opal.nil, $breaker = Opal.breaker, $slice = Opal.slice, $range = Opal.range;

  Opal.add_stubs(['$<=', '$+', '$fib', '$-', '$puts', '$join', '$map', '$to_proc']);
  Opal.Object.$$proto.$fib = function(n) {
    var self = this;

    if (n['$<='](1)) {
      return 1
      } else {
      return self.$fib(n['$-'](1))['$+'](self.$fib(n['$-'](2)))
    };
  };
  return self.$puts(($a = ($b = ($c = ($d = $range(1, 10, false)).$map, $c.$$p = (TMP_1 = function(n){var self = TMP_1.$$s || this;
if (n == null) n = nil;
  return self.$fib(n)}, TMP_1.$$s = self, TMP_1), $c).call($d)).$map, $a.$$p = "to_s".$to_proc(), $a).call($b).$join(", "));
})(Opal);

This example shows off several ruby features:
  • implicit returns
  • ranges
  • enumeration with blocks
  • symbol to_proc

Example: Hamming Distance

def hamming(a,b)
    a.split("").
    zip(b.split("")).
    select {|(a,b)| a != b }.
    length
end

puts hamming("rubyist", "opalist")
puts hamming("happy", "yappy")
puts hamming("goose", "geese")
/* Generated by Opal 0.7.0.beta2 */
(function(Opal) {
  Opal.dynamic_require_severity = "error";
  var self = Opal.top, $scope = Opal, nil = Opal.nil, $breaker = Opal.breaker, $slice = Opal.slice;

  Opal.add_stubs(['$length', '$select', '$!', '$==', '$zip', '$split', '$puts', '$hamming']);
  Opal.Object.$$proto.$hamming = function(a, b) {
    var $a, $b, TMP_1, self = this;

    return ($a = ($b = a.$split("").$zip(b.$split(""))).$select, $a.$$p = (TMP_1 = function(a, b){var self = TMP_1.$$s || this;
if (a == null) a = nil;if (b == null) b = nil;
    return a['$=='](b)['$!']()}, TMP_1.$$s = self, TMP_1), $a).call($b).$length();
  };
  self.$puts(self.$hamming("rubyist", "opalist"));
  self.$puts(self.$hamming("happy", "yappy"));
  return self.$puts(self.$hamming("goose", "geese"));
})(Opal);

Ruby features:
  • Collection helpers: split, zip, select/filter
  • anonymous blocks
  • feaux pattern matching for block arg

Example: Classes

class Greeter
  def initialize(name = "Opal")
    @name = name
  end

  def say_hello
    puts greeting
  end

  def greeting
    "Hello, #{@name}!"
  end
end

class LoudGreeter < Greeter
  def greeting
    super.upcase
  end
end

greeter = Greeter.new
greeter.say_hello
/* Generated by Opal 0.7.0.beta2 */
(function(Opal) {
  Opal.dynamic_require_severity = "error";
  var self = Opal.top, $scope = Opal, nil = Opal.nil, $breaker = Opal.breaker, $slice = Opal.slice, $klass = Opal.klass, greeter = nil;

  Opal.add_stubs(['$puts', '$greeting', '$upcase', '$new', '$say_hello']);
  (function($base, $super) {
    function $Greeter(){};
    var self = $Greeter = $klass($base, $super, 'Greeter', $Greeter);

    var def = self.$$proto, $scope = self.$$scope;

    def.name = nil;
    def.$initialize = function(name) {
      var self = this;

      if (name == null) {
        name = "Opal"
      }
      return self.name = name;
    };

    def.$say_hello = function() {
      var self = this;

      return self.$puts(self.$greeting());
    };

    return (def.$greeting = function() {
      var self = this;

      return "Hello, " + (self.name) + "!";
    }, nil) && 'greeting';
  })(self, null);
  (function($base, $super) {
    function $LoudGreeter(){};
    var self = $LoudGreeter = $klass($base, $super, 'LoudGreeter', $LoudGreeter);

    var def = self.$$proto, $scope = self.$$scope, TMP_1;

    return (def.$greeting = TMP_1 = function() {var $zuper = $slice.call(arguments, 0);
      var self = this, $iter = TMP_1.$$p, $yield = $iter || nil;

      TMP_1.$$p = null;
      return Opal.find_super_dispatcher(self, 'greeting', TMP_1, $iter).apply(self, $zuper).$upcase();
    }, nil) && 'greeting'
  })(self, $scope.get('Greeter'));
  greeter = $scope.get('Greeter').$new();
  return greeter.$say_hello();
})(Opal);

Example: Modules

class Array
  def all?
    each {|n| return false unless n }
  end
end

module Validatable
  def self.included(base)
    base.extend(ClassMethods)
  end

  def valid?
    self.class.validations.
      map {|attribute, block|
        block.call(self.send(attribute))
      }.all?
  end

  module ClassMethods
    def validate(attribute, &block)
      validations[attribute] = block
    end

    def validations
      @validations ||= {}
    end
  end
end

class Person
  include Validatable

  attr_accessor :name

  validate(:name) {|value|
    !value.to_s.empty?
  }
end

brad = Person.new
puts brad.valid?
brad.name = "Bradley"
puts brad.valid?
/* Generated by Opal 0.7.0.beta2 */
(function(Opal) {
  Opal.dynamic_require_severity = "error";
  var $a, $b, self = Opal.top, $scope = Opal, nil = Opal.nil, $breaker = Opal.breaker, $slice = Opal.slice, $klass = Opal.klass, $module = Opal.module, $hash2 = Opal.hash2, brad = nil;

  Opal.add_stubs(['$each', '$extend', '$all?', '$map', '$call', '$send', '$validations', '$class', '$[]=', '$include', '$attr_accessor', '$validate', '$!', '$empty?', '$to_s', '$new', '$puts', '$valid?', '$name=']);
  (function($base, $super) {
    function $Array(){};
    var self = $Array = $klass($base, $super, 'Array', $Array);

    var def = self.$$proto, $scope = self.$$scope;

    return (def['$all?'] = function() {try {

      var $a, $b, TMP_1, self = this;

      return ($a = ($b = self).$each, $a.$$p = (TMP_1 = function(n){var self = TMP_1.$$s || this;
if (n == null) n = nil;
      if (n !== false && n !== nil) {
          return nil
          } else {
          Opal.ret(false)
        }}, TMP_1.$$s = self, TMP_1), $a).call($b);
      } catch ($returner) { if ($returner === Opal.returner) { return $returner.$v } throw $returner; }
    }, nil) && 'all?'
  })(self, null);
  (function($base) {
    var self = $module($base, 'Validatable');

    var def = self.$$proto, $scope = self.$$scope;

    Opal.defs(self, '$included', function(base) {
      var self = this;

      return base.$extend($scope.get('ClassMethods'));
    });

    def['$valid?'] = function() {
      var $a, $b, TMP_2, self = this;

      return ($a = ($b = self.$class().$validations()).$map, $a.$$p = (TMP_2 = function(attribute, block){var self = TMP_2.$$s || this;
if (attribute == null) attribute = nil;if (block == null) block = nil;
      return block.$call(self.$send(attribute))}, TMP_2.$$s = self, TMP_2), $a).call($b)['$all?']();
    };

    (function($base) {
      var self = $module($base, 'ClassMethods');

      var def = self.$$proto, $scope = self.$$scope, TMP_3;

      def.$validate = TMP_3 = function(attribute) {
        var self = this, $iter = TMP_3.$$p, block = $iter || nil;

        TMP_3.$$p = null;
        return self.$validations()['$[]='](attribute, block);
      };

      def.$validations = function() {
        var $a, self = this;
        if (self.validations == null) self.validations = nil;

        return ((($a = self.validations) !== false && $a !== nil) ? $a : self.validations = $hash2([], {}));
      };
            ;Opal.donate(self, ["$validate", "$validations"]);
    })(self);
        ;Opal.donate(self, ["$valid?"]);
  })(self);
  (function($base, $super) {
    function $Person(){};
    var self = $Person = $klass($base, $super, 'Person', $Person);

    var def = self.$$proto, $scope = self.$$scope, $a, $b, TMP_4;

    self.$include($scope.get('Validatable'));

    self.$attr_accessor("name");

    return ($a = ($b = self).$validate, $a.$$p = (TMP_4 = function(value){var self = TMP_4.$$s || this;
if (value == null) value = nil;
    return value.$to_s()['$empty?']()['$!']()}, TMP_4.$$s = self, TMP_4), $a).call($b, "name");
  })(self, null);
  brad = $scope.get('Person').$new();
  self.$puts(brad['$valid?']());
  (($a = ["Bradley"]), $b = brad, $b['$name='].apply($b, $a), $a[$a.length-1]);
  return self.$puts(brad['$valid?']());
})(Opal);

How Ruby is it?

RubySpec

Version Date Examples 0.5.5 2013-11 2,715* 0.6.3 2014-11 3,070 0.7.0.beta1 2014-10 3,445 0.7.0.beta2 2014-11 3,601 0.7.0.beta3 2014-11 3,603

* not all passing (for me!)

For context, ~20k rubyspecs total

How Ruby is it?

Missing A Few Things:

  • Mutable Strings / Symbols
    • no #<< or #gsub!
  • Encodings
  • Threads
  • Frozen Objects
  • method_added/method_removed
    • considered a bug, will be fixed
  • private/protected
  • C Extensions
  • File/Network IO
    • coming in 0.7 for Node.js
  • method_missing

Debugging

Testing

RSpec via opal-rspec

Adds async support:

async 'HTTP requests should work' do
  HTTP.get('/users/1.json') do |res|
    run_async {
      expect(res).to be_ok
    }
  end
end

Notable Libraries

opal-browser

Browser API wrapper:

DOM, CSS, AJAX, WebSockets, SSE, History, Storage, SQL

$document.ready do
  DOM {
    div.info {
      span.red "I'm all cooked up."
    }
  }.append_to($document.body)
end

Notable Libraries

opal-jquery

"toll-free" bridge to JQuery

foos = Element.find('.foo')
# => [<div class="foo">, ...]

foos.class
# => JQuery

foos.on(:click) do
  alert "element was clicked"
end

Notable Libraries

Templating

support for erb and haml

require 'template'

template = Template['user']
context  = User.new('Ford Prefect')

puts template.render(context)
# => "<div>...</div>"

Template.paths
# => [#<Template: 'views/user'>, #<Template: 'login'>]

Notable Libraries

Promise

require 'promise'
first = get_json '/users/1.json'
second = get_json '/users/2.json'

Promise.when(first, second).then do |user1, user2|
  puts "got users: #{user1}, #{user2}"
end.fail do
  alert "Something bad happened"
end

Frameworks

  • Vienna
    • client side
    • MVC
  • Lissio
    • client side
    • Component centric
  • Volt
    • full stack, similar to meteor
    • hybrid mvc / component structure
    • reactive view bindings
    • persistence
    • messaging

Resources

Opal: A New Hope; Old, talks a lot about possibly defunct, smalltalk inspired opal-inspector project

Thanks!

github/bspaulding

twitter/bradspaulding