Monthly Archives: July 2009

Apple Push Notification Serviceを利用した、iPhone クライアントと、Rubyによるサーバの作成。

[とりあえず表示しましたが、これからコードをアップロードしたり改変していきます。]

先週から、システム部で何か動くものを作って発表するという企画?が始まりました。
プログラムプレゼンテーションを始めてみたら意外に良かった

第一弾は自分だったので、1週間しかなかったので、ちょこっとやればできそうで今までやってみたかったiPhone OS 3.0の目玉機能の一つ、Push Notification Serviceの実装をやってみることにしてみました。

iPhoneクライアントはほぼObjective-Cしかありませんのでそれを使い、サーバはRubyで実装しました。

以下が発表したスライドを少し改変したものになります。

コードは今回はbitbucket.orgにアップロードすることにしました!
コードはこちらになります。
http://bitbucket.org/milkcocoa/apns-test/

ただしサーバのファイルに関しては

  • デバイスIDとかの部分はxでマスクしています。
  • キーに関してはスライドで指定した方法で記述してください。

[ポイントを書く。]

mislav-will_paginate pluginを使用したページをRSpecでテストする。

Webアプリケーションでは、たくさんの項目を一覧表示する場合、1ページに全部見せるのではなくpaginate処理をして数ページに分ける場合があります。
たとえばGoogleの検索結果は通常、1ページに10件しか表示されません。

そういうページをRuby on Railsで作成する場合には、mislav-will_paginate pluginというプラグインを使用するということは割とよく知られていることです。

先日もそういうページを作成する必要があり、作成していましたが、RSpecでテストを作成しようとしたところやり方がわからず詰まりました。
そのため、その方法をここに記しておこうと思います。

ドキュメントを読もう!

実はGitHubのpluginのWikiページに最低限必要なことは書いてありました。
気がついていればもっと早く解決していたところでした。
「I’m getting “undefined method `total_pages’” error when rendering in the view!」という部分ですね。

ポイントはpaginateやtotal_pagesというメソッドをstub!で定義してあげることです。

実際のコード

  • Controller: ArticlesController
  • Model: Article
    • title: string
    • body: text

Controllerのテスト(の一部)

[$RAILS_ROOT/spec/controllers/articles_controller_spec.rb]

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe ArticlesController do
  describe "handling GET /articles" do
    before(:each) do
      article = mock_model(Article)
      article.stub!(:find).and_return([article])

      @articles = [article]
      @articles.stub!(:total_pages).and_return(1)
      @articles.stub!(:paginate).and_return(@articles)
    end

    def do_get
      get(:index)
    end

    it "should be successful" do
      do_get
      response.should be_success
    end

    it "should render index template" do
      do_get
      response.should render_template('index')
    end

    it "should find all articles" do
      Article.should_receive(:paginate).with(:page => nil, :per_page => 100).and_return(@articles)
      do_get
    end

    it "should assign the found articles for the view" do
      do_get
      assigns[:articles].should == @articles
    end
  end
end

ちなみに29行目の:page => nilになっているのは、たぶんassingsとかでparameterを指定していないからこうなるのだと思います。
まだshould_receiveをきちんと理解していないで、まともな説明が書けません。

Viewのテスト(の一部)

[$RAILS_ROOT/spec/views/articles/index.html.erb_spec.rb]

require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')

describe "/articles/index.html.erb" do
  include ArticlesHelper

  before(:each) do
    article_98 = mock_model(Article)
    article_98.stub!(:id).and_return(1)
    article_98.stub!(:title).and_return('MyString')
    article_98.stub!(:body).and_return('MyText')

    article_99 = mock_model(Article)
    article_99.stub!(:id).and_return(2)
    article_99.stub!(:title).and_return('MyString')
    article_99.stub!(:body).and_return('MyText')

    @articles = [article_98, article_99]
    @articles.stub!(:total_pages).and_return(1)
    @articles.stub!(:paginate).and_return(@articles)

    assigns[:articles] = @articles
  end

  it "should render list of articles" do
    render "/articles/index.html.erb"
    response.should have_tag("tr>td", "MyString", 2)
    response.should have_tag("tr>td", "MyText", 2)
  end
end

Mac OS X LeopardでCD-ROMからisoファイルを作成する。

中途半端ではありますがファイルサーバもできたことなので、CD-ROMやDVDなどはできる限りHDDの中に入れておきたいなぁと考えています。
物理メディアはメディアを探すところから始まり、わざわざドライブに入れなくてはならなかったり、速度が遅かったりと面倒です。

そこでCD-ROMからisoファイルを作成することにしました。
目指すはWindowsでも読めるもの。
Windowsから作成してもよいのかもしれませんが、いちいち仮想環境を立ち上げるのは面倒なのでMacでやってみます。

使用するソフトはディスクユーティリティとターミナル。

  1. isoファイルにしたいCD-ROMをドライブに入れる。
  2. ディスクユーティリティを起動する。
  3. ディスクユーティリティの左側でCD-ROMがマウントされていることを確認し、そこをクリック。
  4. メニューの[ファイル] – [新規] – [〜からディスクイメージ]を選択。
  5. 「イメージフォーマット」を「DVD/CDマスター」にして、ファイルの保存先を選択する。これで.cdrというファイルが作成される。
  6. ターミナルで次のコマンドを打つ。
    hdiutil makehybrid -iso -joliet -o 出力したいISOファイルの名前.iso cdrのファイル名

Windowsでは有名な仮想CD/DVDドライブであるDAEMON Toolsで読み込んでみたところ、無事に読み込むことができました。

RubyでArrayの部分集合を作成する。

Arrayの部分集合を作成する必要があったので、作ってみました。

class Array
  def subset
    rec = Proc.new do |univ|
      # subset!でポインタが循環しないようにcloneを使う。
      (ret = []) << univ.clone
      univ.each do |e|
        ret += rec.call(univ - [e])
      end
      ret
    end

    rec.call(self).uniq
  end

  def subset!
    self.replace(self.subset)
  end
end

他には同じサイズの配列を作成して、その要素を使用するかどうかtrueかfalseのフラグを立てていくという手法もあるようです。

irbで実行するとこうなります。

>> a = %w(1 2 3 4)
=> ["1", "2", "3", "4"]
>> a.subset
=> [["1", "2", "3", "4"], ["2", "3", "4"], ["3", "4"], ["4"], [], ["3"], ["2", "4"], ["2"], ["2", "3"], ["1", "3", "4"], ["1", "4"], ["1"], ["1", "3"], ["1", "2", "4"], ["1", "2"], ["1", "2", "3"]]

2009/7/20 追記

twitterですばらしい方法を教えていただきました!

ただしRuby 1.8.7で追加されたcombinationというメソッドを使用しているので1.8.7以降限定です。
cf. Ruby 1.8.7で使えるようになったRuby 1.9のメソッドたち – (rubikitch loves (Emacs Ruby CUI))

class Array
  def subset
    (0..self.length).inject([]) do |ret, n|
      ret.push(*self.combination(n))
    end
  end
end

irbでの実行結果。

>> a = %w(1 2 3 4)
=> ["1", "2", "3", "4"]
>> a.subset
=> [[], ["1"], ["2"], ["3"], ["4"], ["1", "2"], ["1", "3"], ["1", "4"], ["2", "3"], ["2", "4"], ["3", "4"], ["1", "2", "3"], ["1", "2", "4"], ["1", "3", "4"], ["2", "3", "4"], ["1", "2", "3", "4"]]

すごくすっきりしました!