ぴよログ

↓に移転したのでこっちは更新されません、多分。

Rails5でnamespace以下のxxx_root_pathヘルパーが定義されなかったので回避した

Rails 5.0.0.1を使って開発中、Rails4のころのようにroutes.rbに書いたものが思ったように動かなかったのでメモしておきます。

以下のようなroutes.rbを書いて、ただのルート(/)とadmin配下のルート(/admin/)を定義します。

Rails.application.routes.draw do
  root 'home#index'
  namespace :admin do
    root 'home#index'
  end
end

そうするとViewではroot_pathやadmin_root_pathといったヘルパーメソッドが使えるはずですが、rake routesなり、Routing Errorのページなりで定義済のURLを一覧してみると、、

f:id:xoyip:20160927140002p:plain

このように admin_root_pathがありません。Viewで呼ぶとエラーとなります。

この辺の処理をしているのはactionpack内のActionDispatchあたりです。少し中を追いかけてみたのですが、admin_root_pathのときに想定しているところを通っていないことはわかったものの、根本的な原因を突き止めるには時間がかかりそうだったのでやめて回避策に逃げました。

要はそのrouteの名前が付けばいいので、admin配下のrootところにas: :rootと書いてあげればOK。

namespace :admin do
  root 'home#index', as: :root
end

これでadmin_root_pathが使えるようになりました。

f:id:xoyip:20160927140016p:plain

Hello, RxSwift #iOSDC

2016/08/20に開催されたiOSDCに参加してきた。浜松市からの移動なので前日はソニックガーデンの自由が丘ワークプレイスに宿泊しての参加。自由が丘→練馬は乗り換え無しで行ける奇跡の立地。楽しく参加できた。スタッフも大勢いた。開催ありがとうございました。

色々感想があるのでそれは別途書くかもしれない。今日は色々聞いた中でもishkawaさんのRxSwiftの発表を見てRxSwiftに興味を持ったことと、会場で質問もさせてもらったのでその部分を試しがてらちょっと検証してみようと思ったので半年ぶりぐらいに投稿する。

今回は発表スライドにもあった例を使ってみることにする。複数のテキストフィールドとボタンがあって、ボタンを押したら2つのフィールドの値を結合してラベルに表示するというもの。

http://blog.ishkawa.org/talks/2016-08-20-iosdc/#/19

まずはこの例をそのまま実装することにした。なお、今回はRxSwift 2.6.0を使用した。

import UIKit
import RxSwift
import RxCocoa

class ViewController: UIViewController {
    @IBOutlet weak var textField1: UITextField!
    @IBOutlet weak var textField2: UITextField!
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var button: UIButton!
    
    let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        Observable
            .combineLatest(textField1.rx_text, textField2.rx_text) { "\($0) \($1)" }
            .sample(button.rx_tap)
            .bindTo(label.rx_text)
            .addDisposableTo(disposeBag)
    }
}

動かしてみるともちろん想定した通りに動く。

このようなコードのときに、「combineLatestに渡すclosureはUITextFiledのテキストが変わる度に毎に呼ばれてしまうのでパフォーマンス的によろしくないこともありますよね?」という点を質問した。それに対するishkawaさんの回答はざっくり要約すると「多分毎回呼ばれるのでそういうこともあるでしょう、でもRxSwiftで制限する仕組みもあるはずです」という感じだった。

ということで2点を試してみようと思う。

まずはクロージャはテキスト変更の度に毎回呼ばれるか、という点。適当にprintして試したところ、次のようになった。

https://gyazo.com/b7a0dafa9b66392e3c62fb589d4a560c

やはりクロージャは都度呼ばれているようだ。なのでこの部分で少々重たい処理をしてしまうと操作する人にはもたつくような印象を与えてしまうことになる。

なので次にここを改善する方法を考えてみる。combineLatestしたストリームをsampleすることで最新の1つを使うことにしているところの順序を逆にすることで、テキスト変更の度にcombineLatestクロージャが呼ばれることがないので、もう少しマシなのかもしれない。つまり、テキストフィールドの更新をsampleして、それらをcombineLatestするといった感じか。

コードにすると、こう。

let field1 = textField1.rx_text.sample(button.rx_tap)
let field2 = textField2.rx_text.sample(button.rx_tap)
Observable
    .combineLatest(field1, field2) {
        print("called")
        return "\($0) \($1)"
    }
    .bindTo(label.rx_text)
    .addDisposableTo(disposeBag)

