introduction to jruby

37
Thought Works Introduction to JRuby NEAL FORD thoughtworker / meme wrangler ThoughtWorks 14 Wall St, Suite 2019, New York, NY 10005 [email protected] www.nealford.com www.thoughtworks.com memeagora.blogspot.com Thought Works Questions, Slides, & Samples Please feel free to ask questions anytime Slides and samples at www.nealford.com

Upload: elliando-dias

Post on 13-May-2015

1.662 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: Introduction to JRuby

ThoughtWorks

Introduction to JRuby

NEAL FORD thoughtworker / meme wrangler

ThoughtWorks14 Wall St, Suite 2019, New York, NY 10005 nford@thoughtworks.comwww.nealford.comwww.thoughtworks.commemeagora.blogspot.com

ThoughtWorks

Questions, Slides, & Samples

Please feel free to ask questions anytime

Slides and samples at www.nealford.com

Page 2: Introduction to JRuby

ThoughtWorks

What This Session Covers:

Motivation for JRuby

What makes Ruby cool?

Classes & objects

Closures

Mixins

Idioms

Ruby + Java

ThoughtWorks

Why Ruby?

Purely object-oriented

Dynamically typed (“duck typed”)

Compact syntax

Closures

Open classes

Awesome meta-programming support

Rails

Page 3: Introduction to JRuby

ThoughtWorks

What is JRuby?

JRuby is the Ruby interpreter, rewritten in Java

MRI (Matz Reference Implementation) written in C and Ruby

JRuby developers ported the C code to Java

Now produces byte code

The compiler is done (available in the JRuby 1.1 release)

Faster than MRI on almost all benchmarks!!!

ThoughtWorks

Why JRuby?

JRuby == Ruby on the JVM

Allows Java capabilities with Ruby syntax

Smart about properties

Access to Java libraries

Ruby libraries from Java

Supports all Ruby syntax and built-in libraries

Easy to port the libraries written in Ruby

C libraries ported to Java

Page 4: Introduction to JRuby

ThoughtWorks

Motivations

Use Java libraries (i.e., Swing) from Ruby code

Use Ruby libraries (i.e., ActiveRecord) from Java

Using Bean Scripting Framework (JDK <= 1.5)

Using JSR 223 Scripting API (JDK > 1.5)

Get an (eventually) faster Ruby

Much slower now

Lessons learned from IronPython

ThoughtWorks

What JRuby Gives You

JRuby classes can

Inherit from Java classes

Implement Java interfaces

Open Java classes (visible from JRuby, not Java)

Running on the JVM provides

Native threads

Unicode Support

Portability

Page 5: Introduction to JRuby

ThoughtWorks

Current Limitations

Does not support continuations/bindings

Fine-grained timer

Benchmarking code depends on finer grained timings than the JVM supports

Solved(?) in a future JVM via a current JSR

Identical thread behavior

Missing File operations

The JVM is missing some file operations needed by File and friends in Ruby

ThoughtWorks

Some Ruby Syntax

Just the interesting stuff

Example

hr.rb

hr_runner.rb

Tests

test_employee.rb

test_manager.rb

test_all.rb

Page 6: Introduction to JRuby

ThoughtWorks

Ruby Blocks

Delineated with either

{ . . . } # idiomatically used for single line blocks

do . . . end # idiomatically used for multi-line blocks

Both support parameters with |my_param|

Examples

hr_blocks.rb

hr_blocks_ruby_way.rb

ThoughtWorks

What Makes a Block a Closure?

A closure object has

Code to run (the executable)

State around the code (the environment, including the local variables)

The closure captures the environment

You can reference the local variable inside the closure...

...even if the variable has gone out of scope

Example

hr_closures.rb

Page 7: Introduction to JRuby

ThoughtWorks

Important Closure Characteristics

Closures combine a block of code with an environment

Sets it apart from function pointers, et al

Java’s anonymous inner classes can access locals (only if they are final)

Require very little syntax

