open_sessionで特異メソッドを定義するときの注意
open_sessionで特異メソッドを定義したら、変数の持ち主に注意しましょうというお話。
次のように定義したとします。
def user open_session do |u| def u.access_to_home get '/home/index' end end end
まず、これはエラーなく実行できます。
get '/home/index' assert_response :success
ところが、これはエラーしてしまいます。
@user = user @user.access_to_home assert_response :success #=> NoMethodError: undefined method `success?' for nil:NilClass
assert_response内で呼びだそうとした@responseは、テストを実行するコントローラがもつ変数です。
一方、@user.access_to_homeのレスポンスを受ける変数は@userセッションがもっているもので、コントローラから見た@responseとは別物なのですね。(あるいは、コントローラ自体がひとつのセッションだと見做すこともできる)
@user.access_to_homeのレスポンスを見るには、userの定義内で呼び出してやる必要があります。
def user open_session do |u| def u.access_to_home get '/home/index' assert_response :success end end end
今度はエラーなく実行できました。
@user = user @user.access_to_home
@user.responseと明示してやる方法もあります。
def user open_session do |u| def u.access_to_home get '/home/index' end end end
@user = user @user.access_to_home @user.response.success? # こちらも実行可 @user.assert_response :success
もしくは、instance_evalとか。
def user open_session do |u| def u.access_to_home(&block) get '/home/index' instance_eval(&block) if block end end end
@user = user @user.access_to_home do assert_response :success end
vimで編集中のrubyテストを実行できるプラグイン
vimでrubyのテストを編集しているとき、テストメソッドごとに結果を確認しながら書き進めたいことなどないでしょうか。
ruby test/unit/hoge.rb -n test_fuga でテストメソッド単位の実行はできるけど、テストケース名が長かったりするとかなり面倒・・・
ところが、そんな悩みを解消してくれる子に出会いました。
GitHub - janx/vim-rubytest: Run ruby test in vim
vimで編集中のテストをその場で実行できるプラグインです。
install
$ git clone git://github.com/janx/vim-rubytest.git
で落として、 plugin/rubytest.vim を ~/.vim/ に配置するだけです。
(READMEには 'Copy all files to your ~/.vim directory.' とありますが、こちらの方法ではできませんでした)
ちなみにvimのバージョンは7.2でした。
usage
vimでの編集中にコマンドを打つとテストが実行されます。
t (\t) すると、カーソルの乗っているテストケースを実行 T (\T) すると、そのスクリプト内の全テストケースを実行
注意点としては、
- 実行中はvimのウィンドウからシェルに切り替わってしまう
- テストケース単体実行と言っても、テストケース名の部分一致で見ている
ていうか、 -n /test_xxx/ で部分一致指定ができたのですね・・・知らなんだ・・・後方一致もできましたし。
完全一致にしたい場合は、~/.vimrc でカスタマイズしてやりましょう。
let g:rubytest_cmd_testcase = "ruby %p -n %c"
さよなら -n オプション!
-
- -
はてブコメント見てたら、quickrun.vimというのもあるんですね。
スクリプト単位の実行なら、シェルに切り替わらないこちらが便利かもです。
参考:http://vim-users.jp/2009/05/hack7/
コメントありがとうございます!
-
- -
さらに追記。
本日ずっとコケ続けていたscreenのインストールに成功したので、コードとテスト結果が同画面で閲覧でき便利になりました。
-
- -
2011/08/22
さらにさらに追記。
railsのテストでrubytestを使っていたところ、次のエラーが出てテストが実行できない不具合に遭遇しました。
:!echo 'ruby integration/hogehoge_test.rb' && cd test && ruby integration/hogehoge_test.rb ruby integration/hogehoge_test.rb /usr/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require': no such file to load -- lib/piyopiyo (MissingSourceFile) ・ ・ ・
テストコード中で require 'lib/piyopiyo' しているところで、そんなのいないよと怒られる。でも実際にはいる。
rakeや、コンソールからの ruby test/integration/hogehoge_test.rb 実行なら落ちない。
よく見ると、rubytestが cd test してからテストを実行しているのがわかります。
rubytestのソースを確認すると、確かに cd しているところがありました。
自分の環境では cd しなくても不都合はないため、次のようにパッチをあてました。(リビジョンはこちら)
[admin@localhost plugin]$ diff ~/.vim/plugin/rubytest.vim.orig ~/.vim/plugin/rubytest.vim 64,67c64,69 < if @% =~ '^test' < let cmd = substitute(cmd, '%p', s:EscapeBackSlash(strpart(@%,5)), '') < exe "!echo '" . cmd . "' && cd test && " . cmd < else --- > > " fix bug 'no such file to load', due to missing load path. > "if @% =~ '^test' > " let cmd = substitute(cmd, '%p', s:EscapeBackSlash(strpart(@%,5)), '') > " exe "!echo '" . cmd . "' && cd test && " . cmd > "else 70c72 < end --- > "end
なお、ソースが新しければこの不具合は起こらないと思います。
おそらくこれ以前のものが該当するのでは、と。
アップデートしましょという話かもしれないガ━━(;゚Д゚)━━ン!!
In App Purchase (Apple アプリ内課金システム) のしくみ
販売コンテンツの幅
- Non-Consumable (非消費型) プロダクト
- Consumable (消費型) プロダクト
- Apple側では購入情報は管理されない
- ダウンロードが消えてしまった場合は再購入
- 別の端末に移せない
- ゲームの武器のようなバーチャルアイテム etc.
特徴
- 2009/10より、本体が無料のアプリでも利用可能になった
- 有料・無料どちらにしても、Appleと有料販売契約(Paid Contract)を結ぶ必要がある
- 課金プロダクトの価格帯は本体アプリと同じ
- デベロッパが自由に設定できない
- 無料にすることもできない
- アプリを起動しない限り課金できない
- 継続課金の場合も自動決済はされない。単に複数回の課金が可能というだけ
Appleのガイドライン
- 課金プロダクトはデジタルコンテンツ、オンラインサービスに限る
- 現実の物販やサービスは不可ということ
- すべてのユーザが平等に購入権を得られなければならない
- ポルノ、不快な文章や中傷、ギャンブル等の不適切なコンテンツは販売できない
- ただし、ギャンブルは以下の条件を満たせば可
- シミュレーションものであること
- 仮想通貨が外部のコンテンツや別アプリの通貨との引換に対応していないこと
- ただし、ギャンブルは以下の条件を満たせば可
Appleの決済を使わないアプリ内課金
- 原則できないと考えたほうがよい(App Store Review Guidelinesにもそのような記述がある)が、以下は可能 ※2010/10月現在
- WEBサイトでの決済
- iOS SDK Hacksには、UIWebView (アプリ内ブラウザ) を立ち上げてサイトを表示すれば決済できるように書いてあるが、2010/12月現在はmobile safariに切り替える方式でないとリジェクトされるという話も聞く。
事例として載っているebi Readerも、現在はmobile safariに切り替わる仕様になっている模様。(iPhone版のみ確認)
- iOS SDK Hacksには、UIWebView (アプリ内ブラウザ) を立ち上げてサイトを表示すれば決済できるように書いてあるが、2010/12月現在はmobile safariに切り替える方式でないとリジェクトされるという話も聞く。
- PayPalを通しての決済
- PayPal提供のSDKをアプリに組み込むと、PayPalの決済を使えるようになる https://www.x.com/
- WEBサイトでの決済
演算子の優先順位
演算子の優先順位を意識していないと予想外の結果になることがあるので注意しましょうというお話。
- 和集合を代入したい場合
irb(main):001:0> hoge = nil || 'hoge' => "hoge" irb(main):002:0> hoge = (nil || 'hoge') => "hoge"
結果は同じ。
- 和集合を配列に追加したい場合
irb(main):003:0> hoge = [] => [] irb(main):004:0> hoge << (nil || 'hoge') => ["hoge"] irb(main):005:0> hoge = [] => [] irb(main):006:0> hoge << nil || 'hoge' => [nil]
より << が強いので、カッコの有無で結果が変わってしまいます。 |
確かに、リファレンスを見ると = と << の優先順位が全然違いますね。
画像の非同期ロード
前回の内容とも関わるのですが、UITableViewCellの画像をWEBからロードする場合も、スクロールが非常に重くなることがあります。
前回同様、配列を使って回避できるかなと思ったのですが、うまく表示させることができませんでした。(もし方法があればご教示頂けると幸いです!)
そこで、画像は非同期通信でロードし、ロードできたものから順次表示されるようにしました。
環境
- iPhoneSDK 3.1.3
方法はこちらのページを参考にさせて頂きました。
非同期通信で画像をロードする方法について - プログラミングノート
大まかな流れとしては、UIImageViewを継承したクラスを作成し、そのクラスに画像データのロードを行わせ、cell.imageViewにaddSubviewする形となります。
ただ、私の環境で試したところ、addSubviewだけでは表示されなかった(cell.imageView.imageが確保されていない感じ)ため、下記のようにデフォルト画像を明示的に指定しました。
// tableView:cellForRowAtIndexPath UIAsyncImageView *ai = [[UIAsyncImageView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)]; [ai loadImage:@"URL"]; [cell.imageView addSubview:ai]; cell.imageView.image = [UIImage imageNamed:@"NoPhoto.png"]; // デフォルト画像を明示的に指定
また、これだけですと、スクロールしてセルが再利用されたとき、新しい画像がロードされるまで古い画像が残っていて違和感がありました。
そこで、UIAsyncImageView.mにinitWithFrameを定義し、セル再利用時もデフォルト画像にリセットされるようにしました。
- (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.image = [UIImage imageNamed:@"NoPhoto.png"]; // セル再利用時にもこのメソッドが呼ばれるため、画像をリセット } return self; }
JSONによるWEBアプリとiPhoneアプリのAPI連携
RailsアプリケーションでJSONデータを返すAPIを実装し、iPhoneアプリから受信する方法をご紹介します。
iPhoneアプリ
json-framworkというフレームワークを使ってJSONを扱うことができます。
- json-framework をiPhoneSDKに組み込む
$HOME/Library/SDKs/JSON/$(PLATFORM_NAME).sdk
-
-
- 「ビルド」=>「他のリンカフラグ」の値に以下を記述します。
-
-ObjC -ljson -all_load
-
-
-
- 私の環境では、シミュレータでは再現せず実機でのみクラッシュするバグが起きたため、 -all_load も追記しました。
-
-
#import "JSON/JSON.h"
-
- 受け皿の実装
// JSONデータのリクエスト NSURL *jsonURL = [NSURL URLWithString:@"http://0.0.0.0:3000/users/show/1.json"]; NSMutableString *jsonData = [NSMutableString stringWithContentsOfURL:jsonURL encoding:NSUTF8StringEncoding error:nil]; // シングルクォーテーションはJSONでは扱えないため、ダブルクォーテーションに変換 [jsonData replaceOccurrencesOfString:@"'" withString:@"\"" options:NSCaseInsensitiveSearch range:NSMakeRange(0,[jsonData length])]; // result にデータを受けて出力 if (jsonData != nil) { id result = [jsonData JSONValue]; // JSONValue が外部メソッド NSLog(@"%@", result); }
Railsアプリケーション
# config/routes.rb map.connect ':controller/:action.:format' map.connect ':controller/:action/:id.:format' # users/show/1 のようにパラメータをパスで扱っている場合
- アクションからJSONデータを返す
render :text => @object.to_json
$ ruby script/console Loading development environment. >> data = {"id" => 1} => {"id"=>1} >> data.to_json => "{id: 1}" # 属性がダブルクォートされていない >> require "json" => ["JSON"] >> data.to_json => "{\"id\":1}" # 属性がダブルクォートされている
require "json" ・・・ render :text => @object.to_json
UITableViewCellのパフォーマンス
UITableView の tableView:cellForRowAtIndexPath:indexPath からデータリクエストを飛ばしたりすると、パフォーマンスが著しく低下することがあります。
私はこちらのAPIの開発中に各セルからJSONデータをリクエストさせていたのですが、スクロールの際に引っかかるような動きがありました。
Rails(WEBアプリ)側のログを確認しながら動かしてみたところ、いちど画面から消えたセルであっても、再び表示されるときに再リクエストを飛ばしていることがわかりました。
ひとつの対処としては、予めデータを配列で取得してしまい、リクエストをその1回に抑えることです。
viewDidLoad 等のタイミングで配列arrayに取得し、tableView:cellForRowAtIndexPath:indexPath 内では [array objectAtIndex:[indexPath row]] として利用します。