これならprint("called")のところはボタンをタップしたときにしか呼ばれない。あまり意味のあるサンプルコードではないので適切かどうかもよくわからないが、一応呼ばれる回数は減らせた。

他にもthrottledebounce(throttleの別名)など一定時間内のものを無視するようなオペレーターも存在するけど、今回の例はボタンをタップするその直前に届いているイベントが欲しいので時間で制御するのは適さない。他の要件なら時間制御も良さそうだ。

こういったオペレーターには色々あって、↓のドキュメントに一覧があるので困ったときは読んでみたいと思っている。

ReactiveX - Operators

というわけで、RxSwiftの世界へ一歩足を踏み入れた話でした。

Macでアプリが起動しないときはコンソール.appを見るとわかるかも

先日、仕事中ヘビーに使っている常駐アプリ達が立ち上がらない現象に遭遇した。とても困ったので一旦仕事の手を止めて復旧努めることにした。

といっても起動時に何かエラーがでるわけでもなんでもなくて、普段使っているランチャーAlfredから起動しようとしても何の音沙汰もなく、最初は途方にくれていた。 一旦落ち着いて、じゃあログでも見るかと思い立ったがログを見る方法がわからない。

しばらく調べてコンソール.appで見られるよという記述にあたったことでコンソールの存在を思い出し、ログを確認することができた。

コンソール.appはアプリケーション→ユーティリティの中にある。

コンソールを起動すると色々なシステムログが流れていることがわかる。

僕の場合は起動しないアプリを起動しようとしたときに右側のエリアに新しいログが現れた。 どのアプリで試しても同じようなエラー Service exited with abnormal code: 173 が出るのできっと同じ理由なんだろうなということがわかった。

では、ということでこのエラーメッセージで検索して調べてみたものの、これといった答えがない。 クリーンインストールで直ったよ!っていう人もいたけれど、それはちょっとね。

いろいろ探す中で「AppStoreの何かが期限切れでうんぬん」と言っている人がいた(英語だったし、サイトを忘れたのでうろ覚えです)。

起動しないアプリたちをよく見てみると、こいつらもAppStore経由でインストールしているアプリ達だということがわかったので とりあえず再インストールだろ、とやってみたら見事解決。

ということでした。

一応コンソール.appに助けられたというわけ。何もエラーが出てなかったらコンソールを使ってみたらどうでしょうか。

Railsなどで無料から使えるエラー通知サービス Rollbar

Railsなりスマホなりでアプリケーション作ったらAirbrakeとかBugsnagとかのエラーハンドリング用のサービスを使って不具合を検知できるようにするよね。

この手のサービスはやはり有料のところが多くて、個人用のアプリケーションとしてはコスト的に使いにくい。無料で使えるものでerrbitというairbrakeクローンみたいなものもあるんだけれど、自分でホストするのはそれはそれで面倒なので敬遠してました。

でも先日、自分のRailsアプリでエラーがでてついにログを見るのが面倒になって本気出して探したら無料で使えるのがありました。

それがこのサービス、Rollbar。

rollbar.com

ざっと導入した感じ、十分使えるなという感じがしています。

  • GitHubログインができる
  • 主要なプラットフォームに対応(RailsとかiOSとか、色々ある)
  • deployやslack連携とかもできる(らしい)

ユーザー登録やプロジェクト作成、Railsへの導入とかは書くまでもないぐらいめちゃくちゃ簡単だったので省略します。

ダッシュボードはこんな感じ。洗練されているとは言えないけど、十分!

無料版の制約は

  • 月5000イベント
  • データ保存は30日間

なので、個人使用なら全然問題なさそう。

お試しください。

RailsアプリにiOSクライアントをサッと作る #sgadvent

f:id:xoyip:20151205223720j:plain

この記事はソニックガーデン Advent Calendar 2015 、6日目の記事です。

どうも大野(@xoyip)です。3日目に続いて登場させてもらいます。今日は5日目までとは少し変わって技術的なHow Toの話で、自分用の覚書でもあります。

ちなみに3日目のはこれ。

xoyip.hatenablog.com

ソニックガーデンではお客さまが実現したいサービスを開発するのにRuby on Railsを使っています。PCやスマートフォンのブラウザから利用されるようなサービスを作ってきました。

