2011年11月23日水曜日

こうして僕はrailsを遅くした。そしてそこそこ直した。


最近blogの更新が途絶えがちですが、githubが面白くて(汗)...

先日、ruby on rails 3.1.2と3.1.3が矢継ぎ早にリリースされたのですが、実は関与者の一人だったりします。自戒の意味もかねて、経緯とかを書いておきます。

・2011/10/31 無作為に選んだissueを直そうと思った。

このissueを調べ始めたのですが、
色々調べてみるとmysqlのdescribeの問題という事が判明し、結果的にschema dumperの出力がバグってるという事が分かった。

そこで、describeを使わない形(informationスキーマを利用)で修正した。

・2011/11/06 master/3-1-stableにマージされた。

その後やりとりがあって、直ってよかったねという事で取り込まれた。

・2011/11/18 rails 3.1.2がリリースされた。

そりゃま~、ARは初めてだったので嬉しかったです(リリースノートにも名前が載ったし)。

・2011/11/18深夜 問題発覚。

ActiveRecord 3.1.2 takes 10 times slower than 3.1.1」というissueがあがる。
良く見てみるとmysqlとあるじゃないか!というのも前のリリースからmysqlについて修正したの私だけ(汗)

その後、某スペイン人(Christos Zisopoulosさん)とのやりとりで、"多くのデータベースが存在するmysql"の場合、遅くなる事が判明。

schema dumperの時のみ、元の修正を利用する、pull requestが取り込まれましたが、
抜本解決ではないので、"show index from"を利用する形で修正を進めた。

・2011/11/19 早朝 修正完了。
・2011/11/19 AM9時頃 某スペイン人により効果が確認され、pull requestした。

その後、railsコミッタのjonleightonにマージしてもらう(master/3-1-stable)。

・2011/11/21 sprockets等他の問題も修正され、rails 3.1.3がリリースされた。

ちなみにsprocketsは
 「windows環境でasset pipe lineが失敗する」
 「ファイルの更新が反映されない場合がある」
問題があり、最終的に2.0.xにダウングレードされました。

途中「速くリリースしろゴラ!」とか言う人がいてビビッタ。

・2011/11/23

超大規模なデータセットの場合、問題がある可能性がありそうな為、再度「show create table」を利用する形で調整中。

・2011/12/5


「show create table」を利用する形での実装が、rails 3.2系にマージされました。


・2011/12/6


さらにrails 3.1系にもback portされました。


■雑感

・世界中で困っている人がいるかと思うとかなりびびった。某スペイン人には多謝。
・この手の、量に対するテストをどう実施していくかは、そもそも根本的な問題。
・私の嘘英語でも何とかやっていけてるのが驚異的。
・gitはコマンド多すぎるが、かなり便利。

2011年10月30日日曜日

(小ネタ)apacheのAddDefaultCharsetはOffにすべき

apacheのディレクティブにAddDefaultCharsetというのがあります。 

text/htmlやtext/plainの文字列エンコーディングを指定できるのですが、これが厄介です。

何故なら一般的なブラウザはHTML内のmetaタグでの文字列エンコーディングより、HTTPレスポンスヘッダーの文字列エンコーディングを優先するからです。 

一つのapacheで複数文字列エンコーディングを指定する場合には
  • metaタグを駆使する
  • こまめにディレクトリを分けてAddDefaultCharsetを使う
とか工夫が必要です。

2011年10月21日金曜日

githubで既にあるIssueにPull Requestをくっつける方法

概要

githubではPull Requestを送信する時に自動的にIssueも作成されます。 しかし「既にあるIssueにPull Requestをくっつける」という事がしたくなる場合があります。

説明

困ったときのStackoverflowによると、githubはWeb-APIが充実しておりそれを利用すると、実現できるようです。参考ページを参考にするとUnix上でcurlを利用してwebリクエストを送信すると簡単でした。
例
$ curl -k \
         -d "pull[base]=master" \
         -d "pull[head]=master" \
         -d "pull[issue]=1234" \
         -u "kennyj:xxxxx" \
         https://github.com/api/v2/json/pulls/rails/rails

-k SSL証明書の問題?を避ける
-d "pull[base]=master" 送信先のbranch名
-d "pull[head]=master" 送信元のbranch名
-d "pull[issue]=1234" バグ票番号
-u "kennyj:xxxxx" ユーザ名とパスワード
https://github.com/api/v2/json/pulls/rails/rails 送信先のユーザ名とリポジトリ名
無駄にIssueを増やさない為にも知ってて損はなさそうです。

参考

How do you attach a new pull request to an existing issue on github?

2011年10月6日木曜日

backbone.jsをrails3で実行できるようにするシェルスクリプト(rails3.1対応版)

何度も同じ事しているので貼っておきます(rails3.1対応版)。
capybara/capybara-webkit/headless対応や、capistrano、staging対応、sub uri対応等を追加しています。
./gen.sh アプリ名
で利用できます(ノーエラーハンドリング上等!)

$ cat gen.sh

2011年10月4日火曜日

rails3.1 with rspec-request , capybaraでcookieを取り扱うには

rails3.1とrspec-requestsをcapybaraで利用した際に、cookieの扱い方が分からなかったのでメモしておきます。
# app/controllers/foo_controller.rb
class FooController < ApplicationController
  def index
    puts cookies["key1"]
    cookies["key1"] = '2'
    cookies.permanent["key2"] = "3"
  end
end

# spec/requests/foo_spec.rb
describe "foo周辺の仕様" do
  def cookies
    Capybara.current_session.driver.browser.current_session.instance_variable_get(:@rack_mock_session).cookie_jar
  end

  it "foo/index" do
    cookies["key1"] = '1'
    visit "/foo/index"
    cookies["key1"].should == '2'
    cookies["key2"].should == '3'
  end
end
上記の検証の過程で、
  • cookiesのキー名は文字列でないといけない(シンボルだと上手くいかない)
  • cookiesメソッドを上書く必要があり
  • permanentでも同じように読める("key2")
という事が判明しました。

ちなみに各プロダクトは下記の組み合わせで確認しました。
rails-3.1.0
rspec-2.6.0
rspec-rails-2.6.1
capybara-1.0.1

2011年9月26日月曜日

capybaraをwebkitやseleniumとかで動かす場合の注意点と解決策

■概要


capybaraとwebkit(たぶんselenium)を利用する際は、capybara側がブラウザとやりとりする為のスレッドを立ち上げる為
RSpec.configure do |config|
  ...
  config.use_transactional_fixtures = false
end
してfixtureのトランザクション制御をあきらめる必要があります。これでは少し都合が悪いので、 DatabaseCleanerを利用して代用する等行う必要があります。

ところがrailsコアチームのjosevalim氏が解決策をぼそっとつぶやいてました。実際にやってみると上手く動くだけでなく実行速度がかなり改善されました。

■解決策


つぶやきで紹介されている方法は非常に簡単です。
spec_helper(test_helper.rb)で下記の用に追記しましょう。
RSpec.configure do |config|
  ...
  config.use_transactional_fixtures = true
end

# 下記追加
class ActiveRecord::Base
  mattr_accessor :shared_connection
  @@shared_connection = nil

  def self.connection
    @@shared_connection || retrieve_connection
  end
end

ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection
でいつもどおり

$ bundle exec rake spec

して見ましょう。specの実行速度が改善したのでは無いでしょうか?

ね、簡単でしょう!

@josevalim氏に多謝!

2011年9月25日日曜日

rails3.0の頃からvalidationにコンテキストを指定できる様になってた

■概要

railsは従来modelで入力チェックを行ってました。#saveメソッドを呼び出し時に、#valid?メソッドが呼び出される事により、入力チェックを行っている人が多いのでは無いでしょうか?

ただし、この仕様は、たまに上手く行かない時があり、「XXという画面ではYYの入力チェックだけしたい」というニーズに答えるにはあまり良い方法がありませんでした(泥臭い方法で解決する)。

ところが、先日railsのソースを読んでいると

activerecord-3.1.0/lib/active_record/validations.rb
   def valid?(context = nil)
      context ||= (new_record? ? :create : :update)
      output = super(context)
      errors.empty? && output
    end
とcontextが引数に渡せる仕様になってるので早速試してみました。

■検証

実験用のPostモデルを下記の用にしました。
class Post < ActiveRecord::Base
  validates_presence_of :title, :on => :bar
end
でrails cで動かしてみました。
$ rails c
Loading development environment (Rails 3.1.0)
ruby-1.9.2-p180 :001 > post = Post.new
ruby-1.9.2-p180 :002 > post.valid?(:bar)
 => false
ruby-1.9.2-p180 :003 > post.valid?(:foo)
 => true
ruby-1.9.2-p180 :004 > post.save(:context => :bar)
 => false
ruby-1.9.2-p180 :005 > post.save(:context => :foo)
  SQL (46.0ms)  INSERT INTO "posts" ("created_at", "title", "updated_at") VALUES (?, ?, ?)  [["created_at", Sat, 24 Sep 2011 18:03:18 UTC +00:00], ["title", nil], ["updated_at", Sat, 24 Sep 2011 18:03:18 UTC +00:00]]
 => true
おぉ!いい感じにcontextを意識できてますね!
ソースを読むとコンテキスト未指定時は、新規なら:create、更新なら:updateをコンテキストとして採用しており、過去との互換性を維持しているという事がわかりました。

■雑感

https://github.com/rails/rails/commit/5c245b91d2dbc0b300e8193310b3f950d0cf6c4b#activerecord/lib/active_record/validations.rb
で追加されてました。2010/05/10に追加された様です。

2011年9月24日土曜日

RewriteCondが効かずに困った

言わずと知れた、apacheのmod_rewriteエンジンですがRewriteCondとRewriteRuleの記述方法にはまってしまいました。普通の書き方は他に任せるとしてハマったポイントを書いておきます。

■RewriteCondは直後のRewriteRuleのみに適用される。
RewriteEngine On
RewriteCond %{HTTP_REFERER} $^ …①
RewriteRule ^/foo/(.*) /foo2/$1 [L] …②
RewriteRule ^/bar/(.*) /bar2/$1 [L] …③
上記の様な設定の時に①は、②にも③にも効きそうですが、実際には②にしか効きません。
だからリファラーがあっても、barはbar2にリダイレクトしてしまいます。

③にも効かせるには、(冗長ですが)下記の様に書く必要があります。

RewriteEngine On
RewriteCond %{HTTP_REFERER} $^
RewriteRule ^/foo/(.*) /foo2/$1 [L]
RewriteCond %{HTTP_REFERER} $^   # 同じ記載をする!
RewriteRule ^/bar/(.*) /bar2/$1 [L]

■RewriteCondはRewriteRuleの後に評価される。

RewriteEngine On
RewriteCond %{HTTP_REFERER} $^
RewriteRule ^/foo/(.*) /foo2/$1 [L]

上記の設定の場合「リファラーが空なら、リクエストが/foo/*の場合、/foo2/*へリダイレクトと思いたい所ですが、実際には「リクエストが/foo/*の場合、リファラーが空なら、/foo2/*へリダイレクト」という処理の流れになります。

「何でRewriteLogLevelを上げて見てみても、RewriteCond実行されてない!効いてないんちゃうか?」って時はこれが原因の可能性が高いです。

2011年9月20日火曜日

spec実行時にdb:test:prepareを呼び出したくない。


■概要

(激しく既出感ありですが。。)何年もrailsやってますが、正しく理解できていなかった事の一つに

  rake spec(test)すると、処理の一環としてdb:test:prepareタスクを呼び出す

という挙動があります。
通常問題にならないのですが、db:test:prepareが、db/schema.rbの情報を元にデーターベースを作成する為、railsが認識できないような項目は抜けて落ちてしまいます。

  • 関数適用したインデックス
  • トリガーやファンクション
  • 別スキーマに作ったオブジェクト ...etc

なので

RAILS_ENV=test rake db:drop
RAILS_ENV=test rake db:create
RAILS_ENV=test rake db:migrate

してからrake specする事を前提に、db:test:prepareを呼び出さずにspecを実行する方法を模索しました。

■案1 db:test:prepare自体を書き換える

参照先にそのまんまの解決策が書かれています。
Rakefileでtask削除用のメソッドを用意します(load_tasksの前に!)。
Rake::TaskManager.class_eval do
  def remove_task(task_name)
    @tasks.delete(task_name.to_s)
  end
end

lib/tasks/db/test.rakeとかを作って、実際にタスクを上書きします
Rake.application.remove_task 'db:test:prepare'

namespace :db do
  namespace :test do 
    task :prepare do |t|
      # rewrite the task to not do anything you don't want
    end
  end
end

参考 http://stackoverflow.com/questions/1097845/how-to-prevent-rake-test-to-call-task-dbtestprepare

■案2 自力でspecを実行する

db:test:prepareを壊すのは怖いので...という方にはこのやり方

lib/tasks/spec.rakeとかを作って
require 'rake'
require 'rspec/core/rake_task'

namespace :spec do
  RSpec::Core::RakeTask.new('no_prepare_db') do |t|
    t.pattern = ['spec/**/*_spec.rb']
  end
  namespace :no_prepare_db do
    %w(controllers helpers lib mailers models requests routing views).each do |dir|
      RSpec::Core::RakeTask.new(dir) do |t|
        t.pattern = ["spec/#{dir}/**/*_spec.rb"]
      end
    end
  end
