ボクココ

熱海で開発するブログ

Rails の bundler でなぜ --path vendor/bundle を付けるのか

今までずっと謎だった。

bundle install

これだけでいいじゃないか、なんでわざわざ --path を付ける必要があるのか、と。

何が嬉しいか

gem をいじっても他のRailsアプリを汚さない
ctagによる検索が可能になる

ctagの存在を知らなかった。。今まで自分をvimmer だと思っていたのが恥ずかしくなるくらい素晴らしい機能。これはいわばEclipseでいうCtrl + クリックでの移動。
Ruby だししょうがないか、と思って今までメソッドとかGrepで検索してましたw

これをRailsアプリ内に置くことで、Gem内のクラスにも移動できるようになる!

 手順

brew install ctags
vim ~/.zshrc
  alias ctags="`brew --prefix`/bin/ctags”
source ~/.zshrc
ctags --langmap=RUBY:.rb --exclude="*.js"  --exclude=".git*" -R .
bundle install --path vendor/bundle

これで
<CTRL-]> 定義元にジャンプ
<CTRL-T> 戻る
ができるようになる。

しかも、 --path vendor/bundle しているおかげで、Gemの方にも移動ができちゃう!素晴らしい・・・。もしかしてこれってかなり当たり前なことだったのかも。

alpaca-tc/alpaca_tagsってので自動でctagを付けてくれるのがあって、それをやろうと思ったんだけど、

