Sencha Touch の Model と Store の使い方メモ

Sencha Touch の Model を Store を使って、 Web API からデータを取得する方法をメモしておく。

まず、次のようなモデルを定義したとする。

Ext.define("App.model.Post", {
  extend: "Ext.data.Model",
  config: {
    fields: [
      { name:"name", type:"string" },
      { name:"body", type:"string" }
    ]
  }
});

Sencha Touch のドキュメントでは下のような、Ext.data.Store を使ってデータを取得する方法が載っていた。

var store = Ext.create("Ext.data.Store", {
  model: "App.model.Post",
  proxy: {
    type: "ajax",
    url: "/posts.json",
    reader: {
      type: "json"
    }
  }
});
store.load({
  callback: function(record, operation, success) {
    // データを取得できたときの処理
  }
});

データを取得するのが1回きりなら、これでもいいんだけど、実際はいろんな箇所で呼び出すはず。 カスタム Store を定義して、再利用できるようにしておいたほうがいい。

Ext.define("App.store.Posts", {
  extend: "Ext.data.Store",
  config: {
    model: "App.model.Post",
    proxy: {
      type: "ajax",
      url: "/posts.json",
      reader: {
        type: "json"
      }
    }
  }
});

カスタム Store を使ってデータを取得するコードは、ほんの少しだけ短くなった。

var store = Ext.create("App.store.Posts");
store.load({
  callback: function(record, operation, success) {
    // データを取得できたときの処理
  }
});

書き捨てのサンプルじゃなく、ちゃんとしたアプリ作るときは、カスタム Store を定義するべきだな。

MSTest を使って RSpec っぽくテストを書いてみた

プライベートで RSpec 使ってテストを書いていると、 仕事でも RSpec っぽくテストを書きたくなる。 でも、仕事で使っている言語は C# だから、Visual Studio単体テスト機能(MSTest) しか使わせてもらえない。

RSpec は describe や context を入れ子に出来るから、 状態によって振る舞いの変わる機能のスペックが書きやすいんだよな。 C# で BDD やるためのフレームワークもあるにはあるけど、 オープンソースソフトウェアの導入許可って滅多に降りない。

なかなか諦め切れなくて、MSTest でなんとか RSpec っぽく書けないか試行錯誤していたら閃いた。 テストクラスを入れ子にすることで、MSTest でも RSpec っぽく書けるかも? 試しに MSTest で無理やり List のスペックを書いてみた。

[TestClass]
public class ListSpec
{
    protected List<int> _subject;

    [TestInitialize]
    public virtual void BeforeEach()
    {
        _subject = new List<int>();
    }

    [TestClass]
    public class CountSpec : ListSpec
    {
        [TestClass]
        public class 空のとき : CountSpec
        {
            [TestMethod]
            public void Countは0を返す()
            {
                Assert.AreEqual(0, _subject.Count);
            }
        }

        [TestClass]
        public class データを1件持っているとき : CountSpec
        {
            [TestInitialize]
            public override void BeforeEach()
            {
                base.BeforeEach();

                _subject.Add(1);
            }

            [TestMethod]
            public void Countは1を返す()
            {
                Assert.AreEqual(1, _subject.Count);
            }
        }
    }
}

入れ子にするとき、中のクラスが外のクラスを継承するのがポイント。 これで TestInitialize や TestCleanup が RSpec の before, after っぽく動いた。 ただ、外側のテストクラスを継承しているから、一斉にテストを走らせたとき、何度も同じテストが実行されてしまう。 テストメソッドを末端のテストクラスに書くように徹底すれば回避できるけど。

あと、Visual Studio 2012 なら、ソリューションエクスプローラーでスペックを見れるのはうれしい誤算だった。

f:id:griefworker:20130510211206p:plain

まぁ、NSpec や SpecFlow を使えるなら、最初からそれ使うのが一番いいんだけどね。

Heroku で FeedNormalizer を使って嵌った

RSS をパースするのに feed-normalizer を使っていたんだけど、Heroku 上で

require "open-uri"
require "feed-normalizer"

feedlink = "http://tnakamura.hatenablog.com/feed"
feed = FeedNormalizer::FeedNormalizer.parse(open(feedlink))
feed.title #=> nil
feed.url #=> nil

