イッツァハローワールド

恥さらしていこうかなとか。

xibを使ってソースコードから表示しようとした時にapplicationDidFinishLaunchingが呼ばれなくて困った話(OS X App)

最近は上京してOSX Appを書いている人です。

困りごと(ていうか届かぬ文句)

  • AppKitまわりの資料が少ない
  • AppKitまわりの資料が古い
  • ↑探すくらいならリファリンス読む
  • Swiftとの合わせ技だとめったにない
  • xibとかの話だとあるわけがない

今回の現象

作りたかったもの

  • OS Xアプリで簡易WebブラウザみたいなWindowにWebView乗っけただけのもの。
  • NSWindowControllerでWindowおよびその上のViewイベントの管理をする。
  • 新しいウィンドウを開くみたいなリンクをクリックしたら新しいWindowControllerを生成する。
  • WindowControllerは管理クラスが管理して、Windowを閉じたら管理クラスから抹殺する。
  • WindowControllerの動きは共通かつ、Window内でViewの遷移がないからStoryBoardは使わずxibを使う。

f:id:hanamiju:20150526005540p:plain

やったこと

  • StoryBoardファイルは消す
  • NSWindowControllerがFiles Ownerのxib(CustomWindow.xib)を作る
  • プロジェクト設定のDeployment Info - Main Interfaceを消す
  • AppDelegateのapplicationDidFinishLaunchingで↑のxibからWindowControllerのカスタムクラスを生成
func applicationDidFinishLaunching(notification: NSNotification) {
        var windowController CustomWindowController:  = CustomWindowController(windowNibName: "CustomWindow")
        //...
        self.window = windowController!.window
        self.window!.setFrame(NSScreen.mainScreen()!.frame, display: true, animate: true)
        self.window!.makeKeyWindow()
    }
  • windowDidLoadで管理クラスにWindowControllerを保存、windowWillCloseで管理クラスから削除

ビルド結果

Windowが表示されない。
ていうか、AppDelegateのapplicationDidFinishLaunchingが呼ばれないからWindowが生成されない。

AppDelegateのクラスの宣言時に@NSApplicationMain書けば(デフォルトで書いてあるけど)applicationDidFinishLaunchingが呼ばれると思っていたんだけど違うらしい。

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
// ...

解決策(スマートではないと思うけど...)

要はapplicationDidFinishLaunchingが呼ばれれば良い。

  • 空のMainMenu.xibを用意する。

f:id:hanamiju:20150526011522p:plain 

  • Files Owner(NSApplication)とAppDelegateをDelegateで結ぶ
  • プロジェクト設定のDeployment Info - Main InterfaceをMainMenuと入れる

まとめ

  • だいぶ特殊用途だと思うけど、今回はこんな感じで解決した。
  • すげー時間かかった。空のNib作らなくてもいける方法ある気がしたけど、今回はタイムアップでした。
  • 今回初めてNSWindowController使った。
  • Viewの切り替えがなかったからNSViewController使わなかった。
  • いっぱいWindowあって、いっぱいViewを切り替えるアプリを作るのは骨だなあと思った。

祝 LINEスタンプ発売されました「真顔くん」

先日(つっても結構前)につくったLINEスタンプが発売されました。

store.line.me

私の精神性を表したようなラインナップとなっております。
f:id:hanamiju:20150516174716p:plain

歩み

絵を描くのが苦手(めんどう)な人がLINEスタンプ発売するために、
どのくらい手を抜いていいものか検証していたんですけど、まさかここまで時間がかかるとは...

  • 2014/11/16 一晩で製作・申請
  • 2014/12/09 リジェクト
  • 2015/02/13 修正版を申請
  • 2015/05/15 申請通過・発売

作業時間

20hくらい

内訳

  • 文言を考える ... 3h
  • 絵の練習 ... 3h
  • 下書き、清書、Illustatorで画像作成 ... 6h
  • 修正 ... 8h

登場人物や体のパーツを使いまわしたのでこんなもんですみましたが、ちゃんと絵を描いてスタンプごと被らないようにしたければこの数倍はかかるんじゃないかという印象です。