end
で、rake spec:no_prepre_dbや、rake spec:no_prepare_db:modelsで、db:test:prepareを呼び出さずに実行できます。

参考 http://old.nabble.com/db%3Atest%3Aprepare-interfering-with-rake-spec-pattern-td31075510.html

2011年9月18日日曜日

rails3.1時代のautocompleteは(一部駄目だし)

railsを始めて触った時は、圧倒的に簡単に利用できるautocomplete機能に驚かされたものです(あ~懐かしい。。)。
あるバージョンからautocompleteはplugin側に外れたのですが、rails3時代にはどのplugin(gem)が利用されているんでしょうか?
rails3.1からはjqueryが標準になったので、rails3 - jquery - autocompleteという切り口で探してみました。
すると、そのまんまなgemがありました。
https://github.com/crowdint/rails3-jquery-autocomplete

■インストール

Gemfileに
gem 'rails3-jquery-autocomplete'

でinstall
$ bundle install

次にコード生成して
rails generate autocomplete:install

# generatorのrails3.1対応はまだみたいなのでautocomplete-rails.jsは、public/assets/javascripts
# に手動コピーしましょう。

jsを読み込みましょう
javascript_include_tag "autocomplete-rails.js"

application.jsでjquery-uiを読み込む用にしましょう。
//= require jquery-ui

後defaultではcssがしょんぼりなので、public/assets/stylesheetsに
https://raw.github.com/crowdint/rails3-jquery-autocomplete-app/master/public/stylesheets/jquery-ui-1.8.2.custom.css
をおきましょう。

■利用方法
controllerに
class ProductsController < Admin::BaseController
  # モデル名、フィールド名
  autocomplete :brand, :name
end

routes.rbに
resources :products do
  get :autocomplete_brand_name, :on => :collection
end

最後にviewに
<%= form_for @product do |f| %>
  <%= f.autocomplete_field :brand_name, autocomplete_brand_name_products_path %>
<% end %>

ね、(そこそこ)簡単でしょう!

■いくつか残念な点

調べてみて感じた残念な点
  1. rails3.1対応はまだ
  2. rspec-request用のサポートが欲しい
  3. label/valueに適応されるmethodが同じ
  4. term以外の動的要素に対応できない
ぼやいててもしょうがないので、やれる事はやろう(続く)

2011/09/18 上記4に対応してみました。https://github.com/kennyj/rails3-jquery-autocomplete
gem 'rails3-jquery-autocomplete', :git => git://github.com/kennyj/rails3-jquery-autocomplete.git
で使えると思います。

2011/09/18 上記1にも対応してみました(rails3.1 branch) 上記に, :branch => 'rails3.1' を追加すると利用できるはずです。

2011/09/19 上記2はspec_helper.rbで、require 'steak/autocomplete' して、config.include Steak::Autocomplete, :type => :request すればrspec-requestsでも動きました。

2011年9月7日水曜日

rails3.1.0でasset pipe lineの問題発見(と解決方法)

■概要
rails3.1.0で、productionモードにてasset pipe lineを使うには、一般的には rake assets:precompile を利用すると思いますが(さもなくばエラーになります)、実行すると下記の用になります。
$ rake assets:precompile
$ ls -la public/assets/
合計 544
drwxrwxr-x 2 kj kj   4096  9月  6 01:33 .
drwxrwxr-x 3 kj kj   4096  9月  6 01:33 ..
-rw-rw-r-- 1 kj kj  96110  9月  6 01:27 application-2438fd50052a4a5b81204dc6fb.js
-rw-rw-r-- 1 kj kj  33515  9月  6 01:27 application-2438fd50052a4a5b81204dc6fb.js.gz
-rw-rw-r-- 1 kj kj      0  9月  6 01:27 application-4635849c44627859332fda6a01.css
-rw-rw-r-- 1 kj kj     20  9月  6 01:27 application-4635849c44627859332fda6a01.css.gz
-rw-rw-r-- 1 kj kj  91273  8月 15 22:49 jquery-ed8d29566738ad005e19fe1c2d.min.js
-rw-rw-r-- 1 kj kj  32054  8月 15 22:49 jquery-ed8d29566738ad005e19fe1c2d.min.js.gz
-rw-rw-r-- 1 kj kj 198431  8月 15 22:49 jquery-ui-2a28fc84ad0e0e47e46cbf901c.min.js
-rw-rw-r-- 1 kj kj  50610  8月 15 22:49 jquery-ui-2a28fc84ad0e0e47e46cbf901c.min.js.gz
-rw-rw-r-- 1 kj kj    318  9月  6 01:33 manifest.yml
-rw-rw-r-- 1 kj kj   6646  9月  6 01:26 rails-a560b5a3a7be0808c5cd76a798.png
上記にjqueryとjquery-uiが含まれている事が理解できません。application.jsの中にはjqueryが内包されています。またjquery-uiはSprocketsのディレクティブで指定もしていません。良くわからない挙動なので追跡してみました。

■原因
actionpack-3.1.0/lib/sprockets/assets.rake にdebuggerを仕込み動きを追いましたが、
 
 26         config.assets.precompile.each do |path|
 27           env.each_logical_path do |logical_path|
 28             if path.is_a?(Regexp)
 29               next unless path.match(logical_path)
 30             else
 31               next unless File.fnmatch(path.to_s, logical_path)
 32             end
 33
...
上記の28行目のpathは、config.assets.precompileの配列のメンバーですが、デフォルトでは
[ /\w+\.(?!js|css).+/, /application.(css|js)$/ ]
になっています(railties-3.1.0/lib/rails/application/configuration.rb 38行目)
上記配列の一番目は、rails.png等jsやcss以外の為に存在しますが、このままではjquery.min.jsやjquery-ui.minjsも29行目でヒットしてしまうじゃないか!!!という事に気付きました。

■解決方法
直してやれ!と思いましたが既に問題になってました。よって
  • 上記もうすぐ取り込まれそうなので3.1.1?を待つ
  • とりあえずconfig/environments/production.rb config.assets.precompile = [ /\.(?!js$|css$)\w+$/, /application.(css|js)$/ ] とする
辺りで対応しましょう。

■2011/09/11追記
解決されている様です。あるべき姿になった感じです! https://github.com/rails/rails/commit/082f53a3bca9dce70adcf41094e246d2c9fed934

2011年8月23日火曜日

capybara-webkitを動かす 2011/08/23時点版

■概要

こちらの素晴らしい記事では、capybara-webkitを利用して、headlessでjsが動く環境を紹介されています。

capybara-webkitのこれまでリリースされているバージョン(~0.5.0)では、capybara1.0.0系への依存が解決できない為、抗う方法をご紹介されています。が、0.6.0からは上手く依存関係を解決されるようになりました!

■手順

こちらと同様ですが、Xvfbとqtの当たらしいバージョン(qt47)をインストールしておきます。
qt47にする理由はphantomjsも動かしたいからです ^o^

$ sudo yum -y install firefox ★ seleniumで動かす必要があれば
$ sudo yum -y install xorg-x11-server-Xvfb xorg-x11-fonts*
$ sudo vim /etc/yum.repos.d/atrpms.repo

[atrpms]
name= CentOS-$releasever - ATrpms
baseurl=http://dl.atrpms.net/el$releasever-$basearch/atrpms/testing/
gpgcheck=1
gpgkey=http://ATrpms.net/RPM-GPG-KEY.atrpms
enabled=0

$ sudo rpm --import http://packages.atrpms.net/RPM-GPG-KEY.atrpms
$ sudo yum -y install sqlite --enablerepo=atrpms ★依存関係からインストールする必要がある?
$ sudo yum -y install qt47-devel qt47-webkit qt47-webkit-devel --enablerepo=atrpms
$ sudo ln -s /usr/bin/qmake-qt47 /usr/bin/qmake (コンパイル時に必要とされるので予め作成)

でrailsプロジェクト側で

$ vim Gemfile
group :development, :test do
  ...
  gem 'capybara', '1.0.1'
  gem 'capybara-webkit', '0.6.0'
  gem 'headless', '0.1.0'
  ...
end
$ bundle install
$ vim spec/spec_helper.rb
...
require "capybara/rails"
require "capybara/rspec"
...
RSpec.configure do |config|
...
end

Capybara.javascript_driver = :webkit
$ vim spec/support/headless.rb
if %w(yes y on).include?(ENV['HEADLESS'])
  require 'headless'

  headless = Headless.new
  headless.start

  at_exit do
    headless.destroy
  end
end
$ vim spec/requests/index_spec.rb
# coding: utf-8

require 'spec_helper'

describe "Index" do
  describe "GET /" do
    it "/index.html", :js => true do
      visit "/"
      click_link "About your application’s environment"
      page.should have_content("No route matches")
    end
  end
end

$ HEADLESS=on bundle exec rspec spec/requests/index_spec.rb
Index
  GET /
    /index.html

Finished in 4.48 seconds
1 example, 0 failures
という訳でいい感じに実行できました~

2011年8月20日土曜日

javaの32bitと64bitの使い分けにはご注意を

最近では64bit OS環境が広く利用されている事と思います。
一般的に64bit環境での32bitコマンドの実行は問題が無いと思いますが、java関係で二つ程問題が発生したので記録しておきます。

■Tomcatが起動出来なくなった。

64bit => 32bit javaに変更した所、Tomcatが起動しなくなりました。調べてみるとTomcat本体の問題では無かったのですが、jsvcを利用して起動している為、問題が発生しているようです(TOMCAT_HOME/bin/startup.sh等では問題なく起動しました)。

一部推測混じりですが

・jsvcを利用するにはコンパイルが必要
・コンパイル時には、configureを実行
・configureのログをよく見てみると、linux自体を"x86_64"と判定
・よってjavaは64bit版を想定してコンパイルしてしまう(推測)

という理由で上手く行かなくなったのではと思われます。

結論:jsvcを利用する場合は、32/64bitをOSとあわせる必要があります。

■rjb(ruby java bridge)が動かなくなった。

こちらも64bit => 32bit javaに変更した所、動かなくなりました。ruby側からJava VMを作成できなくなるようです。

但し上記問題を解決しても、javaのマイナーバージョン違いで急に動かなくなる場合を確認しています(jdk6u22 => jdk6u23)。ログを見た限りではGC発生タイミングでseg faultしてしまうようです。

リリースノートを見ている限りでは、hotspot vmのバージョンが上がったから?等推測してますが根本原因は判明しませんでした。

結論:rjbを利用する場合は、32/64bitをOSとあわせる必要があります。またマイナーバージョン含め実績のあるjava/rubyのバージョン組み合わせ・設定を大事にする必要があります。

2011年8月16日火曜日

(小ネタ)railsのセッションを復元する(rails2.3系で確認)

必要に迫られたので... メモ残しておきます。

$ irb
> require 'rubygems'
> require 'active_support'
> v = ActiveSupport::MessageVerifier.new('Railsのsecret', 'SHA1')
> v.verify('session文字列')

