IT・技術研修ならCTC教育サービス

サイト内検索 企業情報 サイトマップ

研修コース検索

コラム

Ruby & Rails

CTC 教育サービス

 [IT研修]注目キーワード   Python  UiPath(RPA)  最新技術動向  OpenStack  システムトラブルシュート 

第16回 Opal ~JavaScriptもRubyで~ (松永紘) 2014年7月

 6/26にRails4.0.6及び4.1.2がリリースされました(*1)。このリリースは4.1系では初めての、4.0系では4.0.4以来となるバグフィックスリリースです。3回のRelease Candidate版を経て、"Regression"をフィックスしているようですので是非使ってみてください。

 尚、3.2系に対するバグフィックスリリースは既に終了しています(*2)。当該バージョンをお使いの方はRails4系にアップデートされることをお勧めいたします。

Opal

 いまやWeb開発の必須スキルともいえるJavaScriptですが、その独特の仕様ゆえに評価が分かれる言語でもあります(*3)。そこでJQueryやUnderscore.jsなどのライブラリやJavaScriptへのコード変換が行えるCoffeeScriptやTypeScriptなどが脚光を浴びています。

 そのような中今回は、100%のRubyの表記で記述できJavaScriptへのコード変換が行える「Opal(*4)」をご紹介いたします。

 なお、動作環境は以下の通りです。(*5)

  • Windows 7
  • Chrome 35
  • Ruby 2.1.2
  • Rails 4.1.2
  • Opal 0.6.2

 まずはプロジェクトを作成しましょう。「--javascript=opal」のオプションをつけることで、Opalの使用に必要な環境が自動的にインストール・定義されます。

> rails new OpalSample --javascript=opal

 「/Gemfile」にはopal-railsを使用するように記述されています。

(略)
# Use opal as the JavaScript library
gem 'opal-rails'
(略)

 「/app/assets/javascripts/application.js」にはjquery、jquery_ujsの代わりにopal、opal_ujsを使用するように記述されています。

(略)
//= require opal
//= require opal_ujs
//= require turbolinks
//= require_tree .

 次にOpalを実行する画面を作成します。ここではSampleController#indexをジェネレートしました(*6)。

> rails g controller sample index
    create  app/controllers/sample_controller.rb
     route  get 'sample/index'
    invoke  erb
    create    app/views/sample
    create    app/views/sample/index.html.erb
     (略)

 とりあえず適当な演算を行ってみましょう。「app/assets/javascripts/sample.js.opal」を作成し(*7)、以下のように編集します。

# 文字列の出力
puts "Hello Opal!"
# 5の階乗(1)
def factorial(n)
  n > 1 ? n * factorial(n-1) : n
end
puts factorial(5)
# 5の階乗(2)
puts 1.upto(5).inject(:*)
# 5の階乗(3)
class Fixnum
  def factorial
    self > 1 ? self * (self-1).factorial : self
  end
end
puts 5.factorial

 編集後「http://localhost:3000/sample/index」にアクセスし、開発者ツールでコンソールを確認すると結果が表示されているかと思います(*8)。

fig01

 お決まりの「Hello Opal」以外に階乗の計算を3パターンで行ってみましたが、どれも正常に出力されています。当たり前といえば当たり前ですが、オープンクラスにも対応しておりなかなか完成度が高そうです。

Opal-JQuery

 続いてDOM操作を行ってみましょう。OpalにはJQueryライクなOpal-JQueryというライブラリがついていますのでそちらを利用します。

 ここでは特に凝ったことをせずに自動生成されるerbファイルをそのまま用いてみます。h1要素をクリックした際、末尾に赤字で「h1 was clicked!」と表示させてみましょう。

fig02

 自動生成される「app/views/sample/index.html.erb」の中身は以下のようになっています。

<h1>Sample#index</h1>
<p>Find me in app/views/sample/index.html.erb</p>

 JQueryで記述すると以下のようなソースコードになるでしょう。

$(function(){
  $("h1").on("click", function(){
    var p_elem = $("<p>");
    p_elem.text("h1 was clicked"); p_elem.css("color", "red");
    $().append(p_elem);
}); });

 「app/assets/javascripts/sample.js.opal」を以下のように編集します。

# 
Document.ready? do
  Element.find("h1").on(:click) do
    p_elem = Element.new("p")
    p_elem.text("h1 was clicked!")
    p_elem.css("color", "red")
    Element.find("body").append(p_elem)
  end
end

 多少記述の違いはありますが、JQueryライクにDOM操作が行えますね。下記の表に特筆すべき違いを示します。

  JQuery Opal
要素の参照 $("selector") Element.find("selector")
要素の作成 $("<element>") Element.new("element")
JQueryオブジェクトの取得 $(object) Element.parse(object)
documentの取得 $(document) Document

 また、JQueryではキャメルケースでメソッド名やプロパティ名などが定義されていますが、OpalではRubyの文化に合わせてスネークケースで定義されているようです(*9)。

Nativeなコードの実行

 「`(アクサングラーブ)」を使うことで、NativeなJavaScriptのコードを実行することができます(*10)。

`window.open("http://example.com", "example")` 

 windowオブジェクトのほか、前回までに使用したWebSocketやEventSourceなどへアクセスする際にもこの構文を使うことになりますが、「`」の中はJavaScriptの文法になるためOpalを使うメリットが半減してしまいます。

 そのようなケースのために、OpalではKernel#Nativeが用意されています。このメソッドを使うことでRubyライクにJavaScriptのオブジェクトを使用することができます。

