2012年6月25日月曜日

クラスの削除

irbやpryなどでサンプルを試したりしていると、適当なクラス名を使っていたりするため、クラスが重複することがあります。
もう1度class ... endと書いてしますと、クラスを再オープンすることになるので、思わぬ副作用に遭遇する時があります。
一度セッションを閉じるのも手ですが、remove_constを使用してクラスを削除できます。

pry(main)> class MyClass; end
=> nil
pry(main)> Object.const_defined? :MyClass
=> true
pry(main)> Object.class_eval {remove_const :MyClass}
=> MyClass
pry(main)> Object.const_defined? :MyClass
=> false

MyClassはトップレベルに定義したクラスなので、Objectの中に定義された定数(=クラス)を削除します。
remove_constはプライベートメソッドなので明示的にレシーバを指定できません。
そのため、上記のようにclass_eval / instance_evalまたはsendメソッドで呼び出す必要があります。

pry(main)> class MyClass; end
=> nil
pry(main)> Object.const_defined? :MyClass
=> true
pry(main)> Object.send :remove_const, :MyClass
=> MyClass
pry(main)> Object.const_defined? :MyClass
=> false

Moduleの中で定義されているクラスは、該当するModuleの中から削除します。

pry(main)> module M class MyClass; end; end
=> nil
pry(main)> M.const_defined? :MyClass
=> true
pry(main)> M.instance_eval {remove_const :MyClass}
=> M::MyClass
pry(main)> M.const_defined? :MyClass
=> false

2012年6月14日木曜日

ソート

達人出版社アルゴリズムを学ぼうという本を購入しました。
既に知っている内容から、何となくは知っていたけど理解があやふやだったというものが掲載されているので、頭の体操がてらには丁度いい印象です。
本書ではJavaで書かれていますが、Rubyで書きなおしてみます。
本文中に"Pythonとか、Rubyとかを使うべきなんだろうが、やつらはちょっと適当に書けすぎる"とありますが、気にしないことにします。

下記は、第3講で紹介されているソートアルゴリズムになります。(gist)

  • バブルソート
  • 選択ソート
  • 挿入ソート
  • マージソート
  • クイックソート
  • ヒープソート
実質Arrayへの拡張になっていますが、ちょっとはRubyらしく、ブロックを受け取ることことで昇順、降順をはじめ任意の並び替えに対応させています。
普段は至極当然ですが、Array#sortを使っています。

lib/sort.rb

module Sort
  def compare_to? x, y, &block
    return block ? block.call(x, y) : x < y
  end

  def bubblesort &block
    ary = self.clone
    (ary.size).downto(0) do |i|
      (0...(ary.size - 1)).each do |j|
        break if j > i
        unless compare_to? ary[j], ary[j+1], &block
          ary[j], ary[j+1] = ary[j+1], ary[j]
        end
      end
    end
    ary
  end

  def selectionsort &block
    ary = self.clone
    ary.each_index do |i|
      ((i+1)...(ary.size)).each do |j|
        unless compare_to? ary[i], ary[j], &block
          ary[i], ary[j] = ary[j], ary[i]
        end
      end
    end
    ary
  end

  def insertionsort &block
    ary = self.clone
    (1...ary.size).each do |i|
      j = i
      while (j > 0)
        unless compare_to? ary[j-1], ary[j], &block
          ary[j-1], ary[j] = ary[j], ary[j-1]
        end
        j -= 1
      end
    end
    ary
  end

  def mergesort &block
    mergesort_bang self, &block
  end

  def mergesort_bang ary, &block
    return ary if ary.size <= 1

    mid = ary.size / 2
    left = mergesort_bang ary[0...mid], &block
    right = mergesort_bang ary[mid..-1], &block
    merge left, right, &block
  end

  def merge x, y, &block
    work = []
    loop do
      if x.empty?
        work << y
        break
      end
      if y.empty?
        work << x
        break
      end
      compare_to?(x[0], y[0], &block) ? work << x.shift : work << y.shift
    end
    work.flatten
  end

  def quicksort &block
    quicksort_bang self, &block
  end

  def quicksort_bang items, &block
    pivot = items[0]
    parts = items.partition { |i| compare_to? i, pivot, &block }
    lower, upper = parts[0], parts[1]
    if parts.all? { |i| i.size <= 1}
      return lower.concat(upper).flatten
    end
    # split [x][y,z, ...] if "[][x,y,z, ...]"
    lower = upper.shift(1) if lower.size == 0
    upper = lower.shift(1) if upper.size == 0
    p1 = quicksort_bang(lower, &block)
    p2 = quicksort_bang(upper, &block)
    p1.concat p2
  end

  def heapsort &block
    heap = Heap.new &block
    self.each { |i| heap.push i }
    ary = []
    until (heap.values.empty?)
      ary << heap.pop
    end
    ary
  end
end

class Heap
  attr_reader :values
  def initialize values=[], &block
    @values = values
    @block = Proc.new &block if block_given?
  end

  def compare_to? x, y
    return @block ? @block.call(x, y) : x < y
  end

  def parent c
    (c - 1) / 2
  end

  def left c
    2 * c + 1
  end

  def right c
    2 * c + 2
  end

  def push v
    @values.push(v)
    c = @values.rindex v
    while (c > 0)
      break if compare_to? @values[parent(c)], @values[c]
      @values[parent(c)], @values[c] = @values[c], @values[parent(c)]
      c = parent(c)
    end
  end

  def pop
    value = @values.shift
    return value if @values.empty?
    @values.unshift @values.pop
    c = 0
    while (@values[left(c)] || @values[right(c)])
      child = !@values[right(c)] || compare_to?(@values[left(c)], @values[right(c)]) ? left(c) : right(c)
      if compare_to? @values[child], @values[c]
        @values[c], @values[child] = @values[child], values[c]
        c = child
      else
        break
      end
    end
    return value
  end
end

