RubyMotionでのExceptionの謎(というか疑問

次のもくもく会などで聞いてみようと思うのでそのメモ&駄文

まずRubyでの実行結果

irb(main):001:0> begin; [1].fetch(1); rescue IndexError => e; puts   e.class; end
IndexError
=> nil
irb(main):002:0> 

次にRubyMotionでの実行結果

(main)begin; [1].fetch(1); rescue IndexError => e; puts e.class; end
2014-02-16 03:41:48.476 hello_world[3087:707] index 1 out of array (IndexError)
IndexError
=> nil
(main)> 

Exceptionをキャッチしても2014-02-16 03:41:48.476 hello_world[3087:707] index 1 out of array (IndexError)のような発生した際のメッセージは表示される。

個人的に、ちゃんとキャッチしてるのにメッセージが表示されていて気になったので。

ProMotion::TableScreenでViewCellにviewを追加する方法

ProMotion::TableScreenでは、デフォルトでTableViewで使うデータに追加したいviewをsubviewsというキーで含めておけば追加してくれます。

class MyScreen < ProMotion::TableScreen

  def table_data
    @table_data = [
      cells: [
        { title: 'cell 1', subviews: [label_1_1, label_1_2] },
        { title: 'cell 2', subviews: [label_2_1, label_2_2] },
      ]
    ]
  end
end

label_1_1 label_1_2 等はViewCell中での表示位置や表示内容などを設定済み。
こうすることで、PM::TableScreenがcellを作成する際に、subviewsから追加するviewを拾ってaddSubviewしてくれます。

しかもdequeuereusablecellwithidentifierでcellが再利用される際、(度々大抵の人が陥るであろう)"再利用される前にaddSubviewされているview" あるいは "多重にaddSubviewしてしまう" 問題についても、PM::TableScreenがうまくremoveFromSuperviewしてから再度addSubviewしてくれるので安心です。

ProMotion gem すばらしい


しかし、cellへ追加したいviewのコードがScreen(ViewController)に書かなくてはいけないため、自分のコードが"糞コードの海"に沈みかねません。

そこでProMotionでは ProMotion::TableViewCell というUITableViewCellを継承してProMotionでViewCellをうまく扱うためのmoduleをincludeしたクラスが用意されてます。

例に、title(textLabel)もsubtitle(detailLabel)も使ってしまっているので、一つlabelを追加してcountを表示するViewCellを作ってみることにしましょう

今回はtable_dataのcellsにはsubviewsは使わずに、cell_classとcell_styleを追加し、それぞれ作成したMyTableViewCellとsubtitleを使うのでUITableViewCellStyleSubtitleを設定します。
あとは、MyTableVlewCellでsetupメソッドを上書きしてsuperを呼んでデフォルトの初期化処理をさせつつ、追加したいviewを準備した後に再度setup_subviewsを呼びます。

class MyScreen < ProMotion::TableScreen

  def table_data
    @table_data = [
      cells: [
        { title: 'cell 1', subtitle: 'sub title 1', count: 100, cell_class: MyTableViewCell, cell_style: UITableViewCellStyleSubtitle, },
        { title: 'cell 1', subtitle: 'sub title 1', count: 0, cell_class: MyTableViewCell, cell_style: UITableViewCellStyleSubtitle, },
      ]
    ]
  end
end

class MyTableVlewCell < ProMotion::TableViewCell

  attr_accessor :count

  def setup(data_cell, screen)
    super # super呼んで本来の初期化処理とかさせる1度目のset_subviewsも呼ばれる

    count_label = UILabel.new.tap do |label|
      # frameとかfontとか略
      l.text = self.count
    end
    data_cell[:subviews] = [count_label] # data_cellはtable_dataのcellsの配列が1つ入ってる感じ
    set_subviews # 2度目のset_subviews

    self
  end
end

この方法だと、"data_cell[:subviews] が(nilなので実質処理されないけど)、superにより1度に無駄に呼ばれる" などの無駄っぽい処理がありますが、ViewCellに関する処理が1カ所にまとめられるのは利点じゃないでしょうか。

以上、ProMotionのwikiにも書かれていないsubviewの追加方法でしたー

今gemを作るなら bundle gem より ore gem

jweler から移行して bundle gem で作られた(であろう) gem もよく見かけるようになってきたけれど、最近は ore gem が良い感じ

個人的に bundle gem の場合、テストケース関係のファイルが generate してくれないなど、今ひとつかゆいところに手が届かないなーと思っていたので、
bundle plugin みたいなのがあれば良いなーと思っていて作るかーとか思っていたのだけれど、その前にそれっぽい gem があることを発見。

$ mine example --bundler --rspec
      create  lib
      create  lib/example
      create  spec
      create  Gemfile
      create  .gitignore
      create  .rspec
      create  spec/example_spec.rb
      create  spec/spec_helper.rb
      create  .document
      create  example.gemspec
      create  ChangeLog.rdoc
      create  LICENSE.txt
      create  README.rdoc
      create  Rakefile
      create  lib/example/version.rb
      create  lib/example.rb
         run  git init from "."
         run  git add . from "."
         run  git commit -m "Initial commit." from "."

うーん、rspec関係のファイルまで生成してくれる。すばらしい!
(--rspec 以外にも他のオプションもあるけど

あと gem project の generate 以外にもいろいろと出来るので、詳しくは ore gem のREADME見てみると良い感じ

https://github.com/ruby-ore/ore

.simplecov でSimpleCovの設定をスマートに

SimpleCov を使ってcode coverageを取るときに spec_helper.rb にこんな感じで書いていた

if ENV['COVERAGE'] # <= rake spec / rake coverage を分けたい派なので
  require "simplecov"

  SimpleCov.start :test_frameworks do
    add_filter "/vendor/bundle/"
  end
end

# 以下略

RSpec.configure do |config|
  # ここも略...
end

プログラムのroot pathに .simplecov という名前のファイルを作り、SimpleCov の設定部分を書いてしまうことが出来るようになっているらしい (ということに気付いた

.simplecov

SimpleCov.start :test_frameworks do
  add_filter "/vendor/bundle/"
end

ソースはここら辺
https://github.com/colszowka/simplecov/blob/master/lib/simplecov
/defaults.rb#L56

ElixirでDynamoのsample実行とExUnitを使ってみた

Shinjuku.ex第1回目ということで、新宿 Brooklyn Parlorにて、命の水ビールをぐびぐびしながら、6人ぐらいElixirについてもくもくしてきました。

  • Dynamo という Sinatraっぽいフレームワークについて動かしてみる
  • 途中まで読んでいたチュートリアルの続きを最後までもくもく
  • テスト大好き ExUnit を使ってみる

ちなみに、チュートリアルが2つめなのは、チュートリアルをもくもくするのも良いけれど、動く物を試した用が良いよね!ってことでDynamoを動かそうと、なかなかうまく動くところまでいかなくて、途中で心が折れそうになり、横道反れた結果です。

あと、結論だけ書いてもおもしろくないので、失敗/躓いたところ多めで。

Dynamo を動かしてみる

必要なのは以下の3つ

erlang / rebar は、macならhomebrewから簡単にインストール出来る
(自分は、rebarがhomebrewにあるとは思ってなかったので、試行錯誤しました)

Elixir のインストールは Getting Started の 1.1.2 Manual install あたりを参照すれば良いかと
http://elixir-lang.org/getting_started/1.html

rebar というのは、erlangにおけるビルドツールらしい。makeやRubyのrakeみたいなもので、rebar.config というファイルにそのタスクが書かれているっぽい

$ git clone https://github.com/josevalim/dynamo.git
$ cd ./dynamo
$ make setup

make setup で dynamo が依存しているsubmoduleのセットアップとcompileを行う・・・が、普通に失敗する

$ make setup
git submodule update --init
# 中略
Compiled src/ibrowse_http_client.erl
cd deps/cowboy && make
make[1]: rebar: No such file or directory # ← ここら辺
make[1]: *** [deps] Error 1
make: *** [setup] Error 2

どうやら、deps/cowboy の make で rebarが無い模様
(homebrew等でインストールしてパスが通っていればこのエラーには遭遇しないはず。たぶん)

rebar がないので、もう一つのsubmoduleで依存しているdeps/ibrowse のrebarを拝借することに

$ cd ./deps/cowboy
$ ../ibrowse/rebar get-deps
$ ../ibrowse/rebar compile
<||

これで cowboy の準備はokで、dynamo の make setup タスクは完了
なお、get-deps / compile はcowboyのMakefileを見ると書いてある

次に dynamo のディレクトリに戻って make compile
>||
$ cd ../../
$ make compile

ダメな場合、直接下記を実行してみる

$ elixirc -pa deps/cowboy/ebin lib/*/*/*.ex lib/*/*.ex lib/*.ex -o ebin

これで、 ebin ディレクトリの中にコンパイル済み(?)のファイルが出来ているはず

examples/simple.exs の実行

実行の方法は simple.exs 自体に書かれている

$ cat ./examples/simple.exs                                                                                                                                                 [master]
# Run this app from root with:
#
#   elixir -pa ebin --no-stop examples/simple.exs

defmodule MyApp do
# 以下略
$ elixir -pa ebin --no-stop examples/simple.exs
Running ::MyApp on port 3000 with Cowboy # この行が表示されれば起動完了

"Running ::MyApp on port 3000 with Cowboy" が表示されない(listenに失敗してる?)場合もあり、どうやら原因不明に失敗している人もいた。

無事に表示された場合、http://localhost:3000/foo/bar にアクセスしてみる (http://localhost:3000/ じゃないよ) と Hello World! が表示されるはず

ExUnit を使ってみる

ExUnitの使い方をいろいろ見てみたいなら、やっぱりリポジトリの /test/ ディレクトリ下を見るのが良いかも
https://github.com/josevalim/dynamo/tree/master/test

基本的には、Rubyなどでもおなじみの /test ディレクトリ下にテストファイルがあり /test/test_helper.exs みたいなのがある感じ

以下、チュートリアルにあったサンプルにテストを追加してみたファイル
例では math.ex として保存して実行してみた

ExUnit.start []

defmodule Math do
  def sum(a, b) do
    a + b
  end
end

defmodule AssertionTest do
  use ExUnit::Case

  def test_pass do
    assert_equal 30, Math.sum(10, 20)
  end

  def test_failed do
    assert_equal 10, Math.sum(10, 20)
  end
end
$ elixir ./math.ex
.F

1) test_failed (::AssertionTest)
  ** (::ExUnit::AssertionError) Expected 30 to be equal to 10
  stacktrace:
    lib/elixir/builtin.ex:1023: ::Elixir::Builtin.raise/2
    lib/ex_unit/assertions.ex:374: ::ExUnit::Assertions.assert/2
    lib/ex_unit/runner.ex:78: ::ExUnit::Runner.run_test/3
    lib/enum.ex:556: ::Enum.do_each/3
    lib/enum.ex:136: ::Enum.each/3
    lib/enum.ex:131: ::Enum.each/2
    lib/ex_unit/runner.ex:69: ::ExUnit::Runner.run_tests/2

2 tests, 1 failures.

テストケースが2個しかないので表示が寂しいけれど、こんな感じでテストを書くことが出来る

ExUnit でキモになるところは、ExUnit.start [] と書かないと下記のようなエラーになります。

** (exit) "ExUnit::Server is not running. Are you sure you used exunit from command line?"
    lib/ex_unit/server.ex:59: ::ExUnit::Server.check/1
    lib/ex_unit/case.ex:6: ::ExUnit::Case.__using__/2
    /Users/toshiwo/var/misc/elixir/math.ex:8: ::ExUnit::Case.__using__/2
    src/elixir_dispatch.erl:104: :elixir_dispatch.dispatch_macro/6
    lists.erl:1278: :lists.mapfoldl/3
    lists.erl:1279: :lists.mapfoldl/3
    src/elixir_translator.erl:44: :elixir_translator.translate_each/2
    lists.erl:1278: :lists.mapfoldl/3

個人的には ::ExUnit::Server って単語だけで、erlangの message passingなんかを使って、テストケースを並列や分散させて走らせたり出来るのかな〜と妄想全開なところが気になってます
(実際のところの事実はまだ(知らない|調べてない)けれど・・・

まとめ

  • 結構Elixirそのものよりも、erlang に対する理解が乏しいので erlang に対して調べてたりしたことが多かった
  • 他にも Dynamo を動かすにあたり make ファイル読んだり、Elixir と関係ないところが・・・
  • でも、ExUnit 触ってみておもしろかった!TDDじゃなくてもテスト重要!!

以上、こんな感じで3時間ほどもくもくしてました。

Ruby製password manager "pws" を使ってみた

pws というRuby製のパスワードマネージャを使ってみたのでメモ

1Passwordとかでも良いんだけど、ちょっとしたパスワードを貯めておくには良いかも

インストール

gemでインストール

$ gem install pws

ソースは github の↓
https://github.com/janlelis/pws

簡単な使い方

マスターパスワードを決めて、test に対するパスワードを決める

$ pws add test
No password safe detected, creating one at /Users/toshiwo/.pws
Please enter a new master password: # マスターパスワードを決めて
ACCESS GRANTED
Please enter a password for test: # test に対するパスワードを決める
The password for test has been added

ちょっと変わった使い方

パスワードファイルをgit(など)で管理したい
PWS という環境変数でパスワードファイルを指定出来る

ただし、ディレクトリは予め作っておく必要がある

$ export PWS=~/.pws/pws
$ pws add test
No password safe detected, creating one at /Users/toshiwo/.pws/pws # <= 変わる

これで、~/.pws/ ディレクトリ下を git で管理できる

namespace

pws コマンドに -NAMESPACE を付けると、namespace を使うことが出来る
パスワードファイルが pws + namespace になる
また、マスターパスワードもnamespace単位で指定出来る

$ pws -namespace add test
No password safe detected, creating one at /Users/toshiwo/.pws/pws-namespace
Please enter a new master password: 
0

追加したパスワードの取り出し方

pws get を使えば、マスターパスワードの認証後パスワードがクリップボードに規定時間(デフォルトでは10秒間)コピーされる

ここでおもしろいのが、規定時間後 pws get が実行される直前のクリップボードに戻るところがおもしろい

$ pws get test
ACCESS GRANTED
The password for test is now available in your clipboard for 10 seconds
# <= ここで止まる (デフォルトでは10秒間

クリップボードにコピーされる時間を変更したい場合

$ pws get test 100
ACCESS GRANTED
The password for test is now available in your clipboard for 100 seconds
# <= ここで100秒間止まる

help アクションやその他のアクション

pws help で help を出力してくれる
pws のアクションには多種な alias が用意されてる模様
例えば、add と同様のアクションは add / set / store / create があるらしい

ちなみに、各種アクションの後ろの方に書かれている (...) のはそのアクションの引数です

例えば、pws add の場合

NAME の後に PASSWORD を指定すれば、対話式ではなくパスワードが追加できる

$ pws add KEY PASSWORD

まとめ

  • ちょっとしたパスワードの管理に使えそう
  • pws get の際のクリップボードの処理がおもしろい
  • ただし、pws help 以外のコマンドを実行する場合、毎回マスターパスワードが必要になるのがちょっと面倒

rbenv install が失敗する場合の対処方法

TMPDIR を指定して実行してやるとたぶんうまくいく

$ TMPDIR=~/var/tmp rbenv install 1.9.3-p0

さくらのレンタルサーバにrbenvでRubyをインストールしようとして困ったときにこの方法でうまくインストールできた