ところが最近のスマートフォン普及率などから、スマートフォンのネイティブアプリケーションを用意してユーザーに使ってもらいたいというお客さまのご要望も増えてきています。社内でも開発や運用についてどうしていくかの議論が盛んになってきています。

そういう流れから今日はすでに運用しているRailsアプリケーションにiOSクライアントを作るには、という話をサンプルコードで簡単に紹介できればと思います。

今回の事例

Facebookログインを利用した既存のWebサイト(Rails)用のiOSクライアントを作りたい」というケースを考えてみます。

やらなければならないことは

この2つです。

またサンプルコードをGitHubに用意したので、RubyXcodeがあれば実際に動かすことができます。

github.com

github.com

Railsスマートフォン用の認証口を用意する

doorkeeperの導入

まずは外部のクライアントが認証できる仕組みをRails側に用意します。doorkeeperというgemを使うと、超簡単にOAuthプロバイダーの機能をRailsアプリケーションに追加することができます。

https://github.com/doorkeeper-gem/doorkeeper

既存のRailsアプリケーションに対して次のステップを実施すれば簡単に導入可能です。

$ gem 'doorkeeper'
$ bundle
$ rails generate doorkeeper:install
$ rails generate doorkeeper:migration
$ rake db:migrate

あとはroutes.rbに追記すれば大体おわり。

# config/routes.rb

Rails.application.routes.draw do
  # 他のroutes
  use_doorkeeper
end

※設定は上のステップで生成されたconfig/initializers/doorkeeper.rbで。色々あるので説明は省略!

OAuth Applicationの作成

先ほどのセットアップがうまくいっていれば/oauth/applicationsにアクセスして次のような画面を開くことができるようになります。この画面から新しいアプリケーションを作成していきます。

f:id:xoyip:20151205223259p:plain

必要なパラメータを入れます。名前は識別できればなんでもよくて、リダイレクトURIにはiOS側で設定するものと同じものを入れます。今回はintegration-sample-ios://oauth-callback/iosを使うことにしました(スキーマ以外が合っていれば、path などは必要に応じて変えればよいです)。

追記

development環境以外ではURLスキームがhttpsでないとエラーとなります。それを回避するためにはconfig/initializers/doorkeeper.rbforce_ssl_in_redirect_uri falseとしておけば良いです。

追記おわり

f:id:xoyip:20151205223308p:plain

作成するとアプリケーションIDやトークンが発行されます。iOS側で使うので控えておきます。

f:id:xoyip:20151205223320p:plain

APIを用意する

Railsアプリケーション側にJSON APIがなければ作っておきます。今回はiOSからログインできていることを確認するために、ログインしているユーザーの名前やメールアドレスを返すAPIを用意します。

まずAPI用のApplicationController的なものを用意します。

API用のコントローラの全てのアクションでdoorkeeper_authorize!で認証をかけて、ヘルパーメソッドcurrent_userで認証したユーザーを得られるようにしておきます。

# app/controllers/api/api_controller.rb 
class Api::ApiController < ApplicationController
  before_action :doorkeeper_authorize!
  helper_method :current_user

  def current_user
    User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
  end
end

最後に、名前とメールアドレスを返すAPIを定義します。

# app/controllers/api/users_controller.rb  
class Api::UsersController < Api::ApiController
  def show
    @user = current_user
  end
end
# app/views/api/users/show.json.jbuilder
json.extract!(@user, :id, :name, :email)

これをiOSから呼び出せれば連携成功!となるわけです。

iOSクライアントを作る

iOSアプリでは起動時にログイン済みかどうかを判定し、ログイン済みであればAPIを叩いて自分の情報を表示、そうでなければログイン画面を表示という流れになるようなものを作ります。

pod install

ライブラリをいくつか使うのでそれらをインストールします。

# Podfile

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'
use_frameworks!

pod 'Alamofire', '>= 2.0' # APIを呼ぶときに
pod 'SwiftyJSON', '>= 2.3' # APIのレスポンスの処理に
pod 'p2.OAuth2', '~> 2.0.0' # OAuth2の認証に
pod 'KeychainAccess' # トークンの保存に

※iOS9からhttpの通信で叱られるようになります。それだと開発中に困るのでApp Transport Securityの設定でhttpを許すドメインを指定してあげたほうが便利です。

mushikago.com