Crucial because they are used all the time

Cumbersome to create something like an anonymous inner class all the time

ThoughtWorks

Open Classes

Ruby allows you to “open” classes

When you write a class definition that already appears on the classpath, Ruby opens the class

You can make changes to the class

Methods

Member variables

Method characteristics

You can also add methods to object instances

open_employee

Page 8: Introduction to JRuby

ThoughtWorks

Modules

Allow you to group classes, method, and constants

Provide two major benefits

Namespaces

Mixins

Mixins are Ruby’s alternatives to

Multiple inheritance

Interfaces

ThoughtWorks

Mixins

When you include a module into a class, the modules methods are mixed into the class

Methods defined in the module can interact with instance variables of the class

Don’t be fooled by the include keyword

Not like a C/C++ include

Class references the module (i.e., no copying)

Example: hr_mixin.rb

Page 9: Introduction to JRuby

ThoughtWorks

Using Java Classes in Ruby

Must contain require “java”

5 options

Provide full class name when using

Assign full class name to a constant.

Use include_class

frame = javax.swing.JFrame.new("My Title")

JFrame = javax.swing.JFrameframe = JFrame.new("My Title")

include_class "javax.swing.JFrame"frame = JFrame.new("My Title")

ThoughtWorks

Using Java Classes in Ruby

5 options (continued)

Use include_class with an alias

Useful when Java class name matches Ruby class name

Use include_package to scope Java classes in a Ruby module namespace (only in a module)

include_class("java.lang.String") do |pkg_name, class_name| "J#{class_name}"endmsg = JString.new("My Message")

module Swing include_package "javax.swing"endframe = Swing::JFrame.new("My Title")

Page 10: Introduction to JRuby

ThoughtWorks

Calling Semantics

Parenthesis aren’t required when calling methods

Java get/set/is methods are invoked like Ruby accessors

Camelcase Java names can be called with underscores

emp.getName() => e.nameemp.setName(“Homer”) => e.name = “Homer”emp.isManager() => e.manager?

require "java"url = java.net.URL.new("http://www.nealford.com")puts url.to_external_form # method name is toExternalFormputs url.to_uri # method name is toURI

ThoughtWorks

Building Swing in Ruby

Much friendlier syntax

Dynamic typing cuts way down on useless code

Uses blocks intelligently

Define button click behavior in a block at definition

Example

hello_frame.rb

Page 11: Introduction to JRuby

ThoughtWorks

Ruby Methods Added to Core Java

JRuby adds lots of methods to core JDK classes

Decorated all the collections classes to make them “humane”

Added operators to String and type wrappers

<=>, <, <=, =>, >, between?

Example

JRuby Super Console

ThoughtWorks

Proxy Classes

JRuby builds proxies for Java classes

Allows open classes with Java

Add methods to the class

Add methods to the object instance

Example

array_list_proxy.rb

Page 12: Introduction to JRuby

ThoughtWorks

JRuby Inheriting from Java Classes

JRuby classes can inherit from Java classes

Example

Car.java

race_car.rb

ThoughtWorks

Testing Java with JRuby

You can use JRuby to write unit tests against Java code

Including powerful mock-object libraries from Ruby (like Mocha)

Example

OrderInteractionTester.java

OrderInteractionTest.rb

Mocha provides

Mocks and stubs

Automatic verification

Page 13: Introduction to JRuby

ThoughtWorks

JRuby on Rails

Yes, Rails now runs on JRuby

You can build a WAR file from a Rails application and deploy it on a JVM

Still slower than Ruby

Still some manual tweaking for stuff like databases

But it works!

ThoughtWorks

JRuby Advantages over Ruby

Ola Bini’s recipe:

Take one large Rails application with good Selenium test base

Convert database configuration to use JDBC

Start a selenium proxy with "jake test:selenium_proxy"

Start acceptance testing from another window with "jake test:acceptance"

In yet another window, write "jconsole"

Choose your application

Page 14: Introduction to JRuby

