現実世界のJRuby
日本JRubyユーザ会 中村浩士(なひ)
 @nahi nahi@ruby-lang.org
自己紹介

株式会社サリオンシステムズリサーチ勤務
セキュリティ・ネットワーク関連のシステム開発
C/C++ (18年)、Java (13年)、Ruby (13年)

余暇のOSS開発: CRuby (8年)、JRuby (2年)、
          soap4r、httpclient他
JRubyとは - http://jruby.org/

最新リリース版は1.6.5

JVM上で動作するRuby言語

Open Source (CPL, GPL, LGPL)

開発開始から10年
フルタイム開発者登場から5年
本日のゴール

Java開発者の皆様向け

RubyとJRubyについて学ぶ

JRubyはどんなところに使える?
Ruby入門
Java開発者向け
Rubyの特徴

人に優しい文法

豊富なメタプログラミング機能

高い生産性

Ruby on Rails + githubの存在
Rubyツアー 1/8: クラス定義
public class Circle extends Shape {          class Circle < Shape
  private final int radius;                    def initialize(radius)
  public Circle(int radius) {                    @radius = radius
    this.radius = radius;                      end
  }                                            attr_reader :radius
  public int getRadius() {                     def area
    return radius;                               Math::PI * (@radius ** 2)
  }                                            end
  public double getArea() {                  end
    return Math.PI * Math.pow(radius, 2);    puts Circle.new(2).area
  }
  public static void main(String[] args) {
    double area = new Circle(2).getArea();   extends → <
                                             継承は単一継承
    System.out.println(area);
  }
}

   メソッド定義 → def
   コンストラクタ → initialize
Rubyツアー 2/8: インスタンス変数
public class Circle extends Shape {          class Circle < Shape
  private final int radius;                    def initialize(radius)
  public Circle(int radius) {                    @radius = radius
    this.radius = radius;                      end
  }                                            attr_reader :radius
  public int getRadius() {                     def area
    return radius;                               Math::PI * (@radius ** 2)
  }                                            end
  public double getArea() {                  end
    return Math.PI * Math.pow(radius, 2);    puts Circle.new(2).area
  }
  public static void main(String[] args) {
    double area = new Circle(2).getArea();   this → @
    System.out.println(area);
  }
}

   attr_readerはアクセサメソッド定義用メソッド
Rubyツアー 3/8: 動的型付け
public class Circle extends Shape {          class Circle < Shape
  private final int radius;                    def initialize(radius)
  public Circle(int radius) {                    @radius = radius
    this.radius = radius;                      end
  }                                            attr_reader :radius
  public int getRadius() {                     def area
    return radius;                               Math::PI * (@radius ** 2)
  }                                            end
  public double getArea() {                  end
    return Math.PI * Math.pow(radius, 2);    puts Circle.new(2).area
  }
  public static void main(String[] args) {
    double area = new Circle(2).getArea();   変数に型なし
    System.out.println(area);
  }                                          duck-typing
}

   引数の型・数の違いによるメソッドoverloadなし
Rubyツアー 4/8: 全てが値を持つ
public class Circle extends Shape {          class Circle < Shape
  private final int radius;                    def initialize(radius)
  public Circle(int radius) {                    @radius = radius
    this.radius = radius;                      end
  }                                            attr_reader :radius
  public int getRadius() {                     def area
    return radius;                               Math::PI * (@radius ** 2)
  }                                            end
  public double getArea() {                  end
    return Math.PI * Math.pow(radius, 2);    puts Circle.new(2).area
  }
  public static void main(String[] args) {
    double area = new Circle(2).getArea();   return不要
  }
    System.out.println(area);
                                             文の値は最後の式
}
Rubyツアー 5/8:
  全てがオブジェクト、全てがメソッド
public class Circle extends Shape {          class Circle < Shape
  private final int radius;                    def initialize(radius)
  public Circle(int radius) {                    @radius = radius
    this.radius = radius;                      end
  }                                            attr_reader :radius
  public int getRadius() {                     def area
    return radius;                               Math::PI * (@radius ** 2)
  }                                            end
  public double getArea() {                  end
    return Math.PI * Math.pow(radius, 2);    puts Circle.new(2).area
  }
  public static void main(String[] args) {
    double area = new Circle(2).getArea();   Circle: 定数
    System.out.println(area);
  }                                          a*2 == a.*(2)
}

   Circle.new:
    クラスオブジェクトのnewメソッドを呼び出す
Rubyツアー 6/8: ブロック(クロージャ)
      def aaa(name, &block)
        File.open(name) do |file|        (1) File.open用ブロック
          file.each_line do |line|
            yield line
                                         ブロック実行後に自動close
(2)       end                          (1)
        end
      end
                                         (2) each_line用ブロック
                                         1行読み込む毎に呼ばれる
      aaa('a.txt') do |line|
        p line                 (3)
      end
                                         (3) aaa用ブロック
      people.group_by { |e| e.lang }     aaa内部のyieldに呼ばれる
      button1 = ...
      label1 = ...
      button1.on_action do |event|
        label1.text = 'sending...'
      end
                                         ← その他利用例