※p2.OAuth2ライブラリ内のコードに、開こうとしているURLがhttpのときはassertで止まるような記述があります。あまりよろしくないですが開発中はコメントアウトしています。

URLスキームを設定する

p2.OAuth2を使った認証の流れですが、

  1. doorkeeperで取得したIDやトークンを設定する
  2. SafariまたはSafariViewを開いてログインを求める
  3. URLスキームを使ってアプリに戻る

といった感じになっています。doorkeeperで設定したリダイレクトURIは認証後にアプリに戻るために使われます。

URLスキームの設定方法はこんな感じ。

f:id:xoyip:20151205223345p:plain

認証する

認証のところのコード(全体像はサンプルコードを見てもらったほうがよいと思います)。

var oauth2 : OAuth2CodeGrant?

let settings = [
    "client_id": "YOUR_APP_ID",
    "client_secret": "YOUR_APP_SECRET",
    "authorize_uri": "YOUR_SERVER_URL" + "/oauth/authorize",
    "token_uri": "YOUR_SERVER_URL" + "/oauth/token",
    "scope": "",
    "redirect_uris": ["integration-sample-ios://oauth-callback/ios"],
    "keychain": false,
] as OAuth2JSON

// OAuthアプリケーションのトークンなどを使って認証用のオブジェクトを生成
oauth2 = OAuth2CodeGrant(settings: settings)

// アプリ内に埋め込みでSafariViewを出す
oauth2?.authConfig.authorizeEmbedded = true

// 成功時
oauth2?.onAuthorize = { parameters in
    let json = JSON(parameters)
    // トークンを保存
    let keychain = KeychainAccess.Keychain(service: "YOUR_BUNDLE_ID")
    try! keychain.set(json["access_token"].stringValue, key: "access_token")
}

// 失敗時
oauth2?.onFailure = { error in
    print(error)
}

そして最初のビューが表示されたタイミングで、ここで生成したoauth2オブジェクトを使ってログイン画面を表示します。

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    let keychain = KeychainAccess.Keychain(service: "YOUR_BUNDLE_ID")
    // トークンが保存されているかどうかでログイン済みかどうかを判定する
    if let accessToken = try! keychain.get("access_token") {
        // APIを呼ぶ
    } else {
        // contextとして自身(UIViewController)を指定する
        Auth.sharedInstance.oauth2?.authConfig.authorizeContext = self
        // ログイン画面を出す
        Auth.sharedInstance.oauth2?.authorize()
    }
}

最後に、認証完了後、URLスキームでアプリに戻ってきたことをoauth2オブジェクトに教えてあげることで成功時のコールバックonAuthorizeが呼ばれます。

// AppDelegate.swift
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {
    if let path = url.path {
        if path.hasPrefix("/ios") {
            Auth.sharedInstance.oauth2?.handleRedirectURL(url)
            return true
        }
    }
    return false
}

なお今回はoauth2オブジェクトを各所から参照するためにシングルトンを用いています。

APIを使う

ここまでくれば、あとは取得したトークンでAPI呼び出しをするだけです。

ApiClient.sharedInstance.get("/user", parameters: [:], onSuccess: { json in
    let alertController = UIAlertController(title: "ログイン成功", message: "\(json["name"])\nとしてログインしました", preferredStyle: .Alert)
    let defaultAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
    alertController.addAction(defaultAction)
    self.presentViewController(alertController, animated: true, completion: nil)
}, onFailure: { error in
    print(error)
})

jsonにはRailsが返したid、名前、メールアドレスが入っています。アラートで表示(手抜き)してきちんと取得できるのが確認できるはずです。

ちなみにAPIクライアントはこんな感じのコードです。本当のアプリケーションではもっと汎用的に使えるような書き方をしたほうが良いですが、サンプルなのでサッと書いてしまいました。

// ApiClient.swift
class ApiClient: NSObject {
    static let sharedInstance = ApiClient()
    let endpoint : String = "YOUR_SERVER_URL/api"
    
    func get(path: String, parameters: Dictionary<String, String>, onSuccess: (JSON)->Void, onFailure: (NSError)->Void) {
        guard let url = NSURL(string: endpoint + path) else {
            return
        }
       
        let headers = [
            "Authorization": "Bearer \(Auth.sharedInstance.accessToken()!)"
        ]
        
        Alamofire.request(.GET, url, headers: headers, parameters: parameters).responseJSON { response in
            if response.result.isSuccess {
                if let value = response.result.value {
                    onSuccess(JSON(value))
                }
            } else {
                if let error = response.result.error {
                    onFailure(error)
                }
            }
        }
    }
}