を実行したら title と url が nil を返してしまう。RSS をパースしたときはちゃんと取得できたんで、Atom をパースしたときこの問題に遭遇する模様。

ローカルでは取得できるのに、Heroku では取得できなくて嵌った。上記のサンプルを heroku run rails console で実行して発覚。対処方法は現在調査中だけど、ここまで調べるだけでも結構時間使ったんで、忘れないうちにメモっておこう。

追記1

feed.parser #=> "SimpleRSS"

を実行して、内部で使っているパーサーが SimpleRSS ということが分かった。SimpleRSS がちゃんとパースできていない可能性が高い。

追記2

SimpleRSS は parse の中で

tag_cleaned = clean_tag(tag)
instance_variable_set("@#{ tag_cleaned }", clean_content(tag, $2, $3))
self.class.send(:attr_reader, tag_cleaned)

という風に、ATOM のタグと属性をもとに、動的にインスタンス変数とメソッドを作成していた。

そして、Heroku では

self.class.send(:attr_reader, tag_cleaned)

で動的に作成したアクセサが private メソッドになってしまっていた。試しに Heroku で

class Hoge; end
h = Hoge.new("foo")

h.instance_variable_set(:@age, 20) #=> 20
h.class.send(:attr_reader, "age")
h.age #=> NoMethodError: private method `age' called for #<Hoge:0x0000000726ad70 @age=20>

を実行したら、動的に追加したアクセサが private メソッドになっているのを確認できた。ちなみに、ローカル環境で実行すると public メソッドになっていた。

title や author が private なメソッドなので、FeedNormalizer は title や author を取り出せない。その結果、nil が返していたようだ。

追記3

Feedzirra は Heroku でも動いたんで、RSS/Atom パーサーは次から Feedzirra を使おう。

LTSV ロガーを自作して Rails アプリで使ってみた

WEB+DB PRESS Vol.74 の LTSV 特集を読んで LTSV 熱が高まったので、Ruby 用の LTSV ロガーを作って、Rails アプリに組み込んでみた。

LTSV フォーマットでログを出力するライブラリは既にいろんな人が公開しているから、車輪の再発明になってしまったな。

Rack ミドルウェア版は gem が見当たらなかったんで作ってみたけど、Rack::CommonLogger のソースを参考にしたから難しいことやってない。

Rails で使うには、まず Gemfile に追加して

gem "logger-ltsv", require "logger/ltsv"
gem "rack-ltsvlogger", require "rack/ltsvlogger"

config/environments の development.rb や production.rb で、LTSV ロガーを使うように設定すればいい。

  # LTSV フォーマットでログを出力する
  config.logger = LTSVLogger.new($stdout)

  # Rack のログを LTSV フォーマットで出力する
  config.middleware.insert_before(Rails::Rack::Logger, Rack::LTSVLogger, $stdout)
  config.middleware.delete(Rails::Rack::Logger)

今回は Heroku にデプロイするんで、ログは標準出力に出力してみた。

WEB+DB PRESS Vol.74
WEB+DB PRESS Vol.74
posted with amazlet at 13.05.04
井上 誠一郎 奥野 幹也 田中 慎司 西嶋 悠貴 伊藤 直也 登尾 徳誠 天野 祐介 後藤 秀宣 ヒノケン 近藤 宇智朗 近藤 嘉雪 渡邊 恵太 堤 智代 中島 聡 A-Listers はまちや2 川添 貴生
技術評論社
売り上げランキング: 438

Heroku の Dyno を寝かせない

Rails 製 Web アプリを Heroku で動かしているんだけど、Heroku の Dyno は1時間以上放置されると寝ちゃうようだ。

寝起きも悪いんで、1時間おきにかまってあげて Dyno を寝かさないようにした方が良さそう。

Heroku では curl が使えるから、アドオンの Heroku Scheduler を追加して、

curl http://your-app-name.herokuapp.com/

のコマンドを1時間おきに実行するように設定。これで今のところ、Web アプリにアクセスして数10秒待たされる、なんてことは無くなった。

Bootstrap を使ってデザインしたアプリに Sidr でメニューを作成