ThoughtWorks

The Result?

ThoughtWorks

Future Directions

Performance, performance, performance!

Improving the Rails experience

Version 1.0 is the current version

Version 1.1 (with compiler) soon

Page 15: Introduction to JRuby

ThoughtWorks

Benchmarks

ThoughtWorks

Benchmarks

Page 16: Introduction to JRuby

ThoughtWorks

JRuby vs. Groovy

JRuby is an interpreter written in Java

With compilation (in version 1.1)

Interacts with Java classes via proxies

Groovy is an alternative Java syntax

Wraps Java classes with proxies

Compiles to byte code

ThoughtWorks

JRuby vs. Groovy

Groovy can only perform operations allowed by byte code in the JVM

JRuby is free to do anything that Ruby can do

The most powerful mainstream meta-programming language

JRuby on Rails can do things that are difficult and impossible with Groovy

Ruby is the most powerful mainstream language

Page 17: Introduction to JRuby

ThoughtWorks

JRuby vs. Groovy

Java

Groovy

Ruby/JRuby

ThoughtWorks

Resources

The JRuby web site

http://jruby.codehaus.org/

Includes documentation, tutorials, etc.

Charles Nutter’s Blog

http://headius.blogspot.com/

Ola Bini’s Blog

http://ola-bini.blogspot.com/

Page 18: Introduction to JRuby

ThoughtWorks

Questions?Please fill out the session evaluations

Samples & slides at www.nealford.com

This work is licensed under the Creative Commons Attribution-Noncommercial-Share Alike 2.5 License.

http://creativecommons.org/licenses/by-nc-sa/2.5/

NEAL FORD thoughtworker / meme wrangler

ThoughtWorks14 Wall St, Suite 2019, New York, NY 10005 nford@thoughtworks.comwww.nealford.comwww.thoughtworks.commemeagora.blogspot.com

Page 19: Introduction to JRuby

hr.rb 2007-04-22

- 1/1 -

class Employee def initialize(name, salary, hire_year) @name = name @salary = salary @hire_year = hire_year end

attr_reader :salary, :hire_year def to_s "Name is #{@name}, salary is #{@salary}, " + "hire year is #{@hire_year}" end def raise_salary_by(perc) @salary += (@salary * (perc * 0.01)) endend

class Manager < Employee def initialize(name, salary, hire_year, asst) super(name, salary, hire_year) @asst = asst end def to_s super + ",\tAssistant info: #{@asst}" end def raise_salary_by(perc) perc += 2005 - @hire_year super(perc) endend

Page 20: Introduction to JRuby

hr_runner.rb 2007-04-22

- 1/1 -

require 'hr'

def show(emps) emps.each { |e| puts e }end

employees = Array.newemployees[0] = Employee.new("Homer", 200.0, 1995)employees[1] = Employee.new("Lenny", 150.0, 2000)employees[2] = Employee.new("Carl", 250.0, 1999)employees[3] = Manager.new("Monty", 3000.0, 1950, employees[2])

show(employees)

employees.each { |e| e.raise_salary_by(10) }puts "\nGive everyone a raise\n\n"show employees

Page 21: Introduction to JRuby

test_employee.rb 2007-04-22

- 1/1 -

require 'test/unit/testcase'require 'test/unit/autorunner'require 'hr'

class TestEmployee < Test::Unit::TestCase @@Test_Salary = 2500

def setup @emp = Employee.new("Homer", @@Test_Salary, 2003) end

def test_raise_salary @emp.raise_salary_by(10) expected = (@@Test_Salary * 0.10) + @@Test_Salary assert expected == @emp.salary end

end

Page 22: Introduction to JRuby

test_all.rb 2007-04-22

- 1/1 -

require 'test/unit/testsuite'require 'test/unit/ui/tk/testrunner'require 'test/unit/ui/console/testrunner'require 'test_employee'require 'test_manager'