これでiOSアプリケーションとRailsの連携ができるようになりました。ライブラリに頼りっきりではありますが、その分連携するまでをサッと作ることができます。アプリケーションそのものに時間を使ったほうがいいですからね。

何回か書いていますが、サンプルコードを見てもらったほうがわかりやすいと思います。

リモートワークを始めたら家から出なくなった話 #sgadvent

この記事はソニックガーデン Advent Calendar 2015 、3日目の記事です。

(いつもは常体メインの適当文体で書いていますが、アドベントカレンダーってことでなんとなく敬体にしてみます。)

どうも大野(@xoyip)です。ここにもTwitterにもFacebookにもどこも書いていませんでしたが、10月からソニックガーデンという会社で働いてます。

ソニックガーデンというと納品のない受託開発をしているとかRuby on Railsで開発しているとか、コードレビューをしっかりするとかリモートワークをしている人が多いとか、色々なイメージがあると思います。今回はその中でもリモートワークにフォーカスして何かを書いてみます。

リモートワーク

ソニックガーデンでは積極的にリモートワークを取り入れています。リモートワークは場所を選ばすに働くということなので、ソニックガーデンのメンバーは必ずしもオフィスで働く必要はありません。僕は静岡県浜松市に住んでいるので渋谷区にあるソニックガーデンのオフィスに通うなんてことはできず、基本的には自宅で仕事をしています。東京近郊のメンバーでも天候や家庭の事情などで出社せずに働くこともあります。

リモートワークについての詳しいことは、ちょうど今日の朝、代表の倉貫がブログを更新したところ(タイムリー!)なのでそちらもご参照ください。

kuranuki.sonicgarden.jp

僕が基本的には自宅で仕事をしているというのが今日の話の前提です。ちなみに、ソニックガーデンで仕事をする前、9月以前も月の半分近くは自宅で仕事をしていました。

リモートワークには様々な利点があり色々なところで見聞きすることもあるかと思います。。通勤にかかる移動時間を節約でき他のことに充てられるし、食事や休憩を妻や子どもと過ごすとか仕事の合間に家事やちょっとした用事をすませることもできます。

そして、仕事環境を自由にカスタマイズすることができます。幸い今住んでいるところでは仕事部屋として一部屋確保できているので、そこを目一杯使えるデスクを導入して広々と仕事をしています。デスクについては1年半ほど前にこのブログに書いたので見てみてください。

xoyip.hatenablog.com

リモートワーカーの一日

僕がどのような一日を過ごしているかをタイムテーブル的に紹介します。

時刻 やったこと
6:45 起床。朝食や家事、子どもの幼稚園の準備を妻と手分けして行う。
8:30 午前仕事開始、社長ラジオ*1を聴く。
11:30 昼食の用意、昼食。
12:30 お客さまとのミーティング。
14:00 次の仕事を始める。
14:30 子どもが帰ってくる。家が少し騒がしくなる。
17:30 子どもがお腹すいたといって機嫌が悪くなる。早めの夕食!
18:00 もう少し仕事をする。
19:00 仕事終わり。
19:30 子どもを寝かしつける。
20:00 自分の時間。

子どもに合わせて仕事とプライベートを調整できているのがわかると思います。

外出していない…!?

ところで上のタイムテーブル、外出した様子はありません。朝起きてから仕事・食事・家事・子育て、全て家の中での行動です。

リモートワーク、特にコワーキングスペースやカフェなどを使わず自宅を仕事場にしている人の場合、外出することなくすべてが済んでしまうというのは移動などの無駄が省けて効率的です。

その反面、外出しないことによって外の世界との交流が少なくなるとか、体を動かさなくなってしまうというデメリットがあります。半分リモートだったのが完全にリモートになったことで、このデメリットの部分が大きいことがわかりました。

まず交流が減るという点ですが、仕事ではRemottyというツールを使って他のメンバーと顔を合わせています。慣れてしまえばコミュニケーションが足りないという感じはあまりありません。特に問題なのは体を動かさなくなることなのではと思っています。

体を動かさないことについて

