yo_waka's blog

418 I'm a teapot

ActiveRecordのマイグレーションでMySQLのunsignedな数値タイプを指定できるGemライブラリ作った

今の会社でRailsを使うようになって、いわゆるマイグレーションの仕組み超便利。

なんですが、MySQLを使っているのにidや数値にunsignedを指定できないのどうなんだろう。
他のDBはサポートしてないからいらないよねっていうのも分かるんですが、せっかくアダプタが分けられてるならサポートしてもいいんじゃないかな。
ということで、ActiveRecordの勉強がてらマイグレーションでintegerなカラムに"unsigned"を指定できるGemライブラリを作ってみました。

github/activerecord-mysql-unsigned

ついでに初めてのRubyGemsで公開もしてみた。
rubygems/activerecord-mysql-unsigned

ActiveRecord3.2以降と4で動作確認しています。

使い方はGemfileに書いて、マイグレーションファイルで「unsigned: true」をオプションに指定するだけ。

class CreateUsersTable < ActiveRecord::Migration
  def self.change
    create_table :users, force: true do |t|
      t.string  :name, null: false
      t.integer :age,  null: false, unsigned: true
    end
  end
end

既存のテーブルの主キーや数値カラムを置き換えるのが主目的なので、change_columnでも使えます。
このためにv3.2でauto_incrementをオプションで指定できるようにもしてあったり。

class ChangeColumnToUsersTable < ActiveRecord::Migration
  def self.change
    change_column :users, :id,  :integer, null: false, unsigned: true, auto_increment: true # 主キー
    change_column :users, :age, :integer, null: false, unsigned: false
  end
end

v3.2とv4.0の両方対応させるためにActiveRecordのソース読みましたが、中のクラス構造や挙動が結構変わってるんですねー。
v3.2だとunsignedな数値カラムにマイナス値を入れると0が保存される。v4.0だとActiveRecord::StatementInvalidエラーにしてくれる。
v4.0の方が圧倒的に見通しもいいしソースも綺麗。カラムのマイグレーションにもcollationやextraオプションを指定できたりいろいろ便利そうな機能を発見しました。
ウチのサービスも早く4.0にしたいなーっ。。。

WEB+DB PRESS Vol.76にWeb Componentsの記事を書きました

うう、発売されてからだいぶ経ってしまった。。。

WEB+DB PRESSJavaScript連載の第9回目にWeb Componentsについての記事を書かせていただきました。

WEB+DB PRESS Vol.76

WEB+DB PRESS Vol.76

  • 作者: 五十嵐啓人,伊野亘輝,近藤宇智朗,渡邊恵太,須藤耕平,中島聡,A-Listers,はまちや2,川添貴生,片山育美,池田拓司,濱崎健吾,佐藤太一,曾川景介,久保渓,門脇恒平,登尾徳誠,伊藤直也,mala,後藤秀宣,若原祥正,奥野幹也,大林源,WEB+DB PRESS編集部
  • 出版社/メーカー: 技術評論社
  • 発売日: 2013/08/24
  • メディア: 大型本
  • この商品を含むブログを見る

サービス/アプリの作り始めは特に気にする必要もないのですが(最初から検討するリソースがあるならその方がいいけど)、ページ数の多いサイト、インタラクションの多いアプリなどを作っていると、何かしらの方法でクライアントサイドのコンポーネント化を考えますよね。
サーバサイドのビューライブラリでのHTMLテンプレート化、Sassなどのmix-in、RequireJSなどを使って依存関係解決など、HTML/CSS/JavaScriptを個別にコンポーネント化していったり。

そうやってコンポーネント化が進んでくると、HTML/CSS/JavaScriptトータルで見たときの管理コストが逆に増えるケースもあると思います。
このJSであてられてるCSSクラスはどこで定義されてるんだろうとか、どの画面のどのDOMにこのJSコンポーネントは適用されているのか、とか。

大きめのクライアントサイドアプリだと、後から入ってくる人はHTMLの構造を把握するのも結構大変ですよね。
jQueryUIなんかそうですけど、UIコンポーネントを適用するために決まったDOM構造をHTMLに定義しておかないとよく分からないセレクタエラーで動いてくれない。ドキュメント通りのHTML構造にしろ、とかそういうHTMLの枠組みはUIコンポーネントの責務なのでは、、と思うこともしばしば。

そういった不便をブラウザネイティブの機能として解消してくれそうなのがWeb Componentsだと思っています。 なんせカスタムタグ一発でビデオタグみたいなリッチなUIを提供できるわけなので、ライブラリを使う人からしたらとても楽チン。

まだまだ一部の仕様しか使えないWeb Conponentsですが、GooglePolymerに続きMozillaBrickを公開してきたりと、動きが活発になってきて楽しい感じです。

実際にvideoタグやaudioタグなどは既にブラウザに実装されて普通に使われているわけなので、運用も問題なさそう。 ただ、実際に普及するにはIEが実装してくれないとですが><

Nginx1.3でWebsocketをリバースプロキシするメモ

Nginx1.3(2013/04/10時点では開発版)からWebsocketのリバースプロキシを通せるようになったということで、Socket.IOへリバースプロキシさせようとしたけどけどなかなか上手くいかなくて、ようやく上手く動いたのでメモっておく。

Nginxのバージョンは1.3.14です。

http {
    upstream socketio {
        server localhost:3000;
    }
    server {
        listen  80;
        charset  UTF-8;
        server_name  _; # all accept

        location /socket.io {
            proxy_set_header  Host $host;
            proxy_set_header  X-Real-IP $remote_addr;
            proxy_set_header  X-Forwarded-Host $host;
            proxy_set_header  X-Forwarded-Server $host;
            proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header  X-Nginx-Proxy true;

            proxy_pass  http://socketio;

            proxy_http_version  1.1;
            proxy_set_header    Upgrade $http_upgrade;
            proxy_set_header    Connection "upgrade";
        }
    }
}