spec/sort_spec.rb

require 'spec_helper'

shared_examples_for 'sort [](empty)' do
  let(:ary) do
    ary = []
    ary.extend Sort
    ary
  end
  it { should == [] }
end

shared_examples_for 'ascending sort [2, 6, 1, 0, 5, 4, 3]' do
  let(:ary) do
    ary = [2, 6, 1, 0, 5, 4, 3]
    ary.extend Sort
    ary
  end
  it { should == [0, 1, 2, 3, 4, 5, 6] }
end

shared_examples_for "sort ['C', 'B', 'A', 'F', 'E', 'D'] with block as desending" do
  let(:ary) do
    ary = ['C', 'B', 'A', 'F', 'E', 'D']
    ary.extend Sort
    ary
  end
  it { should == ['F', 'E', 'D', 'C', 'B', 'A'] }
end

describe Sort do
  context "#bubblesort" do
    subject { ary.bubblesort }
    it_behaves_like 'sort [](empty)'
    it_behaves_like 'ascending sort [2, 6, 1, 0, 5, 4, 3]'
    it_behaves_like "sort ['C', 'B', 'A', 'F', 'E', 'D'] with block as desending" do
      subject { ary.bubblesort { |a, b| a > b} }
    end
  end

  context "#selectionsort" do
    subject { ary.selectionsort }
    it_behaves_like 'sort [](empty)'
    it_behaves_like 'ascending sort [2, 6, 1, 0, 5, 4, 3]'
    it_behaves_like "sort ['C', 'B', 'A', 'F', 'E', 'D'] with block as desending" do
      subject { ary.selectionsort { |a, b| a > b} }
    end
  end

  context "#insertionsort" do
    subject { ary.insertionsort }
    it_behaves_like 'sort [](empty)'
    it_behaves_like 'ascending sort [2, 6, 1, 0, 5, 4, 3]'
    it_behaves_like "sort ['C', 'B', 'A', 'F', 'E', 'D'] with block as desending" do
      subject { ary.insertionsort { |a, b| a > b} }
    end
  end

  context "#mergesort" do
    subject { ary.mergesort }
    it_behaves_like 'sort [](empty)'
    it_behaves_like 'ascending sort [2, 6, 1, 0, 5, 4, 3]'
    it_behaves_like "sort ['C', 'B', 'A', 'F', 'E', 'D'] with block as desending" do
      subject { ary.mergesort { |a, b| a > b} }
    end
  end

  context "#quicksort" do
    subject { ary.quicksort }
    it_behaves_like 'sort [](empty)'
    it_behaves_like 'ascending sort [2, 6, 1, 0, 5, 4, 3]'
    it_behaves_like "sort ['C', 'B', 'A', 'F', 'E', 'D'] with block as desending" do
      subject { ary.quicksort { |a, b| a > b} }
    end
  end

  context "#heapsort" do
    subject { ary.heapsort }
    it_behaves_like 'sort [](empty)'
    it_behaves_like 'ascending sort [2, 6, 1, 0, 5, 4, 3]'
    it_behaves_like "sort ['C', 'B', 'A', 'F', 'E', 'D'] with block as desending" do
      subject { ary.heapsort { |a, b| a > b} }
    end
  end

end

describe Heap do
  context "#push" do
    context "when push '4' to [5, 8]" do
      subject do
        heap = Heap.new([5,8])
        heap.push 4
        heap.values
      end
      it { should == [4, 8, 5]}
    end
    context "when push '3' to [4, 9, 7, 8]" do
      subject do
        heap = Heap.new([4, 9, 7, 8])
        heap.push 3
        heap.values
      end
      it { should == [3, 4, 7, 8, 9]}
    end
  end
  context "#pop" do
    context "when pop from [4, 9, 7, 8, 10]" do
      let(:heap) { heap = Heap.new([4, 9, 7, 8, 10]) }
      it "returns 4" do
        heap.pop.should == 4
      end
      it "re-build heap as [7, 9, 10, 8]" do
        heap.pop
        heap.values.should == [7, 9, 10, 8]
      end
    end
    context "when pop from [](empty)" do
      let(:heap) { heap = Heap.new }
      it "returns nil" do
        heap.pop.should be_nil
      end
      it "no change heap as []" do
        heap.pop
        heap.values.should == []
      end
    end
  end
end

spec/spec_helper.rb

require 'pry'
require 'pry-nav'
require 'sort'

2012年6月6日水曜日

pry

半信半疑で入れたpryでしたが、思ったより重宝します。
色々なところで紹介されているので詳細は割愛しますが、個人的には

  • 外部エディタと連携
  • APIの検索やドキュメントの閲覧
  • デバッグの連携
あたりで使用しています。

pryはirbの代替というのが主な位置付けだと思いますが、今までirbでやることは大概がちょっとしたコードの試し書きみたいなもの。
ワンライナーで書ける程度ならよいのですが、1メソッド程度でも使い辛さを感じていました。
pryはラインエディタも備えていますが、使い慣れたエディタを使うのが一番かと思います。

2番目のドキュメント閲覧については、ウェブを検索することが多かったのですがちょっとだけ確認したい時などやはり面倒。
emacs使ってるので連動はできるのですが、あまりしっくりきていませんでした。
pryでお試しコード書く時も、そのままの状態でドキュメントを確認できるので作業がスムーズです。

3番目のデバッグですが、どうしてもデバッガはIDE付属のものをはじめ馴染めるものが少ない印象です。
その点、pryであればbinding.pryだけでブレイクポイント設定できるし、条件付きであれば"binding.pry if xxx"と書くだけ。
RSpec等でテストを自動にしていれば、「保存→テスト実行→ブレイクポイントで停止」となるので、非常にスムーズにデバッグがおこなえると思います。

インストールはgemで導入するのみ。

