The Enterprise Strikes Back
The Ruby Trilogy: Part III
The Enterprise Strikes Back
The Ruby Trilogy: Part III
Burke Libbey   Stefan Penner
@burkelibbey   @stefanpenner
Burke Libbey   Stefan Penner
@burkelibbey   @stefanpenner
Burke Libbey   Stefan Penner
@burkelibbey   @stefanpenner
Overview


• Ruby on the JVM (20m)
• Ruby as Glue (20m)
• Testing Java with RSpec (10m)
• Cloud City (15m)
Ruby              Java




+ Productive   + Not Scary
- Scary        - Less productive
Ruby in Java




 + Not Scary
 + Productive
http://jruby.org
• Full ruby implementation on the JVM
• Access to both ruby and Java libraries
• Can be deployed to existing Java
  infrastructure
The many flavours of ruby
 • MRI (and YARV)
 • JRuby
 • IronRuby
 • MacRuby
 • Rubinius
 • ...and several more...
java.lang.System.out.println("Hello World")
The Magic Sauce



 require 'java'
   (contains up to 30% midichlorians)
Calling Java from ruby

f = javax.swing.JFrame.new
f.getContentPane.add(javax.swing.JLabel.new("Hello World"))
close_operation = javax.swing.JFrame::EXIT_ON_CLOSE
f.setDefaultCloseOperation(close_operation)
f.pack
f.setVisible(true)
Impedance Mismatch

f = javax.swing.JFrame.new
f.getContentPane.add(javax.swing.JLabel.new("Hello World"))
close_operation = javax.swing.JFrame::EXIT_ON_CLOSE
f.setDefaultCloseOperation(close_operation)
f.pack
f.setVisible(true)



                 Let’s break this down.
Impedance Mismatch
    “Getters” and “Setters” are non-idiomatic in ruby.
f = javax.swing.JFrame.new
f.getContentPane.add(javax.swing.JLabel.new("Hello World"))
  getContentPane
close_operation = javax.swing.JFrame::EXIT_ON_CLOSE
f.setDefaultCloseOperation(close_operation)
f.pack
f.setVisible(true)

     snake_case is generally preferred to CamelCase
Impedance Mismatch
      JavaBean properties can be accessed like this:
f = javax.swing.JFrame.new
f.content_pane.add(javax.swing.JLabel.new("Hello World"))
close_operation = javax.swing.JFrame::EXIT_ON_CLOSE
f.setDefaultCloseOperation(close_operation)
f.pack
f.setVisible(true)

   “get” disappears, CamelCase changes to snake_case
Impedance Mismatch
                  “set” is replaced by “=”

f = javax.swing.JFrame.new
f.content_pane.add(javax.swing.JLabel.new("Hello World"))
close_operation = javax.swing.JFrame::EXIT_ON_CLOSE
f.default_close_operation = close_operation
f.pack
f.visible = true
              “setDefaultCloseOperation(x)”
                         becomes
               “default_close_operation = x”
Ugliness Abounds

f = javax.swing.
    javax.swing.JFrame.new
f.content_pane.add(javax.swing.JLabel.new("Hello World"))
                   javax.swing.
close_operation = javax.swing.
                  javax.swing.JFrame::EXIT_ON_CLOSE
f.default_close_operation = close_operation
f.pack
f.visible = true



              Namespaces everywhere!
Ugliness Abounds
java_import 'javax.swing.JFrame'
java_import 'javax.swing.JLabel'
f = JFrame.new
f.content_pane.add(JLabel.new("Hello World"))
                   JLabel.new
close_operation = JFrame::EXIT_ON_CLOSE
f.default_close_operation = close_operation
f.pack
f.visible = true



     java_import adds classes to a ruby context
100% Rubified™

java_import 'javax.swing.JFrame'
java_import 'javax.swing.JLabel'

JFrame.new.tap do |f|
  f.content_pane.add JLabel.new("Hello World")
  f.default_close_operation = JFrame::EXIT_ON_CLOSE
  f.pack
  f.visible = true
end
Method Rubification™



• In general, CamelCase Java methods
  can optionally be transliterated to snake
  case.
Uniform Access Principle
     All services offered by a
     module should be available
     through a uniform notation,
     which does not betray whether
     they are implemented through
     storage or through
     computation.


       - Bertrand Meyer
Two Rules



• Everything's an Object
• Objects expose Methods and Only Methods
Method Rubification™


   Java   System.currentTimeMillis()
   Ruby   System.current_time_millis
Seriously though, not
                             actually trademarked.



Method Rubification™


   Java   person.getName()
   Ruby   person.name
Someone should
                                get on that.



Method Rubification™


   Java   person.setAge(42)
   Ruby   person.age = 42
Method Rubification™
FALSE!              TRUE!

      Java   person.isJedi()
      Ruby   person.jedi?