"upgrade"は小文字でないとダメ。ここを最初"Upgrade"にしててハマったという。。

RailsアプリのJavaScriptをkonachaを使ってCLI上でテストする

前回のでアプリケーションのテストがMiniTest::Specで実行できるようになったので、今度はJavaScriptユニットテストをコマンドラインから実行できるようにする。

慣れてるMochaを使ってテストが書けるkonachaと、konachaが使用するCapybaraのPhantomJSドライバであるpoltergeistを使う。

Gemfile

group :test do
  gem 'konacha'
  gem 'poltergeist'
end

konachaの設定はinitializerで。
ドライバをpoltergeistにして、テスト対象ディレクトリを"test/javascripts"に変えた(デフォルトは"spec/javascripts")。

config/initializers/konacha.rb

if defined?(Konacha)
  Konacha.configure do |config|
    require 'capybara/poltergeist'
    config.driver = :poltergeist
    config.spec_dir = "test/javascripts"
  end
end

mochaの設定はテストディレクトリ直下にspec_helper.jsを置いてその中に書くらしい。

test/javascripts/spec_helper.js

// set the Mocha test interface
// see http://visionmedia.github.com/mocha/#interfaces
mocha.ui('bdd');

// ignore the following globals during leak detection
mocha.globals(['util']);

// or, ignore all leaks
mocha.ignoreLeaks();

// set slow test timeout in ms
mocha.timeout(5);

テストディレクトリ以下にある、"hoge_test.js"あるいは"hoge_spec.js"というファイルがテストファイルとなる。 もちろんSprokectsを使ってRailsのasset pipelineを使ってテストしたいJSファイルを読み込むことができる。
precompile済みならapplication.jsを指定するくらいか。

//= require spec_helper
//= require application

あとはchaiアサーションを使ってテストを書いてkonacha:runすればphantomjsでテストが実行される!

RAILS_ENV=test bundle exec rake konacha:run

Rails4で書いたアプリをMiniTest::Specでテストする

Rails4からはActiveSupport::TestCaseがTest::UnitからMiniTest::Unit::TestCaseのサブクラスに変わっている。
MiniTestはSpecなDSLをサポートしているので、RSpecを入れずともBDDスタイルでテストが書けるようになる。
ということで、いろいろtest_helper.rbをゴニョってたらminitest-rails-specというズバリなGemを見つけたので(><)これを使う。

minitest-spec-railsでやってくれることは、ざっくりいうと、RailsActiveSupport::TestCaseにMinitest::Spec::DSLをextendして、ControllerとかHelperクラスをテスト対象クラスに追加して、beforeとかafterとかのDSLを使えるようにしてくれるだけというシンプルな作りになっている。

使えるDSLはチートシートにまとめてくれている人がいるけど、ソースコメントにも使い方が書いてあるので数も多くないしRDocか直接のぞいて見てみるのがよさげ。

environments/test.rbなどに以下を書くと、context(describeのエイリアス)やshould(itのエイリアス)も使えるようになる。

config.minitest_spec_rails.mini_shoulda = true

Gemfile

gem 'rails', '4.0.0.beta1'
group :test do
  gem 'minitest-spec-rails'
  gem 'factory_girl_rails'
end

Factoryはこんな感じで定義しておく

FactoryGirl.define :tester1, class: User do
  name "tester1"
  password "tester1tester1"
  email "tester1@example.com"
end

まずは普通にモデルをテスト

test/models/user_test.rb

require 'test_helper'

describe User do
  context '#attributes' do
    it '#name is required' do
      tester1 = Factory.create :tester1
      
      proc = Proc.new do
        tester1.update(name: "")
      end
      proc.must_raise ActiveRecord::RecordInvalid
    end
  end
end
$ bundle exec rake test:models
Run options: --seed 30682

# Running tests:

.

Finished tests in 1.190526s, 6.1302 tests/s, 3.2388 assertions/s.

1 tests, 1 assertions, 0 failures, 0 errors, 0 skips

よさそう。 続いてAPIコントローラのテスト。 登録ユーザーの一覧をJSONで返すAPIが「Api::UsersController」クラスのindexメソッドに定義されているのでテストする。

test/controllers/api/users_controller_test.rb

require 'test_helper'

# describeメソッドにテストするコントローラクラスをセットすることで、テストメソッド内で"get :index"など書くと、
# ActionController::TestRequestリクエストを作って、
# コントローラのアクションを実行して、
# 結果をActionController::TestResponseで判定できる
describe Api::UsersController do
  before {
    20.times do |idx|
      User.create(
        name: "user#{idx}",
        password: "user#{idx}password",
        email: "user#{idx}@example.com"
      )
    end
  }

  it "#GET /api/users - とりあえず全件返す" do
    get :index
    res = JSON.parse(response.body)
    res["users"].size.must_equal 20
  end
end

ログインしないと叩けないAPIはテスト用にログインメソッド作ってやると書きやすい気がする。

test_helper.rbに追加

class ActionController::TestCase
  include ApplicationHelper

  def start_session_test(user_id)
    @controller.reset_session
    @controller.session[:login_id] = user_id
  end
end

これをリクエストを発行する前に実行すればいい

require 'test_helper'

describe Api::UsersController do
  before {
    @tester1 = Factory.create :tester1
  }

  it "#GET /api/my - ログインユーザー情報を返す" do
    start_session_test @tester1.id # tester1でログイン

    get :my
    res = JSON.parse(response.body)
    res["user"]["name"].must_equal "tester1"
  end
end