Merbのspecで、Merb::Test::ViewHelperを使う場合には、 webratをインストールする必要があります。 merb-core/test/matchers.rbの中で、こんな感じの条件が書かれています。

   1  if defined?(::Webrat)
   2    module Merb::Test::ViewHelper
   3      include ::Webrat::Matchers
   4      include ::Webrat::HaveTagMatcher
   5    end
   6  end

posted by Png genki on Thu 22 Jan 2009 at 06:42

サーバのメモリ使用量が増えてきたので、30%のメモリ使用量削減を謳っているRuby Enterprise Editionを試してみる事にしました。 しかし、onigurumaの拡張ライブラリのインストールで若干手間取ったので、 解決方法をメモしておきます。

   1  # /opt/ruby-enterprise-1.8.6-20090113/bin/gem install oniguruma -- --with-opt-dir=/opt/local

こんな感じでインストールすると良いようです。

posted by Png genki on Thu 22 Jan 2009 at 04:29
21st Wed

acts_as_bits-0.2

変更点

  • (1) Rails2 の dirty column に対応
  • (2) 一括設定機能を追加

   1  class User < ActiveRecord::Base
   2    acts_as_bits :flags, %w( admin create read update delete )
   3  end
   4  
   5  user = User.new
   6  user.admin = true  # (1)
   7  user.changed?      # => true
   8  
   9  user.flags = true  # (2)
  10  user.flags         # => "11111"

インストール

   1  % gem install maiha-acts_as_bits --source=http://gems.github.com

posted by Png maiha on Wed 21 Jan 2009 at 19:56

UIImageで取得できる画像とUIImage.CGImageで取得できる画像が上下逆転してしまいます。原因はUIKitとCoreGraphicsで、座標系のY軸が反転しているからだそうです(takiuchiさん談)。

下記を参考にしました:
http://blog.logichigh.com/2008/06/05/uiimage-fix/

UIImage.CGImageをrorateする

   1    UIImage *sampleImage = [UIImage imageNamed:@"image.png"];	
   2    CGRect rect = CGRectMake(0,0, sampleImage.size.width, sampleImage.size.height);
   3   
   4   //create the context
   5    UIGraphicsBeginImageContext(rect.size);
   6    CGContextRef i_context = UIGraphicsGetCurrentContext();
   7  	
   8    CGContextDrawImage(i_context, rect, sampleImage.CGImage);
   9    CGAffineTransform transform = CGAffineTransformRotate(transform, M_PI);
  10    CGContextConcatCTM(i_context, transform);
  11    UIImage *rotatedImage = UIGraphicsGetImageFromCurrentImageContext();
  12    CGImageRef sampleImageRef = [rotatedImage CGImage];
  13  
  14    //release the context
  15    UIGraphicsEndImageContext();
  16    CGContextRelease(i_context);

ちなみに、Quartz 2Dなどは昔の名前らしく、今は、CoreGraphicsと呼ばれています。最近はCoreImage, CoreGraphics, CoreAnimationのようにCoreシリーズで統一しているようです。

posted by Png satoko on Wed 21 Jan 2009 at 15:28 with 1 comment

まずはネストという言葉に関する定義の問題ですが、 諸橋さんが書いているように 、PostsコントローラはComments コントローラを集約(aggregates)しますが、 内包(compose)する訳ではありません。

たとえば、管理画面に対応するAdminコントローラから、 Commentの削除や修正を行う場合に、AdminコントローラからCommentsコントローラを集約する事を考えると、その必要性が分かりやすいと思います。

PostsコントローラにCommentsを制御するコードを書いてしまう(内包してしまう)と、 Adminコントローラで同じ事をする必要が出来た場合に、 同じようなコードを書く必要が出てきます。

Don't Repeat Yourself!

結果として、メンテナンス性の悪いコードが出来上がります。 この事が、Postsコントローラの中でCommentsリソースを制御する コードを書く事の問題の本質だと思います。 もちろん、あらかじめCommentsを集約する存在が Postsしか無いと分かっている場合には問題ありません。

という事で、 続いて検索やCRUD以外の操作はどうするのかという問題について。