$ gem install pry pry-doc pry-nav
rvmでrubyをインストールした場合は
$ rvm docs generate
を実行しないと、ドキュメントが表示することができません。
これはpryに限ったことでなくriでも同様なのですが、最初これではまりました。
あとは、デバッグでステップ実行のためのエイリアスを設定しておきます。

~/.pryrc

Pry.commands.alias_command 'c', 'continue'
Pry.commands.alias_command 's', 'step'
Pry.commands.alias_command 'n', 'next'

2012年6月5日火曜日

モジュールのテスト

RSpecでモジュールをテストする方法です。
いくつか方法があると思いますがここでは3つ挙げます。

まず一つ目はRSpec.configureでincludeする方法。

lib/app.rb

module Hello
  module World
    def say
      "Hello World!"
    end
  end
end

spec/spec_helper.rb

require 'app'

RSpec.configure do |config|
  config.include Hello::World
end

spec/app_spec.rb

require 'spec_helper'

describe "Hello::World" do
  context "#say" do
    context "with including Module via RSpec.configure" do
      it { say.should == "Hello World!" }
    end
  end
end

2つ目はdescribeブロックでincludeする方法。こちらはspc_helper.rbのRSpec.configure記述が不要です。

spec/app_spec.rb

require 'spec_helper'

describe "Hello::World" do
  include Hello::World
  context "#say" do
    context "with including Module within the 'describe' block" do
      it { say.should == "Hello World!" }
    end
  end
end
これらはsinatraでRack::TESTを使用する方法と同様です。

別の方法としてオブジェクト拡張を使う方法があります。オブジェクト拡張についてはメタプログラミングRubyに記述があります。

spec/app_spec.rb

require 'spec_helper'

describe "Hello::World" do
  context "with anonymous inner class" do
    subject do
      obj = Object.new
      obj.extend Hello::World
      obj
    end
    its(:say) { should == "Hello World!" }
  end
end

1、2番目はRack::Testの他にもhelperのテストでも同様に使用できるでしょう。特定のクラスで使用することを想定してテストしたい場合や、局所的に記述したい時は3番目で書くとよいのではないでしょうか。

2012年5月29日火曜日

conky

定番の監視ツールconkyの導入です。
Xubuntu 12.04にしてから、conky起動後にデスクトップをクリックするとconkyの画面が消失する(プロセスは生きている)現象があったため
しばらく放置していたのですが、設定で解決できました。

まずはインストール。
$ sudo apt-get install conky-all lm-sensors

私のPCのマザーボードにはw83627ehfのドライバが必要で、以前は手動でインストールしていたのですが、
いつの間にか取り込まれていて不要になりました。

$ sudo sensors-detect

基本的にデフォルトでOKだと思いますが、最後に必要なドライバを/etc/modules追記するか聞かれるので、ここだけ追加するように答えます。
もちろん、手動で追加しても構いません。

設定についてはあまり詳しく知らないのですが、デフォルトの/etc/conky/conky.confとネットでの情報を元に記述しました。
ここで"own_window_type desktop"を設定した場合、"own_window_hints undecorated,below,sticky,skip_taskbar,skip_pager"を設定しないと、
最初に述べた、デスクトップをクリックした時にconkyが消失する現象が発生してしまいます。

~/.conkyrc

alignment bottom_left
background no
border_width 1
cpu_avg_samples 6
default_color white
default_outline_color white
default_shade_color white
draw_borders no
draw_graph_borders yes
draw_outline no
draw_shades no
use_xft yes
xftfont ricty Mono:size=10
gap_x 80
gap_y 60
minimum_size 5 5
net_avg_samples 2
no_buffers yes
out_to_console no
out_to_stderr no
extra_newline no
own_window yes
own_window_class Conky
own_window_type desktop
own_window_argb_visual yes
own_window_transparent yes
own_window_hints undecorated,below,sticky,skip_taskbar,skip_pager
double_buffer yes
stippled_borders 0
update_interval 1.0
uppercase no
use_spacer none
show_graph_scale no
show_graph_range no

TEXT
${color grey}Uptime:$color $uptime

${color orange}CPU$alignr $color${freq_g}GHz ${hwmon 0 temp 2 0.001 0}℃
$cpu%${color grey}${cpubar 6}
 ${color grey}CPU1 $color ${cpu cpu1}%
  ${color grey}${cpugraph cpu1 15,200}
 ${color grey}CPU2 $color ${cpu cpu2}%
  ${color grey}${cpugraph cpu2 15,200}
 ${color grey}CPU3 $color ${cpu cpu3}%
  ${color grey}${cpugraph cpu3 15,200}
 ${color grey}CPU4 $color ${cpu cpu4}%
  ${color grey}${cpugraph cpu4 15,200}
 ${color grey}CPU5 $color ${cpu cpu5}%
  ${color grey}${cpugraph cpu5 15,200}
 ${color grey}CPU6 $color ${cpu cpu6}%
  ${color grey}${cpugraph cpu6 15,200}

${color orange}GPU$alignr$color
 ${nvidia temp}℃

${color orange}Memory
 ${color grey}RAM Usage
  ${color}$mem${color grey}/$color$memmax${color grey} - ${color}$memperc%
   ${color grey}$membar
 ${color grey}Swap Usage
  $color $swap${color grey}/$color$swapmax${color grey} - $color$swapperc%
   ${color grey}${swapbar 6}

${color orange}DISK
 $color${fs_used}${color grey}/$color${fs_size}$alignr${fs_used_perc}%
  ${color grey}${fs_bar 6}
 ${color grey}Read $color$diskio_read${color grey} / Write $color$diskio_write

${color orange}Networking
 ${color grey}Up $color ${upspeed eth0}${color grey} / Down $color ${downspeed eth0}

