自己紹介
株式会社サリオンシステムズリサーチ勤務
セキュリティ・ネットワーク関連のシステム開発
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について学ぶ
Ruby入門
Java開発者向け
Rubyの特徴
人に優しい文法
豊富なメタプログラミング機能
高い生産性
Rubyツアー 1/8: クラス定義
public class Circle extends Shape { private final int radius;
public Circle(int radius) { this.radius = radius;
}
public int getRadius() { return radius;
}
public double getArea() {
return Math.PI * Math.pow(radius, 2); }
public static void main(String[] args) { double area = new Circle(2).getArea(); System.out.println(area); } }
extends → <
継承は単一継承
メソッド定義 → def
コンストラクタ → initialize
class Circle < Shape
def initialize(radius) @radius = radius end attr_reader :radius def area Math::PI * (@radius ** 2) end end puts Circle.new(2).area
public class Circle extends Shape { private final int radius;
public Circle(int radius) { this.radius = radius;
}
public int getRadius() { return radius;
}
public double getArea() {
return Math.PI * Math.pow(radius, 2); }
public static void main(String[] args) { double area = new Circle(2).getArea(); System.out.println(area);
} }
class Circle < Shape
def initialize(radius) @radius = radius end attr_reader :radius def area Math::PI * (@radius ** 2) end end puts Circle.new(2).area
Rubyツアー 2/8: インスタンス変数
this → @
attr_readerはアクセサメソッド定義用メソッド
public class Circle extends Shape { private final int radius;
public Circle(int radius) { this.radius = radius;
}
public int getRadius() { return radius;
}
public double getArea() {
return Math.PI * Math.pow(radius, 2); }
public static void main(String[] args) { double area = new Circle(2).getArea(); System.out.println(area);
} }
class Circle < Shape
def initialize(radius) @radius = radius end attr_reader :radius def area Math::PI * (@radius ** 2) end end puts Circle.new(2).area
Rubyツアー 3/8: 動的型付け
変数に型なし
duck-typing
引数の型・数の違いによるメソッドoverloadなし
public class Circle extends Shape { private final int radius;
public Circle(int radius) { this.radius = radius;
}
public int getRadius() { return radius;
}
public double getArea() {
return Math.PI * Math.pow(radius, 2); }
public static void main(String[] args) { double area = new Circle(2).getArea(); System.out.println(area);
} }
class Circle < Shape
def initialize(radius) @radius = radius end attr_reader :radius def area Math::PI * (@radius ** 2) end end puts Circle.new(2).area
Rubyツアー 4/8: 全てが値を持つ
return不要
文の値は最後の式
public class Circle extends Shape { private final int radius;
public Circle(int radius) { this.radius = radius;
}
public int getRadius() { return radius;
}
public double getArea() {
return Math.PI * Math.pow(radius, 2); }
public static void main(String[] args) { double area = new Circle(2).getArea(); System.out.println(area);
} }
class Circle < Shape
def initialize(radius) @radius = radius end attr_reader :radius def area Math::PI * (@radius ** 2) end end
puts Circle.new(2).area
Rubyツアー 5/8:
全てがオブジェクト、全てがメソッド
Circle: 定数
a*2 == a.*(2)
Circle.new:
クラスオブジェクトのnewメソッドを呼び出す
Rubyツアー 6/8: ブロック(クロージャ)
def aaa(name, &block)File.open(name) do |file| file.each_line do |line| yield line end end end
aaa('a.txt') do |line| p line end
(1) File.open用ブロック
ブロック実行後に自動close
(2) each_line用ブロック
1行読み込む毎に呼ばれる
(3) aaa用ブロック
aaa内部のyieldに呼ばれる
← その他利用例
people.group_by { |e| e.lang }button1 = ... label1 = ... button1.on_action do |event| label1.text = 'sending...' end
(1)
(2)
(3)
Rubyツアー 7/8:
Mix-in、オープンクラス
module Utils def name self.class.name end end class Book include Utils def say"Hello from #{name}" end
end
obj = Book.new
p obj.say #=> "Hello from Book" class Book
def say
"I'm #{name}" end
end
p obj.say #=> "I'm Book"
Mix-in: 実装の継承
Utilsモジュールの実装を
BookクラスにMix-in
オープンクラス:
Bookクラスのsayメソッド
を再定義
実装
継承
Rubyツアー 8/8: フックメソッド
class Base@@all = []
def self.inherited(klass) @@all << klass
end end
class Sub < Base p @@all
end
class SubSub < Sub p @@all end
inherited: クラスが継承
された場合に、継承したクラ
スを引数に呼ばれる
その他: included、
method_added、
method_removed、
method_missing
、
const_missing
等
※@@はクラス変数の接頭辞
※クラスオブジェクトのキャッシュは
リークの元なので普通やらない
Ruby言語の特徴(まとめ)
動的型付け
全てがオブジェクト、全てがメソッド
ブロック(クロージャ)の活用
メタプログラミング支援
Mix-in、オープンクラス、各種フックメソッド
JRubyの特長
Ruby on Railsを含む100%の互換性
C言語版Rubyと同等の実行速度
高いスケーラビリティ(並行動作)
Real-World JRuby
JRuby利用実例
Real-World JRuby: JRuby利用実例
Java連携 (Java -> Ruby)
Java連携 (Ruby -> Java)
Javaテスト (RSpec, JtestR)
開発支援 (Ant, Maven, Jenkins)
Java連携
(Java -> Ruby)
Java連携 (Java -> Ruby)
JavaからRubyライブラリを利用
例: 独自定義ファイル解析の
DSL処理系として
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 group :production do host 'www.example.com' end
例: gitdiff.rb - gitライブラリを利用し、リビジョ
ンの変更サマリを取得するRubyコード
Java連携 (Java -> 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",
Javaからの呼び出しと抽出
Java連携 (Java -> Ruby)
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 }
Flying Saucerを使ってHTMLをPDF変換
http://code.google.com/p/flying-saucer/
Java連携 (Ruby -> Java)
% 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
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
Javaテスト
(RSpec, JtestR)
RubyとJRubyの利点を活かしてJavaをテスト
RSpec:
振る舞いをテスト
http://rspec.info
Javaテスト (RSpec)
describe 'ScriptingContainer#put' do before :each do @x = org.jruby.embed.ScriptingContainer.new endit "sets an object to local variable" do obj = Object.new
@x.put("var", obj)
@x.run_scriptlet("var").should == obj end
it "overrides the previous object" do obj = Object.new
@x.put("var", obj) @x.put("var", nil)
@x.run_scriptlet("var").should be_nil end
end
% jruby -S rspec jruby_spec.rb
..
Finished in 0.044 seconds 2 examples, 0 failures %
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
Javaテスト (JtestR)
Ant/Maven統合 + テストサーバ
% ant test
Buildfile: /path/to/build.xml
test:
[jtestr] Other Spec: 4 examples, 0 failures, 0 errors [jtestr]
[jtestr] Total: 4 tests, 0 failures, 0 errors, 0 pending [jtestr]
BUILD SUCCESSFUL
Total time: 9 seconds
<?xml version="1.0" encoding="utf-8"?> <project basedir="." default="test"
name="simple1">
<taskdef name="jtestr"
classname="org.jtestr.ant.JtestRAntRunner"
classpath="build_lib/jtestr.jar"/>
<taskdef name="jtestr-server"
classname="org.jtestr.ant.JtestRAntServer"
classpath="build_lib/jtestr.jar"/>
<target name="test">
<jtestr port="20333"/>
</target>
<target name="test-server">
<jtestr-server port="20333" runtimes="3"/>
</target> </project>
開発支援
(Ant, Maven, Jenkins)
開発支援 (Ant連携)
Rake: Rubyの記述力
を活かして
ビルド手順を記述
Ant、Rakeから相互
にタスクを利用可能
desc "Build JRuby" task :build do
ant "jar" end
task :jar => :build
desc "Clean all built output" task :clean do
delete_files = FileList.new do |fl| fl.
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”/>
開発支援 (Maven連携)
Maven配布物はrubygemsとしてインストール可能
開発環境の部分的Ruby化を支援
%
jruby -S gem install bouncycastle:bcprov-jdk15
require
'
rubygems
'
require
'
maven/bouncycastle/bcprov-jdk15
'
Ruby Plugins for Jenkins
http://bit.ly/JenkinsRuby
JenkinsのプラグインをRubyで記述可能
開発支援 (Jenkins連携)
開発支援 (Jenkins連携)
例: Travis CI設定を読んで自動ビルド
class TravisScriptBuilder < Jenkins::Tasks::Builderdef 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
scaffoldだけ
でも動く
%
jruby -S rails server
=> 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
class TodosController < ApplicationController
def index
@todos = Todo.all
respond_to do |format|
format.html # index.html.erb
format.json { render :json => @todos } end end def create ... 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>
<div class="actions">
<%= f.submit %>
</div>
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
JSONでのCRUD
Railsツアー 7/7: RESTインターフェース
% 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のベンチマーク", ... }