※1 Railsのsecretは、initializer/session_store.rb
※2 session文字列は、cookieから取得

ちなみに得られる結果はrack.sessionと同じです。

2011年8月13日土曜日

ruby1.9時代にrcovは使ってはいけない。simplecovを使おう!

■概要

rubyにおけるテスト網羅率の定番ツールといえばrcovですが、どうもテストの通っている箇所の色付けがおかしいのと網羅率に誤差があると感じてました。

よくよくgithubのrcovのページを見てみると、

NOTE: This fork does not work on Ruby 1.9.x. For coverage on Ruby 1.9 look at SimpleCov. Even if you get results on 1.9 they will probably be inaccurate. Ruby 1.9 has call detection built in for faster, more accurate results.

なんて書いてあります。という訳でSimpleCovを試してみました。

なおこちらで簡単にセットアップできるシェルスクリプト置いてます!

■説明

まずはいつものGemfileに

gem 'simplecov', :require => false

と記載して

$ bundle install

次にspec/spec_helper.rbに下記コードを追加しましょう。
この時大事なのは実行の早い段階(railsが読み込まれる前!)で下記を実行する必要がある事です。

require 'simplecov'
SimpleCov.start 'rails'



$ rake spec

すると、coverageディレクトリ以下にhtmlファイルが出力されます(画像はsimplecovのページより)


このままでも良いのですが、rcovのフォーマットと異なるので、CIに統合するには不便です。そんな時はsimplecov_rcovを利用すると上手く行きます。

group :test do
  gem 'simplecov', :require => false
  gem 'simplecov-rcov', :require => false
end

$ bundle install

spec/spec_helper.rbには、SimpleCovの出力フォーマットを変更するコードを書きます。

require 'simplecov'
require 'simplecov-rcov'
SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter

※くれぐれも早い段階で読み込んで下さい!

すると見慣れたあのHTMLがcoverage/rcov以下に出力されます。
coverage/rcovをCI(というかjenkins)のrcovレポートの場所として指定してあげると良好な結果が得られます!

ちなみにsimplecov-rcovのページで下記の様な記載があり、網羅率測定有無を切り替える方法があります。
if( ENV['COVERAGE'] == 'on' )
  require 'simplecov'
  require 'simplecov-rcov'
  SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter
  ...
end

$ COVERAGE=on rake spec
また複数の出力フォーマットで出力する例もありますので是非見てみて下さい。

2011年8月12日金曜日

rails3とnginxでx-sendfile的な事をしてみる

■概要

rails3.0.9・nginxの組み合わせで、apacheでのX-Sendfile相当の事が実現したく検証しましたが、これまた少しはまりました。

■答えまでの道のり

config/environments/production.rb を見ると

# Specifies the header that your server uses for sending files
  config.action_dispatch.x_sendfile_header = "X-Sendfile"

  # For nginx:
  # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'

という記載があるので、上をコメントアウトし、下をコメント外せば、OKと思ったのですが甘かったです。。上手く行かずエラーが出ます。

nginxのドキュメントを見ても、internalに対応する内部向けurlが、必要なのは分かりますが、いまいち良くわかりません。

ではrailsのsend_fileメソッド(actionpack-3.0.x/lib/action_controller/metal/streaming.rb)のソースを確認しても、pathには絶対パスを渡さないと上手く行かない事が予想されます(冒頭でファイルシステムとしての存在チェックをしているので)。しかしよく見ると

# Sends the file. This uses a server-appropriate method (such as X-Sendfile)
# via the Rack::Sendfile middleware. The header to use is set via

と書いてあるので、次はRack::Sendfile(rack-1.2.x/lib/rack/sendfile.rb)を見てみました。コメントには下記の用にnginx用のサンプルがあります。

  #   location ~ /files/(.*) {
  #     internal;
  #     alias /var/www/$1;
  #   }
  #
  #   location / {
  #     proxy_redirect     off;
  #
  #     proxy_set_header   Host                $host;
  #     proxy_set_header   X-Real-IP           $remote_addr;
  #     proxy_set_header   X-Forwarded-For     $proxy_add_x_forwarded_for;
  #
  #     proxy_set_header   X-Sendfile-Type     X-Accel-Redirect;  # ①
  #     proxy_set_header   X-Accel-Mapping     /files/=/var/www/; # ②
  #
  #     proxy_pass         http://127.0.0.1:8080/;
  #   }

アプリケーションの通常処理を行うlocation "/" と、ファイル転送用のlocation "~ /files(.*)"があり/var/wwwがマッピングされるのかな?と思い、真似してみましたが上手くいきません。。

もう少しRackのソースを読んでみると、上記設定の意味が良くわかります。①はvariationメソッドの戻り値になります。したがってcall内の分岐は"X-Accel-Redirect"に進みます。その後map_accel_pathメソッドを実行するのですが、ここで驚愕の事実に気付きます。

def map_accel_path(env, file)
    if mapping = env['HTTP_X_ACCEL_MAPPING']
      internal, external = mapping.split('=', 2).map{ |p| p.strip }
      file.sub(/^#{internal}/i, external)
    end
  end

②は絶対パスを、nginx用内部urlに変換する為に必要な設定ですが「右辺と左辺をドキュメントでは逆に書いている!」という事に気付きました。(internalとexternal)

よって②は左右逆転にしなければいけません。

proxy_set_header X-Accel-Mapping /var/www/=/files/;

ここまでやって上手くいきました。
/var/www/hoge.txt (send_file) => /files/hoge.txt (rackから出た時点) => /var/www/hoge.txt (nginxでの処理)

■設定方法(まとめ)

・config/environments/production.rb

config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'

のコメントをはずし、一つ上のX-Sendfileはコメントアウトします。

・プログラム

ファイル送信部分は絶対パス指定します。

send_file "/var/www/baz.txt"

・/etc/nginx/nginx.conf

下記の様に、通常処理側には、X-Sendfile-Type・X-Accel-Mapping、内部処理向けには、internal・alias設定を記載すればOKです。

location / {
  ...
  proxy_set_header X-Sendfile-Type X-Accel-Redirect;
  proxy_set_header X-Accel-Mapping /var/www/=/files/;
  ...
}

location /files/ {
  internal;
  alias /var/www;
}

rails3でのsub ディレクトリへのデプロイ

■概要

rails3.0.9・unicorn・nginxの組み合わせで、subディレクトリにアプリケーションをデプロイしようとすると、少しはまったのでメモを残しておきます。

■設定方法

http://ホスト名/foo 以下にアプリをデプロイしたい場合...

○unicorn

unicorn_railsを実行する際に--pathを渡します。

例)
$ unicorn_rails -c config/unicorn.rb -E production -D --path /foo

○nginx

passengerと同じくDocument Root直下に、subディレクトリ名でpublicへのシンボリックリンクをはればOKです。またlocation設定はsubディレクトリ毎に設定するのが良い感じでしょうね。

例)
$ ln -s /path/to/foo_root/public /var/apps/foo
$ vim /etc/nginx/nginx.conf
...
root /var/apps/;
...
location /foo {
  if (-f $request_filename) { break; }
  proxy_set_header X-Real-IP  $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header Host $http_host;
  proxy_pass http://(foo用のunixドメインのソケット);
}

○rails3.0.9

ActionController::Base.config.relative_url_root= が、deprecatedなのでどうしようかと思いますが検証した結果、下記で上手く行きました。
通常のroutingもassetもこれで上手くいきます(config.serve_static_assets = false なので、assetsはnginxが処理します)。

・unicon_rails --pathの設定は、ENV['RAILS_RELATIVE_URL_ROOT']となってrack側に渡される
・ENV['RAILS_RELATIVE_URL_ROOT']は、ActionController::Base.config.relative_url_rootに代入されている(?)
・よってconfig.ruで下記のようにrunを囲む
map ActionController::Base.config.relative_url_root || "/" do
  run FooApp::Application
end
参考:困った時のstackoverflow http://stackoverflow.com/questions/3181746/what-is-the-replacement-for-actioncontrollerbase-relative-url-root

2011年7月31日日曜日

(小ネタ)システムコントロールテーブルは必ず用意しましょう

システムを構築すると、必ず「設定値」が必要になります。

・1ページの表示行数
・XXした際の遷移先URL
・YY通信のタイムアウト時間
...etc

上記設定をどこに置くかは結構大きな問題です。

(例)
①propertiesファイル (javaの場合)
②xxx.rbファイル (rubyの場合)
③データベース
...

上記①や②は最速ですが、設定値の反映には「アプリケーションの再デプロイ or 再起動」が必要になります。よって気軽に変更値の変更という訳にはいきません。

③はデータベースへの問い合わせが必要ですが、アプリケーション実行時に設定値を切り替えられるという利点があります。この手の設定値テーブルを用意しておくと顧客との打合せ時に、無駄な値決めに時間を浪費する事を回避できるというメリットもあります(顧客は情報がそろってから意思決定できるようになるので)。

よって、システムを開発してusersテーブルを作成した次には(?)、上記テーブルを作成しましょう。(心の)師匠、渡辺幸三さんに敬意を評し、「システムコントロール」テーブルと名づけましょう。

system_controls
----
code (PK or UK)
value (設定値)
editable (ユーザが画面上より編集可能か?)
description (編集画面での説明文章)
type (文字列、数字、boolean等入力タイプの区分値。多くなるならドメインテーブル作成。入力チェックと絡めるのも良いかもですね)
updater_id (最終更新者)
updated_at (最終更新日時)

こんな感じかな?

スピードが気になる人は、上記のcode値を利用して、KVSとかキャッシュサーバに突っ込めば良いでしょう。
# 変更、破棄タイミングに気をつけて下さい

ちなみに上記目的のテーブルを横持ち(カラム)で持ちたがる人がいますが、全力で阻止しましょう。柔軟にcodeを追加出来るのがミソなので!!

後DB障害発生時に問い合わせられない事も意識する必要があります(例 例外メール送信先)ので注意が必要です。

2011年7月28日木曜日

backbone.jsがいつのまにかpjax対応していた

■概要

年初にbackbone.jsの調査をしていた頃は、ajaxで画面遷移を行うには、fragmentを利用する事しかできませんでした。

しかし0.5以降では、Backbone.Routerを利用する事によって、pjax(HTML5のpushStateを利用した話題のあれ)な画面遷移が出来るになっています。

pjaxについては、こちらをご参照ください。
またgithubのファイルブラウザはpjaxの良例として有名なのでチェックしてみて下さい。


■何処がpjaxなのか?

まずは下記手順を実施後、http://localhost:3000/を閲覧してみましょう。chrome等html5対応ブラウザで確認して下さい。


次にhelpをクリックしてみましょう。

注目すべきなのは、
①URLが変わっている
②画面下部のレンダリング日時が変わっていない。

次にaboutをクリックしてみましょう。helpの時と同じです。

ブラウザの「戻る」ボタンをクリックしてみましょう。
About => Help => Indexに遷移します。「進む」ボタンもいい感じに遷移します。

次にHelpが画面に表示されている状態で、「F5」を押下してみましょう。
レンダリング日時が変わったはずです。


■説明

ajaxは非同期に(高速に)画面の一部を書き換える技術ですが、ユーザーの利便性の観点から、ブラウザの戻る、進むを利用可能にする必要があります。これまでのajaxでは、#(フラグメント)を利用して実現する事が多かったのですが、あまり良い解決策では有りませんでした。