${color orange}Processes$alignr${color grey}Total:${color}$processes  ${color grey}Running:$color $running_processes
${color grey} TOP CPU
 ${color grey}Name${alignr}PID   CPU%   MEM%
 ${color lightgrey}${top name 1}$alignr${top pid 1} ${top cpu 1} ${top mem 1}
 ${color lightgrey}${top name 2}$alignr${top pid 2} ${top cpu 2} ${top mem 2}
 ${color lightgrey}${top name 3}$alignr${top pid 3} ${top cpu 3} ${top mem 3}
 ${color lightgrey}${top name 4}$alignr${top pid 4} ${top cpu 4} ${top mem 4}

${color grey} TOP MEM
 ${color grey}Name${alignr}PID   CPU%   MEM%
 ${color lightgrey}${top_mem name 1}$alignr${top_mem pid 1} ${top_mem cpu 1} ${top_mem mem 1}
 ${color lightgrey}${top_mem name 2}$alignr${top_mem pid 2} ${top_mem cpu 2} ${top_mem mem 2}
 ${color lightgrey}${top_mem name 3}$alignr${top_mem pid 3} ${top_mem cpu 3} ${top_mem mem 3}
 ${color lightgrey}${top_mem name 4}$alignr${top_mem pid 4} ${top_mem cpu 4} ${top_mem mem 4}

あとはスタートアップに登録すればよいのですが、私の環境の場合、コマンドで"conky -d"の登録だけでも動作しました。
環境によってはsleepを入れて起動を遅らさなければいけないようなので、その場合はスクリプトを書いて登録しましょう。

2012年5月27日日曜日

HTTPメソッドとの対応

sinatraに触れると、だいたい最初の段階でCRUD操作がルーティングのpost/get/put/delete do ... endに対応する、ということを学びます。
しかしながら、多くのブラウザにはgetおよびpostの実装しかないということで、putとdeleteはもうひと工夫必要になります。
この辺りは予備知識があれば容易に分かるところですが、そうでないとなかなか辿り着くのに苦労します。
解決策としては

  • POSTによる代用
  • Javascriptによるput/deleteメソッドの生成
があります。get '/foo/delete' do ... end などとしてgetによる削除、更新処理をしている例もありますが、RESTfulなURI設計とは言い難いのでおすすめしません。
POSTによる代用は、フォームの隠しパラメータに_methodパラメータを用意しそこにメソッド名を入れます。sinatraで利用する場合は、:method_overrideを有効にすることで適切なルーティングがおこなわれます。例えば、
@@ post_delete
form method="POST"  action="/destroy_it"
  input type="hidden" name="_method" value="DELETE"
  button type="submit"
    | delete
のようなフォーム(Slim使用)あって、ボタンを押すとPOSTリクエストがサーバには届きますが、delete "/xxxx" do ... end へルーティングされます。
二番目の方法は読んでそのままですが、jQueryを使用すると下記のような形でリクエストを生成します。
$.ajax({
  type: "DELETE",
  url: "destroy_it",
  success: function(data) {
    $(".message").text(data)
  }
});
以下、両者のサンプルになります。"$ ruby app.rb"で実行できます。

app.rb

require 'sinatra/base'
require 'slim'

class MyApp < Sinatra::Base

  configure :development do
    require 'sinatra/reloader'
    register Sinatra::Reloader
  end

  configure do
    enable :inline_templates
    enable :method_override
  end

  get '/style.css' do
    scss :style
  end

  get '/post_delete' do
    slim :post_delete
  end

  get '/delete_method' do
    slim :delete_method
  end

  delete '/destroy_it' do
    content_type :text
    "delete done!!"
  end

  run! if app_file == $0
end

__END__

@@ layout
doctype html
html
  head
    title My App
    script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"
    link rel="stylesheet" href="/style.css"
  body == yield

@@ post_delete
form method="POST"  action="/destroy_it"
  input type="hidden" name="_method" value="DELETE"
  button type="submit"
    | delete

@@ delete_method
span.delete__
  | delete
div.message

javascript:
  $(function(){
    $(".delete__").click(function(){
       $.ajax({
         type: "DELETE",
         url: "destroy_it",
         success: function(data) {
           $(".message").text(data)
         }
       });
    })
  })

@@ style
span.delete__ {
  cursor: pointer;
  background-color: #dddddd;
  border: #cccccc 2px outset;
  border-radius: 5px;
  padding: 1px 4px 2px 4px;
}

div.message {
  color: red;
  margin: 2px;
  padding: 2px;
}

2012年5月24日木曜日

Xfce Terminal Emulator

フォントや色の類はGUIでも設定できるのですが、起動時の大きさと位置は設定ファイルに記述しなければいけないようです。

~/.config/Terminal/terminalrc

MiscDefaultGeometry=132x43+0+0

デフォルトは80x24。
なお、このファイル、1度GUIで適当に設定しないと生成されないようです。インストール直後は注意。

2012年5月23日水曜日

mozc単語登録

$ /usr/lib/mozc/mozc_tool --mode=word_register_dialog
でダイアログが表示され、可能になります。
ですが覚えられない(覚える気がない)ので、OSのショートカットキーに割り当てています。
Settings Manager - Keyboard - Application Shortcutsで設定します。
ATOK同様に<Ctrl>F7に割り当てたところうまく動作しなかったので、<Super>7に割り当てています。

2012年5月21日月曜日

sinatra (10) - 雑感 -

Sinatraの魅力は何と言っても自由度の高さです。
これまでいくつかのコンポーネントとの連携を書いてきましたが、これらを自由に足し引きすることができます。
sinatraの得意分野であるWebのAPIなどはビューまわりをほとんど使用せずできることもあります。
Sinatraの作者も述べている)ように、sinatraのREADMEは非常によく書かれています。一部翻訳が追いついていないようですが、日本語でも十分な内容です。
書籍としては個人的にSinatra: Up and Runningをおすすめします。私自身あまり英語が得意ではありませんが、ページ数も122とくじけずに読める量でした。値段もEbookで$12.99と今なら1,000円強なので専門誌としては格安です。