win = Native(`window`)
win.open("http://example.com", "example")

 ここでは1行目でJavaScriptのコードを引数にNativeメソッドを呼び出し、変数winに代入しています(*11)。2行目ではRubyライクにopenメソッドを呼び出しています。

Opal-Parser

 Opal-Parserというライブラリを用いることで、Rubyスクリプトを直接HTMLに埋め込むことができます。Rails上で動作させるには、「app/asset/javascripts/application.js」を以下のように編集します。

(略)
//= require opal
//= require opal_ujs
//= require opal-parser    // この行を追加
//= require turbolinks
//= require_tree .

 ここでは、前回のActionController::Liveによるチャット機能のクライアントサイドコードをOpalで書き換えてみたいと思います。

 「app/views/chat/index.html.erb」を以下のように編集します。

<!-- チャット表示部分 -->
<ul id="chat_area">
</ul>
<!-- コメントフォーム -->
<%# Ajaxリクエストで送信 %>
<%= form_tag "/chat/message", :remote => true do %>
  <%= text_field_tag :comment %>
  <%= submit_tag "send" %>
<% end %>
<script type="text/ruby">
  # SSEの接続
  sse = Native(`new EventSource("/chat/stream")`)
  # メッセージ受信時の処理
  sse.onmessage = lambda do |native_event|
    event = Native(`native_event`)
    message_li = Element.new("li")
    message_li.text(event.data)
    Element.find("#chat_area").append(message_li)
  end
</script>

ポイントをいくつか説明します。

[Line12]

<script type="text/ruby">

 OpalをHTMLに埋め込むためにはscriptタグのtype属性に「text/ruby」を指定します。ここではインラインで書いていますが、src属性を用いることで外部ファイルを読み込むこともできます。

[Line14]

  sse = Native(`new EventSource("/chat/stream")`)

 この行ではEventSourceオブジェクトを取得しています。本来は「NativeEventSource = Native(`EventSource`)」でEventSourceクラスを取得後、「sse = NativeEventSource.new("/chat/stream")」としたいところですがこのやり方ではエラーとなるため(*12)、オブジェクトの生成までをJavaScriptコードで行っています。

[Line18]

    event = Native(`native_event`)

 イベントハンドラのコールバック関数に引き渡されるMessageEventオブジェクトをRubyのオブジェクトに変換しています(*13)。

まとめ

 ここまで簡単ではありますが、Opalの紹介をしてまいりました。公式サイトにはより詳しく説明が掲載されていますので、一度ご覧ください。

 使用感としては、ActionController::Liveにおけるクライアントサイドコードを見ていただいても分かる通り、まだ本番運用で使用するには難しいところもあるかなと思います。またエラー発生時のデバッグが難しいという問題も抱えています(*14)。

 ですがRSpecによるテストも行えるようですし、何よりRubyの表現力は魅力的ですので今後に期待していきたいと思います。みなさんも是非試してみてください。

 それでは、Enjoy Ruby!

注釈

*1 : http://weblog.rubyonrails.org/2014/6/26/Rails-4-1-2-and-4-0-6-has-been-released/

*2 : Rails3.2.14リリースの際にアナウンスされています(http://weblog.rubyonrails.org/2013/7/23/Rails-3-2-14-has-been-released/)。

*3 : ここら辺の話はこちらのスライド(http://goo.gl/ovgS7Z)が参考になります。

*4 : http://opalrb.org/

*5 : 今回はコンパイルなどの手間を省くためRails上で動作させていますが、Railsなしでも動作させることができます。

*6 : Opalファイルを作るためのジェネレータが存在しないため、ジェネレート中にエラーが発生します(https://github.com/opal/opal-rails/issues/26)。しかし手動で作成しても問題ないため、ここでは無視します。

*7 : 拡張子は「.rb」もしくは「.opal」である必要があります。ここでは「.opal」を選択してみました。

*8 : putsメソッドを使用するとコンソール上に出力を行います(console.log()と同じ)。

*9 : 例えば、JQueryで定義されているaddClassやremoveClassはOpalではadd_class、remove_classと定義されています。

*10 : 外部コマンドを実行するKernel#`(http://docs.ruby-lang.org/ja/2.1.0/class/Kernel.html#M_--60)と同様の形式です。

*11 : Opalのドキュメント(view-source:http://opalrb.org/docs/interacting_with_javascript/)では「window = Native(`window`)」と説明されていますが、この記述では変数windowに何も代入されず次行でエラーになります。これは左辺の変数windowの宣言が先に行われ、「`window`」ではそちらを参照しにいくためだと考えられます。この挙動が正しいのかどうかはよく分かりません。

*12 : 「Native(`EventSource`)」の戻り値はProcオブジェクトとなるため、クラスのように扱うことができません。これはJavaScriptがクラスレスなプログラミング言語であり、クラスに当たる部分は関数を用いて実現しているためだと考えられます。

*13 : コールバック関数に渡される引数はJavaScriptのオブジェクトになるようです。

*14 : デバッグ(というかOpalのバグ調査)については、こちらにまとめられています(http://qiita.com/yhara/items/2c8a668aeb7173321875)。

 


 

 [IT研修]注目キーワード   Python  UiPath(RPA)  最新技術動向  OpenStack  システムトラブルシュート