宣伝

いろいろ方法あります。

SNSつかう

スタンプ画像あるとより効果的。
私はtwitter, Facebookに投稿しました。

紹介サイトつかう

以下のサイトを使わせてもらいました。
全部無料で審査も不要。手軽に紹介記事書けます。

LINEスタンプ・着せかえ検索サイト - STAMPOO
http://www.stampers.me/
http://forestamp.net/

口コミ

社長に「今日LINEスタンプリリースしました〜!」って言ったら、速攻slackでブロードキャストされました...
宣伝効果抜群。

みんなに使ってもらう

使い勝手がよければ、伝染して欲しくなるはずって信じています。
今回作ったやつは使い勝手いいと信じているのですが、みんなこれ使い始めるとLINEが泥沼化する気がしないでもありません。

これから

スタンプの管理ページからいろんなデータが見れます。

  • 売上レポート
  • スタンプの送受信数

などなど。

スタンプごとの送信、受信の数が見れますのでみんなこんな辛辣な言葉投げてんのか...と心が少し痛くなったりならなかったり。

あんまり使われていないスタンプの修正を試みたりはやっていこうかなと思います。

あと、宇宙人のスタンプが↓のときからリジェクトされたままになっているので時間のあるときに修正しようかなとか。
hanamiju.hatenablog.com

Handoffしようとして、Activityの引き継ぎがうまくいかなかったときの話

WATCHが発売されたので、ウヲッチの実装を実機で動作確認できるようになりました。

Handoffまわりとかシミュレータで動作確認できないので、まあ動いているだろうでコードを書いてはみたのですが実機で動作確認してみたら見事に動いていませんでしたw

そんな、私のための備忘録です。

現象

WATCHで表示していた情報をiPhone側に引き継いで表示させようとしたが、意図したViewが生成できず表示ができなかった。

処理のフロー

  1. ウヲッチからiPhoneにupdateUserActivity:userInfo:webpageURL で通知を実施
  2. application:continueUserActivity:restorationHandler:が呼ばれない→iPhone側が意図した動作をしない
  3. ↑のかわりにapplication:didFailToContinueUserActivityWithType:error:が呼ばれる

エラー内容

Error Domain=NSCocoaErrorDomain Code=4611 "操作を完了できませんでした。(Cocoa エラー 4611。)" UserInfo=0x17006c0c0 {NSUnderlyingError=0x174246540 "操作を完了できませんでした。(LSContinuityErrorDomain エラー -110。)"}

原因

エラーコードの調べ方から調べましたが、どうやら4611はNSUserActivityHandoffUserInfoTooLargeErrorでUserInfoのデータサイズがでかすぎるらしい。

エラーコードの調べ方

NSError コードの調べ方 - 日々是笑心

というわけで

UserInfoの内容を小さく作ったらちゃんと動きました。ばんざい。



エラーコード4611は全然ググっても出てこなかったから、だいぶ詰まりました。
これからはちゃんとエラーコードも見ます。

swift1日目

今までswiftから逃げまわってきた人ですが、
先日先輩からswiftの書籍をもらったので、勉強メモ。


C言語わかる、Obj-Cもかじっている程度なら全然分かるメモかと思います。

swiftやる前にPlayground

その名の通り、swiftプログラムの遊び場、実験場。
テキストエディタswiftコードを書くと実行結果を表示する。
Xcodeにあるの知らなかったぜ...

Playgroundファイル作成

[File]→[New]→[Playground...]でつくる。

変数宣言

var 変数:
// 例
var num: Int
num = 3

// 初期化
var num2: Int = 0

// 型推論
var num3 = 0.5 // 少数で初期化しているからDoubleと推論