pjaxでは、HTML5のpushStateを利用する事により、ブラウザのURLを切り替えつつ、画面の一部のみ切り替える事ができます。無論、URLは通常のURLですので、「通常のHTTPリクエスト」として処理するように実装する事が可能です。下記のIndexControllerでは、Ajax通信かどうかでレイアウトの適用を変更しています。
これができたら何が嬉しいのか?!それは読者への宿題にしておきます(^.^;

ちなみに、IE(≒pushStateをサポートしていないブラウザ)でみたら fragment での遷移に自動的に切り替わってました。。

■体験手順

・まずは前回の記事を参考にプロジェクトを生成する。

$ ./gen.sh pjax

・移動して体験に必要なファイルを用意する

$ cd pjax

app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>pjax2</title>
    <meta charset="utf-8"/>
    <%= stylesheet_link_tag :all %>
    <%= javascript_include_tag :defaults %>
    <%= javascript_include_tag :backbone %>
    <script><%= yield :javascripts %></script>
    <%= csrf_meta_tag %>
  </head>
  <body>
    <%= content_for?(:content) ? yield(:content) : yield %>
  </body>
</html>

app/views/layouts/index.html.erb

<%= content_for :javascripts do %>
$(function() {
  var Workspace = Backbone.Router.extend({
    routes: {
      "":      "index",
      "help":  "help",
      "about": "about"
    },
    index: function() {
      $("#app").load("/");
    },
    help: function() {
      $("#app").load("/help");
    },
    about: function() {
      $("#app").load("/about");
    }
  });
  var ws = new Workspace();
  Backbone.history.start({pushState: true});

  $("#indexButton").click(function() {
    ws.navigate("", true);
  });
  $("#helpButton").click(function() {
    ws.navigate("help", true);
  });
  $("#aboutButton").click(function() {
    ws.navigate("about", true);
  });
});
<% end %>

<%= content_for :content do %>
  <div id="app"></div>
  <hr/>
  <input type="button" name="index" value="index" id="indexButton" />
  <input type="button" name="help"  value="help"  id="helpButton" />
  <input type="button" name="about" value="about" id="aboutButton" />
  最終レンダリング日時:<%= I18n.l Time.now %>
<% end %>

<%= render :file => "layouts/application" %>

app/views/index/index.html.erb

<h1>Index</h1>

app/views/index/help.html.erb

<h1>help</h1>

app/views/index/about.html.erb

<h1>about</h1>

app/controllers/index_controller.rb

class IndexController < ApplicationController
  layout "index"
  def index
    render :layout => !request.xhr?
  end
  def help
    render :layout => !request.xhr?
  end
  def about
    render :layout => !request.xhr?
  end
end

config/routes.rb

最後に
root :to      => "index#index"
match "help"  => "index#help"
match "about" => "index#about"

public/index.html <= 削除

サーバ起動!
$ rails server

backbone.jsをrails3で実行できるようにするシェルスクリプト

※この記事はrails3.0に対する記事です。rails3.1はこちらをご覧下さい。

何度も同じ事しているので貼っておきます。
./gen.sh アプリ名
で利用できます(ノーエラーハンドリング上等!)

●2011/08/15追記 rcov => simplecovに変更し、ci_reporterを追加しました。

$ cat gen.sh

railsでネストしたレイアウトを実現する

railsのviewはレイアウト機能があり、画面の共通化が可能です。

・画面のイメージ・レイアウトを共通化する
・メニューを表示する
...etc

非常に便利な機能ですが、「サイト全体で画面のイメージを共通化しつつ、XXX機能でも共通のレイアウトにする」事が簡単にはできないと思っていました(レイアウトの二重適用)。
ところがRuby on Rails Guidesに実現方法がのっていました。非常に簡単なのでご紹介しておきます。

■application.html.erb (全体的な共通レイアウト)

<html>
<head>
  <title><%= @page_title or 'Page Title' %></title>
  <%= stylesheet_link_tag 'layout' %>
  <style type="text/css"><%= yield :stylesheets %></style>
</head>
<body>
  <div id="top_menu">Top menu items here</div>
  <div id="menu">Menu items here</div>
  <div id="content"><%= content_for?(:content) ? yield(:content) : yield %></div>
</body>
</html>
上記のポイントは二つ。

・yield :stylesheets
=> 機能側で定義したstylesheetをheadタグ内にレンダリングする例です
・content_for?(:content) ? yield(:content) : yield
=> 機能側で:contentがある場合は引数にしてyield、無い場合通常のyield

■news.html.erb(機能毎レイアウト)

<% content_for :stylesheets do %>
  #top_menu {display: none}
  #right_menu {float: right; background-color: yellow; color: black}
<% end %>
<% content_for :content do %>
  <div id="right_menu">Right menu items here</div>
  <%= yield(:news_content) or yield %>
<% end %>
<%= render :file => 'layouts/application' %>
・content_for :stylesheets
=>上記のstylesheetを準備します。同じようにjavascriptも定義可能です。
・content_for :content / yield(:news_content) or yield
=> :contentを定義し、更に:news_contentがある場合は引数にしてyield、無い場合通常のyield
・render :file => 'layouts/application'
=> 上記contentを準備して、全体レイアウトをレンダリング

■xxx.html.erb(各画面)

<h1>タイトル</h1>
...

各画面固有のレンダリング内容

■xxx_controller.rb

class XxxController < ApplicationController
   layout "news"
   def xxx
   end
end

layoutで機能毎共通レイアウトを指定
上記の様に設定すると
application.html.erb
  news.html.erb
    xxx.html.erb

とネストしたレンダリング可能です!

■備考

2011/07/21 22:06 なんてこったい! Rails3レシピブックにのっているじゃないか(^_^;)

2011年7月27日水曜日

coberturaが猛烈に遅い!...けど解決した

■概要

junitの網羅率測定する為に、coberturaを利用する事が多いと思います。

# 当方久しぶりにjavaを触ったので最近のトレンドにはとんと疎くなりました。もっと良いのがあれば教えてください。

しかしこれが猛烈に遅い!軽快に進めたいのですが、ネックになりそうなので調査しました。

■解説

coberturaの実行はantから行っています。通常

1. コンパイル
2. instrument (coberturaを埋め込む)
3. junit実行


の順番で実行する事になりますが、 3. が非常に遅いのです。

3. の実行は
<junit fork="yes" dir="${basedir}">
  <classpath location="${instrumented.dir}" />
  <classpath location="${classes.dir}" />
  <classpath refid="cobertura.classpath" />
  <formatter type="xml" />
  <test name="${testcase}" todir="${reports.xml.dir}" if="testcase" />
  <batchtest todir="${reports.xml.dir}" unless="testcase">
    <fileset dir="${src.dir}">
      <include name="**/*Test.java" />
    </fileset>
  </batchtest>
</junit>

の様な記載を行うと思いますが、上記だと遅くなってしまいます。

何故なら、antがjunitを起動する際にプロセスのforkを行うのですが、forkはTestCase毎に行われるようです。
複数回のforkを通して網羅率の測定を行う必要がある為、「cobertura.ser」というファイルを
かいして測定を行います。

ところがforkする毎にcobertura.serに、シリアライズ・デシリアライズを行うためのその
オーバーヘッドが大きくなります。実際topコマンド確認するとCPUバウンドな処理である事が分かります。


そこでant 1.6.2より導入された、forkmodeを設定します。

<junit fork="yes" forkmode="once" dir="${basedir}">
....

上記の設定により、junitを実行するプロセスは"一度だけ"起動される為、シリアライズ・デシリアライズのオーバーヘッドが無くなり、非常に高速にテストの実行が可能です。

2011年7月21日木曜日

(小ネタ)DBにはトランザクションがありますが

今回は超初心者向け

webでシステムを構築していると、「データ登録をしてメール送信」とか「データ登録してファイル削除」等の処理が必要になる場合があります。そこで

transaction do
  yyy処理      ①
  delete_files ②
  db登録処理   ③
  commit
end

とか

transaction do
  xxx処理    ①
  sendmail   ②
  db更新処理 ③
  commit
end

の様な処理をすると問題大有りです。何故でしょう?

...

データベース処理は通常ロールバックが可能ですが、「ファイルシステムへの変更」や、「送信してしまったメール」は元に戻す事ができません。

# ①②が正常に処理された後に、③で例外が発生した場合取り返しが付かない

よって上記の巻き戻し不能処理をする場合は、できるだけ処理の一番最後にもってくるべきです。例えば:
transaction do
  xxx処理
  db更新処理
  commit
end

sendmail

(小ネタ)IEの512byte問題。圧縮転送したらさらに問題

■概要

通常HTTPリクエストに対して成功した場合は、HTTPレスポンスコード200で返答します。しかし「ページが見つからない場合」は通常404で返します。

しかしカスタマイズした404.html等を準備し、レスポンスコード404で返しても、IEは下記のような残念な画面を表示される事があります。



準備したhtmlを表示したいので何とかする必要があります。

■説明

IEには特別な仕様があり、「HTTPエラーメッセージを簡易表示する(デフォルトはオン)」というオプションの機能により「512byte」以下のContent-Lengthで返信した場合は文字通り「簡易表示」してしまいます。

またこの仕様はたちの悪い事に「圧縮転送後のサイズが512byte」になります。よってgzip転送を行うとそれなりのサイズで返信しても「簡易表示」される可能性があります。

よって
  • 無駄な内容を追加して圧縮後も512byteを超えるようにする
  • 圧縮転送設定を上記の場合はオフにする
のような対処がいります。

■参考

http://neta.ywcafe.net/000558.html

2011年7月12日火曜日

(小ネタ)saとかsysとかdbaとかadministratorとかrootとか本番環境で使ってはダメ

分かっちゃいるけど表題の様なユーザ・ロールを、OS/DBレベルで利用しているケースをみます。
上記のユーザを利用してしまうと

・セキュリティ上潜在的に問題がある
・権限上強力すぎて、通常ユーザに許可されていない行為に気づかない(例 ファイルの書き込み)
・(oracleの)dbaを利用すると「exportされたdumpは、dbaロールを持った人でしかimportできない」という十字架を背負う
・見る人が見たら、考察が甘い事がばれる => 信用を失う
...etc

色々簡単にすましたいのは分かりますが、「正しく理解しないと後で後悔する」ので、一般ユーザ・ロールを利用してあるべき姿で設計しましょう!

(小ネタ)webアプリケーションからの時間取得について

webアプリケーションで、現在日時を取得する場面は多いと思います。

その際注意しなければいけないのは、「どこから時間を取得するか?」という事です。

通常考えられる時間の取得場所は

・DBサーバ(sql文からsysdate等の関数を利用して)
・APサーバ(プログラム開発言語からOS時間を取得)

になると思いますが、アプリケーション側からの一貫した時間取得方法を明確にしておく事は大きな意味があります。

1. インフラレベルで時間が同期できていなかったら?という観点から時間を取得する場所は一定にするべき

無論ntpdで同期しておけば...という話がありますが、意外と合っていない場合があります。

2. テストの観点から「日時を固定、もしくは作為的にある日に設定」をしなければいけない場合がある

業務上の特定日(例 締め日)に固定し、テストしたい事は良くあります。
その場合も日時の取得方法を一貫性のある設計にしておけば、簡単に問題解決を図る(例 mock化)事ができます。

上記を確実に実施する為に、下記のような事を考えておけば良いのではないでしょうか。

・どこでどのように時間を取得するかアーキテクチャ上明確にし規約化する
・規約に反した時間取得方法を行っているプログラムを抽出する為に、規約チェックツールを作成する
  例) / SYSDATE/等でgrepする

ではでは

2011年7月6日水曜日

rails on oracle (oracleで接続する手順) その2

■概要

rails on oracle でいくつかのテーマで検証してみます

●fetchループ

module FetchTest
  SQL = "select p1.* from posts p1 cross join posts p2"
  def self.run
    Post.delete_all
    200.times do |i|
      Post.create(:title => "title_#{i}", :body => "body_#{i}")
    end
    open("test.txt", "w") do |f|
      # ① Post.find_by_sql(SQL).each { |r| f.write "#{r}\r\n" }
      # ② Post.connection.raw_connection.exec(SQL) { |r| f.write "#{r}\r\n" }
    end
  end
end

FetchTest.run

上記の場合①は通常の検索処理。sqlの実行結果を全てメモリ上に展開します。
②の場合、fetchループで繰り返し処理が発生する為、"同時に必要となるメモリ"は少なくてすみます。

# それぞれ実行しながら、topコマンド等でメモリ利用状況を確認しましょう。

よって大量のcsv出力する場合は必ず②のようにする必要があります。

●DROP

railsで実装する場合、割と頻繁にDBスキーマの全削除、再作成をするかと思います(うちだけ?)。その際の機能は充実してきており、adapter内で多数のDROP文が実装されています。