Is ruby an acceptable
         Java?            (http://bit.ly/pfNluA)




• Most Java code can literally be written
  as ruby with few high-level changes.

• A more relaxed type system and syntax
  often has productivity benefits.
Exhibit A
       // interfaces = HashMap{ label => NetworkInterface }
Java   Collection c = interfaces.values();
       Iterator itr = c.iterator();

       while(itr.hasNext()) {
         NetworkInterface interface = itr.next();
         if (interface.isLoopback()) {
           return interface.getDisplayName();
         }
       }
                            VS.

       # interfaces = {label => NetworkInterface}
Ruby   interfaces.values.find(&:loopback?).display_name
Exhibit A
       // interfaces = HashMap{ label => NetworkInterface }
Java   Collection c = interfaces.values();
       Iterator itr = c.iterator();

       while(itr.hasNext()) {
         NetworkInterface interface = itr.next();
         if (interface.isLoopback()) {
           return interface.getDisplayName();
         }
       }
                             VS.

       # interfaces = {label => NetworkInterface}
Ruby   interfaces.values.find(&:loopback?).display_name
                1       2     345          67


             Only 7 characters of syntactic support!
“Are you suggesting I write
   all my Java in ruby?!”

 • Not really...
   • JRuby is much slower than Java
      (doesn’t matter as often as you’d think)


   • Ruby’s added expressiveness makes it
      easier to shoot yourself in the foot.
      (or, in fact, to lazily clone an infinite number of your feet every
      planck length between your gun and your target)
“Are you suggesting I write
   all my Java in ruby?!”
 • ...but maybe sometimes.
 • One possibility:
   • Encode high-level logic in expressive
     and concise ruby code

   • Supporting code in fast, safe Java
   • Mix and match as appropriate
Ruby as Glue


• Ruby is great for:
  • wiring existing codebases together
  • other miscellaneous tasks
Perl      The original
       “Swiss Army Knife”
Perl   Pretty much mean
        the same thing.


       • “glue”
       • “duct tape”
       • “swiss army knife”
Perl   Ruby
Subcategories of “Glue”



     • Wiring stuff together
     • Sysadmin tasks
Wiring stuff together



• There’s a lot we could cover here, but:
Nokogiri



• An extremely user-friendly XML library
• fast! (wraps libxml2)
Nokogiri
require 'open-uri'
require 'nokogiri'
html = open("http://slashdot.org").read
doc = Nokogiri::XML(html)
(doc/".story a").each do |link|
  puts "#{link.text} (#{link.attr('href')})"
end

# Britain's Broadband Censors: a Bunch of Students (//yro.slashdot...
# ...
Don’t do this.

(Nokogiri::XML(open("http://slashdot.org").read)/".story a").each{|a|puts
"#{a.text} (#{a.attr("href")})"}
Sysadmin with Ruby



• Nice system APIs
  (quite similar to perl’s)


• System provisioning libraries/DSLs
Provisioning systems


       • Puppet
       • Chef
       • Vagrant
Puppet
• Describe system, and puppet sets it up
• Nontrivial, but the general idea is:
  • I want mysql
  • I want nginx < 0.8
  • I want this cron job: “....”
  • Go.
                 http://puppetlabs.com/
Puppet
class postgres-server {
  package { postgresql-server:
     ensure => latest
  }
  group { postgres: gid => 26 }
  user { postgres:
    comment => "PostgreSQL Server",
    uid => 26,
    gid => 26,
    home => "/var/lib/pgsql",
    shell => "/bin/bash"
  }
  service { postgresql:
     running => true,
     pattern => "/usr/bin/postmaster",
     require => package["postgresql-server"]
  }
}
Puppet



• Fantastic for defining a reproducible
  production environment
Chef


• Same idea as puppet
• Somewhat less powerful
• Slightly more approachable to most
  ruby developers



            http://www.opscode.com/chef/
Chef
package "sudo" do
  action :upgrade
end

template "/etc/sudoers" do
  source "sudoers.erb"
  mode 0440
  owner "root"
  group "root"
  variables(
    :sudoers_groups => node['authorization']['sudo']['groups'],
    :sudoers_users => node['authorization']['sudo']['users'],
    :passwordless => node['authorization']['sudo']['passwordless']
  )
end
Puppet        Chef

Configuration Language    custom         ruby

     Targeted at        production   production

    “Powerfulness”         Lots      Mostly lots

       Verdict            Good        Different

      Written in          ruby          ruby
Vagrant

• Uses puppet and/or chef
• End product is a virtual machine for
  development use

• Consistent system for all developers, per
  project
Vagrant



• If you use puppet or chef, vagrant lets
  you test production locally
Suggestions


• As a non-ruby-dev:
 • Use puppet
 • Consider vagrant
Suggestions


• As a ruby developer:
  • Consider chef and puppet, use
    whichever suits your taste and needs

  • Consider vagrant
Databases
In the Real World,
In the Real World,

  We have Data
In the Real World,

     We have Data

Which lives in Databases
Databases Are Always
      simple
All our data is
  ALWAYS
in the same DBMS
Reality Check
Luckily
  We have

ODBC + JDBC
Luckily
  We have

ODBC + JDBC
   +
  Ruby
Ruby Gives you Options
• Active Record
• Sequel
• DataMapper
• more
Active Record
 Design pattern coined by Martin Fowler in
 “Patterns of enterprise application
 architecture”.

 Also, Ruby on Rails’s default ORM.

• Lots of Power
• Lots of Opinion
• Might fight with you (for non-standard uses)
https://github.com/rails/rails/tree/master/activerecord
Active Record Syntax (Raw)
require 'active_record'

ActiveRecord::Base.establish_connection({
   :adapter => 'mysql',
   :database => 'test_database',
   :username => 'tester',
   :password => 'test22'
})

connection = ActiveRecord::Base.connection

connection.tables
> ['users', 'products', 'ducks', 'oranges']

users = []
connection.execute('SELECT * FROM users').each_hash do |user|
  users << user
end

users
> .... array of users, each user as a hash.

users.first
> { :id => 1, :username => 'stefan', :password => 'is_super_secure' }
Active Record Syntax (ORM)

require 'active_record'

ActiveRecord::Base.establish_connection({
   :adapter => 'mysql',
   :database => 'test_database',
   :username => 'tester',
   :password => 'test22'
})

# class <camel_case_singular_table_name> < ActiveRecord::Base
class User < ActiveRecord::Base
  # if the table's name does not fit convention, it can be manually overridden.
  # table_name :users_table
end

User.first
> #<User id: 2,   :name => 'stefan', :password => 'is_super_secure' >

User.find(2)
> #<User id: 2,   :name => 'stefan', :password => 'is_super_secure' >

User.where(:name => 'stefan')
> #<User id: 2, :name => 'stefan', :password => 'is_super_secure' >
Active Record Syntax (AREL)

User.first
> #<User id: 2,   :name => 'stefan', :password => 'is_super_secure' >

User.find(2)
> #<User id: 2,   :name => 'stefan', :password => 'is_super_secure' >

User.where(:name => 'stefan')
> #<User id: 2, :name => 'stefan', :password => 'is_super_secure' >

User.where(:name => 'stefan').order('id ASC')
> #<User id: 2, :name => 'stefan', :password => 'is_super_secure' >
Sequel
Elegant Full featured database toolkit for ruby.
• Supports a ton of DBMS’s
• DSL
• ORM


 http://sequel.rubyforge.org/

 Supports
 - ADO, Amalgalite, DataObjects, DB2, DBI, DO, Firebird, ibmdb, Informix,
 JDBC, MySQL, Mysql2, ODBC, OpenBase, Oracle, PostgreSQL, SQLite3,
 Swift, and tinytds
require "sequel"
                                     Sequel Example
# connect to an in-memory database
DB = Sequel.sqlite

# create an items table
DB.create_table :items do
  primary_key :id
  String :name
  Float :price
end

# create a dataset from the items table
items = DB[:items]

# populate the table
items.insert(:name => 'abc', :price => rand * 100)
items.insert(:name => 'def', :price => rand * 100)
items.insert(:name => 'ghi', :price => rand * 100)

# print out the number of records
puts "Item count: #{items.count}"

# print out the average price
puts "The average price is: #{items.avg(:price)}"
Sequel ORM
require "sequel"

# connect to an in-memory database
DB = Sequel.sqlite

# create an items table
DB.create_table :items do
  primary_key :id
  String :name
  Float :price
end

# create a dataset from the items table
class Item < Sequel::Model(:items)
  # associations
  # validations
end

# populate the table
Item.create(:name => 'abc', :price => rand * 100)
Item.create(:name => 'def', :price => rand * 100)
Item.create(:name => 'ghi', :price => rand * 100)

# print out the number of records
puts "Item count: #{Item.count}"

# print out the average price
puts "The average price is: #{Item.avg(:price)}"
Sequel ORM


    +
  jRuby
ready out of the box
Sequel ORM


    +
IRONRuby
 apparently works?
Case Study (Access)
•   Access (top 2 solutions)

    •   Solution 1 (Cross Platform)

        •    jRuby

        •    JDBC

        •    HXTT’s driver (www.hxtt.com)

        •    Sequel

    •   Solution 2 (Windows Only)

        •    Ruby

        •    ADO via WIN32OLE

        •    Sequel
Case Study (Access)
Case Study (Access)
 1. Download jRuby
    http://jruby.org/
Case Study (Access)
 1. Download jRuby
    http://jruby.org/


 2. Install Sequel
     gem install Sequel
Case Study (Access)
 1. Download jRuby
    http://jruby.org/


 2. Install Sequel
     gem install Sequel


 3. Download JDBC Access Driver
   http://www.hxtt.com/access.html
Case Study (Access)
require   'rubygems'
require   'sequel'
require   'sequel/adapters/jdbc'
require   'sequel/jdbc_hxtt_adapter'
require   '../Support/Access_JDBC40.jar'

root = File.expand_path "../../", __FILE__
path_to_db = root + "/Support/AccessThemeDemo.mdb"
DB = Sequel.connect("jdbc:access:////#{path_to_db}")
puts DB.tables

class Notes < Sequel::Model(:Notes)
end

class ThemeImages < Sequel::Model(:tbl_ThemeImages)
end

class Users < Sequel::Model(:tbl_Users)
end
ba-da-bing
But our new app
and our old database
    have different
system requirements.
We Want Isolation
_
  -Need
We Want Isolation
Power by.. Sinatra
                                 Web DSL

        get '/pictures' do
          ['picture1','picture2','picture2']
        end

        post   '/pictures'     {}
        put    '/pictures/:id' {}
        delete '/pictures/:id' {}


                          used at linkedIn
http://engineering.linkedin.com/44/linkedin-app-end-end-jruby-frontier-and-voldemort
sooo... big deal?
RestServer


• any jdbc database into a restful json
  service

• https://github.com/stefanpenner/restserver
Portability/Bundling

• Warbler
http://github.com/nicksieger/warbler

• single jar/war file
• compiled (if needed)
it’s an app!
Putting it all together (p1)
                ScreenShotr
                      download:
https://github.com/stefanpenner/screenshotr/zipball/master


     git@github.com:stefanpenner/screenshotr
Sorry, contrived
   example!
anyways...
Java Gives Us
   • Portability



Ruby Gives Us
   • Happiness
ScreenShotr

Normally Annoying re: Portability

• screen capture
• GUI




                                but thx jRuby
Lets Shoot some Screens
require 'java'

java_import   'java.awt.Robot'
java_import   'java.awt.Toolkit'
java_import   'java.awt.Rectangle'
java_import   'java.awt.Image'
java_import   'java.awt.image.BufferedImage'
java_import   'javax.imageio.ImageIO'

screenRect = Rectangle.new(Toolkit.default_toolkit.getScreenSize())
capture        = Robot.new.createScreenCapture(screenRect)

ImageIO.write(capture,'jpg',java.io.File.new('some_tmp_file.jpg'))
Hold UP
Hold UP
java_import ?
Hold UP
               java_import ?

why not require?
Hold UP
                    java_import ?

why not require?
  requiring classes just makes them discoverable later.
Hold UP
                    java_import ?

why not require?
  requiring classes just makes them discoverable later.


why not import?
Hold UP
                    java_import ?

why not require?
  requiring classes just makes them discoverable later.


why not import?
  Rake defines import
Hold UP
                    java_import ?

why not require?
  requiring classes just makes them discoverable later.


why not import?
  Rake defines import



                                                          weird...
And fill some clipboards

require 'java'

java_import 'java.awt.Toolkit'
java_import 'javax.imageio.ImageIO'

ss = StringSelection.new(public_url)
     Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, nil)
