CoffeeScriptオンリーでWebアプリを作ってみた

この記事は大都会岡山 Advent Calendar 2012向けに書いたものです。

昨日の@ore_publicさんの記事は読みましたか!?さすが僕らのリーダーやで。

さて私、RailsからRubyを始めたワリにはCSSとかJavaScriptとか苦手で、あまり関わらないように生きてきました。しかし、CoffeeScriptの登場で大量のfunctionや"{}"や";"を使わずともJavaScriptを書くことができる時代になりました。ステキ。せっかく時代がこちらへ歩み寄ってきてるのだから、なにか作ってみることにしました。

お題はLuckOfWiseさんが会社の飲み会用に作ってたAndroidアプリを真似てみることに。車輪の再発明はムダじゃない!モチベーションが大切です。

できた。
http://tomikuji.herokuapp.comで使うことができます。ソースはgithubに置いてます。
オータムジャンボ宝くじの番号を入力すると当たりの可能性が表示されます。会社で共同購入したくじをみんなで開封するときに使うと楽しいです。

Rails

普通にRails new tomikujiして空のcontrollerを作っただけです。今回は動作確認やCoffeeScriptコンパイルするのがRailsの役目です。
コントローラとビューはこんな感じ

├── controllers
│   ├── application_controller.rb
│   └── main_controller.rb
└── views
    ├── layouts
    │   └── application.html.erb
    └── main
          └── index.html.erb

CoffeeScript

前方一致で当たりの可能性を表示するロジックはちょっと工夫してみました。

# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/

$(document).ready ->
  if Modernizr.touch
    $(".key").bind 'touchend', (event) ->
      btnClick($(@).text())
  else
    $(".key").click ->
      btnClick($(@).text())

  setTimeout("scrollTo(0,1)",100)

# 当たり番号
hits =
  "1等" : "187077"
  "2等" : "179409"
  "3等" : "119658"
  "4等" : "....30"
  "5等" : ".....9"

# 表示処理
btnClick = (number) ->
  if number == "clear"
    $("#number").text("")
    $("#message").text("")
  else
    score = $("#number").text() + number
    if score.length <= 6
      $("#number").text(score)
      showResult(score)

# 結果表示
showResult = (score) ->
  message = ""
  for key, value of hits
    diff = value.slice(0, score.length)
    if score.match(new RegExp("^#{diff}"))
      message = message + key
  if message == ""
    $("#message").text("残念!")
  else
    $("#message").text("#{message}の可能性があります")

初めはスマートフォン用もclickイベントを使ってたんですが、処理速度がメチャクチャ遅かったんですよね。なので、touchイベントに変えました。clickイベントを使うかtouchイベントを使うかの判断はModernizrを使ってみました。

css

普通のブラウザ用とスマホ用の2つを用意して画面の幅によって切り替えてみました。レスポンシブデザインとかやってみたかったの。

<head>
  <title>富くじ</title>
  <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.9.0/build/base/base-min.css">
  <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0" />
  <%= stylesheet_link_tag    "application", :media => "screen and (min-width: 641px)" %>
  <%= stylesheet_link_tag    "application_small", :media => "screen and (max-width: 640px)" %>
  <%= javascript_include_tag "application" %>
  <%= javascript_include_tag "http://modernizr.com/downloads/modernizr.js" %>
  <%= csrf_meta_tags %>
</head>

通常は複数のscssファイルが1つに統合されてしまうのですがマニフェストファイルを編集して2つに分けています。

stylesheets/
├── application.css
├── application_small.css
├── main.css.scss
└── small.css.scss

app/assets/stylesheets/application.cssにmain.css.scssを読み込むように設定

/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
 * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the top of the
 * compiled file, but it's generally better to create a new file per style scope.
 *
 *= require_self
 *= require main.css.scss
 */

app/assets/stylesheets/application_small.cssにsmall.css.scssを読み込むように設定