なお、前回まで書いたソースはgithubにあります。

2012年5月20日日曜日

sinatra (9) - heroku -

(8)の続き。
今回はherokuに作成したアプリケーションをデプロイします。

公式ではHeroku Toolbeltを使用していますが、これだとOS標準のRubyパッケージもインストールされます。
すでにRubyはrvmでインストールしているので、ここでは従来のgemのherokuをインストールします。 こちらにもあるように、gemパッケージはこれまでどおり提供し続け、Heroku ToolbeltはPythonやJavaなど他言語を使用する方へ向けてのことです。
$ gem install heroku
SSH鍵を生成して登録します。
$ ssh-keygen -t rsa
$ heroku kyes:add
認証が通るか確認してみましょう。Authentication successful.と表示されればOKです。
$ heroku login
hreokuの起動に必要なファイルを編集していきます。
.
├── config
│     ├── mongoid.yml
│     └── unicorn.rb
├── config.ru
├── .gitignore  # 追加
├── Gemfile
├── Gemfile.lock
├── Guardfile
├── lib
│     ├── app.rb
│     └── models.rb
├── Procfile  # 追加
├── public
├── spec
│     ├── app_spec.rb
│     └── spec_helper.rb
└── views
       ├── index.slim
       ├── layout.slim
       └── style.scss
$ gem install foreman

Procfile

web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb
$ foreman start
まずはこれで正しく起動できて、ブラウザから期待する表示ができているか確認してみます。
gitリポジトリを生成します。
$ git init
$ heroku create --stack cedar
通常だと、このあとファイルを追加するのですがherokuのMongoHQを使用するためにもうひと手間かけます。
$ heroku addons:add mongohq:free
各所で書かれていますが、My AccountのBilling Infoを登録しておかないとフリー版であっても使用することができません。

Procfile

web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb -E production

mongoid.yml

development:
  host: localhost
  database: mydb
production:
  uri: <%= ENV['MONGOHQ_URL'] %>
ローカルでproduction環境を確認したいときは、環境変数にMONGOHQ_URL='mongodb://localhost/mydb'などと設定すればよいです。

.gitignore

coverage
.sass-cache
$ git add .
$ git commit -m "init"
$ git push heroku master
正しく起動するとデプロイ先のURLが表示されるので、ブラウザでアクセスしてみましょう。
この時点でMongoHQのドキュメントは空なので、HerokuのMy App - Resources - Add-onsからMongoHQに適当なデータを入れると、MongoDBの部分も動作を確認することができます。
テストでアップロードしたサーバをそのまま放置しておくのも何なので
$ heroku maintenance:on
などとして、使わないときはメンテナンスモードにしておくのも手でしょう。

2012年5月18日金曜日

sinatra (8) - コードカバレッジ -

(7)の続き。
テストを実施すると必要になってくるコードカバレッジ。
今回はRuby 1.9で使用できるSimpleCovを使用します。

Gemfile

source :rubygems

gem 'sinatra', require: 'sinatra/base'
gem 'slim'
gem 'sass'
gem 'unicorn'
gem 'mongoid'
gem 'bson_ext'

group :development do
  gem 'sinatra-reloader', require: 'sinatra/reloader'
end

group :test do
  gem 'rspec'
  gem 'rack-test', require: 'rack/test'
  gem 'guard-rspec'
  gem 'simplecov', require: false
end

spec/sepc_helper.rb

require 'bundler/setup'
Bundler.require :default, :test

require 'simplecov'
SimpleCov.start

require 'app'
require 'models'

RSpec.configure do |config|
  config.include Rack::Test::Methods
end
$ bundle install
$ bundle exec guard
これでテストするたびにカバレッジを計測することができます。なお、前回のソースだと
Coverage report generated for RSpec to /home/kan/workspace/template/coverage. 22 / 23 LOC (95.65%) covered.
と表示されて100%に達していません。
coverageディレクトリ以下に結果のhtmlが生成されているので確認してみると、app.rbのget '/css/:file.css'...のブロックがテストされていないことがわかります。
簡単なテストを追加してみます。

spec/app_spec.rb

require 'spec_helper'

shared_examples_for "response code" do
  it { should be_ok }
end

describe MyApp do

  def app
    @app ||= MyApp
  end

  context "when get '/'" do
    before do
      get '/'
    end
    subject { last_response }
    it_behaves_like "response code"
    its(:body) { should include("Hello World!") }
  end

  context "when get '/css/style.css'" do
    before do
      get '/css/style.css'
    end
    subject { last_response }
    it_behaves_like "response code"
  end

  describe "MongoDB access" do
    before do
      Game.destroy_all if Game.exists?
      Game.create(title: "GOD EATER BURST", platform: "PSP")
    end
    context "when get '/games/title'" do
      before { get 'games/title' }
      subject { last_response }
      its(:body) { should == "GOD EATER BURST" }
    end
    context "when get '/games/platform'" do
      before { get 'games/platform' }
      subject { last_response }
      its(:body) { should == "PSP" }
    end
  end
end
再度テストを実行すると、カバレッジが100%に達すると思います。

2012年5月17日木曜日

sinatra (7) - データベース -

(6)の続き。
データベースは、MySQLSQLiteなど選択に困るほど存在しますが、ここでは ドキュメント指向データベースのMongoDBを使用します。
インストールは公式ページにしたがっておこないます。
$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv 7F0CEB10

/etc/apt/sources.list

deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen
$ sudo update
$ sudo apt-get install mongodb-10gen
次にsinatraで扱えるようにします。
.
├── config
│     ├── mongoid.yml  # 追加
│     └── unicorn.rb
├── config.ru
├── Gemfile
├── Gemfile.lock
├── Guardfile
├── lib
│     ├── app.rb
│     └── models.rb  # 追加
├── public
├── spec
│     ├── app_spec.rb
│     └── spec_helper.rb
└── views
       ├── index.slim
       ├── layout.slim
       └── style.scss