慣れない('A`)
まずここから慣れるの時間かかるな...

定数宣言

let 定数:=

定数は初期値が必要

// 定数宣言
let pi = 3.141592 //OK
pi = 3.1 //エラー(変数ちゃうで)
let root2: Double //エラー(初期値ないよ)

文字列

// 文字列
var emptyString = String()  // 空文字

var num1 = 1
var calcString = "1 + 1 = \(num1 + num1)"   // 文字列補間

文字コード→文字をつくる

// 「\u{文字コード}」で記述
let faceChar = "\u{1f617}"  // 😗

letで宣言すればNSString, varで宣言すればNSMutableString

なるほど。わかりやすい。

配列

// 配列
var year = [2010, 2011, 2012, 2013, 2014, 2015]

// 配列の型を明示的にする
// var 変数名:[型] = [要素, 要素, 要素]
var 都道府県:[String] = ["東京都", "京都府", "沖縄県"]

//空配列 (どの書き方でもいいよ)
var emptyArray = [String]()

var emptyArray2: [String] = Array()

// 配列の型推論
var emptyArray3 = []

辞書

// 辞書
var address = ["都道府県" : "東京都", "市区町村" : "渋谷区"]
// キー指定で値を引っ張る
var prefecture = address["都道府県"]

// 辞書の明示的型宣言
// var 変数名 = Dictionary<キーの型, 値の型>()
var emptyDict2 = Dictionary<String, String>()
// var 変数名 = [ キーの型 : 値の型 ]
var emptyDict3 = [Int:String]()

// 辞書の型推論
var emptyDict4 = [:]

NSArray と NSDictionary

  • NSArray ≠ Array
  • NSDictionary ≠ Dictionary
import Foundation

// Swiftの辞書作成
var dict = ["key": "value"]

// NSDictionary型の辞書として生成
var nsdict = dict as NSDictionary

関数

/*
func 関数名(引数名:引数型) -> 戻り値型 {
    処理
    return 戻り値
}
*/
func func1(x: Int) -> Int {
    return x + 7
}

func1(3) // 10
func1(2) // 9

クロージャ

ああ、Blocksのことですか。

/*
クロージャ
{ (引数名, ...) -> 戻り値型 in
    // 処理
    // return 戻り値
}
*/
// hoge関数の定義
func hoge(handler:(String, String, String) -> Int) {
    var hoge1 = "hage1"
    var hoge2 = "hage2"
    var hoge3 = "hage3"
    
    handler(hoge1, hoge2, hoge3)
    
}

// hoge関数を呼び出す
hoge({(hoge1 ,hoge2, hoge3) -> Int in
    println(hoge1)
    return 0
})

上記の例文でやっていることは

  1. hoge関数を呼び出す(引数にクロージャ
  2. hoge関数で定義したhoge1〜3をクロージャに渡す
  3. クロージャはhage1を出力, 0を返す

クラスと構造体の使い分け

  • 大量にインスタンスを確保、破棄するもの → 構造体
  • たくさんの情報を持っている、ライフタイムが長いもの → クラス

理由

構造体の方がクラスより軽量
  • インスタンスの確保、破棄に関する手間が少ない
  • メモリ量が少ない
構造体はARCが効かない

プロパティ

保持プロパティ

クラス、構造体の中で変数の形で宣言されているもの。
よく見てきたプロパティ。

算出プロパティ

変数を持たず、プロパティにアクセスするメソッドを用意して計算を行うもの。¥

struct Rect {
    // 保持プロパティ
    var x: Float = 0
    var y: Float = 0
    var width: Float = 0
    var height: Float = 0

    // 算出プロパティ
    var centerX: Float {
        get {
            return x + width * 0.5
        }
        set (centerX) {
            x = centerX - width * 0.5
        }
    }
}

プロパティ変更時に別のプロパティも変わらないといけない場合は、算出プロパティにしておけばいちいち全てのプロパティの変更処理書かなくていいから楽ね。

メソッド

タイプメソッド

Obj-Cでいうクラスメソッドのことかな。

  • 構造体のタイプメソッドはfuncの前に "static" をつける
  • クラスのタイプメソッドはfuncの前に "class" をつける

インスタンスメソッド

Obj-Cのインスタンスメソッドと同じイメージ

Optional型

  • Optional 型 : nil の代入を許す
  • 非 optional 型 : nil の代入を許さない

Any

なんでも指定できる。Any型の配列ならなんの型でもオーライ

    var anyArray = [Any]()
    anyArray.append(0)    // 整数
    anyArray.append(1.234)    // 小数
    anyArray.append("hoge")    // 文字列

AnyObject

id型とおんなじイメージ

ラップやらアンラップやら

  • ラップされてる状態 → オブジェクトが、正当なものかもしれないし、nilかもしれない状態。
  • アンラップする → どんなオブジェクト化を確定する。

ここは、もうちょっと勉強してからメモろう。

MetaType

オブジェクトの型

objective-cではオブジェクトのクラス名欲しい時に

Class objectType = [SomeClass class];  

ってやってたけど、swiftでは

var objectType = SomeClass.self

らしい。
http://freecake.yayuhh.com/simple-reflection-in-swift/

感想

('A`)慣れねえ。(2回目)

Playgroundで試しながらの勉強なんで、理解度はいつもより早いんだけどね。
実際にサンプルプログラム書きがてら学ぼう...

iPhoneでお階段登りするために正月休み潰した話

先日アプリをリリースしました。
https://itunes.apple.com/jp/app/o-jie-duan-dengri-zuo-rino/id956171342?mt=8&uo=4&at=10l8JW&ct=hatenablog

ってのを前の前くらいの記事でさらっと言ってたんですが、覚えているうちに製作時に思ったことや考えたことについて書きます。

お階段登りとは

  • iPhoneで擬似的に階段を登って高さを競うゲーム

その他機能

  • 階段は一段づつしか登れない
  • 課金すれば早くたくさん登れる
  • 自分の位置を有名な建築物、山などと比較して表示する。(キャプチャ付きで共有できる)
  • 他のユーザーの到達点に達したらすれ違える。
  • 他のユーザーをタップするとメッセージが出る。

ありがたいことにレビューいただいたサイトの説明がスッゲーわかりやすかったのでこちらもみてみてください。
お階段登り 〜昨日の俺とは数段違うぜ!〜 | アプリレビュー | iPhoroid│脱出ゲーム攻略!国内最大の脱出ゲーム総合サイト

紹介動画

ご丁寧に紹介動画も作りました。
これでアプリの世界観が伝われば嬉しいです。


お階段登り 使い方 - YouTube

AppStoreの説明動画に載せたんだけど、「もっとちゃんと説明しろ」ってリジェクトされました。
(ちゃんと説明してるつもりなのに。)

動機

正月休みの宿題として「ゲームを一本リリース(申請まで)する」を掲げました。
最初に考えたのが、SpriteKitをつかったゴルフゲーでした。基本機能はできてたんだけどゲームの構成とホールのバリエーション考えてたら2014年越しましたw

残り少ねえってことで、どうしようかと思っていたのですが
あと、↓の記事見てすっげえわかりやすくてくだらないゲーム作りたいって思ってクソゲー作りに着手しました。


「誰もやりたくないアプリ」を3日でつくったら、広告収益3,500万円超え。800万ダウンロードの無駄タップアプリ「100万のタマゴ」が世界各国でヒットするまで。 | アプリマーケティング研究所

名前の由来

戒壇廻りと語呂が似ているから(安直)
http://zenkozi.com/highlight/admonish.html

ネタの構想

日常生活で特に意識せずやっていることをクローズアップすると逆に面白く見えるのではないかと考えた結果

  • 階段を登る
  • トイレットペーパーを巻く

がポンとでてきました。
トイレットペーパーを巻くアプリは結構あったのに比べて階段を登るのはほとんどなかったため、「これや!」ってなりました。

見た目で気をつけたこと

「くっだらないゲーム」を表現するために、見た目をチープにするように心がけました。
↓ゲーム画面
f:id:hanamiju:20150309230050p:plain

  • 色使いを頑張らない
    • 今回はモノトーン
  • 最小限の表現にする(必要以上に凝らない)
    • 動く/止まる は適当な集中線の有無のみで表現
  • 手書き調にする
    • 手書きフォント
    • 手書き調のブラシでパスを書く

なぜチープに見せるか

通常時はチープ装いをすることでサブ機能がギャップで映えて面白く見えるのかなとか考えました。

非現実な機能の場合

いろんな足音を選択できる→現実とのギャップ(なんで足音がピヨピヨやねんw)

現実にある機能の場合

上の段の人とすれちがう→チープなくせに妙なことできるっていうギャップ(なんで他のユーザーでてきてるねんw)


最後にチープすぎて意味わかんないものにならないように注意。

技術的に苦労したこと

  • アプリ内課金
  • Auto Layout

いいネタなのでまた別記事で。

技術的じゃないところで苦労したこと

  • 階段づくり(ここに一番時間かけた)

うすうすわかりそうだけど、どうやって階段のアニメーションを実現しているかは秘密で。実装自体はほとんど時間かかっていません。

UIづくり

ゲーム作りなので、極力iOSの標準UI使わないようにカスタムUIで実装しています。

素材作成

最近sketchを購入しましたので、練習がてら素材作成に使いました。
https://itunes.apple.com/jp/app/sketch-3/id852320343?mt=12&uo=4&at=10l8JW&ct=hatenablog

sketch2ももってて使い方はだいたい知ってたんで簡単に使えました。
解像度違いの画像エクスポートがマジで楽。

横画面

指でスワイプできる面積をできるだけ広くしたかったので今回はじめて横画面で製作しました。
Auto Layoutで多デバイス対応するのが本当に骨が折れました...

Auto Layoutで工数かけたくなかったら画面設計時に縦横比に依存しないようにしたり縦横日を固定するよう配慮すべきと学びました。

工数

正月の宿題といいながら、休み中にできたのは1人で階段を登るところまでで、その他の実装、素材作り、サーバ作成込み込みで1月弱くらいかかったです。

今後の展望

いろいろ機能追加します。くだらないことを。

つくってみて

  • 軽い感じで設定した宿題のはずが、Auto Layoutやったり課金やったりサーバつくったりでわりと濃密になりました
  • 構想していたゲームがなかなかの再現度で実現できて嬉しいです
  • 一言で紹介できるアプリって覚えてもらいやすかったりいろいろ楽です

おまけ: 楽しいお階段登りの使い方

一番のヘビーユーザー(私)がお教えしましょう。

ブラインド登り

慣れると画面を見ずとも軽快に階段を登れるので、何かをしながらでもお階段登りができます。

リズムをとる

鈴、拍子木、木魚(7777段登ると使用可能)などの足音はリズムが取りやすいです。
加えて、地味にiPodの音楽鳴らしながらアプリがつかえるので、好きな音楽を聴きながら好きなリズムを奏でましょう。
1曲流すだけで数百段登れます。

木魚を操るコツは木魚6回→鐘1回のサイクルなので「ポク・ポク・ポク・ポク・ポク・ポク・ゴーン(2拍)」のリズムで奏でるのが良いでしょう

催眠効果

BGMを消して一定のリズムで階段を登ると超眠くなります。
私は何度か寝落ちしました。

最後に

ぜひやってみてレビュー欄荒らしてください!
https://itunes.apple.com/jp/app/o-jie-duan-dengri-zuo-rino/id956171342?mt=8&uo=4&at=10l8JW&ct=hatenablog

Node.js + MongoDBでAPIサーバつくるまでに勉強したこと

前回の記事の補足記事です。

Node.js(restify) + MongoDB(mongoose) でAPIサーバつくってHerokuでデプロイするまで - イッツァハローワールド

勉強する前のレベル

カスレベル

  • Node.jsは昔ドットインストールしたことあったっけなってくらいの知識(概要は少し覚えてた)
  • JavaScript自体あんまり使わない
  • MongoDBってなに?マンゴー?

Node.jsの知識

基礎はググるよりも書籍で学ぶ派なので、とりあえず一冊買いました。
わかりやすく説明されているのでオススメです。
少し古い本なので、情報が古かったりしたら都度ググりました。
あと、索引が少なめなのがちと痛い。。。

ドットインストールは主に書籍の補助で使いました。

Expressの知識

今回はExpressではなくrestifyを使っているのですが、Expressは情報が多くAPIサーバ開発のイロハを覚えられました。
上記書籍にExpressの解説もあります。


こちらも書籍の補助で使いました。


ドットインストールの説明がExpress 3.xなので、4.xでやる場合のやり方を参照しました。アリシャス!

restifyの知識

restifyは正確なREST Webサービスを構築に特化したNode.jsのモジュールで、
Expressとはちがいtemplateやレンダリングな機能はない代わりに「厳格な」APIサービスを構築できるようになっているとのこと。でもExpress勉強すればだいたい似た要領なのでわかるはず。

HugeDomains.com
公式のドキュメントです。超英語。

mongoDBの知識


ちなみに書籍は買ってません。
概要をドットインストールで学んだ程度です。

mongooseの知識

mongooseはNode.jsを使ってmongoDBにアクセスするためのライブラリです。
わりとググれば情報がでてきます。

Mongoose v6.2.3: SchemaTypes
公式ドキュメント。こちらも超英語。
こちらもわかんない時に参照する程度です。

勉強の進め方

  1. 書籍で概要理解+サンプルコードひたすら書いて動くものを作る
  2. ドットインストールで補習授業+動くものを作る
  3. やりたいことをとりあえずやってみる。
  4. つまづいたらググったり公式ドキュメント読んだり書籍開いたり
  5. 手順3,4の繰り返し

まとめ

いつもの勉強法晒しただけですね...

Node.js(restify) + MongoDB(mongoose) でAPIサーバつくってHerokuでデプロイするまで

先日ようやくリリースしたアプリ「お階段登り」では、ただ階段を上るだけのクソアプリと思いきや、他のユーザーの位置まで来たらすれ違ってみたり、一番上にいる人の段数をとってきたり、影でコソコソやっております。(クソなのには変わりませんが。)
https://itunes.apple.com/jp/app/o-jie-duan-dengri-zuo-rino/id956171342?mt=8&uo=4&at=10l8JW&ct=hatenablog


コソコソやるためにサーバーを自作しました。

サーバー側でやってること例

  • ユーザー登録
  • 自分のスコアを更新する
  • 1位のスコアを取得する
  • 自分が1位かどうか返す
  • 自分のスコアに一番近いユーザの情報を返す(→ユーザのスコアまで到達したらすれ違い実行)

アプリからはHTTPリクエスト投げ、JSON形式でレスを取得してレスによって描画を変えたりします。
こんなAPIサーバー的なものをNode.jsをつかって実装しました。

備忘録でサーバー作成からHerokuへのデプロイまでやり方を残します。

環境

  • Mac OS 10.10.2
  • Node.js v0.10.25
  • MongoDB 2.6.7

事前に勉強したこと

本件の記事と直接関係ないので、別記事にします。
↓こちら

Node.js + MongoDBでAPIサーバつくるまでに勉強したこと - イッツァハローワールド

なんでわざわざRestifyつかったか。

ググったら「REST Webサービスの構築に特化したフレームワーク」って出てきたのでおおそうかつかってみよ!って感じで使いました。
今回つくるような簡単なもんならExpressで十分に思えます。(作った後でわかりましたw)

あと、英語のドキュメントで四苦八苦してみたかったっていうのもちょこっとあります。

今回作るものの仕様

  • ユーザーごとに以下のデータを持つ
    • ユーザーID(id)
    • スコア(score)
  • サーバーはAPICRUDな動きをする
  • レスポンスはJSONで返す

あとは適当に。

まずはRestifyをつかって簡単なサンプルコードをつくる

Node.js入れるところとかは割愛します。

restifyをインストール

$ npm install restify

サンプルコード実装

以下は、http://0.0.0.0:8080/hello_worldにアクセスするとmessage : Hello World!って内容のJSONを返す例。

// sample.js
var restify = require('restify');

// サーバー生成
var server = restify.createServer();

// http://0.0.0.0:8080/hello_worldにGETリクエストしたときの処理
function helloWorld(req, res, next){
	// レスポンス
	res.send({message: 'Hello World!'});
}

// パスと関数の紐付け
// ↓はhttp://0.0.0.0:8080/hello_worldにGETリクエストしたらhelloWorld関数を実行
server.get('/hello_world', helloWorld);

server.listen((8080), function() {
	console.log('%s listening at %s', server.name, server.url);
});

実行

コマンド叩いて実行。

$ node sample.js

http://0.0.0.0:8080/hello_worldを開いてみる

f:id:hanamiju:20150305020831p:plain

できた。こんなかんじで、APIごとに関数と、関数とパスとの紐付けを定義すればわりと簡単に作れます。
以下はmongooseを落として、MongoDBをつかってCRUDします。

実際のAPIサーバ実装やってみる

まずMongoDB, mongoose入れる

$ npm install mongodb
$ npm install mongoose

コード

// sample.js
var restify = require('restify');
var server = restify.createServer();	// サーバー生成
server.use(restify.bodyParser());	// POSTデータをパースするひとを登録
server.use(restify.queryParser());	// クエリをパースするひとを登録

var mongoose = require('mongoose');
var uristring = 'mongodb://localhost/';

var db = mongoose.connect(uristring);
var Schema = mongoose.Schema;

// スキーマの定義
var userSchema = new Schema({
	score: Number,
});

// モデルを生成
mongoose.model('user', userSchema);
var User = mongoose.model('user');

// Create
function createUser(req, res, next){
	var user = new User();	// コレクションを作成
	user.score = 0;
	// つくったコレクションを保存
	user.save(function(err, data){
		res.send({function:'Create', status: 'OK', id: data._id});
	});
}

// Read
function getUsers(req, res, next){
	User.find(function(arr, data){
	// すべてのコレクションの情報を返す
		res.send(data);
	});
}

// Delete
function deleteUser(req, res, next){
	// 入力されたIDのコレクションを削除する
	User.remove({ _id: req.params.id }, function(err){
		res.send({function:'Delete', status: 'OK'})
	});
	return next();
}

function updateScore(req, res, next){
	// IDを検索
	User.findById(req.params.id, function (err, data) {
		// エラー処理
		if (err) {
			res.send({function:'Update', status:'NG', reason: 'User not found.'});
			return next();
		};
		// 入力パラメータ異常
		if (req.params.id == '' || req.params.id === undefined ) {
			res.send({function:'Update', status:'NG', reason: 'User not found.'});
			return next();
		};
		if (req.params.score == '' || req.params.score == 0) {
			res.send({function:'Update', status:'NG', reason: 'Score is invalid.'});
			return next();
		};
		// 入力されたIDのスコアを入力されたものに変更する
		User.update({ _id: req.params.id }, { $set: {score: req.params.score} },
		{ upsert: false, multi: true }, function(err) {
			if (err) {
				res.send({function:'Update', status:'NG', reason: 'Update faild'});
				return next();
			};
			res.send({function:'Update', status:'OK'});
		});
	});
}

server.get('/get_users', getUsers);
server.post('/delete_user', deleteUser);
server.put('/update_score',updateScore);
server.post('/create_user',createUser);

server.listen((8080), function() {
	console.log('%s listening at %s', server.name, server.url);
});

動作確認

実際に各API呼んだらどんなレスポンスを返すか見てみます。
動作を見るためにChromeのエクステンションのAdvanced REST Clientをつかいます。

create(create_user)

まずはコレクションを生成してみます。
Advanced REST Clientを起動し、Reqestタブを開きます。
POSTを選択し、パスを入力します。
f:id:hanamiju:20150305221011p:plain

入力したら「Send」ボタンを押下するとレスポンスが帰ってきます。
f:id:hanamiju:20150305221058p:plain

これでひとつコレクションができました。

get(get_users)

つぎに生成したコレクションを取得します。
こんどはGETを選択します。
f:id:hanamiju:20150305221224p:plain

「Send」ボタン押下で、先ほど作ったコレクションが取得できます。
f:id:hanamiju:20150305221253p:plain

update(update_score)

コレクションのscoreを更新します。
こんどはPUTを選択し、更新するid, 更新後のscoreを入力します。
ためしに1234を入れてみます。
f:id:hanamiju:20150305221640p:plain

「Send」ボタン押下で、更新完了が帰ってきます。
f:id:hanamiju:20150305221724p:plain

再度GETしてスコアを見てみます。
f:id:hanamiju:20150305221758p:plain
更新されました。

入力エラー用のコードも書いてみたので動作を確認してみます。
ためしに、idを存在しない値に書き換えて「Send」ボタンを押下すると
f:id:hanamiju:20150305221852p:plain
エラーが帰ってきました。

delete(delete_user)

つくったコレクションの削除を行います。
POSTを選択して、削除したいコレクションのidを入力します。
f:id:hanamiju:20150305222018p:plain

「Send」ボタン押下でコレクションが削除されます。
f:id:hanamiju:20150305222121p:plain

GETして確かめてみます。
f:id:hanamiju:20150305222233p:plain
DBが空になってました。

やった!

Herokuへデプロイしてみる

ここまでで実装はほぼ終わり。Herokuへデプロイしてみます。

Heroku toolbeld(Heroku推奨)のインストール


Herokuにログイン

$ heroku login

Herokuへ登録した時のメールアドレスとパスワードを聞かれるので入力します。

package.json作成

適当に入力します。

$ npm init

今回作成したのはこんな感じです。

{
  "name": "sample",
  "version": "0.0.1",
  "description": "sample api server",
  "main": "sample.js",
  "dependencies": {
    "mongodb": "~1.4.33",
    "mongoose": "~3.8.24",
    "restify": "~2.8.5"
  },
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "hanamiju",
  "license": "MIT"
}

Heroku用のProcfileを作成

web: node sample

こやつにHerokuから呼ぶjsファイルを記載します。拡張子はいらないです。

Herokuのプロジェクト作成

プロジェクトを生成する。今回はapiserverkunnという名前で登録しました。

$ heroku create apiserverkunn

MongoLabをいれる

先ほどまではローカル環境にMongoDBを構築してましたが、デプロイにあたりMongoLabを入れます。
MongoLabはHerokuのアドオンのひとつで、HerokuでMongoDBを496MBまで無料で使えます。最高。
というわけでアドオンを探します。Mongoとか検索ワードで入れればでてきます。
f:id:hanamiju:20150305212133p:plain

こいつのfreeプランをapiserverkunnに適用させます。
上記のようにブラウザからでもいいし、

$ heroku addons:add mongolab

でもオーライ。

sample.jsファイルを修正する。

sample.jsはこのままだとローカル環境でしか動かないので、ファイルを一部修正します。
まずはMongoDBとの接続時のパス

var uristring = 'mongodb://localhost/';  // 変更前
var uristring = process.env.MONGOLAB_URI || 'mongodb://localhost/'; // 変更後

これで、process.env.MONGOLAB_URIに接続し、存在しない場合はmongodb://localhost/に接続される。
つまり、ローカルと本番でコードを改変せずに済むということ。

つぎにサーバー処理開始時

server.listen((8080), function() {    // 変更前
server.listen((process.env.PORT || 8080), function() {    // 変更後

簡単に実行環境毎の設定でアプリケーションを実行することができます。

Herokuへデプロイ

$ git init
$ git add .
$ git commit -m "hoge"
$ git push heroku master

結果を見てみる

あらかじめcreateしといてから、getしてレスポンスを見てみます。
f:id:hanamiju:20150305223334p:plain

ちゃんと表示されました。ばんざい!

まとめ

  • Node.js (restify) つかえばRESTなAPIサーバーが簡単に作れる。
  • Herokuがあれば簡単にデプロイできる
  • MongoLabつかえば、タダで大容量のDB使える。

→ 簡単なCRUDサーバーならタダで簡単に作れる。