Rubyツアー 7/8:
Mix-in、オープンクラス
module Utils
  def name                        Mix-in: 実装の継承
    self.class.name    実装         Utilsモジュールの実装を
  end
end
class Book
                                  BookクラスにMix-in
  include Utils      継承
  def say
    "Hello from #{name}"
  end
end
obj = Book.new
p obj.say #=> "Hello from Book"

class Book
                                  オープンクラス:
  def say                         Bookクラスのsayメソッド
    "I'm #{name}"
  end                             を再定義
end
p obj.say #=> "I'm Book"
Rubyツアー 8/8: フックメソッド
class Base
  @@all = []                  inherited: クラスが継承
  def self.inherited(klass)
    @@all << klass
                              された場合に、継承したクラ
  end                         スを引数に呼ばれる
end

class Sub < Base
  p @@all
                              その他: included、
end                           method_added、
class SubSub < Sub            method_removed、
  p @@all
end                           method_missing、
※@@はクラス変数の接頭辞                 const_missing等
※クラスオブジェクトのキャッシュは
 リークの元なので普通やらない
Ruby言語の特徴(まとめ)

動的型付け

全てがオブジェクト、全てがメソッド

ブロック(クロージャ)の活用

メタプログラミング支援
  Mix-in、オープンクラス、各種フックメソッド
JRubyの特長

Ruby on Railsを含む100%の互換性

C言語版Rubyと同等の実行速度

高いスケーラビリティ(並行動作)

Javaとの親和性の高さ
Real-World JRuby
    JRuby利用実例
Real-World JRuby: JRuby利用実例
Java連携 (Java -> Ruby)

Java連携 (Ruby -> Java)

Javaテスト (RSpec, JtestR)

開発支援 (Ant, Maven, Jenkins)

ウェブ開発 (JRuby on Rails)
Java連携
(Java -> Ruby)
   ユースケース
Java連携 (Java -> Ruby)

JavaからRubyライブラリを利用
import org.jruby.embed.ScriptingContainer;
public class HelloWorld {
    public static void main(String[] args) {
        ScriptingContainer ruby = new ScriptingContainer();
        ruby.runScriptlet("puts "hello,world!"");
    }
                                           source 'http://localhost/'
}
                                          group :development do
                                            host 'localhost'
                                            port 12345
                                            reloadable true

例: 独自定義ファイル解析の                              debug true
                                          end

  DSL処理系として                               group :production do
                                            host 'www.example.com'
                                          end
Java連携 (Java -> Ruby)

例: gitdiff.rb - gitライブラリを利用し、リビジョ
ンの変更サマリを取得するRubyコード
    require 'rubygems'
    require 'git'
    def diff_summary(dir, from, to)
      diff = Git.open(dir).diff(from, to)
      diff.stats[:files].map { |file, st|
        insertions = st[:insertions] || 0
        deletions = st[:deletions] || 0
        "#{file} +#{insertions} -#{deletions}"
      }
    end
    # =>[ "src/org/jruby/Ruby.java +32 -20",
    #     "src/org/jruby/RubyArray.java +93 -17",
    #     "src/org/jruby/RubyBasicObject.java +7 -0", ...
Java連携 (Java -> Ruby)