source :rubygems

gem 'sinatra', require: 'sinatra/base'
gem 'slim'
gem 'sass'
gem 'unicorn'
gem 'mongoid'
gem 'bson_ext'

group :development do
  gem 'sinatra-reloader', require: 'sinatra/reloader'
end

group :test do
  gem 'rspec'
  gem 'rack-test', require: 'rack/test'
  gem 'guard-rspec'
end
$ bundle install

config/mongoid.yml

development:
  host: localhost
  database: mydb
動かすだけであれば、設定はこの程度でも動作します。
公式により詳細な設定が書かれています。

lib/models.rb

class Game
  include Mongoid::Document
  field :title, type: String
  field :platform, type: String
end

lib/app.rb

require 'models'

class MyApp < Sinatra::Base

  configure :development do
    Bundler.require :development
    register Sinatra::Reloader
    Slim::Engine.set_default_options pretty: true
    Mongoid.logger = Logger.new(STDOUT)
    Mongoid.logger.level = Logger::INFO
  end

  configure do
    set :root, File.expand_path('../../', __FILE__)
    Mongoid.load! File.join(settings.root, 'config', 'mongoid.yml')
  end

  get '/' do
    slim :index
  end

  get '/games/:field' do
    content_type :text
    game = Game.first
    game ? "#{game.__send__ params[:field]}" : "nil"
  end

  get '/css/:file.css' do
    scss params[:file].to_sym
  end
end
Mongoid.load! File.join(settings.root, 'config', 'mongoid.yml')
で設定を読み込みます。
このまま使用するとRSpecを使用した時もクエリの内容等、デバッグログが出力されてしまうので抑制します。
Mongoid.logger = Logger.new(STDOUT)
Mongoid.logger.level = Logger::INFO
用途に合わせて出力先や出力レベルを調整するとよいでしょう。
注意しなければならないのはMongoid.load!よりも前に定義しなくてはいけないことです。Mongo Ruby Driverが公開APIを提供していないため、masterの設定より先に記述しなくてはいけないようです。ソースを除くとrailsでは別途railsで指定したloggerを参照しているようです。
models.rbでサンプルとなるモデルを定義しています。このあたりsinatraは自由なのでapp.rbに定義してもよいし、モデルごとにファイルを分割しても問題ありません。使える型についても公式に記述があります。
ルートも増えたところで対応するテストケースも追加しておきましょう。

spec/spec_helper.rb

require 'bundler/setup'
Bundler.require :default, :test

require 'app'
require 'models'

RSpec.configure do |config|
  config.include Rack::Test::Methods
end

spec/app_spec.rb

require 'spec_helper'

describe MyApp do

  def app
    @app ||= MyApp
  end

  context "when get '/'" do
    before do
      get '/'
    end
    subject { last_response }
    its(:status) { should == 200 }
    its(:body) { should include("Hello World!") }
  end

  describe "MongoDB access" do
    before do
      Game.destroy_all if Game.exists?
      Game.create(title: "GOD EATER BURST", platform: "PSP")
    end
    context "when get '/games/title'" do
      before { get 'games/title' }
      subject { last_response }
      its(:body) { should == "GOD EATER BURST" }
    end
    context "when get '/games/platform'" do
      before { get 'games/platform' }
      subject { last_response }
      its(:body) { should == "PSP" }
    end
  end
end

2012年5月16日水曜日

sinatra (6) - アプリケーションサーバ -

(5)の続き。
これまでWEBrickで動かしましてきましたが、これをunicornに変更します。
他にもthinpassenger等々あるので、この辺は用途と好みで。
公式でもunicornはnginx等を手前に置いて使いましょう、とのことですが heroku上で単体で載せているもあります。
.
├── config
│     └── unicorn.rb  # 追加
├── config.ru
├── Gemfile
├── Gemfile.lock
├── Guardfile
├── lib
│     └── app.rb
├── public
├── spec
│     ├── app_spec.rb
│     └── spec_helper.rb
└── views
       ├── index.slim
       ├── layout.slim
       └── style.scss

Gemfile

source :rubygems

gem 'sinatra', require: 'sinatra/base'
gem 'slim'
gem 'sass'
gem 'unicorn'

group :development do
  gem 'sinatra-reloader', require: 'sinatra/reloader'
end

group :test do
  gem 'rspec'
  gem 'rack-test', require: 'rack/test'
  gem 'guard-rspec'
end
$ bundel install
$ bundle exec unicorn
動作確認するだけなら、これだけでも動作します。
設定ファイルは公式でミニマム版と、フルセット版が掲載されています。 ここではごく簡単に記述します。

config/unicorn.rb

worker_processes 2
timeout 30
$ bundle exec unicorn -c ./config/unicorn.rb
デーモンで起動するときは-Dオプション付きで起動し、停止するときはkill -QUIT (pid)で停止することができます。

2012年5月15日火曜日

sinatra (5) - CSSフレームワーク -

(4)の続き。

SCSSはCSSとほぼ同じ文法で記述でき、なおかつ冗長かなと思えばSCSS記法を使え重宝します。
同様のフレームワークにLESSもありますので、好みに応じて使えばよいでしょう。
ググると両者の特徴や違いを説明しているサイトが見つかります。

.
├── config
├── config.ru
├── Gemfile
├── Gemfile.lock
├── Guardfile
├── lib
│     └── app.rb
├── public
├── spec
│     ├── app_spec.rb
│     └── spec_helper.rb
└── views
       ├── index.slim
       ├── layout.slim
       └── style.scss  # 追加

Gemfile

source :rubygems

gem 'sinatra', require: 'sinatra/base'
gem 'slim'
gem 'sass'

group :development do
  gem 'sinatra-reloader', require: 'sinatra/reloader'
end

group :test do
  gem 'rspec'
  gem 'rack-test', require: 'rack/test'
  gem 'guard-rspec'