class TestSuite_AllTests def self.suite suite = Test::Unit::TestSuite.new("HR Tests") suite << TestEmployee.suite suite << TestManager.suite return suite endend

# Test::Unit::UI::Tk::TestRunner.run(TestSuite_AllTests)Test::Unit::UI::Console::TestRunner.run(TestSuite_AllTests)

Page 23: Introduction to JRuby

hr_blocks_4_ruby_way.rb 2007-04-22

- 1/2 -

class Employee attr_reader :name, :salary, :hire_year def initialize(name, salary, hire_year) @name = name @salary = salary @hire_year = hire_year end def to_s "Name is #{@name}, salary is #{@salary}, " + "hire year is #{@hire_year}" end def raise_salary_by(perc) @salary += (@salary * 0.10) end end

class EmployeeList def initialize @employees = Array.new end def add(an_employee) @employees.push(an_employee) self end def delete_first @employees.shift end def delete_last @employees.pop end def show @employees.each { |e| puts e } end

Page 24: Introduction to JRuby

hr_blocks_4_ruby_way.rb 2007-04-22

- 2/2 -

def initialize @employees = Array.new end def add(an_employee) @employees.push(an_employee) self end def delete_first @employees.shift end def delete_last @employees.pop end def show @employees.each { |e| puts e } end

def [](key) return @employees[key] if key.kind_of?(Integer) return @employees.find do |anEmp| key == anEmp.name end return nil endend

list = EmployeeList.newlist.add(Employee.new("Homer", 200.0, 1995)). add(Employee.new("Lenny", 150.0, 2000)). add(Employee.new("Carl", 250.0, 1999))

list.showputs "Employee #1 is " + list[0].to_sputs "Employee named 'Homer' is " + list["Homer"].to_s

Page 25: Introduction to JRuby

hr_closures.rb 2007-04-22

- 1/1 -

#!/usr/bin/env ruby

class Employee def initialize(name, salary) @name = name @salary = salary end

attr_accessor :name, :salary def to_s "#{@name} makes #{@salary}" endend

Page 26: Introduction to JRuby

hr_closures_runner.rb 2007-04-22

- 1/1 -

#!/usr/bin/env ruby## Created by Neal Ford on 2007-04-20.# Copyright (c) 2007. All rights reserved.

require 'hr_closures'

def high_paid(emps) threshold = 40000 return emps.select {|e| e.salary > threshold}end

def paid_more(amount) return Proc.new {|e| e.salary > amount}end

employees = Array.newemployees << Employee.new("Homer", 15000)employees << Employee.new("Monty", 100000)employees << Employee.new("Smithers", 80000)employees << Employee.new("Carl", 50000)employees << Employee.new("Lenny", 55000)

puts "Closures that capture local variable scope"20.times { print '-'}putsputs high_paid(employees)

putsputs "Closures that capture definition scope"20.times { print '-'}putsis_high_paid = paid_more(60000)

puts is_high_paid.call(employees[0])puts is_high_paid.call(employees[2])

Page 27: Introduction to JRuby

make_counter.rb 2007-08-20

- 1/1 -

def make_counter var = 0 # lambda { var += 1 } proc { var += 1}end

c1 = make_counterc1.call # => 1c1.call # => 2c1.call # => 3

c2 = make_counter # => #<Proc:0x000258dc@-:3>

puts "c1 = #{c1.call}, c2 = #{c2.call}"# >> c1 = 4, c2 = 1

Page 28: Introduction to JRuby

open_employee.rb 2007-11-07

- 1/1 -

require File.dirname(__FILE__) + "/../01. classes/hr"

=begin rdoc Open class demonstration, add a method and atribute to an existing class=endclass Employee attr_accessor :birth_year def age Time.now.year - birth_year; end end

e = Employee.new "Homer", 20000e.birth_year = 1950puts "Age is #{e.age}"

=begin rdoc Add method to an object instance=end

def e.big_raise(by_perc) @salary += (@salary * (by_perc * 0.1))end