但し適宜 purge recyclebin してあげないとゴミがたまりそうです。

●indexの表領域

性能向上の為、データ/インデックスを別HDDに保存する場合、データ/インデックスで表領域を分ける必要があります。oracle_enhanced_adapterは成長しており、migrationで

add_index :posts, :title, :tablespace => 'foo'

のように指定できます。

●varchar2問題

oracleのvarchar2/char2は歴史的な経緯で、"バイト単位"での長さ指定になります。
よってrailsからスキーマ作成する際は、string => nvarchar2へのマッピングを以前はしていたのですが、最近のoracle enhanced adapterは進化しています!

lib/active_record/connection_adapters/oracle_enhanced_adapter.rb によれば、nls_length_semantics というオプションがデフォルトで'CHAR'となっており、このオプションを指定する事によってvarchar2/char2を文字数単位で長さ指定出来る様になります。

よって何も考えなくても、mysql等と同じように指定できる様になりました。
実際下記の用にスキーマが出来ています。
SQL> desc posts;
 名前                     NULL?    型
 -------------------------- -------- ----------------------------
 ID                         NOT NULL NUMBER(38)
 TITLE                               VARCHAR2(255 CHAR) <= CHARで255
 BODY                                CLOB
 CREATED_AT                          DATE
 UPDATED_AT                          DATE
●oracle enhanced adapter独自項目の設定 oracle enhanced adapterには独自の設定項目があり...と思ったら良いページがありました。一部ソースを読んだ結果下記の様です。
# config/initializers/oracle.rb 等に下記内容を設置
ActiveSupport.on_load(:active_record) do
  ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
    # NUMBER(1)をbooleanにマッピングするか
    self.emulate_booleans = true
    # DATE型を(rubyの)Date型にマッピングするか
    self.emulate_dates = false
    # カラム名が(^|_)date(_|$)とマッチする場合Date型にマッピングするか
    self.emulate_dates_by_column_name = false
    # カラム名が(^|_)idとマッチする場合Integer型にマッピングするか
    self.emulate_integers_by_column_name = true
    # CHAR(1)かVARCHAR(1)、またはカラム名が_FLAG/_YNで終了する場合Booleanにマッピングするか
    self.emulate_booleans_from_strings = true
    # シーケンサー作成する際のオプションを指定する
    self.default_sequence_start_value = "1 NOCACHE INCREMENT BY 1"
  end
end
●雑感
ソース見て思ったのですが、oracle enhanced adapter マジで進化してます!
かなりきっちりしてる感じです(識別子30文字制限問題、in句の1000上限問題 ...etc)

2011年7月2日土曜日

履歴管理システムの無視ファイル

cvs/subversion/git等、色々な履歴管理システムがありますが、いずれも "履歴管理しないファイル" を適切に指定する事が大切です。

# *.classを無視していない為、すごい事になっているチーム結構みた事あります  (;´д`)トホホ…

いつも調べてる様な気がしたので、これを気にまとめようかなと調べたら

...

かなり良いの発見しました!

github/gitignore

githubのチームが作成しています。色々な言語・フレームワークのgitignoreファイルサンプルがたくさんあります。ちなみにRails.gitignore