この問題を考えるための重要な視点として、最近のRailsやMerbでは、 リソースを制御するためのリソースコントローラと、 それ以外の制御をするための汎用コントローラの役割が分けて考えられるようになってきているという事があります。

上述の話の中で登場するコントローラを当てはめてみると、 以下のようになります。

コントローラの分類
リソースコントローラ汎用コントローラ
PostsAdmin
CommentsDashboard

リソースコントローラとは、特定のモデルに対してCRUD(create, read, update, delete)操作を行う事に特化したコントローラです。

そう考えると、検索やCRUD以外の操作は、 汎用コントローラが行うのが自然な気がします。 例えば、ブログの記事(Posts)やコメント(Comments) の検索を行う場合であれば、 SearchコントローラがPostsコントローラやCommentsコントローラ を集約すればOK. 疑似コード的に表現すると、以下のような感じになります。

   1  Search#posts with_scope(query){ Posts#index }
   2  Search#comments with_scope(query){ Comments#index }

この構造は2年ぐらい前にRailsで使ってみたのですが、 Railsのコントローラの実装がネストに向いていないので、 パフォーマンス上の問題に苦しみました。 Railsのコントローラは、フィルタの実行、アクションの実行だけでなく、レスポンスの作成を担っているので、ネストさせた場合に、 いったん作成したレスポンスを破棄する必要があるなど、無駄が大きいのです(Railsではお馴染みのDoubleRenderErrorが発生するのもこのせいです)

しかし今であれば、Merbを使う事によってこの問題は解決します。 Merbのコントローラはレスポンスの作成をする必要が無く、非常にシンプルなので、ネストさせてもパフォーマンスの問題はほとんどありません。

ということで、Merbを使えばみんなHappyになるよという話でした。

前回の記事の追補

前回の記事では、Aggregatorの#showで、POST, PUT, DELETEを Aggregatedに委譲するという話を書きましたが、 例えばPostsコントローラがCommentsとTrackbacksの 2つのリソースを集約している場合に、 どちらのリソースに対する操作なのかを判別する方法を用意する必要があるので、どうやって実現するか書いておきます。

   1  posts/1            -> Posts#show
   2  posts/1/comments   -> Posts#show -> Comments
   3  posts/1/trackbacks -> Posts#show -> Trackbacks

こんな感じに、posts/1/:resource に対するPOST, PUT, DELETEを、 :resourceに対応するリソースコントローラに委譲する感じですね。

posted by Png genki on Wed 21 Jan 2009 at 10:19

所属している研究室が絡んでいる国際会議があって、そこの準備をしてきました。

任された仕事は、良く分からない音響・照明機材の操作や会場の準備などさまざまあったのですが、ここでは汎用性の高い無線ルータの設置術を書きたいと思います。

これから書くことは、僕が考えた理想的なスケジュールとなっています。

たぶん、スケジュール通りに進むことはないと思うので、あしからず・・・。

学会1週間前

無線ルータを調達し、設定をしておきます。

設定は、会場を使うには会場費を支払わないといけない場合があるため、自分の机などで行います。

主な設定内容は、

  • SSID
  • キー
  • DHCP接続数 です。

SSID&キーは学会の名前、場所にちなんだものをつけておくと良いのではないでしょうか。

今までに参加した学会も、だいたい学会の名前だったりしました。

例)

  • SSID: xxgakkai-2009
  • キー: XXkyoto2009

これらSSID&キーは何らかの方法を使って、参加者にアナウンスします。 (僕の場合は、研究室の秘書さんを通じて、参加者にアナウンスしていただきました。)

そして、DHCPでの接続数は参加人数に合わせて変更しておきます。1週間前になると、だいたいの参加人数の見込みがわかると思うので。

小さい教室を複数使って行う学会では、教室のキャパシティによってDHCPの接続数を変更すると良いと思います。

最後に、ルータの設定をいじられないように、管理者パスワードはランダムにつけておくのが良いと思います。

ランダムなパスワードをつけるには、n文字の半角英数文字列を生成する を使ってみてください。

簡単に設定しましたが、セキュリティがザルなのが残念なところ・・・。