/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
 * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the top of the
 * compiled file, but it's generally better to create a new file per style scope.
 *
 *= require_self
 *= require small.css.scss
 */

あとはconfig/environments/production.rbに明示的にプリコンパイルするcssファイルを記述します。

  # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
  config.assets.precompile += %w( application_small.css )

こんな感じでCoffeeScriptを使ってWebアプリを作ることができました。ちょっとしたアプリならクライアントだけで実現できるのがいいですね。
さて、明日はハカセのアイコンでお馴染みのにゃーさんです。お楽しみに!

irbやrails cの履歴が表示されなくなった時の対処方法

先日rvmからrbenvに変更したのですが、irbの履歴が出てこなくなりました。いや、正確には出てくるのですが、一度終了してしまうと、以前の履歴が表示されないのです。当然rails cも同じで不便な思いをしていました。

Twitterでつぶやくと@nysalorさんが対応方法を教えてくれたのでその方法を紹介します。

IRB.confの確認

irbを起動して次のコマンドを入力します。

> IRB.conf

CONF[:SAVE_HISTORY]の値の確認

CONF[:SAVE_HISTORY]の値がnilになっている場合、履歴は表示されません。

.irbrcの編集

ホームディレクトリに.irbrcを作成し次を記述します。

IRB.conf[:SAVE_HISTORY] = 1000


irbrails cを起動してCONF[:SAVE_HISTORY]の値が設定した数値になっていることを確認して下さい。
今度は一度終了しても以前の履歴を覚えてくれてると思います。

Java Preferencesが消えた世界でいかにしてJavaのバージョンを切り替えるか

(2012.11.20 追記)
aoetkさんからコメントでご指摘いただきました。Javaコントロールパネルからpathをコピーする方法はApple的に推奨されていないようです。
http://developer.apple.com/library/mac/#qa/qa1170/_index.html

/usr/libexec/java_homeコマンドがpathを返すようなので、その実行結果をJAVA_HOMEに設定します。

.bash_profileか.bashrcにJAVA_HOMEを設定

export JAVA_HOME=`/usr/libexec/java_home`
export JAVA=$JAVA_HOME/bin




↓↓↓ここから元記事↓↓↓
いつの間にやらMacOS XからJava Preferencesが消失してしまいました。
Java言語で開発を行うことはないのですがJenkinsはできれば1.7系で動かしたいのです。
さてどうやって切り替えようかと思っていたのですが会社でpatorashさんが良い方法を教えてくれました。

Javaのpathを探す

システム環境設定→その他→Javaを起動する

JavaコントロールパネルのJavaタブ→表示をクリック

パスの内容をコピー

.bash_profileか.bashrcにJAVA_HOMEを設定

JAVA_HOMEはさっきコピーした内容をそのままペーストするのではなく、最後の/bin/javaは取り除きます。
例えば次のような内容であれば
/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java
次のようになります。

export JAVA_HOME="/Library/Internet Plug-ins/JavaAppletPlugin.plugin/Contents/Home"
export JAVA=$JAVA_HOME/bin

切り替わったか試してみましょう。

$ source .bash_profile
$ java -version
java version "1.7.0_09"
Java(TM) SE Runtime Environment (build 1.7.0_09-b05)
Java HotSpot(TM) 64-Bit Server VM (build 23.5-b02, mixed mode)

OK!良い感じです。

rbenv rehashを行わなくて良い方法

rvmがオワコン扱いされだしたのはいつの頃からでしょうか?私はrbenvのrehashが嫌でしつこくrvmを使い続けていたのですが、ここ最近のrvmでのrubyインストールの不安定さに嫌気が差してrbenvに乗り換えることにしました。

$ cd
$ rm -rf .rvm
$ brew install rbenv$ brew install ruby-build
$ echo 'eval "$(rbenv init -)"' >> .bash_profile
$ brew install readline
$ rbenv install 1.9.3-p194
$ rbenv global 1.9.3-p194

ちょとirbで動作確認してみます。

