Codeigniterでヘッダの扱いでハマった話

仕事でネイティブアプリと通信するAPIのサーバサイド実装をしています。

アプリの強制アップデート機能を実装するために、HTTPヘッダに"App-Version"という項目を追加して、Inputクラスのget_request_headerで取得しようとしました。 最初にやってみた実装は下記の通り。

$this->input->get_request_header('App-Version');

で、取得できませんでした。

とりあえず、出力させてみようということで、下記を実行してみました。

log_message('debug', var_export($this->input->request_headers(), true));

で、ログを確認すると、キーがApp-versionになってました。

$this->input->get_request_header('App-version');

にしたら、無事、取得出来ました。


request_headersメソッド内のコメントを見ると、

// take SOME_HEADER and turn it into Some-Header

ってかいてあったから、versionの頭も大文字になるものだとばっかり。。。

CodeIgniterのバージョンは、2.1.4です。

最新のバージョンでどうなってるかはわかんないですが、こうゆうことが、ちらほら出てくるので、CodeIgniterはあんまり好きになれないんですよねー。。。

社内勉強会を開催しました

10/9の20時より、サーバ・インフラ社内勉強会を開催しました。
(自分が面接官をやらないといけなかった関係で、こんな時間になってしまいました)

参加者は下記のような感じ。

  • 中堅Android・サーバエンジニア
  • 中堅サーバ・インフラエンジニア(鹿児島からリモート!)
  • 若手エンジニア
  • 若手ビジネスプロデューサー(いわゆる営業)
  • 中堅?フロントエンドエンジニア(最後の数分のみ参加)

スライドはこちら

結構初歩的なことしか喋ってないですが、サーバやインフラのことを理解してない人には勉強になったんじゃないかな、と思います。
中堅の人にはツッコミを入れてもらい、私も勉強になりましたし。
「勉強会開いて下さい!」って言ってきたフロントエンドエンジニアが参加できなかったのは残念でしたがw
鹿児島からも発表してもらって、自分も勉強出来ました。

続くかも、って書いてますが、社外への常駐が増えそうなので、難しいかなーと思います。
まぁ、機会があればまたやってみたいと思います。

SlideViewerをリリースしました

本日、SlideViewerというAndroidアプリをリリースしました。

SlideViewer

とりあえず、Speaker Deckのクライアントアプリです。

GitHub にコードは一通り置いてあります。

Speaker Deckのスライドページを開く際に、SlideViewerアプリが選択できます。
APIなどが提供されていなかったので、スクレイピングしています。
そのため、初回の読み込みは遅いのですが、スライド表示中にある程度先読みするため、読んでる間にストレスは溜まりづらいかと思います。
何より、標準ブラウザで見た時は大きさが中途半端になってしまうのが、幅いっぱいに表示されます。

ただ、最低限必要な機能は実装したのでリリースしましたが、もうちょっと改修する予定です。
画面回転に対応できてなかったり、改めて見ると、残念なところが多いですね。。。
で、そこら辺も完了したら、Slide Shareとか、他?も閲覧できるようにしたいと思います。どこまでモチベーションが持つかが勝負ですが。

そもそも、一番の問題は、スクレイピングしていることを怒られないか、という点ですが。

Integerを"=="で比較してはいけない

追記:Javaの話です。

public class Main {
    public static void main(String args[]) throws Exception {
        Integer i1 = 1;
        Integer i2 = get1();
        if (i1 == i2) {
            System.out.println("equal");
        } else {
            System.out.println("not equal!!");
        }
    }
    public static Integer get1() {
        // DBとかから取ってきたつもり
        return new Integer(1);
    }
}

は、"not equal!!"と言われてしまいます。
(ただし、コンパイラや実行環境に依存するかもしれません。)