old_salary = e.salarye.big_raise(5)puts "Big money! From #{old_salary} to #{e.salary}"

=begin rdoc Overwriting a method from an open class=end

def e.raise_salary_by(perc) @salary -= (@salary * (perc * 0.01))end

old_salary = e.salarye.raise_salary_by(5)puts "Small money! From #{old_salary} to #{e.salary}"

Page 29: Introduction to JRuby

hr_mixin.rb 2007-04-22

- 1/2 -

module Debug def who_am_i? "#{self.class.name} (\##{self.object_id}): #{self.to_s}" endend

class Employee include Debug def initialize(name, salary, hire_year) @name = name @salary = salary @hire_year = hire_year end def to_s "Name is #{@name}, salary is #{@salary}, " + "hire year is #{@hire_year}" end def raise_salary_by(perc) @salary += (@salary * 0.10) endend

class Manager < Employee #include Debug def initialize(name, salary, hire_year, asst) super(name, salary, hire_year) @asst = asst end def to_s super + ",\tAssistant info: #{@asst}" end def raise_salary_by(perc) perc += 2005 - @hire_year super(perc) endend

def show(emps) emps.each { |e| puts e }

Page 30: Introduction to JRuby

hr_mixin.rb 2007-04-22

- 2/2 -

end

class Manager < Employee #include Debug def initialize(name, salary, hire_year, asst) super(name, salary, hire_year) @asst = asst end def to_s super + ",\tAssistant info: #{@asst}" end def raise_salary_by(perc) perc += 2005 - @hire_year super(perc) endend

def show(emps) emps.each { |e| puts e }

end

employees = Array.newemployees[0] = Employee.new("Homer", 200.0, 1995)employees[1] = Employee.new("Lenny", 150.0, 2000)employees[2] = Employee.new("Carl", 250.0, 1999)employees[3] = Manager.new("Monty", 3000.0, 1950, employees[2])

show(employees)

puts "\n\nWho are they?"puts employees[0].who_am_i?puts employees[3].who_am_i?

Page 31: Introduction to JRuby

hello_frame.rb 2007-04-22

- 1/2 -

#!/usr/bin/env ruby

require "java"

BorderLayout = java.awt.BorderLayoutJButton = javax.swing.JButtonJFrame = javax.swing.JFrameJLabel = javax.swing.JLabelJOptionPane = javax.swing.JOptionPaneJPanel = javax.swing.JPanelJTextField = javax.swing.JTextField

# BlockActionListener is ActionListener whose constructor takes a Ruby block.# It holds the block and invokes it when actionPerformed is called.class BlockActionListener < java.awt.event.ActionListener # super call is needed for now def initialize(&block) super @block = block end def actionPerformed(e) @block.call(e) end end

# Extend Swing JButton class with a new constructor that takes the button text# and a Ruby block to be invoked when the button is pressed.class JButton def initialize(name, &block) super(name) addActionListener(BlockActionListener.new(&block)) end end

# Define a class that represents the JFrame implementation of the GUI.class HelloFrame < JFrame def initialize super("Hello Swing!") populate pack resizable = false defaultCloseOperation = JFrame::EXIT_ON_CLOSE end

Page 32: Introduction to JRuby

hello_frame.rb 2007-04-22

- 2/2 -

@block.call(e) end end

# Extend Swing JButton class with a new constructor that takes the button text# and a Ruby block to be invoked when the button is pressed.class JButton def initialize(name, &block) super(name) addActionListener(BlockActionListener.new(&block)) end end

# Define a class that represents the JFrame implementation of the GUI.class HelloFrame < JFrame def initialize super("Hello Swing!") populate pack resizable = false defaultCloseOperation = JFrame::EXIT_ON_CLOSE end