a = 1
a = 2
# ↑キーを押してキーヒストリが効くことを確認。readlineがうまく組み込まれてないとヒストリが効かない
require 'psych' #yamlパーサーの確認。rvmはここで詰まることがよくある
require 'zlib' #同じくrvmはここで詰まることがよくある
require 'openssl' #同じく(略)

私の環境では問題ありませんでした。
次にrbenv で gem を使った時に rbenv rehash しなくて良くするというエントリを参考にしてシェルスクリプトで関数定義を行い、gemおよびbundleコマンドのオーバーライドを行いました。私のPCはMacOS Xなんですがrehashコマンドは無いみたいなので参考元のスクリプトからコピーする際に省いています。

$ vi .bash_profile
#適当な場所に定義
function gem(){
  $HOME/.rbenv/shims/gem $*
  if [ "$1" = "install" ] || [ "$1" = "i" ] || [ "$1" = "uninstall" ] || [ "$1" = "uni" ]
  then
    rbenv rehash
  fi
}

function bundle(){
  $HOME/.rbenv/shims/bundle $*
  if [ "$1" = "install" ] || [ "$1" = "update" ]
  then
    rbenv rehash
  fi
}

では試してみましょう。

$ rspec
rbenv: rspec: command not found # rspecコマンドは見つからない
$ gem install rspec
$ rspec
/Users/kazuhisa/.rbenv/versions/1.9.3-p194/lib/ruby/gems/1.9.1/gems/rspec-core-2.12.0/lib/rspec/core/configuration.rb:784:in `load': cannot load such file -- /Users/kazuhisa/spec (LoadError)
#rspecを実行してテストファイルが見つからないのでエラーになってる

自動的にrbenv rehashしてくれてるみたいですね。良い感じです。
しばらくはrbenvを使ってみようと思います。

Scientific Linux release 6.3にQt4.8.xをインストールする

2012年11月9日にcapybara-webkitの0.13.0がリリースされました。このバージョンからQt4.6.x系ではコンパイルに失敗してしまうようです。現在テスト用サーバーとして使っているScientific Linux release 6.3にはyumでは4.6.x系までしかインストールできないようなのでソースを持ってきてコンパイルしてみました。

ダウンロードサイトはこちら。
http://qt-project.org/downloads

次の手順でインストールしました。

$ wget http://releases.qt-project.org/qt4/source/qt-everywhere-opensource-src-4.8.3.tar.gz
$ tar zxvf qt-everywhere-opensource-src-4.8.3.tar.gz 
$ cd qt-everywhere-opensource-src-4.8.3
$ ./configure
Which edition of Qt do you want to use ?

Type 'c' if you want to use the Commercial Edition.
Type 'o' if you want to use the Open Source Edition.
o [Enter]

This is the  Open Source Edition.

You are licensed to use this software under the terms of
the Lesser GNU General Public License (LGPL) versions 2.1.
You are also licensed to use this software under the terms of
the GNU General Public License (GPL) versions 3.

Type '3' to view the GNU General Public License version 3.
Type 'L' to view the Lesser GNU General Public License version 2.1.
Type 'yes' to accept this license offer.
Type 'no' to decline this license offer.

yes [Enter]

$ gmake
$ sudo gmake install

最後に.bashrcにpathを追加しました。

$ vi ~/.bashrc

PATH=/usr/local/Trolltech/Qt-4.8.3/bin:$PATH:$HOME/.rvm/bin # Add RVM to PATH for scripting

MacOSXと比較してLinuxのcapybara-webkitは動作が非常に遅いのですが、新バージョンで改善してるといいな。

半分ジョーク、半分マジ ken_allをRubygemsで公開しました

今年の夏、FizzBuzz 問題どや顔で解くひとなんかよりも "KEN_ALL.csv" をうまく扱える人の方が社会的貢献度高いという話題がtwitterで盛り上がったのを覚えていますでしょうか?私もそのときは「そんなんあったなぁ。懐かしい」と思っていたのですが、秋になってから隣の席の人がKEN_ALL.CSVの扱いでキレて頭を悩ませてるのをみて、社会的貢献をしようと思い立ったわけです。

...嘘です。Rails Engineを使ったgemを作りたいなぁと思っていたところにネタが転がってきただけです。手段のためには目的は選ばないのです。

ken_all | RubyGems.org | your community gem host

使い方

Railsで簡単に最新の郵便番号情報をネットから取り込むことができます。
1.Gemfileに追加

gem "ken_all"

2.プロジェクトのディレクトリでrakeタスクを実行

$ rake ken_all:install:migrations #migrationファイルをプロジェクトに作成
$ rake db:migrate #テーブルの作成
$ rake ken_all:import #郵便番号の取込

取り込んだデータはKenAll::PostalCodeモデルで使用することができます。
詳細はGithubのREADMEを見て下さい。

Rails Engineについて

Rails EngineとはRailsで作成したアプリケーションの一部を他のアプリケーションに取り込むことができる機能です。Rails用ページネーションとして有名なKaminariRails Engineを使用して作られているそうです。
Engineで私が気に入った点はテストのしやすさです。
開発中のEngineはdummyディレクトリ以下に作成されているRailsプロジェクトでいつでも実行することができます。rspec(※)も通常のRailsプロジェクトでやってる通りに書くことができます。

Rails Engineでtest unitではなくrspecを使う方法はwhile(false){.net}: Testing Rails Engines With Rspecの手順で行けます

KEN_ALLの不思議な魅力

KEN_ALL.CSVは業務システムを構築する人は一度は通る道だと思います。本家「日本郵便」が配信しているデータにもかかわらず仕様があやふやなまま何度も手直しされて今に至っているのが、データを軽く眺めるだけでも理解できます。具体的には郵便番号データの落とし穴郵便番号データの落とし穴」に落ちてしまいました。に詳しく解説されているとおりです。
おそらくパンチャーの方が汎用機に向かって一つ一つ手入力されているのでしょう。長い住所を途中で改行し複数行のデータを作るのはとても大変な作業だとお察しします。
そう、無機質であるはずのデータの向こうに人のぬくもりを感じるのです!
これって素晴らしいことだと思いませんか?

しかし、現実的には良い感じに手直しした郵便番号データをzipcloudさんが提供してくださってるのでそちらを使うののが正しいです。

ただ、データを眺めるうちに私は何とかしてプログラム処理のみで整形できないか試してみたくなったのです。
具体的にはzipcloudさんのページに書かれている解説の通りの処理を行なっているのですが次の部分だけは処理することができませんでした。

・町域名で、丸括弧で囲まれている部分を除去
 ※町域名の文字数が多いために複数行に分割されてしまっている場合は、1行にマージしておいてから丸括弧を除去します。
 ※括弧内の文字が「地名」や「ビルの階」など、住所として使えそうなものは町域名の末尾に追加します。
 ※括弧内に地名が複数列挙されている場合は複数行に分割します。

郵便番号データの町域名のカッコの扱いは非常に曖昧で、色々な使われ方をしています。何とかルールを見出してプログラムで処理したかったのですがうまくいきませんでした。
なので、ken_allのgemも外部ファイルを取り込む機能を使ってzipcloudさんのデータをimportするのが現実的かなと思っています。

$ rake ken_all:import:file FILE=/path/to/x-ken-all.csv #外部のファイルから取り込みます

まとめ

  • Rails Engine便利。みんなプロジェクトで作ってる部品や仕組みを手軽に公開しよう。
  • 郵便番号データは素直にzipcloudさんで公開されているものを使おう。
  • KEN_ALL.CSV謎深い。でも魅力的。

railsで帳票を出力するもう一つの方法 hashed-jasper-railsを作りました

rubyで帳票を出力するならThinReportsでキマリです。railsならthinreports-railsね。
ただ、ThinReportsは多段のグループヘッダ/フッタを設定することができなかったり、細かい設定はちょっと苦手な感じを受けました。
以前Javaのプロジェクトで触ったJasperReportsの感触が良かったのでRubyから何とか使えないかと調べてみたところjasper-railsというものを発見。JRubyではなくRjbを使ってjvmを操作するようです。

問題点

jasper-railsは概ね良い感じなんですが次の点が気になってきます。

  • Controllerに関連するViewのテンプレートしか使えない

コレはコレで良い仕様だとは思うのですがテンプレートを一箇所に集約して、いろんなControllerから使いたい場合は不便です。好きな場所に置きたい!

内部でActiveRecord::Relation#to_xmlしてるみたいで単一のモデル情報しか渡せないみたいです。テーブルをJoinした結果をhashに入れて渡したい!

作ってみた

jasper-railsをラップしてhashed-jasper-railsというものを作ってみました。実は今年の春頃に作ってたんですがイマイチ納得出来ない作りだったのでアナウンスを控えていました。さて、詳しく使い方を説明しますね。

iReportの準備

iReportはJasperReportsで使うための帳票テンプレートをデザインするツールです。
次のURLからダウンロードして設定しましょう。
http://community.jaspersoft.com/project/ireport-designer

次に日本語を使うためにiTextAsian.jarをダウンロードします。
http://itextpdf.sourceforge.net
iTextAsian.jarはClasspathに追加しておきましょう。
Macであればダウンロードしたファイルは/Library/Java/Extensionsにコピーしておけばよいでしょう。

iReportでiTextAsian.jarを読み込みます。
メニューのiReport -> 環境設定 -> Classpathタブとたどって先ほどのiTextAsian.jarを追加しましょう。これで日本語が使えるようになります。

iReportでデザイン

iReport自体の使い方はググればたくさん出てきます。ここでは注意点だけ。

  • 全体のプロパティのQuery Textは/モデル名複数形/モデル名単数形とする。例えばSaleモデルのインスタンスであれば/sales/saleとなる。
  • The language for the dataset queryはxPathとする。
  • 日本語フォントを使用する項目は次のように設定する。

Pdf Font name はHeiseiKakuGo-W5もしくはHeiseiMin-W3を指定。
Pdf EncodingはUniJIS-UCS2-Hとする。

jasper-railsの使い方

hashed-jasper-railsはjasper-railsの機能を全て使うことができます。
ここでは売上情報が設定されたSaleモデルが存在しており、views/salesにreport.jrxmlというコンパイル済みテンプレートファイルが置かれているものとします。

Gemfile

RailsプロジェクトのGemfileに次の1行を追加してbundle updateして下さい。

gem 'hashed-jasper-rails'
Controller

次の設定を行なって下さい。

class SalesController < ApplicationController
  respond_to :html, :xml, :pdf

  def report
    sales = Sale.all
    respond_with sales
  end

  (略)
end

http://localhost:3000/sales/report.pdf
をWebブラウザで叩くとpdfが取得できるはずです。良い感じですね。

hashed-jasper-railsの使い方

datasourceをhashで渡してみる

まずはテンプレートのQuery Textを/jasper/recordに設定して下さい。この値はオプションで変更することができます。
actionは次のように設定できます。

def report
  sales = [
      {:name => "Izumi Konata", :amount => 10_000},
      {:name => "Hiiragi Tsukasa", :amount => 20_000},
      {:name => "Hiiragi Kagami", :amount => 30_000}
  ]
  jasper_pdf :resource => sales
end

http://localhost:3000/sales/report
をWebブラウザで叩くとpdfが取得できるはずです。respond_withを使ってないのでURIの最後の拡張子は不要であることに注意して下さい。

テンプレートを指定してみる

:templateオプションでviews以下のpathを指定することもできます。

jasper_pdf :resource => sales, :template => "sales/list"

まとめ

既存の機能を生かしつつラップするのに苦労しました。Railsで複雑な帳票を出力するときに使ってみて下さい。