end

views/style.scss

html {
  font-size: 62.5%;
}
body {
  font-size: 1.4rem;
}
h1 {
  font-size: 2.4rem;
}

views/layout.slim

doctype html
html
  head
    title My App
    link rel='stylesheet' href='css/style.css'
  body == yield

lib/app.rb

class MyApp < Sinatra::Base

  configure do
    set :root, File.expand_path('../../', __FILE__)
  end

  configure :development do
    Bundler.require :development
    register Sinatra::Reloader
    Slim::Engine.set_default_options :pretty => true
  end

  get '/' do
    slim :index
  end

  get '/css/:file.css' do
    scss params[:file].to_sym
  end
end
$ bundle install
$ bundle exec rackup

コマンドラインでscssファイルからスタイルシートに変換もできますが、こう記述しておけば実行時に変換してくれます。
また、スタイルシートを追加、分割してもlayout.slimに追加するだけで、app.rbはそのままで構いません。railsだと一括でスタイルシートを読むヘルパーが用意されていますが、同様にヘルパーを定義するなり別途用意してもよいと思います。

2012年5月14日月曜日

sinatra (4) - テンプレートエンジン -

(3)の続き。
テンプレートエンジンを追加します。ErbHamlなど色々ありますが、今回はSlimを使用します。
.
├── config
├── config.ru
├── Gemfile
├── Gemfile.lock
├── Guardfile
├── lib
│     └── app.rb
├── public
├── spec
│     ├── app_spec.rb
│     └── spec_helper.rb
└── views
        └── index.slim  # 追加
        └── layout.slim  # 追加

Gemfile

source :rubygems

gem 'sinatra', require: 'sinatra/base'
gem 'slim'

group :development do
  gem 'sinatra-reloader', require: 'sinatra/reloader'
end

group :test do
  gem 'rspec'
  gem 'rack-test', require: 'rack/test'
  gem 'guard-rspec'
end

lib/app.rb

class MyApp < Sinatra::Base

  configure do
    set :root, File.expand_path('../../', __FILE__)
  end

  configure :development do
    Bundler.require :development
    register Sinatra::Reloader
  end

  get '/' do
    slim :index
  end
end

views/index.slim

h1 Hello World!
$ bundle install
$ bundle exec rackup
これで表示できますが、画面のソースを確認するとヘッダ等は記述されていません。
index.slimに直接追記してもよいですが、ここではlayout.slimとして分割します。

views/layout.slim

doctype html
html
  head
    title My App
  body == yield
展開されたhtmlのソースは圧縮されているので、インデント表示させたい時はオプションを変更しましょう。
  configure :development do
    # (略)
    Slim::Engine.set_default_options :pretty => true
  end

2012年5月13日日曜日

sinatra (3) - テスト -

(2)の続き。
テスト環境を整備します。
.
├── config
├── config.ru
├── Gemfile
├── Gemfile.lock
├── Guardfile  # 追加
├── lib
│     └── app.rb
├── public
├── spec
│     ├── app_spec.rb  # 追加
│     └── spec_helper.rb  # 追加
└── views

Gemfile

source :rubygems

gem 'sinatra', require: 'sinatra/base'

group :development do
  gem 'sinatra-reloader', require: 'sinatra/reloader'
end

group :test do
  gem 'rspec'
  gem 'rack-test', require: 'rack/test'
  gem 'guard-rspec'
end

spec/spec_helper.rb

require 'bundler/setup'
Bundler.require :default, :test

require 'app'

RSpec.configure do |config|
  config.include Rack::Test::Methods
end

spec/app_spec.rb

require 'spec_helper'

describe MyApp do

  def app
    @app ||= MyApp
  end

  context "when get '/'" do
    before do
      get '/'
    end
    subject { last_response }
    its(:status) { should == 200 }
    its(:body) { should include("Hello World!") }
  end
end
$ bundle install
$ bundle exec rspec
これで2つのテストが通ると思います。
加えて、テストもオートロードされるようGuardを使用します。

Guardfile

guard 'rspec', :version => 2, :cli => "-c -fs" do
  watch(%r{^spec/.+_spec\.rb$})
  watch(%r{^lib/(.+)\.rb$})     { |m| "spec/#{m[1]}_spec.rb" }
  watch('spec/spec_helper.rb')  { "spec" }
  watch(%r{^views/(.+)\.(slim|scss)$})
end
$ bundle exec guard
Xubuntu 12.04になってnotificationもまともに動くようになっているようです。 (11.10まで私の環境では不安定でした)

2012年5月12日土曜日

sinatra (2) - オートリロード -

(1)の続き。
アプリケーションを書きなおすたびにsinatraを立ち上げ直すのは面倒臭いのでオートリロードに対応させます。

Gemfile

source :rubygems

gem 'sinatra', require: 'sinatra/base'

group :development do
  gem 'sinatra-reloader', require: 'sinatra/reloader'
end

lib/app.rb

class MyApp < Sinatra::Base

  configure do
    set :root, File.expand_path('../../', __FILE__)
  end

  # http://www.sinatrarb.com/contrib/reloader
  configure :development do
    Bundler.require :development
    register Sinatra::Reloader
  end

  get '/' do
    'Hello World!'
  end
end
$ bundle install
$ bundle exec rackup
起動後に'Hello World!'を適当な文字に書き換え、ブラウザをリロードすれば表示が変更されると思います。

2012年5月11日金曜日

sinatra (1)

sinatraはちょっとしたアプリケーションや、少しずつ育てていきたい時に重宝します。
規模感が合致すればrailsを使えばよいですし、sinatraをよりrailsっぽく使うのであればPadrinoあたりを使えばよいと思います。
個人的にはPadrinoの初期生成でも煩わしさを感じたので、自分で生成しています。
Github等で様々な人がテンプレートを紹介しているので、参考にしてみるとよいでしょう。
ミニマムで以下のような構成です。config, public, spec, viewsは今のところ空のディレクトリです。
.
├── config
├── config.ru
├── Gemfile
├── Gemfile.lock
├── lib
│     └── app.rb
├── public
├── spec
└── views