Twitter Bootstrap はレスポンシブ Web デザインをサポートしているから、 小さい画面ではナビゲーションバーのメニューが隠れて、代わりにボタンが表示される。

このボタンをタップするとメニューがドロップダウン(?)表示されるんだけど、これ好みじゃない。 FacebookGmailiPhone アプリみたいに、横からスライド表示されるメニューにしたい。

そこで、Sidr と Bootstrap を組み合わせて、それっぽいメニューを実装してみた。

この手のメニューを実装するプラグインは Sidr のほかに PageSlide や Snap なんかがあるけど、 試した中では Sidr が一番 Bootstrap と組み合わせやすかった。他のは動かすだけで一苦労。

Bootstrap と Sidr を組み合わせたサンプルがこちら。

<!DOCTYPE html>
<html>
  <head>
    <title>Sample</title>
    <meta charset="utf-8"/>
    <link rel="stylesheet" href="bootstrap/css/bootstrap.css" />
    <link rel="stylesheet" href="bootstrap/css/bootstrap-responsive.css" />
    <link rel="stylesheet" href="sidr/stylesheets/jquery.sidr.dark.css" />
  </head>
  <body>
    <div class="navbar navbar-inverse navbar-fixed-top">
      <div class="navbar-inner">
        <div class="container-fluid">
          <a id="sidebar_button" href="#" class="btn btn-navbar" style="float:left;">
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </a>

          <a href="#" class="brand">Sample</a>
        </div>
      </div>
    </div>

    <div class="container-fluid">
      <div class="row-fluid">
        <table class="table">
          <tbody>
            <tr>
              <td>
                <input type="checkbox" />
                Task
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>

    <!--これがメニューになる-->
    <div id="sidr">
      <ul>
        <li>
        Lists
        <ul>
          <li><a href="#">list</a></li>
          <li><a href="#">list</a></li>
          <li><a href="#">list</a></li>
          <li><a href="#">list</a></li>
        </ul>
        </li>
        <li>
        Tags
        <ul>
          <li><a href="#">tag</a></li>
          <li><a href="#">tag</a></li>
          <li><a href="#">tag</a></li>
          <li><a href="#">tag</a></li>
        </ul>
        </li>
      </ul>
    </div>

    <script type="text/javascript" src="jquery.js"></script>
    <script type="text/javascript" src="bootstrap/js/bootstrap.js"></script>
    <script type="text/javascript" src="sidr/jquery.sidr.min.js"></script>
    <script type="text/javascript">
      $(function() {
          var $tbody = $("tbody");
          var $template = $tbody.html();
          for (var i = 0; i < 20; i++) {
              $tbody.append($template);
          }
      });

      // サイドバーにする要素を指定しない場合、
      // id に sidr が設定された要素を使う。
      $("#sidebar_button").sidr();
</script>
  </body>
</html>

Bootstrap のナビバーボタンは本来右端に表示されるけど、直接スタイルを指定して左端に表示している。

この HTML を Chrome で表示。

f:id:griefworker:20130423200231p:plain

左上のボタンをクリックすると

f:id:griefworker:20130423200243p:plain

スライドしてメニューが表示された。

ここまでは期待通り。 本格的にアプリに組み込むのはこれからなので、もしかしたらこの先で躓くかもしれない。 その時は Sidr のソースコードを改変するか、おとなしく自作しよう。

道頓堀くくる

おやつで無性にしょっぱいものが食べたくなったので、 大丸地下2階のうまかもん横丁にある『道頓堀くくる』に行ってみた。 そういえば大阪のたこ焼きは初めて食べる気がする。 銀だこは築地だし、蛸やは地元福岡だったし。

注文したのは、たこ焼き8個(520円)。

f:id:griefworker:20130420155107j:plain

外はふわっとしていて、中はとろ~り。 つまり、ふわとろ。 これが大阪のたこ焼きか。 ソースは主張し過ぎない濃さで、 生地の味をしっかり味わえた。 生地の表面がカリッとしている濃厚ソースのたこ焼きを想像していたけど、 これはこれでアリだな。

8個食べたけど、小粒だったので、腹八分目止まり。 食事としては量的に物足りないけど、おやつとしては丁度いいな。

関連ランキング:その他 | 天神南駅西鉄福岡駅(天神)天神駅