Javaからの呼び出しと抽出
 public class GitDiff {
     public static void main(String[] args) throws Exception {
         ScriptingContainer ruby = new ScriptingContainer();
         ruby.runScriptlet("require 'gitdiff'");

        ruby.put("dir", "/home/nahi/git/jruby/");
        ruby.put("from", "8c6dba0f...");
        ruby.put("to", "7837c84a...");

        List array = (List) ruby.runScriptlet(
          "diff_summary(dir, from, to)");
        for (Object obj : array) {
            System.out.println(obj.toString());
        }
        ...
Java連携
(Ruby -> Java)
   ユースケース
Java連携 (Ruby -> Java)

RubyからJavaの機能を利用する
Javaの対話環境としての利用も可能
 % jruby -S irb
 > require 'java'
 => true
 > ni = java.net.NetworkInterface.networkInterfaces.to_a.first
 => #<Java::JavaNet::NetworkInterface:0x4d33b92c>
 > ni.getName
 => "eth0"
 > ni.isUp
 => true
 > ni.getMtu
 => 1500
 > ni.inetAddresses.map { |addr| addr.to_s }
 => ["/fe80:0:0:0:20c:29ff:fead:4bed%2", "/192.168.96.129"]
Java連携 (Ruby -> Java)

Flying Saucerを使ってHTMLをPDF変換
http://code.google.com/p/flying-saucer/
% ls flyingsaucer-R8
core-renderer.jar iText-2.0.8.jar ...
% jruby -S irb -Iflyingsaucer-R8
> require 'java'
> require 'iText-2.0.8.jar'
> require 'core-renderer.jar'
> rr = org.xhtmlrenderer.pdf.ITextRenderer.new
> doc = <<EOD
<html><body><h1>Hello JRuby</h1>
<p>from <a href="http://code.google.com/p/flying-saucer/">Flying
Saucer</a>.</p></body></html>
EOD
> rr.set_document_from_string(doc)
> rr.layout
> File.open("out.pdf", "w") { |f| rr.create_pdf(f.to_outputstream) }
JRubyFX:
  JRuby binding for JavaFX 2.0
Java FX 2.0を利用してJRuby GUIアプリ開発
https://github.com/nahi/jrubyfx

(デモ)
JRubyFX: SVGLoader example
Javaライブラリの組み合わせ
   require 'jrubyfx'
   # https://github.com/skrb/SVGLoader
   require 'SVGLoader.jar'
   java_import 'net.javainthebox.caraibe.svg.SVGLoader'
   class SVGLoaderApp
     include JRubyFX
     def start(stage)
       root = build(Group) {
         children << SVGLoader.load("/duke.svg").root
       }
       with(stage,
            title: 'SVGLoader sample',
            scene: build(Scene, root)).show
     end
   end
   SVGLoaderApp.start
Javaテスト
(RSpec, JtestR)
    ユースケース
Javaテスト (RSpec)

   RubyとJRubyの利点を活かしてJavaをテスト
                               describe 'ScriptingContainer#put' do
                                 before :each do
   RSpec:                          @x = org.jruby.embed. ScriptingContainer.new
                                 end
   振る舞いをテスト                      it "sets an object to local variable" do
                                   obj = Object.new
   http://rspec.info               @x.put("var", obj)
                                   @x.run_scriptlet("var").should == obj
% jruby -S rspec jruby_spec.rb   end
..                               it "overrides the previous object" do
                                   obj = Object.new
Finished in 0.044 seconds          @x.put("var", obj)
2 examples, 0 failures             @x.put("var", nil)
%                                  @x.run_scriptlet("var").should be_nil
                                 end
                               end
Javaテスト (JtestR)

JtestR: 各種Ruby用テストライブラリ同梱
http://jtestr.codehaus.org/
   describe "X509Name" do
     it "should use given converter for ASN1 encode" do
       converter = mock(X509NameEntryConverter)
       name = X509Name.new('CN=localhost', converter)
       converter.stubs('getConvertedValue').
         with(DERObjectIdentifier.new(CN), 'localhost').
         returns(DERPrintableString.new('converted')).
         times(1)
       name.toASN1Object.to_string.should == '...'
     end
   end
Javaテスト (JtestR)

Ant/Maven統合 + テストサーバ
                               <?xml version="1.0" encoding="utf-8"?>
                               <project basedir="." default="test"
                                   name="simple1">
                                 <taskdef name="jtestr"
                                   classname="org.jtestr.ant.JtestRAntRunner"
% ant test                         classpath="build_lib/jtestr.jar" />
Buildfile: /path/to/build.xml    <taskdef name="jtestr-server"
                                   classname="org.jtestr.ant.JtestRAntServer"
test:                              classpath="build_lib/jtestr.jar" />
                                 <target name="test">
   [jtestr] Other Spec: 4 examples, 0 failures, 0 errors
   [jtestr]                        <jtestr port="20333"/>
                                 </target>
   [jtestr] Total: 4 tests, 0 failures, 0 errors, 0 pending
   [jtestr]                      <target name="test-server" >
                                   <jtestr-server port="20333" runtimes="3"/>
                                 </target>
BUILD SUCCESSFUL
                               </project>
Total time: 9 seconds
開発支援
(Ant, Maven, Jenkins)
       ユースケース
開発支援 (Ant連携)
                                 desc "Build JRuby"
Rake: Rubyの記述力                   task :build do
                                   ant "jar"
を活かして                            end
                                 task :jar => :build
ビルド手順を記述                         desc "Clean all built output"
                                 task :clean do
                                   delete_files = FileList.new do |fl|
                                     fl.
Ant、Rakeから相互                           include("#{BUILD_DIR}/**").
                                       exclude("#{BUILD_DIR}/rubyspec").
にタスクを利用可能                              include(DIST_DIR).
                                       include(API_DOCS_DIR)
                                   end
<target name=”load-rake-task”>     ...
  <taskdef name=”rake” classname=”org.jruby.ant.Rake”/>
</target>
<target name=”default” depends=”load-rake-task”>
  <rake task=”jar”/>
</target>
開発支援 (Maven連携)

Maven配布物はrubygemsとしてインストール可能

開発環境の部分的Ruby化を支援

  % jruby -S gem install bouncycastle:bcprov-jdk15

  require 'rubygems'
  require 'maven/bouncycastle/bcprov-jdk15'
  ...
開発支援 (Jenkins連携)

Ruby Plugins for Jenkins
http://bit.ly/JenkinsRuby

JenkinsのプラグインをRubyで記述可能
開発支援 (Jenkins連携)
例: Travis CI設定を読んで自動ビルド
class TravisScriptBuilder < Jenkins::Tasks::Builder
  def prebuild(build, listener)
    travis_file = build.workspace + '.travis.yml'
    unless travis_file.exist?
      listener.error "Travis config `#{travis_file}' not found"
      raise "Travis config file not found"
    end
    ...
  def perform(build, launcher, listener)
    run_scripts(setup_env)
    ...
  def run_scripts(env)
    %w{before_script script after_script}.each do |type|
      scan_multiline_scripts(config[type]).each do |script|
        launcher.execute(env, script,
          :chdir => workspace, :out => listener)
        ...
ウェブ開発
(JRuby on Rails)
    ユースケース
ウェブ開発 (JRuby on Rails)

Ruby on Rails - http://rubyonrails.org

  ウェブアプリケーションフレームワーク

  フルスタック

  CoC: (XML)設定より規約(に従って開発)

  DRY: 同じことを繰り返さない
Railsツアー 1/7:
アプリケーションの生成

MVC、テスト、サードパーティライブラリ等
常に同じディレクトリ構成
  % jruby -S rails new myapp
        create
        create README
        create Rakefile
        ...
        create vendor/plugins
        create vendor/plugins/.gitkeep
            run bundle install
  Fetching source index for http://rubygems.org/
  Using rake (0.9.2.2)
  Installing multi_json (1.0.3)
  ...
  Installing sass-rails (3.1.5)
  Installing uglifier (1.1.0)
  Your bundle is complete! Use `bundle show [gemname]` to
  see where a bundled gem is installed.
Railsツアー 2/7: scaffold

アプリの雛形作り(慣れると不要)
% jruby -S rails g scaffold todo done:boolean description:string
     invoke   active_record
     create     db/migrate/20111128065332_create_todos.rb
     create     app/models/todo.rb
     invoke     test_unit
     create       test/unit/todo_test.rb
     create       test/fixtures/todos.yml
      route   resources :todos
     invoke   scaffold_controller
     create     app/controllers/todos_controller.rb
     invoke     erb
     create       app/views/todos
     ...
     invoke   scss
     create     app/assets/stylesheets/scaffolds.css.scss
%
Railsツアー 3/7: DBマイグレーション

スクリプトによるDBスキーマ履歴管理
  class CreateTodos < ActiveRecord::Migration
    def change
      create_table :todos do |t|
        t.boolean :done
        t.string :description
        t.timestamps
      end
    end
  end

  % jruby -S rake db:migrate
  == CreateTodos: migrating
  ====================================================
  -- create_table(:todos)
     -> 0.0040s
     -> 0 rows
Railsツアー 4/7: サーバ起動
             % jruby -S rails server
scaffoldだけ   => Booting WEBrick
             => Rails 3.1.3 application starting in development
でも動く         on http://0.0.0.0:3000
             => Call with -d to detach
             => Ctrl-C to shutdown server
             [2011-11-28 15:58:15] INFO WEBrick 1.3.1
             [2011-11-28 15:58:15] INFO ruby 1.8.7 (2011-11-27)
             [java]
Railsツアー 5/7: 生成されたコード

モデル          class Todo < ActiveRecord::Base
             end
                                            <%= form_for(@todo) do |f| %>
                                             <div class="field">
                                              <%= f.label :done %><br />
                                    ビュー       <%= f.check_box :done %>
                                             </div>

コントローラ                                       <div class="field">
                                              <%= f.label :description %><br />
                                              <%= f.text_field :description %>
                                             </div>
class TodosController < ApplicationController<div class="actions">
  def index                                   <%= f.submit %>
    @todos = Todo.all                        </div>
    respond_to do |format|                  <% end %>

      format.html # index.html.erb
      format.json { render :json => @todos }
    end
  end
  def create
    ...
end
Railsツアー 6/7:
ActiveRelation (Arel)
遅延SQL生成用のDSL
   Todo.where(:done => false)
     SELECT "todos".* FROM "todos" WHERE "todos"."done" = 'f'
   Todo.where(:done => false).where('created_at < "2011-11-29"')
     SELECT "todos".* FROM "todos" WHERE "todos"."done" = 'f' AND
   (created_at < "2011-11-29")
   Todo.where(:done => false).order("created_at DESC").limit(1)
     SELECT "todos".* FROM "todos" WHERE "todos"."done" = 'f'
   ORDER BY created_at DESC LIMIT 1



スコープ       class Todo < ActiveRecord::Base
             scope :finished, where(:done => true)
           end

           Todo.finished.size
             SELECT COUNT(*) FROM "todos" WHERE "todos"."done" = 't'
Railsツアー 7/7: RESTインターフェース

JSONでのCRUD
% jruby -rubygems -e 'require "httpclient"; puts HTTPClient.get("http:
//localhost:3000/todos/1.json").body'
=>
{ "created_at":"2011-11-28T06:59:14Z",
   "description":"牛乳を買う", ... }

% jruby -rubygems -e 'require "json"; require "httpclient";
puts HTTPClient.post("http://localhost:3000/todos",
   JSON.generate(:todo =>
     {:description => "JRubyのベンチマーク", :done => false}),
   "Accept" => "application/json", "Content-Type" => "application/json"
).body'
=>
{ "created_at":"2011-11-28T07:36:19Z",
   "description":"JRubyのベンチマーク", ... }
ウェブ開発 (JRuby on Rails)

Railsの全ての機能 + 既存Javaライブラリ活用

Javaアプリと同居可能

SpringMVCからRailsへのリファクタリング事例
  1) "Petclinic"にJRubyでREST APIを追加
  2) Railsの同居
  3) Spring利用の機能をRailsで置き換え