Error detected while processing function alpaca_tags#create_tags#update..alpaca_tags#util#system:
line   10:
E117: Unknown function: vimproc#popen2
E116: Invalid arguments for function 10
E15: Invalid expression: s:Watch.new(vimproc#popen2(command), a:message)

こんなエラーが出たから諦めたw とりあえずは bundle install とか何らかのタイミングで ctags コマンド打つようにすればいいや。

Railsのテスト,デプロイ,ドキュメント生成をBitbucket, Jenkins で行う

今回はJenkinsとBitbucket の連携をします。
Bitbucket はプライベートリポジトリを何個でも作れて、5人までなら無料で使えるという優れもの。少人数開発ならこれを使わない手はないです。 Github Enterprise だとお金かかる部分が浮きます。 さらに! Wiki 機能もあり、今回はここに自動生成したドキュメントを反映できるようにします。

またJenkinsはどっかのリモートに置くとそれだけでお金がかかるし、無料のJenkins ホスティングサービスは柔軟性が無いので使いません。その代わりにしばらくはMacのローカルでJenkinsサーバを立てて運用していきます。

ローカルでJenkinsを立てると、Bitbucketへのフックができなくなるので、git push した瞬間にJenkinsを走らせる、みたいなことはできないのでご注意を。

やりたいこと

Git push したら Jenkins で以下の手順を実施する

  • テストの実行
  • 全て通ったらHerokuにアプリケーションをデプロイ
  • 成功したらそのドキュメントをBitbucket に更新

これにより、毎回デプロイやドキュメント更新をコマンドで打つ必要が無くなり、手間が減る。そして開発により専念することができるようになる。

主要ポイント

細かい設定とかは他のサイトに色々情報が載ってるので、大まかな部分だけ解説。

RSpec によるテスト

基本的にShellでゴリゴリ書いていきます。 Jenkins に rake plugin とかあるけど、ローカルで既に入れてあるし、わざわざまたそのプラグインから入れるのも大げさな気がしたので使わないことにした。

シェルの実行で以下のコマンドを叩きます。

COVERAGE=true AUTODOC=1 bundle exec rake spec

COVERAGEとAUTODOCについてちょっと解説。

カバレッジの出力

Jenkins でテストがどのくらい通ってるのかを一覧で見れます。これは Rails で Jenknsを回すなら是非入れたい所。

  gem 'simplecov', '~> 0.7.1'
  gem 'simplecov-rcov'

spec_helper.rb に色々追加して、Jenkinsにrcov pluginを入れてビルド後の処理でcoverage/rcovを指定すればOK.

自動ドキュメント生成

これは結構すごい。

  gem "autodoc"

spec_helper に設定をちょちょっと書いて、RSpecの itに autodoc: true を指定する。そして specを流すだけで APIのサンプルrequest と responseの .md ファイルができあがる。

doc/api というディレクトリが出来るので、後でこれをBitbucketのwikiに載せる。 toc.md を作ったほうがいい。これは目次を作ってくれるんだけど,autodocはデフォルトでは作らないようになってる。詳細はautodocのドキュメント参照。

Heroku にプッシュ

ここはHerokuのドキュメント見ながら git push heroku master までこぎつける。
自分のアプリではHeroku用の設定(gemfileにrails_12factorを追加したり)する必要があったけど、それもシェルでゴリゴリ書いた。

Wiki にアップ

Bitbucket にはWiki 機能があって、これも一つのGitリポジトリとなっている。そのため、その Wiki を git clone して自動生成した .md ファイルを追加して push するとなんと Bitbucket 上でリンク付きのAPIドキュメントができちゃう!

シェルはこんな感じ

git clone https://{{your account}}@bitbucket.org/hogehoge.git/wiki

# 古いwikiを削除
rm -rf wiki/api

# 新しく作ったwiki をコピー
cp -r doc/* wiki/

cd wiki

git add -A
git commit -a -m "added wiki" || pwd
git push origin master || pwd

cd ..
rm -rf wiki

|| pwd してるのは、そこでexit code が0以外になっちゃうとJenkinsが失敗したと見なして以降の処理をしてくれなっくなってしまうので、それの対応。 もっといいシェルは書けると思うので、参考程度で。。

Bitbucket のWiki のホームを作って、そこに [API Rquest Response](./api/toc.md) のようなリンクを作れば完了。

Doorkeeper を使ったRailsのテストをRequest Spec で作る

今回はちょいとマニアック。

問題

APIのテストはController Specではなく、Request Specに書くべきなのだが、DoorkeeperのサンプルコードはController Specで書かれている。こんな感じのコード

describe Api::V1::ProfilesController do
  describe 'GET #index' do
    let(:token) { double :accessible? => true }

    before do
      controller.stub(:doorkeeper_token) { token }
    end

    it 'responds with 200' do
      get :index, :format => :json
      response.status.should eq(200)
    end
  end
end

何も気にせず、そのまま書くとRequest Specの中でcontrollerを呼んじゃっているので、ちゃんとスタブ化されずに401が返ってきてしまう。 てことでこのスタブを違う方法で利用して動作するようにしないといけない。

解決

指定したコントローラのインスタンスのスタブを作るようにする。

  describe 'show user' do
    let(:path) { '/v1/users/' }
 
   it 'should show user' do
      user = FactoryGirl.create(:user, :valid_mail, :same_pass)
      token = double(accessible?: true, resource_owner_id: user._id.to_s)
      V1::UsersController.any_instance.stub(:doorkeeper_token) { token }

      get path
      res = JSON.parse(response.body)
      response.status.should eq(200)
      expect(res["item"]["email"]).to eq("test1@gmail.com")
    end
  end

ポイントはV1::UsersController.any_instance.stub(:doorkeeper_token)の部分。ここでRequest SpecでもControllerをスタブ化することが出来るようになる。

users/show ではcurrent_user の情報を出すだけのコードになっている。 上記のテストはそのcurrent_user を直前でFactoryGirt.createしたものとしてちゃんと取って来れるかっていうテストになる。

ひとこと

FactoryGirl をちゃんとまともに使うようになったけど、これ便利ね。 ちなみに自分のRails API プロジェクトでは versionist ってgemも使ってる。これのジェネレータがなかなか良い。

ユーザ登録・API 認証の仕組みを Rails で実現する

スマホアプリから会員の新規登録、ログインが両方できるようにAPIを作成中。ようやく自前でアクセストークンを作ってOAuth認証が出来たのでまとめておく。

まず何がしたいか?

  • スマホアプリでAPI認証ができるように、OAuthを自前で作成したい。

-> スマホアプリ側ではユーザ名とパスワードを入力すればトークンが取って来れて、そのトークンで各APIにアクセスすればユーザ固有の情報が取って来れるようになる仕組みを作る。

  • スマホアプリ側でユーザ作成も出来るようにしたい。

-> APIでユーザが作れるようにする。もちろんhttps前提。

環境

gemfile

ruby '2.0.0'
gem 'rails', '4.0.0'

gem 'rails-api'
gem 'active_model_serializers'

gem 'mongoid', '4.0.0.alpha1'
gem "moped", '2.0.0.beta6'

gem 'sorcery'
gem 'rack-cors', :require => 'rack/cors'

gem 'doorkeeper'

認証ではDeviseではなく、Sorcery を。理由は Rails の認証で Devise ではなく Sorcery という選択 - ボクココ

ORM は ActiveRecord ではなく Mongoid を利用。

Sorcery と Doorkeeper のセットアップ

ここら辺はもうマニュアル通りで。前章のGemfile構成なら特に問題なくいけるはず。

rails g mongoid:config
rails g sorcery:install
rails g doorkeeper:install

config/initializers/doorkeeper.rb

doorkeeper で使用するORM と認証方法を指定。今回はdoorkeeperのpassword token で認証するので、以下のような感じでセット。

  orm :mongoid4
  resource_owner_from_credentials do |routes|
    User.authenticate(params[:username], params[:password])
  end

config/application.rb

Moped::BSONが undefined だよっていちいち言ってくるので、以下のように修正

Bundler.require(*Rails.groups)

# @see https://github.com/mongoid/mongoid/issues/3455
Moped::BSON = BSON

API でユーザ作成

通常はHTMLでユーザを作るけど、SorceryならAPIで簡単にユーザ作成できる! app/controllers/users_controller.rb

class UsersController < ApplicationController
  doorkeeper_for :show

  def create
    @user = User.new(user_params)
    if @user.save
      head :created
    else
      head :bad_request
    end
  end

  def show
    render json: current_user
  end

  def user_params
    params.require(:user).permit(:email, :password, :password_confirmation)
  end
end

これで localhost:9000/users にpost で users[email], users[password], users[password_confirmation] を送れば作れる。

Doorkeeper の関門

ここが最大の難所。 まずちょっと解説する。 Doorkeeper で OAuth 認証をする訳だが、Doorkeeper には Application って概念があって、これは例えば MyAppWeb, MyAppAndroid, MyAppiPhone みたいに分けたり、サードパーティAPIを提供したりで Application を作る分ける必要がある。それの id と secret とユーザのid, password をセットで送ることでアクセストークンが取得できる。

ここで問題は、DoorkeeperはこのApplicationの作成をHTMLベースで作ること前提でしか作られていない、という点だ。 doorkeeper のソースを見ると、 app/controllers/doorkeeper/applications_controller.rb あたりに書いてある。同様にViewも提供されているので、普通なら問題なく作れる。

ただ、今回はrails-apirailsアプリを作っているため、レンダリングの仕組みはrequireしていない。もちろんそこで妥協するのも手ではあるが、それではかっこわるすぎる。てことでなんとかAPIでapplicationを作れるようにしなきゃならない。

Doorkeeper 内の applications_controller.rb を再オープン

問題となっているこいつを再定義してやる。具体的には app/controllers/doorkeeper/applications_controller.rb を作成。

module Doorkeeper
  class ApplicationsController < Doorkeeper::ApplicationController
    respond_to :json

    before_filter :authenticate_admin!
    before_filter :set_application, :only => [:show, :edit, :update, :destroy]

    def index
      @applications = Application.all
      render json: @applications
    end

    def create
      @application = Application.new(application_params)
      if @application.save
        render json: {status: "created"}
      else
        render json: {status: "failed"}
      end
    end

 ~~~~~~
  end
end

これで、http://localhost:3000/oauth/applications あてにpost でapplication[name]、application[redirect_uri] を含めて送れば登録できる!

アクセストークン取得

これさえクリアすればゴールは近い。

http://localhost:3000/oauth/token.json へ post で grant_type=password, client_id, client_secret, username, password を入れてやればAccessTokenが取得できる。

取得したアクセストークンで、 http://localhost:3000/users/ にGET で access_tokenパラメータを付けてやれば、 doorkeeper_for を通り抜け、current_user が取って来れるようになる。

あ、ちなみに app/controllers/application_controller.rb はこうする。

class ApplicationController < ActionController::API
  def current_user
    @current_user ||= User.find_by_id(doorkeeper_token.resource_owner_id) if doorkeeper_token
  end
end

この先

まずテストコード書くか。そしてJenkins環境を作る。そんでもってJenkinsからHerokuへ自動デプロイするようにして、 vagrantで作ったvmに cap deploy できるようにして本番デプロイ(AWS or さくらVPS)をできるようにする。

そしたらアプリ固有の実装に突入!!

熱海へ引っ越し。これからは田舎で開発すべき

今日、東京のど真ん中から熱海に引っ越した。 ここで、自分の事業を始めていく。毎回「なんで熱海なの」って聞かれるので、今回はその経緯と理由について書いておく。

熱海でスタートアップをすると決めた経緯

まずは親がここにリゾートを持っていたから、ってのがある。これを言った時点で、ああ金持ちか、って思われるかもしれないけど、今の熱海のリゾートマンションって激安で、150万くらいから買える。車買ってる人の方がよっぽど金持ちだ。 そして月にかかるのは電気代と管理費合わせても3万ちょいくらい。生活するのにめちゃめちゃお得。
後は後述するけど、東京はうるさいしもっとリラックスできる所でプログラミングしたかったってのがある。そういった意味でここでスタートアップをしようと決意した。

熱海にして良いと思った理由

1、温泉に入り放題 リラックスという意味でこれはかなり自分にとって良い点だ。肩までゆっくり毎日温泉に入れる。足や腕をのばしてストレッチが出来る。こんなことを東京でできるだろうか?さらにガス代はかからないし水道代も浮くし、節約にもなる。

2、海に近い これも大きい。自分はよく走るのだけど、朝日を浴びながら海辺を走るのが最高に気持ちいい。その後に温泉はいってからのプログラミングをすると頭が冴えて効率が上がる。

3、余計な予定が入らない 東京にいないというだけで、なかなか会うことができないので、それが逆に集中できる環境を生む。東京ならどっかの勉強会いこっかな、とかどっかのミーティング参加しないとな、とかそんな余計な思いが生まれて本業のプログラミングができない理由がたくさんできてしまう。それらを完全に遮断するにはそこから離れるのがベスト。

東京で始めなくて大丈夫なのか

まぁこの点はプログラミングで食ってる人ならわかると思うけど、同じ場所で働く必要なんてほとんどなくなってきている。会いたければSkype使って話せば良いし、仕事上の成果はGithubで管理すれば良いし、進捗もプロジェクト管理ツールを使えば良いだけだ。その他会社でやらなきゃいけないことなんてもはやない。 むしろ余計なMTGや会話が発生する分、行くだけ無駄。

外部の人など、本当に会わないといけない時はさすがに東京にいないと不便だけど、そういった状況にはまだなっていない。

それでも一緒の場所で働かないのは無理だって思ってる人はこの本を読めば考えが変わるかもしれない。

そんなこんなでこれから生活していきまっす〜。

事業について

既得とそれを打破するには。

f:id:cevid_cpp:20130519125634j:plain

ルールは誰が決めるのか

スタートアップにとって厄介なのがこのルール。法律。 何か奇抜なことをしようとしたら、それに引っかかって出来なくなる。たくさんのルールに縛られた世の中じゃ、新しいことなんてできやしない。

いったいこのルールは誰が決めるのか?

それは先に成功した大企業であったり、お金をもらった政治家だ。 彼らは基本的に保守的。何か新しいものがでたときに、新しいルールを作って自分たちの都合のいいように作り替える。 大企業はお金はあるから、資金そういったことに費やしてベンチャーを潰しにかかる。

ここからいえるのは、既存の業種で新しく始めても、成功しちゃったら叩かれて一発アウトになる確率が高い、ということだ。

典型的な例はホリエモンだ。 誰もがご存知の通り、彼は成功しすぎた。その他大企業に取って、「インターネット」という得体の知れないもので儲けている人間が納得できなかったのだろう、とばっちりを付けて彼を牢屋に投げた。 日興とかそれを上回る悪さをしているのに、叩かれずに今も残ってる。 この事件以降、日本はベンチャーに投資するという文化が薄れていった。若者に投資することが無くなり、FXとかにお金が回るようになる。 そんな国でベンチャーを立ち上げて成功できるのか?国として成長できるのか? これが日本だ。

ルールに縛られないために

こんな国なもんだから、新しく食品業界で!金融業界で!テレビ業界で!って言っても、新しく始めにくい制度が大量に出来て、スタートアップを寄せ付けない構造になっている。

てことは、まだルールができていない所を攻めるしかない。 まだ誰も始めていないこと。ルールが決まりきっていないこと。 ベンチャーの条件はそういう始まりかけた業界で始めるってのがとても大事だ。

うまくいったら、その後自分たちでルールを作ることができる。 大企業はまだ未知の分野に対しては予算に合わないから始めようとしない。そこを突くしかない。

その他の選択肢として、海外に行く、という選択肢もある。特に新興国はこれからインターネットやその他家電製品などを使おうとしている所。 彼らは最初に使うものを選ばないといけない。そこに勝負をかける。

日本で老人が長い間使ってきたメーカーやWebサイトなどは滅多に変えることはない。お金は持ってるから、他の安いのとかどうこうの問題でもない。そんな老人大国の日本でやるより、よっぽど新興国でやったほうがうまく行く確率は高い。 変化を与えやすいってこと。

時代は常に変わっている。技術や政治、市場の変化。 この変化のズレで生じる、新しいニーズ。まだ出来たてだから、大企業が予算をつぎ込んでこないところ。その隙間を縫ってベンチャーを始める。うまくいったら自分でルールを作る。 歴史はこうして繰り返される。

ストイック Androidアプリでの広告周りのお話

広告系をかなりいじった。 app-c.net っていうのがあって無料でPRしてくれるかわりに、SDKを組み込んで広告入れてくれって感じでメールが来たのでそれを入れてみた。 要望したPRが掲載されないなーと思って運営に連絡したら、「このアプリは品位を下げるものと判断しました」といわれ、PRされないはめに。 ただでさえAdmobの方が収益たくさん入っているのに、PRしてくれないなら入れる意味ナッシング。アプリ内課金とか自分で実装しちゃってるし、APIでデータ連携するのもPaaS使えばすぐ作れるしってのでBaaS使う理由もない。てことでAdmobに戻した。

Adbobにinterstitial ad ってのがあって、これは全画面に出てくる広告らしい。これはクリック率が高いから適切なタイミングで出せばユーザはクリックしてくれるものと思われる。てことで導入してみた。

f:id:cevid_cpp:20140327225303p:plain

実装詳細はこちら。 https://developers.google.com/mobile-ads-sdk/docs/admob/advanced?hl=ja#android

ここで一点落とし穴があって、AdListener の onDismissScreen() で広告閉じ終わった後に次の処理をさせようと思って処理を書いたのだが、たまーに広告自体がでないことがある。ネットワーク接続なければ必ずでない。 そうなると広告でなかったときに次のアクションが呼び出せなくなってしまう事態が発生する。

それを防ぐために、このリスナで次の処理を待たせるのではなく、show()で開くと同時に次のアクションも実行するようにした。 ユーザとしては次のアクションをやっている最中に広告が出るというウザい状態になるので、このテクニックは一部の場合に限定すべき。

広告は増えたけど、その分面白い機能を実装したので、広告増えた分はユーザに許してもらえないかなぁと思いつつリリースした今日だった。