どのぐらい体を動かしていないかを、1日に歩く歩数という観点でみてみます。

厚労省の健康日本21というサイトに年齢階級別にみた歩数という統計データが掲載されています。このデータによると30代男性は平均的には1日に8866歩 歩いているらしいですね。20年近く前の古いデータではありますが、この20年の間に移動手段が急激に進化したとかはないので20年経った今でもそれほど大きな差はないのではないかなと思います。

参照:健康日本21

さて次はリモートワークをしていて外出無しで過ごすことが多い僕の歩数を見てみましょう。Apple Watchを使って計測している最近の1ヶ月の歩数はこんな感じでした。

この期間の平均値は1日平均3320歩。

3320歩と言うと平均的な歩数8800歩の半分にも満たない数です。実際には体を動かさないのは良くないなと意識して歩いたり走ったりするようにしているのと、休日の買い物やレジャー等で歩数が増えている日があるのとでようやく平均3000歩超えできているのであって、意識せずに過ごしていたとしたら、平均な歩数はおそらく2000歩程度になるのではないかと思います。言い換えると、ランニングして3000歩かよ、です。

2000歩と言うと統計の4分の1以下です。本当に動いていないですね。

ちなみにソニックガーデンの渋谷オフィスへ行ったときは7000歩程度でした。他にも駅への移動や乗り換えなどがあった日を見てみると、6000歩〜8000歩程度の歩数は稼げています。都心部などで電車通勤している人はよく歩いていると言えそうです。体を動かすという意味では適度な通勤は有りなのかもしれません。

対策

体を動かさなくなると身体機能が低下していきます。僕自身の感覚でも筋力や体力の低下を感じつつあります。自宅で仕事をするということは、意識して運動しなければどんどん体が弱っていく可能性がある環境に身を置いているわけです。健康的にリモートワークを続けたいのであれば、意識して歩いたり走ったりスポーツをしたりする時間を確保するしかありません。

僕の場合だと完全リモートになった10月以降、これらを実践しています。

  • ランニング
  • 自転車
  • エアロバイク
  • 散歩

始めた頃は子どもを送り出してから仕事を始めるまでの間に。最近は寒くなってきたので仕事を早めに始めてお昼前に軽く運動するのがいいかなと思って試しています。

ところで、ソニックガーデンではセルフマネジメントで仕事を進められることが一人前の条件とされています(この話は別の誰かがアドベントカレンダーで書いてくれる気がしているのでそちらにお任せしよう)。ソニックガーデンのリモートワーカーには健康面でのセルフマネジメント力も求められるというわけですね。

さて、さきほどは代表の倉貫のブログ記事を紹介しましたが、今年(2015年)の12月半ばには新しい書籍「リモートチームでうまくいく マネジメントの〝常識〟を変える新しいワークスタイル」が出る予定です。ソニックガーデンとリモートワークについての詳しいことはこちらを読んでもらったほうが良いかもしれません。

RailsアプリをHeroku→さくらVPSに移行

Herokuのプランが変わったのでRailsアプリケーションをさくらに移行した。

ここ最近Herokuの料金体系が変わってFree Dynoは18時間までしか動かせないようになったらしい。それにともなってこんなHackをする人まで出てきているようで、Freeでとりあえず動かしたいという人たちに与える影響はそれなりにあるように思える。

ssig33.com - 最悪!意地でも Heroku を無料で使う

そういう僕も家族用に自分で作ったウェブアプリケーションを運用していたんだけど、料金体系が変わる前から自分のさくらVPSに移行したいと思っていたところにこのプラン変更が来たのでこれを機にと思い移行してみることにした。

ちなみに移行したアプリケーションは、

というシンプルなものだった。Herokuで色々なアドオンを使いまくってるぜというわけではなかったのでそんなには手間ではなかったと思う。

移行先は色々な用途のために元々使っていたさくらVPSの1Gプラン。月額900円。

  • CentOS 6.4
  • 2 core
  • 1GBメモリ

Postgresqlのインストール

別のRailsアプリケーションも動かしていたけれどそちらはMySQLを使っていたので、移行先のサーバーにはPostgresqlが入っていなかった。

インストールして初期設定を済ます。