http://bit.ly/refactoring-to-rails
JRuby on Railsのデプロイ

WAR形式 → 任意のアプリサーバで動作
専用アプリサーバ:
 Trinidad (Tomcatベース)
     http://www.engineyard.com/
  TorqueBox (JBossベース)
    clustering、messaging、scheduling他
     http://torquebox.org/
PaaS: Engine Yard Cloud
  http://www.engineyard.com
JRubyのこれから
 InvokeDynamic と IR
Java SE 7: InvokeDynamic
新たなメソッド呼び出しバイトコード
bootstrapメソッド

dynamic language support (java.lang.invoke.*)
  MethodHandle
  MethodType
  SwitchPoint
  CallSite
JRubyにおけるInvokeDynamic効果

Java 5/6での
メソッド
呼び出し
JRubyにおけるInvokeDynamic効果

Java 7 +
JRuby 1.7.0.dev
(開発中)
JRubyにおけるInvokeDynamic効果
        Java 6/7上で動作させたJRubyの比較
        ※横軸は速度(大きいほうが速い)
IR: JRubyの新しい内部表現形式

最適化方式の
抜本的な変更を
模索

構文木ベースの
最適化から
新中間表現
ベースへ
まとめ: JRuby - http://jruby.org

Java開発者にも学び易いRuby言語