あまり意味ないですが、any接続拒否とかにしておくと良いかもしれないですね・・・。

気休めですが・・・。

学会前日

看板の設置などにまぎれて、設定内容の確認をさせてもらいます。

僕の場合は、1時間だけ会場をお借りして作業を行いました。

ここでの確認内容は、

  • ネットへの接続
  • チャンネルの設定

ネットへの接続は必須です。つながらないと話になりません。

チャンネルは、他に無線ルータが設置されていると迷惑をかけることになるかもしれないので、バッティングしないように設定しておきましょう。

確認が済んだら、撤収しましょう。

置いたままにしてると、なくなるかもしれないので・・・。

学会当日

朝早くから出むいて、設置します。

そして、最後の確認を行いましょう。

あとは、クレームが出てこないように祈っていましょう。 (国際会議だと、英語で捲し立てられて怖いです。)

実際に学会の準備をしてみて・・・

上に書いたようにスケジュールが進むことはないですね・・・このように進めば、理想的なのですが・・・。

僕の場合、会場のLANを使うには申請がいったのですが、事務処理をしてくれるところで手違いがあって、ネットワークの制御をしている大元のところで設定を失敗してたらしく、どうしようもない状況になってしまいました。

(おかげで、僕は英語で捲し立てられました・・・。)

それと、やっぱりセキュリティがザルなのが気になりました。

最近はUSBメモリとかが激安なので、その中にSSIDとキーをテキストで保存しておいて、学会のお土産として配布するのがいいかもしれません。

WPA-AESで16進数の64桁の文字列をキーに使っていても、コピー&ペーストで済みますし、参加者に連絡する手間も省けると思うので。

学会によっては、名札の裏に個人のアカウントを発行して、それを使ってインターネット接続するような場合もあるそうです。

今度準備に関わるときは、いろいろと提案してみよう。

以上、学会での無線ルータの設置でした。

posted by Png y_tsuda on Wed 21 Jan 2009 at 01:12

ちょうどタイミング良く@maihaさんから、@yuguiさんが 近くに来てるという情報をもらったので、 @yamazさんも交えて、ネストしたリソースを扱うコントローラの問題の答えを得るべく、ミーティングをしました。

問題の定義:

モデル層で Post has_many Comment な関係にある時に、 Commentのリストとコメント投稿フォームを含むPosts#show画面(典型的な例としてはブログの一記事表示画面)から、Commentを投稿した場合に、

  1. Comments#createで受け取ると、Commentのsaveに失敗した時に、Post#showを表示したいが、redirect resource(@comment.post) すると、@comment.errorsの情報がロストしてしまう。
  2. Posts#create_commentなどで受け取ると、Commentリソースの処理をPostsコントローラで行う事になって責任の範囲が不明確になり、格好が悪い。

解決策

Merbベースでコンセプトを示します。 まずは、Postsコントローラの中で、以下のような包含関係を宣言するようにします。

   1  class Posts < Application
   2    has_many :comments
   3  end
   4  
   5  class Admin < Application
   6    has_many :comments
   7  end

これにより、コントローラ同士の協調関係を、コントローラ自身が知っているという事になります。 具体的にhas_manyがやることは、以下のようなbeforeフィルターをshowアクションに対してセットする事です。

   1  class Posts < Application
   2    before :only => :show do
   3      controller = Comment.new(request)
   4      @comment = case request.method
   5      when "POST"; controller._dispatch(:create)
   6      when "PUT"; controller._dispatch(:update)
   7      when "DELETE"; controller._dispatch(:destroy)
   8      end
   9    end

:showアクションに対して、本来は使われない"POST", "PUT", "DELETE" の各メソッドでのリクエストがあった場合に、Commentsコントローラに処理を回します。"GET"の場合は普通にPosts#showが行われます。 Commentsコントローラ側では、メソッドの返り値としてcommentオブジェクトを返します。

   1  def create(comment)
   2    Comment.create(comment)
   3  end

作成に失敗した場合は、@comment.errorsにエラー情報が入っているので、 Posts#showの中から利用出来ます。

Posts#show内のComment投稿フォームは以下のような感じで、 Post#showに対してサブミットします。

   1  <%= form_for @comment, :action => resource(@post) do %>
   2    <%= partial "comments/form" %>
   3  <% end =%>