*.rbc
*.sassc
.sass-cache
capybara-*.html
.rspec
/.bundle
/vendor/bundle
/log/*
/tmp/*
/db/*.sqlite3
/public/system/*
/coverage/
/spec/tmp/*
**.orig
config/*.yml
rerun.txt
pickle-email-*.html

のようですね。確かに一通りそろってる感じ!

■追記

2011/08/04 下記もいるような気がする
/spec/reports/

後上記の設定ならdatabase.ymlも消えるので、database.yml.example => database.ymlへのコピーが手順的に入りますね!

2011年6月28日火曜日

rails on oracle (oracleで接続する手順) その1

■概要

railsをoracleで接続したい人はあまり多くないと聞いた & 最近の情報があまり見当たらないので、ニッチな人向けに纏めておこうと思います。

■Oracle XEのインストール

まずはサーバ側。既に何らかのOracleが存在する場合はここはパスして下さい。

・http://www.oracle.com/technetwork/database/express-edition/downloads/102xelinsoft-102048.html
ここからoracle-xe-univ-10.2.0.1-1.0.i386.rpm をダウンロード

※アカウントが必要なので先に登録して下さい。
※今日(2011/06/28)調べたらOracle XE 11.2 betaなる文字が!!

・↑をインストール

$ sudo rpm -ivh oracle-xe-univ-10.2.0.1-1.0.i386.rpm
$ sudo /etc/init.d/oracle-xe configure

↓管理アプリのポートを聞かれてます。他とバッティングする場合は変えましょう(例 9080)。
Specify the HTTP port that will be used for Oracle Application Express [8080]:

↓Oracleの待ち受けポートです。通常はdefaultのままで良いでしょう。
Specify a port that will be used for the database listener [1521]:

↓SYSとSYSTEMのパスワードをきかれてるので適当な物にしましょう。
Specify a password to be used for database accounts. Note that the same
password will be used for SYS and SYSTEM. Oracle recommends the use of
different passwords for each database account. This can be done after
initial configuration:

↓OS起動時に起動するかと言われてるので、通常稼動環境ではyです。
Do you want Oracle Database 10g Express Edition to be started on boot (y/n) [y]:

・確認

$ sudo /etc/init.d/oracle-xe status

■Oracle Instance Clientのインストール

続いては接続側。できるだけ接続側は綺麗にしておきたいので、Oracle Instance Clientから接続しようと思います。

・http://www.oracle.com/technetwork/jp/topics/index-099943-ja.html より対応するバージョンをダウンロード
oracle-instantclient11.2-basic-11.2.0.1.0-1.x86_64.rpm
oracle-instantclient11.2-devel-11.2.0.1.0-1.x86_64.rpm
oracle-instantclient11.2-sqlplus-11.2.0.1.0-1.x86_64.rpm
をダウンロードしてみました。

・↑をインストール

$ sudo rpm -ivh oracle-instantclient11.2-basic-11.2.0.1.0-1.x86_64.rpm
$ sudo rpm -ivh oracle-instantclient11.2-devel-11.2.0.1.0-1.x86_64.rpm
$ sudo rpm -ivh oracle-instantclient11.2-sqlplus-11.2.0.1.0-1.x86_64.rpm

・環境変数に下記を設定

$ vim ~/.bash_profile
export LD_LIBRARY_PATH=/usr/lib/oracle/11.2/client64/lib/:$LD_LIBRARY_PATH
export NLS_LANG=JAPANESE_JAPAN.AL32UTF8
export PATH=/usr/lib/oracle/11.2/client64/bin/:$PATH

$ source ~/.bash_profile

・接続確認

$ sqlplus system/パスワード@サーバのIP:1521/XE

■railsから接続

・ruby-oci8のインストール

$ gem install ruby-oci8 -v2.0.6
(Oracle Instance Clientの場合は、LD_LIBRARY_PATHを設定しておけばコンパイルに支障が無いようです)

・rails試しに作成

$ rails new oracle_test -d oracle
$ cd oracle_test
$ vim Gemfile
ruby-oci8の辺りを↓のように記載
gem 'ruby-oci8', '~> 2.0.6'
# gem 'activerecord-oracle_enhanced-adapter', '~> 1.3.2'
gem 'activerecord-oracle_enhanced-adapter', '~> 1.3.2', :git => 'git://github.com/rsim/oracle-enhanced.git' (1.3.2以降にユーザー作成機能が追加されていますので以後はそれで実行しています)

$ bundle install
$ vim config/database.yml
development:
adapter: oracle_enhanced
database: //サーバのIP:1521/XE
username: kennyj_development
password: xxxxx

test:
adapter: oracle_enhanced
database: //サーバのIP:1521/XE
username: kennyj_test
password: xxxxx

production:
adapter: oracle_enhanced
database: //サーバのIP:1521/XE
username: kennyj_production
password: xxxxx

$ rake db:create:all (userまで作ってくれます!)
Please provide the SYSTEM password for your oracle installation
>xxxxx (development)
Please provide the SYSTEM password for your oracle installation
>xxxxx (test)
Please provide the SYSTEM password for your oracle installation
>xxxxx (production)

$ rails g scaffold Post title:string body:text (いつもの奴生成)
$ rake db:migrate
== CreatePosts: migrating ====================================================
-- create_table(:posts)
-> 0.0349s
== CreatePosts: migrated (0.0350s) ===========================================

$ rake test (test実行)

$ rails s
(ブラウザで確認)
http://localhost:3000/posts

■さらに使いやすく...

次回はもう少し濃い話題(fetchループ, nvarchar, 表領域, oracle_enhanced特有の初期設定...etc)を書ければと思います。

■雑感

oracle_enhanced異様に進化している気がする...

2011年6月22日水曜日

(小ネタ)本番のみロードバランサーがある場合の注意点

方式設計小話。

webシステムを開発し、本番運用に至る過程で、下記のうち最低2つの環境があるのではと思います(所属している組織によって呼び方は色々変わるかと思いますが)。

・個人開発環境
・結合環境
・ステージング環境
・本番環境

上記の様に複数の環境があるにも関わらず、予算の都合上、「本番のみ複数台の冗長化構成。フロントにロードバランサー」という構成が多いのではと思います。そしてこの事実の与えるインパクトを過小評価してしまいトラブルが起こる場合があります

ロードバランサー&冗長化というキーワードを聞いた場合は、下記を必ず早期に確認しなければいけません。

No.1 セッション情報の引継ぎ。ロードバランサー担当者にお願いする事項は無いか
No.2 SSL通信の解除はどこが行うか?
No.3 どうやってhttp/httpsを見分けるか?
No.4 サーバ名は正しく返せるか?
No.5 ファイルの置き場所

順次説明していきます。

○No.1

ruby on railsの様に、クライアント側に実質状態維持の責務を移している場合は全く問題がありません。
しかしJ2EEでのHttpSessionでは大問題になります。HttpSessionは、各々のサーバのメモリー上に保持される為、2台以上サーバをたてる場合はこのメモリーの共有化がネックになります。
セッションのレプリケーションや永続化による回避方法もありますが、性能上大きな問題になる可能性もあります(機械的なセッションのシリアライズ、デシリアライズは意外にコストがかかる為)。
よってロードバランサーの設定で、「jsessionid」等のcookie内でのキーを利用し、片系に固定してしまうという手が良く使われます。

# 但しこれでは "本当の意味で"冗長化&高可用性にはならないですよね。

いずれにしろ、この課題に対してどう取り組むかは早期に決定されるべきです。

○No.2

ロードバランサーにも二種類有り
・SSL通信を自力で複合化するタイプ
・SSL通信は素通りさせるタイプ
があります。

SSL通信を複合化する責務はどこにあるのか(ロードバランサー or webサーバ)は早期に明確にする必要があります。

○No.3

webアプリケーションには、http通信でよい部分と、https通信でなければいけない部分があります。もしNo.2のロードバランサーにてSSL(https)通信が複合化される場合、webサーバ自身は、httpなのかhttpsなのか分からなくなる場合があります。

この問題がクリティカルになる可能性も大きい為、「どうやって見分けるのか?」(例 httpヘッダーに特定のキーが含まれる)を早期に確認しておくべきです。

○No.4

絶対パスとしてurlを生成する際、ホスト名が必要になります。その場合サーバ自身のサーバ名を利用して生成されると、冗長化構成の各サーバでバラバラになってしまう恐れがあります。

絶対パス生成の仕組みは早期に確認しておくべきです。

○No.5

各サーバからファイルシステムとして共用されるべき物が、どのような方式(例 NFSマウント)で実現されているかを確認する必要があります。

# これは意識している人が多いですが。

----

小ネタの割にはずいぶん長くなってしまいました。。
ある意味常識的な話題ですが、割とあいまいにしている人が多いので戒めの意味も込め記載させて頂きました。

(小ネタ)何故例外メールのアドレスや件名をDBに持ってはいけないのか?

設計小話。

webシステムを設計する際、メール送信の仕組みをデータベース化し、文面のテンプレート化、From/To/Cc/Bccの管理を行うようなケースは多いと思います。より抽象化したメッセージング機構とかがあっても良いかもしれません。
画面でGUIを設けてあげると、顧客自身でメンテナンスする事ができる為大変喜ばれるでしょう。

しかし例外メール(a.k.a. 障害報告メール。railsでいうExceptionNotifier)の送信先とかを、データベースに持たせる事は絶対にしてはいけません。

何故でしょう?
(分からない方は1分程度考えてみてください)

.
..
...
....
..... 終了!

例外メールが送信されるべき場合は
・プログラムバグに起因する想定外の動きをした場合
・ネットワーク、DBのトラブルに起因する設計時点より備えなければいけない場合
前者は特に問題になりませんが、後者が重要です。

DB障害が発生した

例外メールを出さなければいけない

送信先を取得する為に、DBを参照しなければいけない

接続できない!

送信できない!!!

# エンジニアはこういう小学生でも分かりそうなミスを時折犯してしまう場合があります。

環境に応じて、事前に顧客と合意したアドレスにシンプルに送信するのが吉だと思います。
場合によっては記載内容を吟味しないとセキュリティ的にまずい場合があるので気をつけて下さい。

# 当たり前な事ですが割りと質問が多いので記事にしておきます。
# ログを見張る別の系を用意できる場合は今回のケース外という事で(汗)

2011年6月21日火曜日

俺gem java_binをバージョンアップした

■概要

拙作gem、java_bin を約1年ぶりに更新しました!
















ダウンロードは http://rubygems.org/gems/java_bin からどうぞ!
...て何者か良く分からないので少し説明します。

■java_binとは

全文検索サーバの有名なプロダクトの一つに、Apache Solr があります。内部ではLuceneライブラリ(ツイッター内部でも利用されている)を利用しており、一説では white house のサイトでも利用されているようです。

アプリケーション側からSolrへの通信は、httpプロトコルで行いますが、その際のレスポンスフォーマットを、複数の中から選択する事ができます(wtパラメータ)。

通常rubyから利用する場合は、wt=ruby もしくは wt=jsonを利用する事になりますが、wt=javabin を利用する事により、バイナリフォーマットを指定する事ができます。しかしrubyでは解釈する事が出来ない為、C言語拡張を利用しparserを作成しました。







バイナリフォーマットを利用する事により、下記の点が期待できます。

・parseコストの削減
・通信量の削減
・型に応じたインスタンス化

実際parseコストに関しては、3~6倍程度の高速化が実現できています。

■今回何が新しくなったのか

0.4.0では下記の点で新しくなっています。

・solr 3.1以降のjavabin format2に対応
・RailsInstallerでのコンパイルサポート

0.3.5を一年近くプロダクションで利用してきましたが、無問題でした!
rubyからsolrを利用する際に、高速化したい!というニッチなお悩みをお持ちの方は是非 ^o^

2011年6月16日木曜日

gemやmavenで気をつけるべき事

gem(ruby)や、maven(java)を利用し始めると、

「何て便利なんだ!」

と感じると思います。実際非常に便利です。

しかし何年にも渡って運用するようなシステムの場合、「ハード故障に伴うリプレース」や「インフラ移行」が発生する場合があります。
その際に絶対忘れてはいけないのは、

「ライブラリ提供側が、いつまでも場所を提供してくれると考えてはいけない」

という事です。

実際gemで起こった事があるのは、

・githubが提供場所では無くなった(gemcutterへ移行?)
・gems.rubyonrails.orgが提供場所では無くなった(gemcutterへ移行?)
・sqlserverのアダプターの特定のバージョン(1.0.0.9250)が取得できなくなった。
  現在は http://rubygems.org/gems/activerecord-sqlserver-adapter/ ですが欲しいバージョンがありません。

と、かなり慌ててしまう状況です。

# さらに性質が悪いのがmaven。
# 私が出くわしたのは「リポジトリはあるがライブラリの該当バージョンが提供されなく無くなった」状況。
# なんと「*.jarの中身をテキストエディタで見ると、503のHTML」。
# しかもエラーを出力しないという難しい状況になります(どのファイルが無くなったのか分からない)

よってgemの場合cacheディレクトリ以下のgemファイルはちゃんととっておきましょう!」というのが教訓になります。もちろんアプリ側ではversionを絶対指定しておく」というのも大事ですね。

もちろんrailsの場合、vendor配下にコミットしたら良いのでは?という意見もあるかもしれませんが、度々デプロイする場合、純粋にサイズが大きすぎて遅い場合があります。
コンテキストに合わせて最良の方法を考えましょう。

rails自体の開発環境を作る4(完結)

■概要

さくらvpsを再インストールし下記を目標に再構築した際の記録を残しておきます。

・サーバとして一般的に行う設定がなされている事
・最低限のサーバセキュリティを維持している事
・rails自体の開発出来る事
・node.js、coffeescriptが動く事

■パッケージ管理・導入(centos5.5 x86_64版 さくらvps)

●パッケージ最新化
# yum -y update

●当面必要そうなパッケージをインストール
# yum -y install vim-enhanced nmap curl

●webサーバ/DB系をインストール
# yum -y install httpd httpd-devel
# yum -y install sqlite sqlite-devel
# yum -y install mysql mysql-server mysql-devel
# yum -y install postgresql84 postgresql84-contrib postgresql84-devel postgresql84-libs postgresql84-server

●rpmforgeの設定を行う
# wget http://packages.sw.be/rpmforge-release/rpmforge-release-0.5.2-2.el5.rf.x86_64.rpm
# rpm -i rpmforge-release-0.5.2-2.el5.rf.x86_64.rpm
# rm rpmforge-release-0.5.2-2.el5.rf.x86_64.rpm
# vim /etc/yum.repos.d/rpmforge.repo
(8行目)
enabled = 1 から enabled = 0 に変更(デフォルトで動かないようにする)

●gitインストール
# yum -y install git --enablerepo=rpmforge

●memcachedインストール
# yum -y install libevent libevent-devel
(素直に依存パッケージが解決しないので手動でインストールする)
# wget http://packages.sw.be/perl-Net-SSLeay/perl-Net-SSLeay-1.36-1.el5.rfx.x86_64.rpm
# wget http://packages.sw.be/perl-Net-SSLeay/perl-Net-SSLeay-1.36-1.el5.rfx.i386.rpm
# wget http://packages.sw.be/perl-IO-Socket-SSL/perl-IO-Socket-SSL-1.34-1.el5.rfx.noarch.rpm
# rpm -Uvh perl-Net-SSLeay-1.36-1.el5.rfx.x86_64.rpm
# rpm -Uvh perl-Net-SSLeay-1.36-1.el5.rfx.i386.rpm
# rpm -Uvh perl-IO-Socket-SSL-1.34-1.el5.rfx.noarch.rpm
# rm *.rpm
# yum -y install memcached memcached-devel --enablerepo=rpmforge

参考 http://nakoruru.jp/?p=439

●その他開発用パッケージのインストール
# yum -y install openssl-devel curl-devel readline-devel zlib-devel
# yum -y install java
# yum -y install libxml2 libxml2-devel libxslt-devel
# yum -y install libyaml-devel libffi-devel --enablerepo=rpmforge

●sqlite3が古いので自分でインストール
# wget http://www.sqlite.org/sqlite-autoconf-3070603.tar.gz
# tar zxvf sqlite-autoconf-3070603.tar.gz
# cd sqlite-autoconf-3070603
# ./configure
# make
# make install

※yumでインストールした物と競合しているはず。
ただyumでの依存関係があるので、あえて競合してインストールしてみた。

■設定

●日本語化
# vim /etc/sysconfig/i18n
(1行目)
LANG="C" から LANG="ja_JP.UTF-8" に変更

●不要デーモンの停止設定
# chkconfig auditd off
# chkconfig autofs off
# chkconfig avahi-daemon off
# chkconfig bluetooth off
# chkconfig cups off
# chkconfig firstboot off
# chkconfig gpm off
# chkconfig haldaemon off
# chkconfig hidd off
# chkconfig kudzu off
# chkconfig lvm2-monitor off
# chkconfig mcstrans off
# chkconfig mdmonitor off
# chkconfig messagebus off
# chkconfig netfs off
# chkconfig nfslock off
# chkconfig pcscd off
# chkconfig portmap off
# chkconfig rawdevices off
# chkconfig restorecond off
# chkconfig rpcgssd off
# chkconfig rpcidmapd off
# chkconfig smartd off
# chkconfig xfs off
# chkconfig yum-updatesd off

参考 http://tanaka.sakura.ad.jp/archives/001065.html

●firewall設定
# system-config-securitylevel-tui
(firewallをenabledにし、22/80/443/3000/10022(後述)/8080(後述)を開けた)

●作業用ユーザ作成
# useradd kennyj
# passwd kennyj

●sudo設定
# /usr/sbin/visudo
(一番下に追記)
kennyj  ALL=(ALL)       ALL

●ssh設定
# vim /etc/ssh/sshd_config
(13行目)
#Port 22 => Port 10022 (22番は非常に攻撃されやすいので)
(39行目)
#PermitRootLogin yes => PermitRootLogin no
(59行目)
#PermitEmptyPasswords no => PermitEmptyPasswords no

# /etc/init.d/sshd restart

●firewall再設定
# vim /etc/sysconfig/iptables
(22番ポートを閉じる)

●時間同期設定(ntp)
(さくらVPSでは最初から設定されていました)
# yum -y install ntp
# vim /etc/ntp.conf
# ntpdate ntp.nict.jp (とりあえず一旦近づけておく)
# /etc/init.d/ntpd start
# chkconfig ntpd on

参考 http://centossrv.com/ntp.shtml

●postgresql準備
# service postgresql initdb

●mysql準備
# vim /etc/my.cnf
(文字列エンコーディングをUTF-8に設定)
[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
user=mysql
old_passwords=1
default-character-set = utf8

[mysqld_safe]
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
default-character-set = utf8

[mysql]
default-character-set = utf8

●必要に応じてサービス起動設定
# chkconfig httpd on
# chkconfig mysqld on
# chkconfig postgresql on
# chkconfig memcached on

●一旦再起動
# reboot

※これ以降はkennyjユーザで作業しています。

■javascript関係

●node.js/npm/coffeeスクリプトのインストール
$ wget http://nodejs.org/dist/node-v0.4.8.tar.gz
$ tar zxvf node-v0.4.8.tar.gz
$ cd node-v0.4.8
$ ./configure
$ make
$ sudo make install
$ cd ..
$ curl http://npmjs.org/install.sh > npm.sh
$ chmod a+x npm.sh
$ sudo PATH=/usr/local/bin:$PATH ./npm.sh
$ sudo PATH=/usr/local/bin:$PATH npm install -g coffee-script
$ rm npm.sh

■ruby関係

●rvmのインストール
$ bash < <(curl -s https://rvm.beginrescueend.com/install/rvm)
$ source ~/.bash_profile
$ rvm pkg install iconv
$ rvm install 1.9.2 --with-iconv-dir=$rvm_path/usr
$ rvm install ree   --with-iconv-dir=$rvm_path/usr
$ rvm install 1.8.7 --with-iconv-dir=$rvm_path/usr
$ rvm install jruby
$ rvm use --default 1.9.2

●railsインストール
$ rvm 1.8.7,1.9.2,ree,jruby gem install rails --no-rdoc --no-ri

※jruby1.6.1では落ちたので注意
  arel2.0.10がNG。jruby1.6.2では直っています
  http://jira.codehaus.org/browse/JRUBY-5581
  https://github.com/jruby/jruby/commit/dd1d9382a72af181e9d3998f2680154ebf1e651c

●sassのインストール
$ rvm 1.8.7,1.9.2,ree,jruby gem install sass --no-rdoc --no-ri

■rails自体の開発環境

●mysqlの設定
$ sudo /etc/init.d/mysqld start
$ mysql -u root
mysql> GRANT ALL PRIVILEGES ON activerecord_unittest.*  to 'rails'@'localhost';
mysql> GRANT ALL PRIVILEGES ON activerecord_unittest2.* to 'rails'@'localhost';

●postgresqlの設定
$ sudo /etc/init.d/postgresql start
$ sudo -u postgres createuser --superuser $USER

●gitの設定
$ git config --global user.name "kennyj"          # 名前
$ git config --global user.email kennyj@gmail.com # メールアドレス

●railsの取得とテスト
$ git clone git://github.com/rails/rails.git
$ cd rails
$ bundle install
$ cd activerecord
$ rake mysql:build_databases
$ rake postgresql:build_databases
$ cd ..
$ rake test

●railsを修正しパッチを作成する
$ git checkout -b xxxxx # ブランチ名
(ソース修正)
$ git commit -a -m "コミットメッセージ"
$ git checkout master
$ git pull
$ git checkout xxxxx
$ git rebase master
$ git commit -a
$ git format-patch master --stdout > xxxxx.diff
$ cat xxxxx.diff

■更新記録

2011/06/23 ntpについて追記
サービス起動設定について追記

2011/08/02 rvm package -> rvm pkgに変更

ツイッターの基本構造をクラスモデリングする

■概要

今さらながら、ツイッターの基本構造をクラスモデリングしてみました。あまり新しい気付きはありませんな~

■まずはユーザー関係

・ユーザーは、他のユーザーを複数人フォローする事ができる。


・ユーザーは、他のユーザーをリストとしてまとめる事ができ、複数のリストを持つ事ができる。
リストにはプライバシー設定ができ、公開/非公開を選べる。



# 多対多の表現が一定していないのは秘密です(汗)
# 後公開/非公開は完全区画での継承で表現してみました。

・ユーザーは、他のユーザーの公開しているリストをフォローする事ができる。



■次はツイート関係

・ユーザーは、複数回ツイートを行う事ができる。



・ユーザーは、ツイートに返信できる。



・ユーザーは、ツイートをお気に入り登録する事ができる。



・ユーザーは、ツイートをリツイートする事ができる。



■ここまでで、気に入っていない所

・「フォローする・されるが」、ユーザーとリストに重複してあるのが今いち?
  「フォローされる物」という概念を持ち込むべきか?
・公開・非公開を完全区画の継承で表現したが。。
・リツイートの構造
・ダイレクトメッセージの取り扱い。ツイートとはだいぶ性質が異なる。
・クラス図が描ける軽量のUMLツールって、今何が人気なんだ?

2011年5月22日日曜日

rails自体の開発環境を作る3

■概要

rails3をモジュール別にテストしていますが、activerecordだけはDBの準備があるので少し面倒そうです。

■activerecord

○sqlite3

$ cd activerecord
$ RUBYOPT=-W0 rake test_sqlite3

......
2986 tests, 9101 assertions, 0 failures, 0 errors, 2 skips

とくに問題なさそうだ。

○mysql & postgresql

まずはデータベースの準備

# yum install mysql mysql-devel mysql-server
# yum install postgresql84 postgresql84-contrib postgresql84-devel postgresql84-libs postgresql84-server

bundlerが、"DB無し設定"なので、再取得する。

$ rm .bundle/config
$ bundle install (adapterのコンパイルが成功するはず)

mysqlの下準備は、ユーザの権限付与とDB構築

# /etc/init.d/mysqld start
$ mysql -u root
mysql> GRANT ALL PRIVILEGES ON activerecord_unittest.* to 'rails'@'localhost';
mysql> GRANT ALL PRIVILEGES ON activerecord_unittest2.* to 'rails'@'localhost';
$ cd activerecord
$ rake mysql:build_databases

postgresqlの下準備は、DB作成とユーザ周り

# service postgresql initdb
# /etc/init.d/postgresql start
$ sudo -u postgres createuser --superuser $USER
$ cd activerecord
$ rake postgresql:build_databases

で、テストを実行

$ RUBYOPT=-W0 rake test
......
2977 tests, 9114 assertions, 0 failures, 0 errors, 2 skips

お、動いた!!(postgresqlで2つ程落ちてますが。。)

■次回予告

ここまでの作業を纏めて、目指せコントリビュータ!な感じにしようと思います。

2011年5月21日土曜日

論理データベース設計(ER図)のチェックリスト

■概要

職業がら、顧客と会話しデータベースの論理設計をする事が良くあります。
自分で設計する分には良いんですが、他人が設計した物を効率的にチェックする為に備忘録がてら記載しておきます。

ちなみに当方は「渡辺幸三さんの著作」と「アナリシスパターン」の影響を一番受けています。

■チェック項目
○キーについて
・データの一意性が、利用者(顧客)と意識があっているか
・PKの無いテーブルはないか
・UKが明示されているか
・多すぎる複合キーはないか
・自然キーとサロゲートキーを意識できているか
・ID / コード・番号を使い分けているか
・FKを意識できているか
・FK/区分との使い分けは考えられているか
・属性項目のドメインを規定する物なので使い分けの基準が重要

○属性について
・ドメイン管理を意識して属性名を付けているか
・継承項目、導出項目が明示されているか
・継承・導出ルールが明示・意識されているか
・時点記録を意識して導出項目を設計しているか?

○多重度・関係について
・多重度が間違っていないか
・1-1関連が存在しないか
・"0以上"なのか"1以上"なのか考察できているか
・親子と参照を意識して使っているか
・派生関係を意識できているか

○正規化について
・繰り返し項目が除去できているか(もしくは意識できているか?)
・PKからの関数従属性のみになっているか
・キー組み合わせについてデータが表現出来ているか

○その他について
・インスタンスレベルでイメージできているか
・インスタンスにするべき概念がテーブルになってないか
・パラレルに重複した構造・関係はないか
・プログラムとの連携を意識できているか
・知識・操作レベルを意識できているか
・データのライフサイクルを意識できているか
・区分が設計されているか
・状態の管理が意識できているか
・人間はミスをするという事を意識できているか
・業務にキャンセルがありえる事を意識できているか

■雑感

思いつくまま記載しても結構あるな。思い出したら追加していこう。。

2011年5月18日水曜日

rails自体の開発環境を作る2

■概要

前回とりあえず環境が出来たのでモジュール毎にtestcaseを実行していきます。

■actionmailer

$ cd actionmailer
$ RUBYOPT=-W0 rake test
......
Finished in 7.010759 seconds.

181 tests, 487 assertions, 0 failures, 0 errors, 0 skips

全部OKだ!

■actionpack

$ cd ../actionpack
$ RUBYOPT=-W0 rake test
......
Skipping MemCacheStoreTest tests. Start memcached and try again.

どうもmemcachedが必要そうです。

# yum install libevent libevent-devel
# cd /usr/local/src
# wget http://memcached.googlecode.com/files/memcached-1.4.5.tar.gz
# tar xvfz memcached-1.4.5.tar.gz
# cd memcached-1.4.5
# ./configure
# make
# make install
# cp /usr/local/src/memcached-1.4.5/scripts/memcached.sysv /etc/init.d/memcached
# vim /etc/init.d/memcached
(memcachedコマンドが見つからないと言われるので、/usr/local/bin/memcachedとした)
# /etc/init.d/memcached start

で再度挑戦

$ RUBYOPT=-W0 rake test
...F......F...
Finished in 68.901053 seconds.

1) Failure:
test_default_params(TestRoutingMapper) [test/dispatch/routing_test.rb:1448]:
<"home"> expected but was
.

2) Failure:
test_non_greedy_regexp(TestRoutingMapper) [test/dispatch/routing_test.rb:1791]:
expected but was
.

3117 tests, 14823 assertions, 2 failures, 0 errors, 0 skips
二つほど落ちているが、一旦無視する。

2011/05/22追記 => 今日見たら全部OKでした。

■activemodel

$ cd ../activemodel
$ RUBYOPT=-W0 rake test
......
Finished in 1.524920 seconds.

492 tests, 1323 assertions, 0 failures, 0 errors, 0 skips

全部OKだ!

■activeresource

$ cd ../activeresource
$ RUBYOPT=-W0 rake test
......
Finished in 3.307448 seconds.

284 tests, 877 assertions, 0 failures, 0 errors, 0 skips

※いくつかDEPRECATIONが出ていました。

2011/05/22追記 => 今日見たら出てませんでした。

■activesupport

$ cd ../activesupport
$ RUBYOPT=-W0 rake test
......
Finished in 7.790476 seconds.

2477 tests, 10526 assertions, 0 failures, 0 errors, 0 skips

全部OKだ!

■railties

$ cd ../railties/
$ RUBYOPT=-W0 rake test
......

すごく時間がかかりますが...3つ程落ちました。。

■activerecord

...次回に続く...

2011年5月17日火曜日

rails自体の開発環境を作る1

■概要

そういえばrails自体の開発環境を作成したい場合どうしたら良いのか?
http://guides.rubyonrails.org/contributing_to_ruby_on_rails.htmlを参考にcentos5.xに環境を作成してみる。

■環境確認&準備

バージョン確認
$ ruby -v
ruby 1.9.2p180 (2011-02-18 revision 30909) [x86_64-linux]

必要なプロダクト等インストール
$ sudo yum install libxml2 libxml2-devel libxslt-devel
$ sudo yum install sqlite sqlite-devel
$ sudo gem install bundler

■railsを取得&テスト実行

$ git clone git://github.com/rails/rails.git
$ cd rails
$ bundle install --without db
$ rake test (全体をテスト。但しmysql/mysql2/postgresql依存は落ちます)

なおモジュール単位でのテストの実行は(例 actionpack)

$ cd actionpack
$ rake test

で良いようです。ちなみにwarningが発生しているので

$ RUBYOPT=-W0 rake test

とすればwarningを抑えられます。続く...

■雑感

う~む、意外にwarningが結構多発しているんですね(AM以外は何かしら発生します)。
後memcached用意した方が良さそうですね。。

2011年5月7日土曜日

rails 3.1 RC4の変更点を翻訳してみた

こちらで公開されているrails3.1 beta1の変更点を翻訳してみました。

(更新)2011/07/20 rails3.1 RC4にしました。

作業途中&超適当翻訳ですが、無いよりはましだと思うので公開します。
埋め込み方がいまいち良くわからないので、こちらへどうぞ。
(凹まない程度の)ツッコミ募集中です!

# どなたか綺麗に埋め込む方法教えてください。↓実験中。

2011年4月22日金曜日

javascriptでのCIを目指して、phantomjsの環境を作成する4

■概要

前回までで、phantomjsが大体わかったので、jasmine with CoffeeScriptな環境を作成します。

■説明

・CoffeeScriptのコンパイルを楽したい。ここではwatchrを利用してみます。

$ sudo gem install watchr

$ mkdir demo; cd demo
$ mkdir src; mkdir spec
$ vim coffee.watchr

watch(/(src|spec)\/.*\.coffee/) { |md|
  system("coffee -cb #{md[0]}")
}

$ watchr coffee.watchr

(別の端末で)

$ vim src/test.coffee

console.log "a"

$ ls src
test.coffee test.js <= できてます!

・ではjasmineを利用してブラウザからテストを実行します。OOPでは古典的な"カウンター"を作ってみます。
$ wget http://pivotal.github.com/jasmine/downloads/jasmine-standalone-1.0.2.zip
$ unzip *.zip
$ rm *.zip
$ vim spec/CounterSpec.coffee

describe "Counter", ->
  beforeEach ->
    @counter = new Counter()

  describe "#constructor", ->
    it "の初期値は0である", ->
      expect(@counter.value()).toEqual(0)

  describe "#incr", ->
    it "は値を1増加させるはず", ->
      @counter.incr()
      expect(@counter.value()).toEqual(1)

$ vim src/Counter.coffee

class Counter
  constructor: () ->
    @count = 0

  incr: () ->
    @count++

  value: () ->
    @count

$ vim SpecRunner.html (Counter.js/CounterSpec.jsの読み込みを記載する)

ブラウザで http://localhost/demo/SpecRunner.htmlを確認するといい感じです!。
最後にphantomjsからjasmineのSpecRunner.htmlを起動します。
$ vim src/run-jasmine.coffee

if phantom.state.length is 0
  if phantom.args.length isnt 1
    console.log 'Usage: run-jasmine.js URL'
    phantom.exit()
  else
    phantom.state = 'run-jasmine'
    phantom.open phantom.args[0]
else
  window.setInterval ->
    if document.body.querySelector('.finished-at')
      console.log document.body.querySelector('.description').innerText
      for el in document.body.querySelectorAll('div.jasmine_reporter > div.suite.failed')
        console.log ''
        for e in el.querySelectorAll('.description')
          console.log e.innerText
      phantom.exit()
  , 100

$ phantomjs src/run-jasmine.coffee http://localhost/demo/SpecRunner.html
2 specs, 0 failures in 0.039s <= テスト結果が得られてます。いいじゃん!

2011年4月21日木曜日

javascriptでのCIを目指して、phantomjsの環境を作成する3

■概要

前回に引き続きphantomjsのQuickStartを参考にCoffeeScriptを書いてみる。

■説明

・Loading & Rendering

Loadingの説明を見ているといくつかポイントが見えてきます。

・コマンドライン引数に指定されたスクリプトは、複数回呼び出される
※phantom.exit()するまで
・phantom.stateは値が維持されるので、初回呼び出しかどうか判断可能
初回は空文字列
・phantom.openは引数で指定されたページを読み込む
読み込み終了までblockingされる
・phantom.loadStatusはphantom.open後の状態が保持される
"success" もしくは "fail"

という訳で、LoadPage(とRendering)をCoffeeScriptで書いてみます。

$ vim google.coffee

LoadPage = (name, url, action) ->
  if !phantom.state
    phantom.state = name
    phantom.open url
  else
    action()
    phantom.exit()

LoadPage "google", "http://www.google.co.jp", ->
  phantom.render "#{phantom.state}.png"

$ phantomjs google.coffee
$ ls google*
google.coffee google.png

ちゃんと出来ました!なおphantom.viewportSizeの設定をしないとwidth/heightは適当な値になるようです














ちなみにdom treeは読み込み後documentオブジェクトを操作できます。

LoadPage "google", "http://www.google.co.jp", ->
  console.log document.getElementById("prm").innerText

$ phantomjs google.coffee
世界中から寄せられた日本へのメッセージが日本語で見られるサイトを開設しました。


■参照

phantomjsのQuickStart

■雑感

次回は本当にCIができたらな~と思いつつ(ゴールは近いはず)

2011年4月20日水曜日

javascriptでのCIを目指して、phantomjsの環境を作成する2

■概要
前回 phantomjsのインストールに成功したので、色々触って見ます。

・ちなみにphantomjsってCoffeeScriptいけるんですね!

$ phantomjs

Usage: phantomjs [options] script.[js|coffee]
  [script argument [script argument ...]]

・次からはphantomjsのQuickStartを、CoffeeScriptに書き写しながら進めます。

■説明

・まずはhello world

$ vim hello.coffee

console.log "hello, world!"
phantom.exit()

$ phantomjs hello.coffee
hello, world!

・次に同期sleep

$ vim delay.coffee

for t in [10..1]
  console.log t
  phantom.sleep 1000
console.log 'BLAST OFF'
phantom.exit()

$ phantomjs delay.coffee
10
9
...
1
BLAST OFF

・ちなみに非同期sleepは

$ vim adelay.coffee

fibs = [0, 1]

ticker = window.setInterval ->
  console.log fibs[fibs.length - 1]
  fibs.push fibs[fibs.length - 1] + fibs[fibs.length - 2]
  if fibs.length > 10
    window.clearInterval ticker
    phantom.exit()
, 300

$ phantomjs adelay.coffee
1
1
2
...
21
34
functionオブジェクトが最後の引数ではない場合ここにカンマ何ですね。。

・コマンドライン引数は

$ vim arguments.coffee

if phantom.args.length is 0
  console.log 'Try to pass some args when invoking this script!'
else
  phantom.args.forEach (arg, i) -> console.log "#{i}: #{arg}"
phantom.exit()

$ phantomjs arguments.coffee
Try to pass some args when invoking this script!

$ phantomjs arguments.coffee a b c
0: a
1: b
2: c

次回は、感じのページオープンとdom tree周り経由でCIにつなげたいと思う今日この頃

■参考

phantomjsのQuickStart

■雑感

CoffeeScriptの構文は、あまり違和感が無いな~
CoffeeScriptを習得するには、js -> coffeeの翻訳が一番?

■更新履歴

2011/04/20 http://tech.kayac.com/ を参考にさせて頂き、setIntervalの記載方法を変更しました。

2011年4月13日水曜日

javascriptでのCIを目指して、phantomjsの環境を作成する1

■概要
qunit-tapとproveを使ってJSの単体テストのCIをする方法に感化されて、まずは環境づくりに挑戦してみます。

■インストール手順

centos 5.5に環境を作るまでの手順です。

○phantomjsをインストール

http://code.google.com/p/phantomjs/wiki/BuildInstructionsのコメントとかも参考にしながら進めました。

# vim /etc/yum.repos.d/atrpms.repo

[atrpms]
name= CentOS-$releasever - ATrpms
baseurl=http://dl.atrpms.net/el$releasever-$basearch/atrpms/testing/
gpgcheck=1
gpgkey=http://ATrpms.net/RPM-GPG-KEY.atrpms
enabled=1

# rpm --import http://packages.atrpms.net/RPM-GPG-KEY.atrpms
# yum install qt47-devel qt47-webkit qt47-webkit-devel

# git clone git://github.com/ariya/phantomjs.git && cd phantomjs
# qmake-qt47
# make
# cp bin/phantomjs /usr/local/bin (PATHの通ってる場所にコピー)

○phantomjsの確認

http://code.google.com/p/phantomjs/を参考に起動を確認しようと思いました

# vim test.js

if (phantom.state.length === 0) {
    phantom.state = 'pizza';
    phantom.open('http://www.google.com/m/local?site=local&q=pizza+in+new+york');
} else {
    var list = document.querySelectorAll('div.bf');
    for (var i in list) {
        console.log(list[i].innerText);
    }
    phantom.exit();
}

# phantomjs test.js
2011-04-13T00:00:32 [WARNING] phantomjs: cannot connect to X server


うーん問題あるみたいですねぇ。

○xvfbの準備

どうもxvfbをインストールして仮想画面を立ち上げないとphantomjsは使えない感じなのでインストール&起動します。

# yum install xorg-x11-server-Xvfb xorg-x11-fonts*
$ Xvfb :2 -screen 0 800x600x24 2> /dev/null &
$ export DISPLAY=:2.0
$ phantomjs test.js

Adrienne's Pizza Bar Restaurant
54 Stone Street, New York, NY
(212) 248-3838 -

John's Pizzeria
278 Bleecker St, New York, New York
(212) 243-1680 -

...


おーなんだか動いてますね!

○/dev/null 指定しないと、何か変なエラーメッセージが出てるんですけど..

FreeFontPath: FPE "built-ins" refcount is 2, should be 1; fixing.
Could not init font path element unix/:7100, removing from list!

=> fontの設定関係の問題っぽいのですが、どうも解決策がわかりません。。とりあえずほっています

○Xvfbどうやって止めたらいいんだ?

$ kill -9 `cat /tmp/.X2-lock`; rm -f /tmp/.X2-lock

こんな感じかな?

2011年4月6日水曜日

indexを作成すると本当に更新性能は下がるのか?

■概要
RDBMSの参照性能を向上させる為に、indexを作成する事は常識だと思います。
ところが「indexを張りすぎると更新性能が悪くなる」と言われており、理屈的にも理解できます(データおよびindexを更新する必要があるから)。
しかし実際どの程度劣化するのか知りたいので、実験してみたいと思います。

■前提
・さくらVPS 512Mコース
・CentOS 5.x
・mysql 5.0.77
・利用テーブル(下記の様なテーブルで順次indexを作成しなおしテストした)
drop table test_table;
create table test_table (
  id integer auto_increment not null
, v1 varchar(30) not null
, v2 varchar(30) not null
, v3 varchar(30) not null
, v4 varchar(30) not null
, v5 varchar(30) not null
, primary key(id)
);
--create index v1_index on test_table(v1);
--create index v2_index on test_table(v2);
--create index v3_index on test_table(v3);
--create index v4_index on test_table(v4);
--create index v5_index on test_table(v5);
・テストデータ 10万件insert (下記の様なrubyスクリプトを作成し出来上がったinsert.sql文ファイルを実行した)
def random_string(len)
  (0...len).map{ ('a'..'z').to_a[rand(26)] }.join
end

open("insert.sql", "wb") do |f|
  f.write("truncate table test_table;\r\n")
  f.write("set @time:=now();\r\n")
  (0...100000).each do |i|
    v1 = random_string(30)
    v2 = random_string(30)
    v3 = random_string(30)
    v4 = random_string(30)
    v5 = random_string(30)
    vals = "'" << [v1, v2, v3, v4, v5].join("','") << "'"
    f.write("insert into test_table (v1, v2, v3, v4, v5) values (#{vals});\r\n")
    puts i
  end
  f.write("commit;\r\n")
  f.write("select timediff(now(), @time);\r\n")
end

$ mysql -u xxx < insert.sql

■結果

index数 1回目 2回目 3回目 4回目 5回目 平均処理時間(秒)
0
15
20
14
10
14
12.6
1
19
18
16
15
14
16.4
2
15
14
18
18
15
16.0
3
18
14
17
20
21
18.0
4
23
18
18
19
18
19.2
5
19
22
19
24
20
20.8



■結論

確かに下がるには、下がるようだ。。という訳でセオリー通りindexの張りすぎには注意が必要のようです。

ただ全体的にy = ax + bの一次関数likeな上昇なので、"+b"がどこに消費されるのかが気になるところです(sql文のparse?)

2011年3月31日木曜日

最近のOSでsubversion1.4.xがコンパイルしたい

■概要

ubuntu 10.4等最近のOSで、subversion1.4.xをconfigure => make => make install しようとするとconfigureの段階で問題が発生します。

$ tar zxvf subversion-1.4.6.tar.gz 
$ cd subversion-1.4.6
$ ./autogen.sh
...
configure.in:146: warning: LTOPTIONS_VERSION is m4_require'd but not m4_defun'd
build/libtool.m4:67: LT_INIT is expanded from...
build/libtool.m4:102: AC_PROG_LIBTOOL is expanded from...
configure.in:146: the top level
configure.in:146: warning: LTSUGAR_VERSION is m4_require'd but not m4_defun'd
configure.in:146: warning: LTVERSION_VERSION is m4_require'd but not m4_defun'd
configure.in:146: warning: LTOBSOLETE_VERSION is m4_require'd but not m4_defun'd
Creating configure...
configure.in:146: warning: LTOPTIONS_VERSION is m4_require'd but not m4_defun'd
build/libtool.m4:67: LT_INIT is expanded from...
build/libtool.m4:102: AC_PROG_LIBTOOL is expanded from...
configure.in:146: the top level
configure.in:146: warning: LTSUGAR_VERSION is m4_require'd but not m4_defun'd
configure.in:146: warning: LTVERSION_VERSION is m4_require'd but not m4_defun'd
configure.in:146: warning: LTOBSOLETE_VERSION is m4_require'd but not m4_defun'd
configure:5904: error: possibly undefined macro: m4_ifval
      If this token and others are legitimate, please use m4_pattern_allow.
      See the Autoconf documentation.
configure:8391: error: possibly undefined macro: _LT_SET_OPTIONS
configure:8391: error: possibly undefined macro: LT_INIT
...

どうもlibtoolの変更に伴う問題みたいなので(仕方なく)調べました。

■解決方法

参考ページによると*.m4系の内容が別々のファイルに分離したのが問題っぽいので乱暴ながら一時的に一つにするといいみたいです。

$ cd /usr/share/aclocal
$ sudo cp libtool.m4 libtool.m4.org
$ sudo chmod 666 libtool.m4
$ sudo cat lt~obsolete.m4 ltoptions.m4 ltsugar.m4 ltversion.m4 >> libtool.m4

これで下記の手順でビルド&インストールできます!
$ cd subversion-1.4.6
$ ./autogen.sh
$ ./configure
$ make
$ sudo make install

■参考ページ

https://bugs.launchpad.net/ubuntu/+source/php5/+bug/262251