JavaとRubyの連携方式が豊富

現実世界で使われるフレームワーク、ライブラリ
 Rails、RSpec、JtestR、Jenkins
 git、scripting、DSL

Java開発者のツールベルトに

現実世界のJRuby

  • 1.
  • 2.
  • 3.
    JRubyとは - http://jruby.org/ 最新リリース版は1.6.5 JVM上で動作するRuby言語 OpenSource (CPL, GPL, LGPL) 開発開始から10年 フルタイム開発者登場から5年
  • 4.
  • 5.
  • 6.
  • 7.
    Rubyツアー 1/8: クラス定義 publicclass Circle extends Shape { class Circle < Shape private final int radius; def initialize(radius) public Circle(int radius) { @radius = radius this.radius = radius; end } attr_reader :radius public int getRadius() { def area return radius; Math::PI * (@radius ** 2) } end public double getArea() { end return Math.PI * Math.pow(radius, 2); puts Circle.new(2).area } public static void main(String[] args) { double area = new Circle(2).getArea(); extends → < 継承は単一継承 System.out.println(area); } } メソッド定義 → def コンストラクタ → initialize
  • 8.
    Rubyツアー 2/8: インスタンス変数 publicclass Circle extends Shape { class Circle < Shape private final int radius; def initialize(radius) public Circle(int radius) { @radius = radius this.radius = radius; end } attr_reader :radius public int getRadius() { def area return radius; Math::PI * (@radius ** 2) } end public double getArea() { end return Math.PI * Math.pow(radius, 2); puts Circle.new(2).area } public static void main(String[] args) { double area = new Circle(2).getArea(); this → @ System.out.println(area); } } attr_readerはアクセサメソッド定義用メソッド
  • 9.
    Rubyツアー 3/8: 動的型付け publicclass Circle extends Shape { class Circle < Shape private final int radius; def initialize(radius) public Circle(int radius) { @radius = radius this.radius = radius; end } attr_reader :radius public int getRadius() { def area return radius; Math::PI * (@radius ** 2) } end public double getArea() { end return Math.PI * Math.pow(radius, 2); puts Circle.new(2).area } public static void main(String[] args) { double area = new Circle(2).getArea(); 変数に型なし System.out.println(area); } duck-typing } 引数の型・数の違いによるメソッドoverloadなし
  • 10.
    Rubyツアー 4/8: 全てが値を持つ publicclass Circle extends Shape { class Circle < Shape private final int radius; def initialize(radius) public Circle(int radius) { @radius = radius this.radius = radius; end } attr_reader :radius public int getRadius() { def area return radius; Math::PI * (@radius ** 2) } end public double getArea() { end return Math.PI * Math.pow(radius, 2); puts Circle.new(2).area } public static void main(String[] args) { double area = new Circle(2).getArea(); return不要 } System.out.println(area); 文の値は最後の式 }
  • 11.
    Rubyツアー 5/8: 全てがオブジェクト、全てがメソッド public class Circle extends Shape { class Circle < Shape private final int radius; def initialize(radius) public Circle(int radius) { @radius = radius this.radius = radius; end } attr_reader :radius public int getRadius() { def area return radius; Math::PI * (@radius ** 2) } end public double getArea() { end return Math.PI * Math.pow(radius, 2); puts Circle.new(2).area } public static void main(String[] args) { double area = new Circle(2).getArea(); Circle: 定数 System.out.println(area); } a*2 == a.*(2) } Circle.new: クラスオブジェクトのnewメソッドを呼び出す
  • 12.
    Rubyツアー 6/8: ブロック(クロージャ) def aaa(name, &block) File.open(name) do |file| (1) File.open用ブロック file.each_line do |line| yield line ブロック実行後に自動close (2) end (1) end end (2) each_line用ブロック 1行読み込む毎に呼ばれる aaa('a.txt') do |line| p line (3) end (3) aaa用ブロック people.group_by { |e| e.lang } aaa内部のyieldに呼ばれる button1 = ... label1 = ... button1.on_action do |event| label1.text = 'sending...' end ← その他利用例
  • 13.
    Rubyツアー 7/8: Mix-in、オープンクラス module Utils def name Mix-in: 実装の継承 self.class.name 実装 Utilsモジュールの実装を end end class Book BookクラスにMix-in include Utils 継承 def say "Hello from #{name}" end end obj = Book.new p obj.say #=> "Hello from Book" class Book オープンクラス: def say Bookクラスのsayメソッド "I'm #{name}" end を再定義 end p obj.say #=> "I'm Book"
  • 14.
    Rubyツアー 8/8: フックメソッド classBase @@all = [] inherited: クラスが継承 def self.inherited(klass) @@all << klass された場合に、継承したクラ end スを引数に呼ばれる end class Sub < Base p @@all その他: included、 end method_added、 class SubSub < Sub method_removed、 p @@all end method_missing、 ※@@はクラス変数の接頭辞 const_missing等 ※クラスオブジェクトのキャッシュは リークの元なので普通やらない
  • 15.
  • 16.
  • 17.
    Real-World JRuby JRuby利用実例
  • 18.
    Real-World JRuby: JRuby利用実例 Java連携(Java -> Ruby) Java連携 (Ruby -> Java) Javaテスト (RSpec, JtestR) 開発支援 (Ant, Maven, Jenkins) ウェブ開発 (JRuby on Rails)
  • 19.
    Java連携 (Java -> Ruby) ユースケース
  • 20.
    Java連携 (Java ->Ruby) JavaからRubyライブラリを利用 import org.jruby.embed.ScriptingContainer; public class HelloWorld { public static void main(String[] args) { ScriptingContainer ruby = new ScriptingContainer(); ruby.runScriptlet("puts "hello,world!""); } source 'http://localhost/' } group :development do host 'localhost' port 12345 reloadable true 例: 独自定義ファイル解析の debug true end DSL処理系として group :production do host 'www.example.com' end
  • 21.
    Java連携 (Java ->Ruby) 例: gitdiff.rb - gitライブラリを利用し、リビジョ ンの変更サマリを取得するRubyコード require 'rubygems' require 'git' def diff_summary(dir, from, to) diff = Git.open(dir).diff(from, to) diff.stats[:files].map { |file, st| insertions = st[:insertions] || 0 deletions = st[:deletions] || 0 "#{file} +#{insertions} -#{deletions}" } end # =>[ "src/org/jruby/Ruby.java +32 -20", # "src/org/jruby/RubyArray.java +93 -17", # "src/org/jruby/RubyBasicObject.java +7 -0", ...
  • 22.
    Java連携 (Java ->Ruby) Javaからの呼び出しと抽出 public class GitDiff { public static void main(String[] args) throws Exception { ScriptingContainer ruby = new ScriptingContainer(); ruby.runScriptlet("require 'gitdiff'"); ruby.put("dir", "/home/nahi/git/jruby/"); ruby.put("from", "8c6dba0f..."); ruby.put("to", "7837c84a..."); List array = (List) ruby.runScriptlet( "diff_summary(dir, from, to)"); for (Object obj : array) { System.out.println(obj.toString()); } ...
  • 23.
    Java連携 (Ruby -> Java) ユースケース
  • 24.
    Java連携 (Ruby ->Java) RubyからJavaの機能を利用する Javaの対話環境としての利用も可能 % jruby -S irb > require 'java' => true > ni = java.net.NetworkInterface.networkInterfaces.to_a.first => #<Java::JavaNet::NetworkInterface:0x4d33b92c> > ni.getName => "eth0" > ni.isUp => true > ni.getMtu => 1500 > ni.inetAddresses.map { |addr| addr.to_s } => ["/fe80:0:0:0:20c:29ff:fead:4bed%2", "/192.168.96.129"]
  • 25.
    Java連携 (Ruby ->Java) Flying Saucerを使ってHTMLをPDF変換 http://code.google.com/p/flying-saucer/ % ls flyingsaucer-R8 core-renderer.jar iText-2.0.8.jar ... % jruby -S irb -Iflyingsaucer-R8 > require 'java' > require 'iText-2.0.8.jar' > require 'core-renderer.jar' > rr = org.xhtmlrenderer.pdf.ITextRenderer.new > doc = <<EOD <html><body><h1>Hello JRuby</h1> <p>from <a href="http://code.google.com/p/flying-saucer/">Flying Saucer</a>.</p></body></html> EOD > rr.set_document_from_string(doc) > rr.layout > File.open("out.pdf", "w") { |f| rr.create_pdf(f.to_outputstream) }
  • 26.
    JRubyFX: JRubybinding for JavaFX 2.0 Java FX 2.0を利用してJRuby GUIアプリ開発 https://github.com/nahi/jrubyfx (デモ)
  • 27.
    JRubyFX: SVGLoader example Javaライブラリの組み合わせ require 'jrubyfx' # https://github.com/skrb/SVGLoader require 'SVGLoader.jar' java_import 'net.javainthebox.caraibe.svg.SVGLoader' class SVGLoaderApp include JRubyFX def start(stage) root = build(Group) { children << SVGLoader.load("/duke.svg").root } with(stage, title: 'SVGLoader sample', scene: build(Scene, root)).show end end SVGLoaderApp.start
  • 28.
  • 29.
    Javaテスト (RSpec) RubyとJRubyの利点を活かしてJavaをテスト describe 'ScriptingContainer#put' do before :each do RSpec: @x = org.jruby.embed. ScriptingContainer.new end 振る舞いをテスト it "sets an object to local variable" do obj = Object.new http://rspec.info @x.put("var", obj) @x.run_scriptlet("var").should == obj % jruby -S rspec jruby_spec.rb end .. it "overrides the previous object" do obj = Object.new Finished in 0.044 seconds @x.put("var", obj) 2 examples, 0 failures @x.put("var", nil) % @x.run_scriptlet("var").should be_nil end end
  • 30.
    Javaテスト (JtestR) JtestR: 各種Ruby用テストライブラリ同梱 http://jtestr.codehaus.org/ describe "X509Name" do it "should use given converter for ASN1 encode" do converter = mock(X509NameEntryConverter) name = X509Name.new('CN=localhost', converter) converter.stubs('getConvertedValue'). with(DERObjectIdentifier.new(CN), 'localhost'). returns(DERPrintableString.new('converted')). times(1) name.toASN1Object.to_string.should == '...' end end
  • 31.
    Javaテスト (JtestR) Ant/Maven統合 +テストサーバ <?xml version="1.0" encoding="utf-8"?> <project basedir="." default="test" name="simple1"> <taskdef name="jtestr" classname="org.jtestr.ant.JtestRAntRunner" % ant test classpath="build_lib/jtestr.jar" /> Buildfile: /path/to/build.xml <taskdef name="jtestr-server" classname="org.jtestr.ant.JtestRAntServer" test: classpath="build_lib/jtestr.jar" /> <target name="test"> [jtestr] Other Spec: 4 examples, 0 failures, 0 errors [jtestr] <jtestr port="20333"/> </target> [jtestr] Total: 4 tests, 0 failures, 0 errors, 0 pending [jtestr] <target name="test-server" > <jtestr-server port="20333" runtimes="3"/> </target> BUILD SUCCESSFUL </project> Total time: 9 seconds
  • 32.
  • 33.
    開発支援 (Ant連携) desc "Build JRuby" Rake: Rubyの記述力 task :build do ant "jar" を活かして end task :jar => :build ビルド手順を記述 desc "Clean all built output" task :clean do delete_files = FileList.new do |fl| fl. Ant、Rakeから相互 include("#{BUILD_DIR}/**"). exclude("#{BUILD_DIR}/rubyspec"). にタスクを利用可能 include(DIST_DIR). include(API_DOCS_DIR) end <target name=”load-rake-task”> ... <taskdef name=”rake” classname=”org.jruby.ant.Rake”/> </target> <target name=”default” depends=”load-rake-task”> <rake task=”jar”/> </target>
  • 34.
    開発支援 (Maven連携) Maven配布物はrubygemsとしてインストール可能 開発環境の部分的Ruby化を支援 % jruby -S gem install bouncycastle:bcprov-jdk15 require 'rubygems' require 'maven/bouncycastle/bcprov-jdk15' ...
  • 35.
    開発支援 (Jenkins連携) Ruby Pluginsfor Jenkins http://bit.ly/JenkinsRuby JenkinsのプラグインをRubyで記述可能
  • 36.
    開発支援 (Jenkins連携) 例: TravisCI設定を読んで自動ビルド class TravisScriptBuilder < Jenkins::Tasks::Builder def prebuild(build, listener) travis_file = build.workspace + '.travis.yml' unless travis_file.exist? listener.error "Travis config `#{travis_file}' not found" raise "Travis config file not found" end ... def perform(build, launcher, listener) run_scripts(setup_env) ... def run_scripts(env) %w{before_script script after_script}.each do |type| scan_multiline_scripts(config[type]).each do |script| launcher.execute(env, script, :chdir => workspace, :out => listener) ...
  • 37.
  • 38.
    ウェブ開発 (JRuby onRails) Ruby on Rails - http://rubyonrails.org ウェブアプリケーションフレームワーク フルスタック CoC: (XML)設定より規約(に従って開発) DRY: 同じことを繰り返さない
  • 39.
    Railsツアー 1/7: アプリケーションの生成 MVC、テスト、サードパーティライブラリ等 常に同じディレクトリ構成 % jruby -S rails new myapp create create README create Rakefile ... create vendor/plugins create vendor/plugins/.gitkeep run bundle install Fetching source index for http://rubygems.org/ Using rake (0.9.2.2) Installing multi_json (1.0.3) ... Installing sass-rails (3.1.5) Installing uglifier (1.1.0) Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.
  • 40.
    Railsツアー 2/7: scaffold アプリの雛形作り(慣れると不要) %jruby -S rails g scaffold todo done:boolean description:string invoke active_record create db/migrate/20111128065332_create_todos.rb create app/models/todo.rb invoke test_unit create test/unit/todo_test.rb create test/fixtures/todos.yml route resources :todos invoke scaffold_controller create app/controllers/todos_controller.rb invoke erb create app/views/todos ... invoke scss create app/assets/stylesheets/scaffolds.css.scss %
  • 41.
    Railsツアー 3/7: DBマイグレーション スクリプトによるDBスキーマ履歴管理 class CreateTodos < ActiveRecord::Migration def change create_table :todos do |t| t.boolean :done t.string :description t.timestamps end end end % jruby -S rake db:migrate == CreateTodos: migrating ==================================================== -- create_table(:todos) -> 0.0040s -> 0 rows
  • 42.
    Railsツアー 4/7: サーバ起動 % jruby -S rails server scaffoldだけ => Booting WEBrick => Rails 3.1.3 application starting in development でも動く on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server [2011-11-28 15:58:15] INFO WEBrick 1.3.1 [2011-11-28 15:58:15] INFO ruby 1.8.7 (2011-11-27) [java]
  • 43.
    Railsツアー 5/7: 生成されたコード モデル class Todo < ActiveRecord::Base end <%= form_for(@todo) do |f| %> <div class="field"> <%= f.label :done %><br /> ビュー <%= f.check_box :done %> </div> コントローラ <div class="field"> <%= f.label :description %><br /> <%= f.text_field :description %> </div> class TodosController < ApplicationController<div class="actions"> def index <%= f.submit %> @todos = Todo.all </div> respond_to do |format| <% end %> format.html # index.html.erb format.json { render :json => @todos } end end def create ... end
  • 44.
    Railsツアー 6/7: ActiveRelation (Arel) 遅延SQL生成用のDSL Todo.where(:done => false) SELECT "todos".* FROM "todos" WHERE "todos"."done" = 'f' Todo.where(:done => false).where('created_at < "2011-11-29"') SELECT "todos".* FROM "todos" WHERE "todos"."done" = 'f' AND (created_at < "2011-11-29") Todo.where(:done => false).order("created_at DESC").limit(1) SELECT "todos".* FROM "todos" WHERE "todos"."done" = 'f' ORDER BY created_at DESC LIMIT 1 スコープ class Todo < ActiveRecord::Base scope :finished, where(:done => true) end Todo.finished.size SELECT COUNT(*) FROM "todos" WHERE "todos"."done" = 't'
  • 45.
    Railsツアー 7/7: RESTインターフェース JSONでのCRUD %jruby -rubygems -e 'require "httpclient"; puts HTTPClient.get("http: //localhost:3000/todos/1.json").body' => { "created_at":"2011-11-28T06:59:14Z", "description":"牛乳を買う", ... } % jruby -rubygems -e 'require "json"; require "httpclient"; puts HTTPClient.post("http://localhost:3000/todos", JSON.generate(:todo => {:description => "JRubyのベンチマーク", :done => false}), "Accept" => "application/json", "Content-Type" => "application/json" ).body' => { "created_at":"2011-11-28T07:36:19Z", "description":"JRubyのベンチマーク", ... }
  • 46.
    ウェブ開発 (JRuby onRails) Railsの全ての機能 + 既存Javaライブラリ活用 Javaアプリと同居可能 SpringMVCからRailsへのリファクタリング事例 1) "Petclinic"にJRubyでREST APIを追加 2) Railsの同居 3) Spring利用の機能をRailsで置き換え http://bit.ly/refactoring-to-rails
  • 47.
    JRuby on Railsのデプロイ WAR形式→ 任意のアプリサーバで動作 専用アプリサーバ: Trinidad (Tomcatベース) http://www.engineyard.com/ TorqueBox (JBossベース) clustering、messaging、scheduling他 http://torquebox.org/ PaaS: Engine Yard Cloud http://www.engineyard.com
  • 48.
  • 49.
    Java SE 7:InvokeDynamic 新たなメソッド呼び出しバイトコード bootstrapメソッド dynamic language support (java.lang.invoke.*) MethodHandle MethodType SwitchPoint CallSite
  • 50.
  • 51.
  • 52.
    JRubyにおけるInvokeDynamic効果 Java 6/7上で動作させたJRubyの比較 ※横軸は速度(大きいほうが速い)
  • 53.
  • 54.
    まとめ: JRuby -http://jruby.org Java開発者にも学び易いRuby言語 JavaとRubyの連携方式が豊富 現実世界で使われるフレームワーク、ライブラリ Rails、RSpec、JtestR、Jenkins git、scripting、DSL Java開発者のツールベルトに