Gemfile

source :rubygems

gem 'sinatra', require: 'sinatra/base'

config.ru

$:.unshift File.join(File.dirname(__FILE__), 'lib')
require 'bundler'
Bundler.require

require 'app'
run MyApp

lib/app.rb

class MyApp < Sinatra::Base

  # http://www.sinatrarb.com/configuration.html
  configure do
    set :root, File.expand_path('../../', __FILE__)
  end

  get '/' do
    'Hello World!'
  end
end
$ bundle install
$ bundle exec rackup
WEBRickが立ち上がるので、ブラウザ経由のhttp://localhost:9292で結果を確認できると思います。

2012年5月10日木曜日

デスクトップの文字

XubuntuのGoogle+でみかけたtipsを適用。
デスクトップの文字の背景が透過になります。

~/.gtkrc-2.0
style "xfdesktop-icon-view" {
  XfdesktopIconView::label-alpha = 0
  fg[NORMAL] = "#ffffff"
  fg[SELECTED] = "#000000"
}
widget_class "*XfdesktopIconView*" style "xfdesktop-icon-view"

2012年5月9日水曜日

emacsのインストール

特に難しいことはしません。mozcを使用しているので関連パッケージを合わせてインストールします。

$ sudo apt-get install emacs23
$ sudo apt-get install ibus-el emacs-mozc emacs-mozc-bin

最近は、githubに~/.emacs.d以下を保存しておいて、emacsインストール後、そのまま放り込んでいます。
githubにアップしている方は他にも結構いますね。履歴管理も合わせておこなえるので非常に便利です。

gitのコミットログやless等から編集でemacsを使用するように、~/.bashrcに追記します。
export VISUAL="emacsclient -a emacs"
export EDITOR="emacsclient -a emacs"

2012年5月6日日曜日

rubyのインストール

xubuntuへのruby導入です。標準パッケージを用いてもよいのですが、ここではrvmを使用します。
rbenvもありますが、rvmは依存パッケージ示してくれるのでこちらを使用しています。


$ sudo apt-get install curl
$ bash < <( curl -s  https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)

~/.bashrc
[[ -s "$HOME/.rvm/scripts/rvm" ]] && . "$HOME/.rvm/scripts/rvm"
一時期インストール時に追加してくれていたのですが、どこかのタイミングで無くなっていたので手動で追記。

$ rvm requirements
依存パッケージが表示されるので、それに従いパッケージを追加

$ rvm install 1.9.3
$ rvm use 1.9.3 --default


(その他の操作)
rvmのアップデート
$ rvm get latest
$ rvm reload
$ rvm version

使用可能なバージョンの一覧表示
$ rvm list known

Rubyの削除
(gemset諸々)
$ rvm remove (version)

(本体のみ)
$ rvm uninstall (version)

2012年5月5日土曜日

フォントの追加

Rictyの導入です。
Rictyの生成スクリプトやMigu 1MフォントはあらかじめDLしておきます。


準備
$ sudo apt-get install fontforge
$ sudo apt-get install ttf-inconsolata

Migu 1Mのインストール
$ mkdir ~/.fonts
$ unzip (path)/migu-1m-20111002.zip -d ~/.fonts
$ fc-cache -fv

Rictyの生成
$ sh ricty_generator.sh auto
Rictyのインストール
$ cp Ricty-{Regular,Bold}.ttf ~/.fonts/
$ fc-cache -fv

2012年5月2日水曜日

NASへのアクセス

$ sudo apt-get install nfs-common smbfs

Xubuntu 11.10まではgvfs-backendsが必要だったのですが、12.04では最初からインストール済みでした。

マウント
$ sudo mount -t cifs -o username=(user),uid=(user),gid=(group) //(IP addresss)/path /(mount point)/

起動時にマウント
/etc/fstab
//(IP addresss)/path /(mount point)/ cifs credentials=/etc/naspasswd,uid=(user),gid=(group),iocharset=utf8,rw,defaults 0 0

/etc/nasspasswd
username=(user name)
password=(password)

2012年5月1日火曜日

Xubuntu 12.04

4/26にXubuntu12.04がリリースされました。
リリースノートを見ると、デスクトップに新しいショートカットも追加されていて使いやすさもアップしそうです。

私はいつもベータ版をバーチャルマシンで動作確認⇒リリース後に新規インストールしています。
日本語Remixは使っていません。英語のままインストール後、Languageサポートで日本語の追加、IMでibus起動ぐらいで事足ります。デスクトップは一部メニューで間延びするのが気持ち悪いので英語にしています。何かあった時に、英語のメニューや名称で検索できるのは意外と便利です。

デュアルディスプレイ

インストール時、2ndモニタが無効になっていたので、nvidia-settingsで2ndモニタのConfigurationをTwin Viewに設定。

追加パッケージ

  • ibus-mozc
  • build-essential
  • manpages-ja manpages-ja-dev
  • git-core
  • paco
  • tree
  • audacious (cueファイルが再生できるので)
  • chromium-browser
  • libreoffice-writer libreoffice-l10n-ja

削除パッケージ

  • abiword (代替はlibreoffice writer。abiwordは日本語周りの動作が怪しい。)
  • thunderbird (自宅ではWebメールしか使わないのでメーラ不要。)

日本語入力

$ killall ibus-daemon
$ ibus-daemon -d -x
$ ibus-setup
IMにmozcを追加。

自動起動の無効

  • Power Manager
  • Blueman Applet

停止するサービス

  • bluetooth
  • dns-clean
  • pppd-dns

その他設定

.bashrc

alias man='man -L ja'

/etc/sysctl.conf

vm.swappiness = 10