upload to server
gem install rest-client

require 'rubygems'

require 'rest-client'

RestClient.post('http://../resource',
                                :file => File.new('path/to/file'))
upload to server
gem install rest-client

require 'rubygems'

require 'rest-client'

RestClient.post('http://../resource',
                                :file => File.new('path/to/file'))
<Insert Segue Here>
...and Vice Versa



• We’ve done a lot of calling Java from ruby.
• The reverse is possible as well.
...and Vice Versa

import org.jruby.embed.InvokeFailedException;
import org.jruby.embed.ScriptingContainer;

// ...
ScriptingContainer container = new ScriptingContainer();
container.runScriptlet("puts "That's no Moon"");




                  http://en.wikipedia.org/wiki/Jruby
Testing Java with Ruby




“i find your lack of
 tests disturbing.”
Testing Java with Ruby

• JtestR is wonderful
  • Includes most of ruby’s leading
    testing libraries

  • supports ant and maven
  • easy to add to a project
  • takes advantage of jruby/java bridge
JtestR Installation

• Download jarfile from jtestr.codehaus.org
• Add to classpath
• Add a task to maven/ant
• Create tests in ./test or ./spec
• Run task
Example!
import java.util.HashMap

describe "An empty", HashMap do
  before :each do
    @hash_map = HashMap.new
  end

  it "accepts new entries" do
    @hash_map.put "foo", "bar"
    @hash_map.get("foo").should == "bar"
  end

  it "returns a keyset iterator that throws an exception on next" do
    proc do
      @hash_map.key_set.iterator.next
    end.should raise_error(java.util.NoSuchElementException)
  end
end
“i saw a city
in the clouds”
MYTH: Ruby can’t scale.
FACT: Ruby scales like a BOSS
FACT: Ruby scales like a BOSS
           (because it has to)
Fog

• “The Ruby Cloud Services Library”
• Lets you upload to S3, provision
  instances in EC2, set DNS records in
  DNSimple...

• ...and much more.
Fog




“
• Chewbacca-approved.
 whargarblbgarblgr-

                          ”
                 garblgragh
Fog




“
• Chewbacca-approved. (chewbaccaproved?)
 whargarblbgarblgr-
                       garblgragh          ”
2.5 imperial tons of
     providers
(         )eh.
2.5 imperial tons of
            H
     providers
Sinatra Example


#config.ru
require './lib/screen_shotr/server'
run ScreenShotr::Server.new
require 'sinatra'
require 'fog'
require 'digest/sha1'
module ScreenShotr
  class Server < Sinatra::Base
    def storage
      #@storage ||= Fog::Storage.new({
      # :provider => 'AWS',
      # :aws_access_key_id => ACCESS_KEY_ID,
      # :aws_secret_access_key => SECRET_ACCESS_KEY})

      @storage ||= Fog::Storage.new({
          :local_root => '~/fog',
          :provider   => 'Local'
      })

    def directory
      storage.directories.find("data").first or
        storage.directories.create(:key => 'data' )
    end

# snip ....
get '/' do
  "hello, world!"
end
post '/picture/create' do
  file     = params[:file]
  data     = file[:tempfile]

  #super secure filename
  filename = file[:filename]

  key = Digest::SHA1.hexdigest("super random seed"+Time.now.to_s)
  key << '.jpg'

  file = directory.files.create(
    :body => data.read,
    :key => key
  )
  file.public_url or "http://0.0.0.0:9292/picture/#{key}"
end
get '/picture/:key' do
  file = directory.files.get(params[:key])
  send_file file.send(:path)
end
rackup
and...
Fog Storage:
Kind of cool.
Fog Compute:
 Wicked cool.
Heroku

Classic Stack (run rack/rails)
Cedar Stack (run “anything”)
Heroku
Classic Stack (run rack/rails)
$ heroku create
Created sushi.herokuapp.com | git@heroku.com:sushi.git

$ git push heroku master
-----> Heroku receiving push
-----> Rails app detected
-----> Compiled slug size is 8.0MB
-----> Launching... done, v1
http://sushi.herokuapp.com deployed to Heroku
Heroku
Cedar Stack (run “anything”)
$ cat Procfile
web:           bundle                 exec         rails server -p $PORT
worker:        bundle                 exec         rake resque:work QUEUE=*
urgentworker: bundle                  exec         rake resque:work QUEUE=urgent
clock:         bundle                 exec         clockwork clock.rb

$ heroku scale web=4 worker=2 urgentworker=1 clock=1
Scaling processes... done




“Officially Everything”:
  Ruby, Node.js,Clojure, Java, Python, and Scala
“help me ruby...
you’re my only hope!”
“good... the force is
   strong with you.


                  Thanks!




a powerful rubyist you
    will become.”

The Enterprise Strikes Back

  • 1.
    The Enterprise StrikesBack The Ruby Trilogy: Part III
  • 2.
    The Enterprise StrikesBack The Ruby Trilogy: Part III
  • 3.
    Burke Libbey Stefan Penner @burkelibbey @stefanpenner
  • 4.
    Burke Libbey Stefan Penner @burkelibbey @stefanpenner
  • 5.
    Burke Libbey Stefan Penner @burkelibbey @stefanpenner
  • 6.
    Overview • Ruby onthe JVM (20m) • Ruby as Glue (20m) • Testing Java with RSpec (10m) • Cloud City (15m)
  • 7.
    Ruby Java + Productive + Not Scary - Scary - Less productive
  • 8.
    Ruby in Java + Not Scary + Productive
  • 9.
  • 10.
    • Full rubyimplementation on the JVM • Access to both ruby and Java libraries • Can be deployed to existing Java infrastructure
  • 11.
    The many flavoursof ruby • MRI (and YARV) • JRuby • IronRuby • MacRuby • Rubinius • ...and several more...
  • 12.
  • 13.
    The Magic Sauce require 'java' (contains up to 30% midichlorians)
  • 14.
    Calling Java fromruby f = javax.swing.JFrame.new f.getContentPane.add(javax.swing.JLabel.new("Hello World")) close_operation = javax.swing.JFrame::EXIT_ON_CLOSE f.setDefaultCloseOperation(close_operation) f.pack f.setVisible(true)
  • 15.
    Impedance Mismatch f =javax.swing.JFrame.new f.getContentPane.add(javax.swing.JLabel.new("Hello World")) close_operation = javax.swing.JFrame::EXIT_ON_CLOSE f.setDefaultCloseOperation(close_operation) f.pack f.setVisible(true) Let’s break this down.
  • 16.
    Impedance Mismatch “Getters” and “Setters” are non-idiomatic in ruby. f = javax.swing.JFrame.new f.getContentPane.add(javax.swing.JLabel.new("Hello World")) getContentPane close_operation = javax.swing.JFrame::EXIT_ON_CLOSE f.setDefaultCloseOperation(close_operation) f.pack f.setVisible(true) snake_case is generally preferred to CamelCase
  • 17.
    Impedance Mismatch JavaBean properties can be accessed like this: f = javax.swing.JFrame.new f.content_pane.add(javax.swing.JLabel.new("Hello World")) close_operation = javax.swing.JFrame::EXIT_ON_CLOSE f.setDefaultCloseOperation(close_operation) f.pack f.setVisible(true) “get” disappears, CamelCase changes to snake_case
  • 18.
    Impedance Mismatch “set” is replaced by “=” f = javax.swing.JFrame.new f.content_pane.add(javax.swing.JLabel.new("Hello World")) close_operation = javax.swing.JFrame::EXIT_ON_CLOSE f.default_close_operation = close_operation f.pack f.visible = true “setDefaultCloseOperation(x)” becomes “default_close_operation = x”
  • 19.
    Ugliness Abounds f =javax.swing. javax.swing.JFrame.new f.content_pane.add(javax.swing.JLabel.new("Hello World")) javax.swing. close_operation = javax.swing. javax.swing.JFrame::EXIT_ON_CLOSE f.default_close_operation = close_operation f.pack f.visible = true Namespaces everywhere!
  • 20.
    Ugliness Abounds java_import 'javax.swing.JFrame' java_import'javax.swing.JLabel' f = JFrame.new f.content_pane.add(JLabel.new("Hello World")) JLabel.new close_operation = JFrame::EXIT_ON_CLOSE f.default_close_operation = close_operation f.pack f.visible = true java_import adds classes to a ruby context
  • 21.
    100% Rubified™ java_import 'javax.swing.JFrame' java_import'javax.swing.JLabel' JFrame.new.tap do |f| f.content_pane.add JLabel.new("Hello World") f.default_close_operation = JFrame::EXIT_ON_CLOSE f.pack f.visible = true end
  • 22.
    Method Rubification™ • Ingeneral, CamelCase Java methods can optionally be transliterated to snake case.
  • 23.
    Uniform Access Principle All services offered by a module should be available through a uniform notation, which does not betray whether they are implemented through storage or through computation. - Bertrand Meyer
  • 24.
    Two Rules • Everything'san Object • Objects expose Methods and Only Methods
  • 26.
    Method Rubification™ Java System.currentTimeMillis() Ruby System.current_time_millis
  • 27.
    Seriously though, not actually trademarked. Method Rubification™ Java person.getName() Ruby person.name
  • 28.
    Someone should get on that. Method Rubification™ Java person.setAge(42) Ruby person.age = 42
  • 29.
    Method Rubification™ FALSE! TRUE! Java person.isJedi() Ruby person.jedi?
  • 30.
    Is ruby anacceptable Java? (http://bit.ly/pfNluA) • Most Java code can literally be written as ruby with few high-level changes. • A more relaxed type system and syntax often has productivity benefits.
  • 31.
    Exhibit A // interfaces = HashMap{ label => NetworkInterface } Java Collection c = interfaces.values(); Iterator itr = c.iterator(); while(itr.hasNext()) { NetworkInterface interface = itr.next(); if (interface.isLoopback()) { return interface.getDisplayName(); } } VS. # interfaces = {label => NetworkInterface} Ruby interfaces.values.find(&:loopback?).display_name
  • 32.
    Exhibit A // interfaces = HashMap{ label => NetworkInterface } Java Collection c = interfaces.values(); Iterator itr = c.iterator(); while(itr.hasNext()) { NetworkInterface interface = itr.next(); if (interface.isLoopback()) { return interface.getDisplayName(); } } VS. # interfaces = {label => NetworkInterface} Ruby interfaces.values.find(&:loopback?).display_name 1 2 345 67 Only 7 characters of syntactic support!
  • 33.
    “Are you suggestingI write all my Java in ruby?!” • Not really... • JRuby is much slower than Java (doesn’t matter as often as you’d think) • Ruby’s added expressiveness makes it easier to shoot yourself in the foot. (or, in fact, to lazily clone an infinite number of your feet every planck length between your gun and your target)
  • 34.
    “Are you suggestingI write all my Java in ruby?!” • ...but maybe sometimes. • One possibility: • Encode high-level logic in expressive and concise ruby code • Supporting code in fast, safe Java • Mix and match as appropriate
  • 35.
    Ruby as Glue •Ruby is great for: • wiring existing codebases together • other miscellaneous tasks
  • 36.
    Perl The original “Swiss Army Knife”
  • 37.
    Perl Pretty much mean the same thing. • “glue” • “duct tape” • “swiss army knife”
  • 38.
    Perl Ruby
  • 39.
    Subcategories of “Glue” • Wiring stuff together • Sysadmin tasks
  • 40.
    Wiring stuff together •There’s a lot we could cover here, but:
  • 41.
    Nokogiri • An extremelyuser-friendly XML library • fast! (wraps libxml2)
  • 42.
    Nokogiri require 'open-uri' require 'nokogiri' html= open("http://slashdot.org").read doc = Nokogiri::XML(html) (doc/".story a").each do |link| puts "#{link.text} (#{link.attr('href')})" end # Britain's Broadband Censors: a Bunch of Students (//yro.slashdot... # ...
  • 43.
    Don’t do this. (Nokogiri::XML(open("http://slashdot.org").read)/".storya").each{|a|puts "#{a.text} (#{a.attr("href")})"}
  • 44.
    Sysadmin with Ruby •Nice system APIs (quite similar to perl’s) • System provisioning libraries/DSLs
  • 45.
    Provisioning systems • Puppet • Chef • Vagrant
  • 46.
    Puppet • Describe system,and puppet sets it up • Nontrivial, but the general idea is: • I want mysql • I want nginx < 0.8 • I want this cron job: “....” • Go. http://puppetlabs.com/
  • 47.
    Puppet class postgres-server { package { postgresql-server: ensure => latest } group { postgres: gid => 26 } user { postgres: comment => "PostgreSQL Server", uid => 26, gid => 26, home => "/var/lib/pgsql", shell => "/bin/bash" } service { postgresql: running => true, pattern => "/usr/bin/postmaster", require => package["postgresql-server"] } }
  • 48.
    Puppet • Fantastic fordefining a reproducible production environment
  • 49.
    Chef • Same ideaas puppet • Somewhat less powerful • Slightly more approachable to most ruby developers http://www.opscode.com/chef/
  • 50.
    Chef package "sudo" do action :upgrade end template "/etc/sudoers" do source "sudoers.erb" mode 0440 owner "root" group "root" variables( :sudoers_groups => node['authorization']['sudo']['groups'], :sudoers_users => node['authorization']['sudo']['users'], :passwordless => node['authorization']['sudo']['passwordless'] ) end
  • 51.
    Puppet Chef Configuration Language custom ruby Targeted at production production “Powerfulness” Lots Mostly lots Verdict Good Different Written in ruby ruby
  • 52.
    Vagrant • Uses puppetand/or chef • End product is a virtual machine for development use • Consistent system for all developers, per project
  • 53.
    Vagrant • If youuse puppet or chef, vagrant lets you test production locally
  • 54.
    Suggestions • As anon-ruby-dev: • Use puppet • Consider vagrant
  • 55.
    Suggestions • As aruby developer: • Consider chef and puppet, use whichever suits your taste and needs • Consider vagrant
  • 56.
  • 57.
  • 58.
    In the RealWorld, We have Data
  • 59.
    In the RealWorld, We have Data Which lives in Databases
  • 60.
  • 61.
    All our datais ALWAYS in the same DBMS
  • 62.
  • 63.
    Luckily Wehave ODBC + JDBC
  • 64.
    Luckily Wehave ODBC + JDBC + Ruby
  • 65.
    Ruby Gives youOptions • Active Record • Sequel • DataMapper • more
  • 66.
    Active Record Designpattern coined by Martin Fowler in “Patterns of enterprise application architecture”. Also, Ruby on Rails’s default ORM. • Lots of Power • Lots of Opinion • Might fight with you (for non-standard uses) https://github.com/rails/rails/tree/master/activerecord
  • 67.
    Active Record Syntax(Raw) require 'active_record' ActiveRecord::Base.establish_connection({ :adapter => 'mysql', :database => 'test_database', :username => 'tester', :password => 'test22' }) connection = ActiveRecord::Base.connection connection.tables > ['users', 'products', 'ducks', 'oranges'] users = [] connection.execute('SELECT * FROM users').each_hash do |user| users << user end users > .... array of users, each user as a hash. users.first > { :id => 1, :username => 'stefan', :password => 'is_super_secure' }
  • 68.
    Active Record Syntax(ORM) require 'active_record' ActiveRecord::Base.establish_connection({ :adapter => 'mysql', :database => 'test_database', :username => 'tester', :password => 'test22' }) # class <camel_case_singular_table_name> < ActiveRecord::Base class User < ActiveRecord::Base # if the table's name does not fit convention, it can be manually overridden. # table_name :users_table end User.first > #<User id: 2, :name => 'stefan', :password => 'is_super_secure' > User.find(2) > #<User id: 2, :name => 'stefan', :password => 'is_super_secure' > User.where(:name => 'stefan') > #<User id: 2, :name => 'stefan', :password => 'is_super_secure' >
  • 69.
    Active Record Syntax(AREL) User.first > #<User id: 2, :name => 'stefan', :password => 'is_super_secure' > User.find(2) > #<User id: 2, :name => 'stefan', :password => 'is_super_secure' > User.where(:name => 'stefan') > #<User id: 2, :name => 'stefan', :password => 'is_super_secure' > User.where(:name => 'stefan').order('id ASC') > #<User id: 2, :name => 'stefan', :password => 'is_super_secure' >
  • 70.
    Sequel Elegant Full featureddatabase toolkit for ruby. • Supports a ton of DBMS’s • DSL • ORM http://sequel.rubyforge.org/ Supports - ADO, Amalgalite, DataObjects, DB2, DBI, DO, Firebird, ibmdb, Informix, JDBC, MySQL, Mysql2, ODBC, OpenBase, Oracle, PostgreSQL, SQLite3, Swift, and tinytds
  • 71.
    require "sequel" Sequel Example # connect to an in-memory database DB = Sequel.sqlite # create an items table DB.create_table :items do primary_key :id String :name Float :price end # create a dataset from the items table items = DB[:items] # populate the table items.insert(:name => 'abc', :price => rand * 100) items.insert(:name => 'def', :price => rand * 100) items.insert(:name => 'ghi', :price => rand * 100) # print out the number of records puts "Item count: #{items.count}" # print out the average price puts "The average price is: #{items.avg(:price)}"
  • 72.
    Sequel ORM require "sequel" #connect to an in-memory database DB = Sequel.sqlite # create an items table DB.create_table :items do primary_key :id String :name Float :price end # create a dataset from the items table class Item < Sequel::Model(:items) # associations # validations end # populate the table Item.create(:name => 'abc', :price => rand * 100) Item.create(:name => 'def', :price => rand * 100) Item.create(:name => 'ghi', :price => rand * 100) # print out the number of records puts "Item count: #{Item.count}" # print out the average price puts "The average price is: #{Item.avg(:price)}"
  • 73.
    Sequel ORM + jRuby ready out of the box
  • 74.
    Sequel ORM + IRONRuby apparently works?
  • 75.
    Case Study (Access) • Access (top 2 solutions) • Solution 1 (Cross Platform) • jRuby • JDBC • HXTT’s driver (www.hxtt.com) • Sequel • Solution 2 (Windows Only) • Ruby • ADO via WIN32OLE • Sequel
  • 76.
  • 77.
    Case Study (Access) 1. Download jRuby http://jruby.org/
  • 78.
    Case Study (Access) 1. Download jRuby http://jruby.org/ 2. Install Sequel gem install Sequel
  • 79.
    Case Study (Access) 1. Download jRuby http://jruby.org/ 2. Install Sequel gem install Sequel 3. Download JDBC Access Driver http://www.hxtt.com/access.html
  • 80.
    Case Study (Access) require 'rubygems' require 'sequel' require 'sequel/adapters/jdbc' require 'sequel/jdbc_hxtt_adapter' require '../Support/Access_JDBC40.jar' root = File.expand_path "../../", __FILE__ path_to_db = root + "/Support/AccessThemeDemo.mdb" DB = Sequel.connect("jdbc:access:////#{path_to_db}") puts DB.tables class Notes < Sequel::Model(:Notes) end class ThemeImages < Sequel::Model(:tbl_ThemeImages) end class Users < Sequel::Model(:tbl_Users) end
  • 81.
  • 82.
    But our newapp and our old database have different system requirements.
  • 83.
  • 84.
    _ -Need WeWant Isolation
  • 85.
    Power by.. Sinatra Web DSL get '/pictures' do ['picture1','picture2','picture2'] end post '/pictures' {} put '/pictures/:id' {} delete '/pictures/:id' {} used at linkedIn http://engineering.linkedin.com/44/linkedin-app-end-end-jruby-frontier-and-voldemort
  • 86.
  • 87.
    RestServer • any jdbcdatabase into a restful json service • https://github.com/stefanpenner/restserver
  • 88.
  • 89.
  • 90.
    Putting it alltogether (p1) ScreenShotr download: https://github.com/stefanpenner/screenshotr/zipball/master git@github.com:stefanpenner/screenshotr
  • 91.
  • 92.
  • 93.
    Java Gives Us • Portability Ruby Gives Us • Happiness
  • 94.
    ScreenShotr Normally Annoying re:Portability • screen capture • GUI but thx jRuby
  • 95.
    Lets Shoot someScreens require 'java' java_import 'java.awt.Robot' java_import 'java.awt.Toolkit' java_import 'java.awt.Rectangle' java_import 'java.awt.Image' java_import 'java.awt.image.BufferedImage' java_import 'javax.imageio.ImageIO' screenRect = Rectangle.new(Toolkit.default_toolkit.getScreenSize()) capture = Robot.new.createScreenCapture(screenRect) ImageIO.write(capture,'jpg',java.io.File.new('some_tmp_file.jpg'))
  • 96.
  • 97.
  • 98.
    Hold UP java_import ? why not require?
  • 99.
    Hold UP java_import ? why not require? requiring classes just makes them discoverable later.
  • 100.
    Hold UP java_import ? why not require? requiring classes just makes them discoverable later. why not import?
  • 101.
    Hold UP java_import ? why not require? requiring classes just makes them discoverable later. why not import? Rake defines import
  • 102.
    Hold UP java_import ? why not require? requiring classes just makes them discoverable later. why not import? Rake defines import weird...
  • 103.
    And fill someclipboards require 'java' java_import 'java.awt.Toolkit' java_import 'javax.imageio.ImageIO' ss = StringSelection.new(public_url) Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, nil)
  • 104.
    upload to server geminstall rest-client require 'rubygems' require 'rest-client' RestClient.post('http://../resource', :file => File.new('path/to/file'))
  • 105.
    upload to server geminstall rest-client require 'rubygems' require 'rest-client' RestClient.post('http://../resource', :file => File.new('path/to/file'))
  • 106.
  • 107.
    ...and Vice Versa •We’ve done a lot of calling Java from ruby. • The reverse is possible as well.
  • 108.
    ...and Vice Versa importorg.jruby.embed.InvokeFailedException; import org.jruby.embed.ScriptingContainer; // ... ScriptingContainer container = new ScriptingContainer(); container.runScriptlet("puts "That's no Moon""); http://en.wikipedia.org/wiki/Jruby
  • 109.
    Testing Java withRuby “i find your lack of tests disturbing.”
  • 110.
    Testing Java withRuby • JtestR is wonderful • Includes most of ruby’s leading testing libraries • supports ant and maven • easy to add to a project • takes advantage of jruby/java bridge
  • 111.
    JtestR Installation • Downloadjarfile from jtestr.codehaus.org • Add to classpath • Add a task to maven/ant • Create tests in ./test or ./spec • Run task
  • 112.
    Example! import java.util.HashMap describe "Anempty", HashMap do before :each do @hash_map = HashMap.new end it "accepts new entries" do @hash_map.put "foo", "bar" @hash_map.get("foo").should == "bar" end it "returns a keyset iterator that throws an exception on next" do proc do @hash_map.key_set.iterator.next end.should raise_error(java.util.NoSuchElementException) end end
  • 113.
    “i saw acity in the clouds”
  • 114.
  • 115.
    FACT: Ruby scaleslike a BOSS
  • 116.
    FACT: Ruby scaleslike a BOSS (because it has to)
  • 117.
    Fog • “The RubyCloud Services Library” • Lets you upload to S3, provision instances in EC2, set DNS records in DNSimple... • ...and much more.
  • 118.
  • 119.
    Fog “ • Chewbacca-approved. (chewbaccaproved?) whargarblbgarblgr- garblgragh ”
  • 120.
    2.5 imperial tonsof providers
  • 121.
    ( )eh. 2.5 imperial tons of H providers
  • 146.
  • 147.
  • 148.
    module ScreenShotr class Server < Sinatra::Base def storage #@storage ||= Fog::Storage.new({ # :provider => 'AWS', # :aws_access_key_id => ACCESS_KEY_ID, # :aws_secret_access_key => SECRET_ACCESS_KEY}) @storage ||= Fog::Storage.new({ :local_root => '~/fog', :provider => 'Local' }) def directory storage.directories.find("data").first or storage.directories.create(:key => 'data' ) end # snip ....
  • 149.
    get '/' do "hello, world!" end
  • 150.
    post '/picture/create' do file = params[:file] data = file[:tempfile] #super secure filename filename = file[:filename] key = Digest::SHA1.hexdigest("super random seed"+Time.now.to_s) key << '.jpg' file = directory.files.create( :body => data.read, :key => key ) file.public_url or "http://0.0.0.0:9292/picture/#{key}" end
  • 151.
    get '/picture/:key' do file = directory.files.get(params[:key]) send_file file.send(:path) end
  • 152.
  • 153.
  • 155.
  • 156.
  • 167.
    Heroku Classic Stack (runrack/rails) Cedar Stack (run “anything”)
  • 168.
    Heroku Classic Stack (runrack/rails) $ heroku create Created sushi.herokuapp.com | git@heroku.com:sushi.git $ git push heroku master -----> Heroku receiving push -----> Rails app detected -----> Compiled slug size is 8.0MB -----> Launching... done, v1 http://sushi.herokuapp.com deployed to Heroku
  • 169.
    Heroku Cedar Stack (run“anything”) $ cat Procfile web: bundle exec rails server -p $PORT worker: bundle exec rake resque:work QUEUE=* urgentworker: bundle exec rake resque:work QUEUE=urgent clock: bundle exec clockwork clock.rb $ heroku scale web=4 worker=2 urgentworker=1 clock=1 Scaling processes... done “Officially Everything”: Ruby, Node.js,Clojure, Java, Python, and Scala
  • 170.
  • 171.
    “good... the forceis strong with you. Thanks! a powerful rubyist you will become.”