On Github bspaulding / ruby-in-your-browsers
An Introduction To Opal
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:
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);
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);
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);
"a gemstone consisting of hydrated silica, typically semitransparent and showing varying colors against a pale or dark background"
New Oxford American Dictionary
National Gemstone of Australia
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);
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);
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);
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);
* not all passing (for me!)
For context, ~20k rubyspecs total
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
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
"toll-free" bridge to JQuery
foos = Element.find('.foo') # => [<div class="foo">, ...] foos.class # => JQuery foos.on(:click) do alert "element was clicked" end
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'>]
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
github/bspaulding
twitter/bradspaulding