コンセプトなので実際に動くかどうかまだ検証してないですが、 こんな感じで良いのではないかと。

MerbだとControllerがresponseの生成を担当していないので、 コントローラをまたいだ処理のネストが高速に行えます。 Railsの場合は、個々のアクションの実行がresponseの生成を伴うので、 この方法だとオーバヘッドが大きくて難しいかもしれません。

posted by Png genki on Tue 20 Jan 2009 at 02:02

蛇足感がありますが、ちょっと補足しておきます。

Rails勉強会@東京、面白かったんですが。

そもそも何が問題かというと、 Commentsコントローラが担当すべきCommentリソースの処理を、 Postsコントローラで書かなきゃいけないのが格好わるいのでなんとかしたい、という事なんです。

Child belongs_to Parent な関係にあるChildリソースを描画する時は、 多くの場合Parentの描画を伴う事になるので、 そもそもControllerの仕組みが入れ子関係を上手く扱えるようになっているとありがたいのだけど。

View上で階層関係になっているものを、フラットなControllerで処理するという事がそもそも無理があるのかもしれない。 Controller側も階層化させるか、さもなくばセッション中に言ったように、 子供のリソースはAjaxで処理するという形にするのが奇麗かな。

posted by Png genki on Mon 19 Jan 2009 at 20:44

[追記 2009.01.20]script/generate featureの項を追加

moroさんの記事を読んで、Cucumber wktk!と思っていたのでtry

http://d.hatena.ne.jp/moro/20081112/1226486135
http://d.hatena.ne.jp/moro/20081118/1226977015
http://gist.github.com/26024

こちらの導入手順を参考しました:
http://wiki.github.com/aslakhellesoy/cucumber/ruby-on-rails

必要なgemをinstall

  • rspec
  • rspec-rails
  • cucumber
  • webrat

ローカルには0.1.15を入れたのですが、念のため0.1.13で依存しているというgem:aslakhellesoy-webratもinstallしました。

   1    gem sources -a http://gems.github.com
   2    sudo gem install aslakhellesoy-webrat

Webratを使うようにCucumberのenvに書く

${RAILS_ROOT}/features/support/env.rb

   1  require "webrat"
   2  Webrat.configure do |config|
   3    config.mode = :rails
   4  end     

script/generate feature

moroさんところのcucumberの構造辺りで紹介されているgeneratorでfeatureやstepsを生成:

   1  $ ruby script/generate feature Product

Webratの仕様が変わってた

moroさんのgistを下記に保存して使用し始めたのですが、

   1  ${RAILS_ROOT}/features/step_definitions/webrat_ja_steps.rb

細かい仕様が変更されていました:
ディレクトリstep_definitionsの位置も変わっているのに加えて、visits => visit、fills_in => fill_inなどが変更されていました。ただ、前もって調べなくても、rake featuresすると仕様変更された旨のmessageが出るので安心です。

   1   visit home_path 
   2   fill_in "Email", :with => "good@example.com"

またpendingされたstepには、下記のようにsnippetsが出力されて便利だなぁと思いました:

   1  You can use these snippets to implement pending steps which have no step definition:
   2  When /^パラメータを入力する product_comment\[body\]=dummy comment!$/ do
   3  end

Refs

http://moriq.tdiary.net/20081022.html
http://d.hatena.ne.jp/hs9587/20081231/1230691812
http://barkingiguana.com/2008/11/11/getting-started-with-story-driven-development-for-rails-with-cucumber

posted by Png satoko on Mon 19 Jan 2009 at 15:44

Merbでは、URLとアクションのマッチングをconfig/router.rbの中で定義しますが、ちょっと複雑なパターンマッチングを行いたい場合は、以下のように正規表現を使ってRouteを定義する事ができます。

   1  match(%r{^/gems/(.*)$}).to(
   2    :controller => 'gems', :action => 'show', :name => "[1]")

正規表現にマッチしたグループを、パラメータ側から"[1]"のように後方参照する事ができます。

posted by Png genki on Mon 19 Jan 2009 at 10:56