def populate name_panel = JPanel.new name_panel.add JLabel.new("Name:") name_field = JTextField.new(20) name_panel.add name_field button_panel = JPanel.new # Note how a block is passed to the JButton constructor. greet_button = JButton.new("Greet") do name = name_field.text # Demonstrate display of HTML in a dialog box. msg = %(<html>Hello <span style="color:red">#{name}</span>!</html>) JOptionPane.showMessageDialog self, msg end button_panel.add greet_button # Note how a block is passed to the JButton constructor. clear_button = JButton.new("Clear") { name_field.text = "" } button_panel.add clear_button

contentPane.add name_panel, BorderLayout::CENTER contentPane.add button_panel, BorderLayout::SOUTH endend # of HelloFrame class

HelloFrame.new.visible = true

Page 33: Introduction to JRuby

comparisons.rb 2007-08-20

- 1/1 -

require File.dirname(__FILE__) + "/../01. classes/hr"

class Employee include Comparable

def <=>(other) name <=> other.name endend

list = Array.newlist << Employee.new("Monty", 10000)list << Employee.new("Homer", 50000)list << Employee.new("Bart", 5000)

puts list

list.sort!

puts list

p "Monty vs. Homer", list[0] < list[1]p "Homer vs. Monty", list[0] > list[1]

p "Homer is between Bart and Monty?", list[1].between(list[0], list[2])

Page 34: Introduction to JRuby

array_list_proxy.rb 2007-04-22

- 1/1 -

#!/usr/bin/env ruby

require "java"include_class "java.util.ArrayList"list = ArrayList.new%w(Red Green Blue).each { |color| list.add(color) }

# Add "first" method to proxy of Java ArrayList class.class ArrayList def first size == 0 ? nil : get(0) endendputs "first item is #{list.first}"

# Add "last" method only to the list object ... a singleton method.def list.last size == 0 ? nil : get(size - 1)endputs "last item is #{list.last}"

Page 35: Introduction to JRuby

Car.java 2007-04-22

- 1/1 -

public class Car { private String make; private String model; private int year;

public Car() {}

public Car(String make, String model, int year) { this.make = make; this.model = model; this.year = year; }

public String getMake() { return make; } public String getModel() { return model; } public int getYear() { return year; }

public void setMake(String make) { this.make = make; } public void setModel(String model) { this.model = model; } public void setYear(int year) { this.year = year; }

public String toString() { return year + " " + make + " " + model; }}

Page 36: Introduction to JRuby

race_car.rb 2007-04-22

- 1/1 -

#!/usr/bin/env rubyrequire "java"

include_class "Car"

c = Car.new("Honda", "Accord", 1997)puts c

class RaceCar < Car attr_accessor :top_speed

def initialize( make=nil, model=nil, year=0, top_speed=0) super(make, model, year) @top_speed = top_speed end

def to_s "#{super} can go #{@top_speed} MPH" endend

c = RaceCar.new("Ferrari", "F430", 2005, 196)puts c

c = RaceCar.new("Porche", "917")c.year = 1971c.top_speed = 248puts c

Page 37: Introduction to JRuby

order_interaction_test.rb 2007-08-20

- 1/1 -

require 'test/unit'require 'rubygems'require 'mocha'

require "java"require "Warehouse.jar"OrderImpl = com.nealford.conf.jmock.warehouse.OrderImplOrder = com.nealford.conf.jmock.warehouse.OrderWarehouse = com.nealford.conf.jmock.warehouse.WarehouseWarehouseImpl = com.nealford.conf.jmock.warehouse.WarehouseImpl

class OrderInteractionTest < Test::Unit::TestCase TALISKER = "Talisker" def test_filling_removes_inventory_if_in_stock order = OrderImpl.new(TALISKER, 50) warehouse = Warehouse.new warehouse.stubs(:hasInventory).with(TALISKER, 50).returns(true) warehouse.stubs(:remove).with(TALISKER, 50) order.fill(warehouse) assert order.is_filled end def test_filling_does_not_remove_if_not_enough_in_stock order = OrderImpl.new(TALISKER, 51) warehouse = Warehouse.new warehouse.stubs(:hasInventory).returns(false) order.fill(warehouse) assert !order.is_filled end end