$ wget http://yum.postgresql.org/9.4/redhat/rhel-6-x86_64/pgdg-centos94-9.4-1.noarch.rpm
$ sudo rpm -ivh pgdg-centos94-9.4-1.noarch.rpm
$ sudo yum install -y postgresql94-server postgresql94-devel postgresql94-contrib
$ sudo /etc/rc.d/init.d/postgresql-9.4 initdb
$ sudo /etc/rc.d/init.d/postgresql-9.4 start
$ sudo chkconfig postgresql-9.4 on
$ sudo su - postgres
$ createuser -s hiromasa

rbenv

対象のアプリケーションが2.0.0-p598で動いていたので、対象のマシンのrbenvにもそのバージョンを入れ、bundlerのインストールも済ませておく。bundlerはよく忘れる。

$ rbenv install 2.0.0-p598
$ rbenv local 2.0.0-p598
$ gem i bundler

Passenger

これまで動かしていたRailsアプリケーションは全部Ruby1.9.3系で動いていたが、今回Ruby2系も導入したことにより、Passengerも使い分けなければいけないことになった。

幸い、それを指南してくれる記事があったのでまんまこれと同じようにやったら使い分けることができた。よかった。

y-ken.hatenablog.com

本当に助かりました。ありがたい。

ここまででサーバー側は大体OKになったと思う。

デプロイ

Herokuの恩恵の一つにデプロイの簡単さがあげられる。しばらく自前でのデプロイをしてこなかったため、いざ自分でやろうとするとものすごく面倒だということを思い出した。

Capistrano

2年前にちょちょっと使っていたCapistranoは3系になって色々進化していた。一応3系がベータのときに使っていたことがあり、そのときのデプロイスクリプトも残っていたので、今回のデプロイもそれを参考にすれば楽勝だろうとタカをくくっていたが、変なところで動かなくてハマった。

エラーの内容を残しておくのを忘れたのでうろ覚えでどんな現象だったのかを書いておくと、デプロイ途中で何かをcpするコマンドがあるんだけど、本来は

cp a b c destination/

という風になっていてほしいところを、

cp a
b
c
destination/

と改行が入った状態でコマンドが実行されるようで、bとかcみたいなコマンドはありません、というエラーで終了してしまっていた。

調べても全然わからないので別のデプロイツールを探すことに。

Mina

Capistranoが嫌になったのでRuby Toolboxで他のデプロイツールを探したら、Minaってのを見つけました。

今回やりたかったデプロイプロセスはすごく単純で、

  • ソース持ってくる
  • bundleする
  • db:migrateする
  • asset:precompileする
  • Apacheを再起動する

っていうだけだったので設定はほとんどいらなかった。サーバーの情報やソース管理のURLぐらいかな。

Minaの使い方はREADMEにある通りで、まず、

$ mina init

して設定ファイルを生成し、次にその設定ファイルの一部をこんな風に書き換え、

require 'mina/rbenv'  # コメントを外す

set :user, 'hiromasa'
set :domain, 'vps' # ~/.ssh/configのHostを使うと便利
set :deploy_to, "/home/hiromasa/apps/myappname"
set :repository, 'https://user:password@bitbucket.org/hiromasa/myapp.git'

task :environment do
  invoke :'rbenv:load' # コメントを外す
end

次にセットアップコマンドを叩く。

$ mina setup

これをやるとティレクトリ等が生成され、database.ymlsecrets.ymlが用意される。データベースのパスワードなどはここに書けばいいのでソース管理に含める必要はないということになる。

最後にデプロイコマンドを叩く。

$ mina deploy

capistranoに比べてシンプルに書けた気がする。少なくともcpで変なエラーは起きなかったし。

databaseの移行

Herokuからデータを移行する。

$ heroku pg:backups capture -a myapp
$ curl -o latest.dump `heroku pg:backups public-url -a myapp`

これでPostgresqlのdumpが手に入るので、さくらVPSのデータベースにインポートしてあげれば良い。

# ローカル
$ scp latest.dump vps:~/

# リモート
pg_restore --verbose --clean --no-acl --no-owner -U hiromasa -d mydatabse latest.dump

これで無事動くようになりました。

これで18時間で止まることもなくなったし、日本のサーバーになったのでレスポンスが速くなり快適に使えるようになった。おしまい。

チーム開発実践入門 ~共同作業を円滑に行うツール・メソッド (WEB+DB PRESS plus)

チーム開発実践入門 ~共同作業を円滑に行うツール・メソッド (WEB+DB PRESS plus)