Integerはプリミティブ型ではないので、==では、同一のインスタンスかどうかを確認してしまっているんですね。(たぶん

普通、比較といえば、値の同一性を知りたいと思うので、

public class Main {
    public static void main(String args[]) throws Exception {
        Integer i1 = 1;
        Integer i2 = get1();
        if (i1.equals(i2)) { // ←ここを変えた
            System.out.println("equal!!");
        } else {
            System.out.println("not equal!!");
        }
    }
    public static Integer get1() {
        // DBとかから取ってきたつもり
        return new Integer(1);
    }
}

まぁ、こうしますよね。

ただ、上記の場合は i1がnullの場合はNullPointerExceptionが発生します。当たり前ですね。
なので、 i1 が必ずnullでは無い場合にしか使えないです。
nullの場合、一致とみなすか、不一致とみなすかは文脈によるかと思いますので、割愛します。
org.apache.commons.lang.ObjectUtils.equals を利用するのも手ですね。

8/10追記 java.util.Objects.equals ってのがJava7からあるみたいです。(コメントで教えていただきました。)

ちゃんと考えれば当たり前なんですが、誰かの書いたコードで、ぼんやり読んでるとハマりますね。(というか、ハマりました。

Travis CIでAndroidアプリをビルドしてDeployGateに置く

継続的インテグレーションAndroidでもしたい!ということで、

ここらへんを使い、

  1. GitHub(Publicリポジトリ)にPUSH
  2. Travis CIが動いてapk作成
  3. apkをDeployGateにアップロード
  4. DeployGateアプリが入っている端末に通知が行く

ということを実現したいと思います。

対象は、現在全リファクタリング中のこちら。

https://github.com/noboru-i/kyouen-android/tree/renew

.travis.yml の設定

language: android
env:
  global:
    # deploygate
    - DEPLOYGATE_USER=noboru-i
    - secure: "SOev0rrhdJ6fp5CkJeQAh6mm6EmmWEWCVrSt7hn3mvjSbaz/yKdmiGsHp8NcTeLZrXuk6ay6mFee3TVqsT17Xn5+cGG5nnWFPqI2Os3AlSWdHkhLrv8f3KdfwJeTiBSymw4TDWSd/kVCB42q51BdnODda+GozCymA6qoZpf/GQs="
    # secret_strings.xml
    - secure: "ZXnouETJ67n/DCjCFPdWoIm3eo0WsYhfuy9Y25IjYn6gij4F8v038bjYr+Z5QBsnLri1mU0QQeNy14cPaaYBoblTsmPm0cZ+Vv0wmYgAkVc2C+E67k/YUAD0puvUzUGsQ1z6N7gQ0tFjL4l4map/4iEkGtney6OYdyuljE75D2g="
    - secure: "Sqi+dwZcd6qGpBLYUq+deqQBSX056N0eoQ9RfMjWB7gG/WfQn4XQaTF13jKdqp0MKzRj8P61uunoxZIpHNYNWeqzxfIpXKrzOdGv85r8PnbjDgH87N22ufqwPdbfAVydUEPP8hEEJ5vbCN2Fyoq1CHPNjwBpMIiD27LMVjiytn8="
    - secure: "iUO0ldYPT3jYjoRafH0Milc3DNhgqzZQGk7tFkvg7WPF+YPEYDOMjwmhY0kTDjbxU+iqkHE7WOL66MtJSlvTJjNfs2uWqrXrmgMAXLusbRukscJmL2ZwGhNwBi4tOOl4V1FO8NRqYcdRkkHgJRUXyKAafLrdNePVm7uWUwjl2e0="
    - secure: "PCeJAQ8/XrDWktlTbKtJLCcjhjOfsw/tcPPCrG1urvQMJrgzi6m6pTWo62RSpWVo1iYePiqvDgPGACrT082vtnoJ7TJ3FZEhty1xsDUhDBJ9qvmHEPpxLrtLGwZ1+doT/yxinQAqqucx8vsFJcTh8zOrGLknwEvqxhNj5HHKzkg="
    - secure: "MyGGNdkyv+qEf8yhORcSzjsHbDjT2yILka/aMYgInt0oyF3T6O6JwwRvrXHEulPVHWsWu4LCxX8e3HeyyQ0OkIjc67nwIsajCDwDlVUcnkjqyV3r9xPn0HlltwvS63QuQGln6NQIUBk9nVIuaA+vno7bod1Px0VESu9kDgUiqgI="
notifications:
  email: false
android:
  components:
    - build-tools-19.1.0
    - android-19
    - sysimg-19
    - extra-android-support
  licenses:
    - android-sdk-license-5be876d5
    - '.*intel.+'
before_install:
  - android list sdk --no-ui --all --extended
script:
  - ./gradlew uploadDeployGateDebug

env.globalには下記を記載しています。

  • deploygateの設定
  • secret_strings.xmltwitterのsecret tokenなど)の設定

作成については、下記のコマンドで。

travis encrypt GCM_SENDER_ID=9999999999999

これを、後述のgradle taskでxmlに書き込みます。

Projectのbuild.gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.12.+'
        // deploygate
        classpath 'com.deploygate:gradle:0.6'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

DeployGateのgradle pluginをここで設定しています。

アプリのbuild.gradle

apply plugin: 'com.android.application'
apply plugin: 'deploygate'

android {
    // Error:duplicate files during packaging of APK /XXX/app-debug-unaligned.apk 対策
    packagingOptions {
        exclude 'META-INF/license.txt'
        exclude 'META-INF/notice.txt'
    }

    compileSdkVersion 19
    buildToolsVersion "19.1.0"

    defaultConfig {
        applicationId "hm.orz.chaos114.android.tumekyouen"
        minSdkVersion 12
        targetSdkVersion 19
        versionCode 18
        versionName "0.10.0"
    }
    buildTypes {
        release {
            runProguard false
//            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    lintOptions {
        abortOnError false
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:support-v4:19.0.+'
    compile 'com.android.support:appcompat-v7:19.+'

    // deploygate
    compile 'com.deploygate:sdk:3.1'
}
apt {
    arguments {
        androidManifestFile variant.processResources.manifestFile
        resourcePackageName 'hm.orz.chaos114.android.tumekyouen'
    }
}

deploygate {
    userName = "$System.env.DEPLOYGATE_USER"
    token = "$System.env.DEPLOYGATE_TOKEN"

    apks {
        debug {
            sourceFile = file("${project.buildDir}/outputs/apk/app-debug-unaligned.apk")
        }
    }
}

uploadDeployGateDebug.dependsOn assembleDebug

task secretKeyReplace {
    description 'Replace secret keys from system environment.'
    doLast {
        def filePath = "app/src/main/res/values/secret_strings.xml"
        def br = new BufferedReader(new FileReader(filePath))
        def lines = br.readLines()
        br.close()

        // key情報の抽出
        def secretKeys = new HashSet()
        def rExpression = /INPUT_YOUR_([A-Z_]+)/
        for (String line : lines) {
            if (line.contains("INPUT_YOUR_")) {
                def matcher = (line =~ rExpression)
                secretKeys << matcher[0][1]
            }
        }

        // 文字列の置換
        def newLines = new ArrayList<String>()
        for (String line : lines) {
            String newLine = line
            for (String key : secretKeys) {
                if (!System.getenv()[key]) {
                    continue
                }
                newLine = newLine.replace("INPUT_YOUR_" + key, System.getenv()[key])
            }
            newLines << newLine
        }

        def pw = new PrintWriter(new BufferedWriter(new FileWriter(filePath)))
        for (def line : newLines) {
            pw.println(line)
        }
        pw.close()
    }
}

preBuild.dependsOn secretKeyReplace

buildToolsVersion は、最新が20.0.0だけど、ymlに書いた android-sdk-license-5be876d5 がまだ対応してないようなので、19.1.0になってます。

deploygateタスクの設定(ユーザ名 / パスワード)は環境変数から取得しています。
環境変数への設定は.travis.ymlに書いてあります。
DEPLOYGATE_TOKEN も、暗号化して書いてあります。

secretKeyReplace taskで、secret_strings.xmlにある、"INPUT_YOUR_XXX"を取得、その"XXX"をキーとして環境変数を取得、replaceして同じファイルを置き換えます。
きっと、もっと良いやり方あると思うんだけど、ググっても出てこなかったので、力技です。
Publicリポジトリにキー情報は置けないけど、Travis CIでapkを作るときには必要。みんなどうやってるんだろう?

まとめ

というわけで、GitにPUSHするとDeployGateでアプリが配布されるところまで出来ました。

Publicリポジトリに置けない認証情報を設定する方法が、あんまりいけてないのはなんとかしたいところ。

あとは、テストコード書いて、リグレッションテストを回していきたいと思います。
Robolectricを使うつもりで書いてるので、まとまったらまた書こうと思います。

CocoaPodsでインストールしたのにヘッダが file not found

何故かpod installしてあるのに、 いろいろな書き方でやってみたけど、どれもヘッダがnot foundになってしまった。

#import "AFNetworking.h"
#import <AFNetworking.h>
#import "AFHTTPRequestOperationManager.h"
#import <AFHTTPRequestOperationManager.h>
#import <AFNetworking/AFHTTPRequestOperationManager.h>

よくよく考えると、とりあえずpod installだけしてあって、その機能を使おうとしたのは初めてだった。

いろいろ探してると、

objective c - iOS - Build fails with CocoaPods cannot find header files - Stack Overflow

こんなのを見つけた。

なので、それを設定しようとして、その画面を開いたら、

f:id:suzaku114:20140511023631p:plain

そんなもん無いんですけどー。。。

で、既にあるプロジェクトファイルといろいろ見比べたけど、それっぽい違いが見つからない。

そもそもどうやったら、ここに表示されるものが出来るのか調べたら、

複数のTarget/Configurationを持つiOSプロジェクトの構成Tips - やらなイカ?

こんなの見つけた。

で、動いているプロジェクトには「Pods.xcconfig」があるのに、今のプロジェクトには無い。

Pods/Pods.xcconfig のものをインポートしているようだったので、同じようにプロジェクトに追加したら動いた!

なんで無くなってたんだろう。。。

Bootstrap3ベースの管理画面を作る

  • ruby 2.1.1
  • Rails 4.1.0
  • bootstrap-sass 3.1.1.1
  • devise 3.2.4

まずは、Railsアプリケーションの作成。

  • irkitというプロジェクト名
  • unit testをスキップ
  • データベースはsqlite3
  • bundle installを行わない
rails new irkit -T -d sqlite3 --skip-bundle
cd irkit

とりあえず、git管理下に置きましょう。

git init
git add .
git commit

Gemfileに下記を追加します。

# 認証処理
gem 'devise', '~> 3.2.4'

# bootstrap
gem 'bootstrap-sass', '~> 3.1.1.1'
gem 'bootstrap-sass-extras', '~> 0.0.6'

vendor/bundle以下にbundle install。

bundle install --path=vendor/bundle

.gitignore に下記を追加します。

/vendor/bundle

また、一旦gitにコミットします。

git add .
git commit

deviseの設定を行います。

rails generate devise:install
rails generate devise user
bundle exec rake db:migrate

rootページが必要なので、controllerを作成します。

rails generate controller welcome index

config/routes.rb にコメントがたくさん書いてあるのを消して、下記のようにします。

Rails.application.routes.draw do

  root 'welcome#index'

  devise_for :users

  get 'welcome/index'
end

app/controllers/application_controller.rb を下記のようにします。

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception

  before_action :authenticate_user!
end

とりあえず、一回サーバを起動してみます。

rails s

http://localhost:3000/ にアクセスして、 http://localhost:3000/users/sign_in にリダイレクトされ、認証画面が出て来れば、とりあえずOKです。

次に、bootstrapの設定です。
app/assets/stylesheets/application.css を application.css.scss にリネームし、下記を追加します。

@import "bootstrap";

app/assets/javascripts/application.js のrequire部分を下記のようにします。

//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require bootstrap
//= require_tree .

bootstrapの設定を行います。
app/views/layouts/application.html.erb がコンフリクトしていると言われますが、y で。

rails g bootstrap:install
rails g bootstrap:layout application fluid

エラーメッセージがぎりぎりに出てくるのが嫌なので、application.css.scss で全体的にずらします。

body {
  padding-top: 70px;
}

というより、deviseの画面でメニューとかが出てるのがおかしいので、layoutを変えます。
app/controllers/application_controller.rb を下記のように変更します。

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception

  before_action :authenticate_user!

  layout :layout_by_resource

  protected

  def layout_by_resource
    if devise_controller?
      "devise"
    else
      "application"
    end
  end
end

app/views/layouts/devise.html.erb を作成します。
基本的には、app/views/layouts/application.html.erb をコピペ。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
    <%= viewport_meta_tag %>
    <title><%= content_for?(:title) ? yield(:title) : "Irkit" %></title>
    <%= csrf_meta_tags %>

    <!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
    <!--[if lt IE 9]>
      <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.6.1/html5shiv.js" type="text/javascript"></script>
      <script src="//cdnjs.cloudflare.com/ajax/libs/respond.js/1.3.0/respond.js" type="text/javascript"></script>
    <![endif]-->

    <%= stylesheet_link_tag "application", :media => "all" %>

    <!-- For third-generation iPad with high-resolution Retina display: -->
    <!-- Size should be 144 x 144 pixels -->
    <%= favicon_link_tag 'apple-touch-icon-144x144-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png', :sizes => '144x144' %>

    <!-- For iPhone with high-resolution Retina display: -->
    <!-- Size should be 114 x 114 pixels -->
    <%= favicon_link_tag 'apple-touch-icon-114x114-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png', :sizes => '114x114' %>

    <!-- For first- and second-generation iPad: -->
    <!-- Size should be 72 x 72 pixels -->
    <%= favicon_link_tag 'apple-touch-icon-72x72-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png', :sizes => '72x72' %>

    <!-- For non-Retina iPhone, iPod Touch, and Android 2.1+ devices: -->
    <!-- Size should be 57 x 57 pixels -->
    <%= favicon_link_tag 'apple-touch-icon-precomposed.png', :rel => 'apple-touch-icon-precomposed', :type => 'image/png' %>

    <!-- For all other devices -->
    <!-- Size should be 32 x 32 pixels -->
    <%= favicon_link_tag 'favicon.ico', :rel => 'shortcut icon' %>
    <%= javascript_include_tag "application" %>
  </head>
  <body>

    <div class="container">
      <%= bootstrap_flash %>
      <%= yield %>

      <footer>
        <p>&copy; Company 2014</p>
      </footer>

    </div> <!-- /container -->

  </body>
</html>

とりあえず、もう一回コミットしておく。

git add .
git commit

deviseのviewをファイルに出力しておきます。

rails generate devise:views

ためしに、app/views/devise/sessions/new.html.erb を下記のように書き換えます。
(基本的に、 http://getbootstrap.com/examples/signin/ のパクリ)

<%= form_for(resource, as: resource_name, url: session_path(resource_name), html: {role: 'form', class: 'form-signin'}) do |f| %>
  <h2>Sign in</h2>
  <%= f.email_field :email, autofocus: true, class: 'form-control', required: true, placeholder: 'Email address' %>
  <%= f.password_field :password, class: 'form-control', required: true, placeholder: 'Password' %>

  <% if devise_mapping.rememberable? -%>
    <div><%= f.check_box :remember_me %> <%= f.label :remember_me %></div>
  <% end -%>

  <div><%= f.submit "Sign in", class: 'btn btn-lg btn-primary btn-block' %></div>

  <%= render "devise/shared/links" %>
<% end %>

なんとなく、それっぽくなります。

f:id:suzaku114:20140505014921p:plain

あと、jsとcssがバラバラにダウンロードされると、ログがウザいので、 config/environments/development.rb を変更します。

config.assets.debug = false

適当なモデルを作成します。

rails g scaffold command name:string json:text
bundle exec rake db:migrate
rails g bootstrap:themed commands

app/views/layouts/application.html.erb のli部分を下記に変更します。
ついでに、"col-md-3"で作成されているサイドバー部分を削除します。

<li><%= link_to "command", commands_path %></li>

あと、ボタンの文字色が変なので、app/assets/stylesheets/scaffolds.css.scss にある下記のaタグの部分を削除します。

a {
  color: #000;
  &:visited {
    color: #666;
  }
  &:hover {
    color: #fff;
    background-color: #000;
  }
}

なんとなくデータを入れると、それっぽく見えます。

f:id:suzaku114:20140505151406p:plain

とりあえず、ここまでの作業時点のタグは下記の通り。 https://github.com/noboru-i/irkit/tree/hatena-20140505

メモ取りながら開発するって、凄い疲れる。。。

Ruby on Rails 4 アプリケーションプログラミング

Ruby on Rails 4 アプリケーションプログラミング

RailsによるアジャイルWebアプリケーション開発 第4版

RailsによるアジャイルWebアプリケーション開発 第4版

既にローカルでgit管理しているものをgithubにpushする

想定している状況

  • とりあえず、ローカルで開発をしていた
  • もちろん、git initして、作業毎にgit commitをしている
  • ブランチは切ってなくて、masterブランチで作業している
  • 一通り実装したので、githubに公開しようと思った
  • githubリポジトリを作ったけど、どうやってpushしていいかわからん
  • githubリポジトリを作った時の、LICENSEやらREADME.mdは残しておきたい*1

手順

github上でSSHのclone URLをコピーしてきます。
例:git@github.com:noboru-i/irkit.git

git管理しているディレクトリにて、下記のコマンドを実行します。

git remote add origin git@github.com:noboru-i/irkit.git
git fetch

これにより、origin/masterとして、github上のmasterブランチを参照できるようになりました。

なので、origin/masterを、ローカルのmasterにリベースします。*2(masterブランチをチェックアウトしている前提)

git rebase origin/master

これで、ローカルのmasterブランチはLICENSEやらREADME.mdが存在している状態になります。

なので、これをpushします。

git push origin master

f:id:suzaku114:20140505154842p:plain

一本道になりました。
一番左のコミットがgithubで自動的にコミットされたもので、それ以降のものがローカルでコミットしていたものです。
時系列はひっくり返ってしまいますが、意味的にはこんな感じでいいんじゃないでしょうか?

ちなみに、mergeしたらこんな感じに、入り口2つになったので、なんか微妙だなーと。

f:id:suzaku114:20140505154025p:plain

もっといいやり方があるような気はします。

余談

マージしてpushしちゃったあとの復旧方法は下記の通りです。

git reset --hard c62d6a3
git co -b o_master
git reset --hard e14b7566ca35c5910dd0769d3bdadb59f1499231
git push -f origin o_master:master
git co master
git b -d o_master
  • c62d6a3:1個前(margeする前)のコミット
  • e14b7566ca35c5910dd0769d3bdadb59f1499231:githubの初期コミット

なにをやっているかというと、下記のような感じ。

  • masterのマージコミットを破棄
  • o_masterブランチにて、元のorigin/master を再現し、強制push
  • masterをチェックアウトしなおし、不要になったo_masterブランチを削除

開発効率をUPする Git逆引き入門

開発効率をUPする Git逆引き入門

  • 作者: 松下雅和,船ヶ山慶,平木聡,土橋林太郎,三上丈晴
  • 出版社/メーカー: シーアンドアール研究所
  • 発売日: 2014/04/09
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログ (4件) を見る

GitHub実践入門 ~Pull Requestによる開発の変革 (WEB+DB PRESS plus)

GitHub実践入門 ~Pull Requestによる開発の変革 (WEB+DB PRESS plus)

*1:別に、LICENSEやらを破棄していいのなら、force pushしちゃえばいいと思う

*2:日本語あってるかな?

Play framework 2でinMemory以外のDBでテストを行う

下記を買って、Play Framework 2を使っています。

テストを書こうと思って、Chapter7のFakeAppをコピーしてやってみました。

で、実行してみたところ、PostGIS関連の特殊カラム関係でエラーになってしまいました。

そのため、inMemoryDatabaseをやめて、実際のPostgreSQLを利用しようと思って、ハマりまくりました。

あと、GlobalSettingsを継承したクラスでDBアクセスしてたので、さらにわけのわからんエラーが出ました。

まず、startAppでFakeApplicationを作成。

@BeforeClass
public static void startApp() throws IOException {
    Map<String, Object> map = new HashMap<>();
    map.put("db.default.url",
            "jdbc:postgresql://127.0.0.1:5432/sample_db");
    map.put("db.default.user", "web_dev");
    map.put("db.default.password", "password");

    app = fakeApplication(map, new GlobalSettings());
    start(app);
    String evolutionContent = FileUtils.readFileToString(app
            .getWrappedApplication().getFile(
                    "conf/evolutions/default/1.sql"));
    String[] splitEvolutionContent = evolutionContent.split("# --- !Ups");
    String[] upsDowns = splitEvolutionContent[1].split("# --- !Downs");
    createDdl = upsDowns[0];
    dropDdl = upsDowns[1];
}

これで、リクエスト時のDBはPostgreSQLに向く。

あと、DDL文を実行し、テーブルを作成。

@Before
public void createCleanDb() {
    final DataSourceConfig dataSourceConfig = new DataSourceConfig(){{
        setDriver("org.postgresql.Driver");
        setUrl("jdbc:postgresql://127.0.0.1:5432/sample_db");
        setUsername("web_dev");
        setPassword("password");
    }};
    ServerConfig serverConfig = new ServerConfig(){{
        setName("test");
        setDataSourceConfig(dataSourceConfig);
        setRegister(false);
        setDdlRun(false);
        setDdlGenerate(false);
        setDefaultServer(false);
    }};

    // transactionで囲み、commitしないと反映されなかった
    EbeanServer ebeanServer = EbeanServerFactory.create(serverConfig);
    ebeanServer.beginTransaction();
    ebeanServer.execute(ebeanServer.createCallableSql(dropDdl));
    ebeanServer.execute(ebeanServer.createCallableSql("commit;"));
    ebeanServer.execute(ebeanServer.createCallableSql(createDdl));
    ebeanServer.execute(ebeanServer.createCallableSql("commit;"));
    ebeanServer.commitTransaction();
}

DROP文→CREATE文をとりあえず投げておけばOKかと思ったら、コメントで書いてあるように、transactionで囲んでそれぞれcommitしないとうまく動かなかった。

もっと普通のやり方があってもいいと思うけど、調べててもなかなか出てこなくて困った。

Play Framework自体あんまり情報が見つからないイメージ。しかも、バージョンごとに結構違ってて困る。

公式を探すしか無い印象。

テストのやり方とか、Ruby on Railsほど整ってない感じ。

とはいえ、Rubyの開発者よりはJavaの開発者の方が調達しやすいとかの関係で、Javaの案件もやらざるを得ない感じ。 (PHPは個人的に好きでない。。。)

Springもそのうちまともにやってみたいとは思うけど、「Play」だったり「Spring」だったり、なんでこんなにググラビリティの悪いものが多いんだろう。。。

chefでデータベースとかユーザとか作りたい

Vagrantで開発環境を共有したりしているのですが、

  • データベースの作成
  • ユーザの作成

とかってどこでやるべきなんでしょう?

「本来ここでやるべき」とか知ってる人がいたら教えて下さい。

とりあえず現状は、MySQLのインストールとかをchefでやっているので、ついでにchefでやってしまおうと思います。

knife cookbook create database-prepare -o site-cookbooks/
vim site-cookbooks/database-prepare/recipes/default.rb

mysql-clientのインストールと、SQL文の実行を記述しておきます。

package 'mysql-client-5.5' do
    action :upgrade
end

package 'libmysqlclient-dev' do
    action :upgrade
end

template '/tmp/mysql-schema.sql' do
    source 'mysql-schema.sql.erb'
end

bash 'apply schema' do
    code "mysql -uroot -h#{node[:database][:host]} #{"-p" + node[:database][:password] if node[:database][:host] != 'localhost'} < /tmp/mysql-schema.sql"
end

templateは下記のように。

<%
host = node[:database][:host]
name = node[:database][:name]
user = node[:database][:user]
password = node[:database][:password]
%>
<% if host == 'localhost' %>
CREATE DATABASE IF NOT EXISTS `<%= name %>` DEFAULT CHARACTER SET utf8;

CREATE DATABASE IF NOT EXISTS `<%= name %>_test` DEFAULT CHARACTER SET utf8;
GRANT ALL PRIVILEGES ON `<%= name %>_test`.* TO <%= user %>@localhost IDENTIFIED BY '<%= password %>';
GRANT ALL PRIVILEGES ON `<%= name %>_test`.* TO <%= user %>@"%" IDENTIFIED BY '<%= password %>';
<% end %>

GRANT ALL PRIVILEGES ON `<%= name %>`.* TO <%= user %>@localhost IDENTIFIED BY '<%= password %>';
GRANT ALL PRIVILEGES ON `<%= name %>`.* TO <%= user %>@"%" IDENTIFIED BY '<%= password %>';
FLUSH PRIVILEGES;

localhost の場合のみ"CREATE DATABASE"を行っているのは、RDSだと勝手に作られるから。 あと、"_test"ってのを作ってるのも、ローカルでのみテストを行うから。 そのへんはプロジェクト・案件によって変わってくるのかなー、と。

で、MySQLはこんな感じで簡単に出来たんだけど、PostgreSQL が面倒だった。

  • "IF EXISTS"的なものが見つからない。
  • 同じようにSQLファイルを用意しても、エラーになってしまった。

というわけで、毎回コマンドを発行することに。

package 'postgresql-client-common' do
    action :upgrade
end

execute "create-role" do
    exists = <<-EOH
        su - postgres -c "psql -c\\"SELECT * FROM pg_user WHERE usename='#{node[:database][:user]}'\\" | grep -c #{node[:database][:user]}"
    EOH
    command <<-EOC
        su - postgres -c "psql -c\\"CREATE ROLE #{node[:database][:user]} WITH LOGIN PASSWORD '#{node[:database][:password]}';\\""
    EOC
    not_if exists 
end

execute "create-database" do
    exists = <<-EOH
        su - postgres -c "psql -c\\"SELECT * FROM pg_database WHERE datname = '#{node[:database][:name]}'\\" | grep -c #{node[:database][:name]}"
    EOH
    command <<-EOC
        su - postgres -c "psql -c\\"CREATE DATABASE #{node[:database][:name]} OWNER #{node[:database][:user]} ENCODING 'UTF8' LC_CTYPE 'en_US.UTF-8' LC_COLLATE 'en_US.UTF-8' TEMPLATE template0;\\""
        su - postgres -c "psql -d #{node[:database][:name]} -f /usr/share/postgresql/9.3/extension/postgis--2.1.2.sql"
    EOC
    not_if exists 
end

ちなみに、Vagrantfileにはパラメータを設定しておく。

chef.json = {
  :database => {
  #  :type => "mysql",
    :type => "postgresql",
    :host => "localhost",
    :name => "sample_db",
    :user => "web",
    :password => "password"
  }
}

入門Chef Solo - Infrastructure as Code

入門Chef Solo - Infrastructure as Code