複数バージョンのrubyを共存させるツールとして、過去にはRVMを使っていたが少し前にrbenvに変えた。
Rails3世代になり、RVMのgemsetはbundlerで代替可能になったので、RVMの豊富な機能が少し邪魔に感じたので。同時に、サーバーサイドでも管理が簡単なのでrbenvを使うようにした。あまり推奨はされてないようだけど。

rbenvの通常インストール方法はsstephenson/rbenv – GitHubにあるのでそちらを参照するとよい。

また、rbenvで使うrubyインストーラーであるsstephenson/ruby-build – GitHubも一緒に使うとすごく便利。

以下、サーバーで行った作業のログをメモ。


# rbenvインストール
$ git clone git://github.com/sstephenson/rbenv.git /usr/share/rbenv

# profile.dにrbenvの設定を追加
$ vi /etc/profile.d/rbenv.sh
# ここから
export PATH="/usr/share/rbenv/bin:$PATH"
export RBENV_VERSION=ree-1.8.7-2011.12
export RBENV_DIR=/usr/share/rbenv
export RBENV_ROOT=/usr/share/rbenv
eval "$(rbenv init -)"
# ここまで

# /etc/profile.d/rbenv.shに実行権限を与える
$ chmod 755 /etc/profile.d/rbenv.sh

# 一旦上記設定を読み込む
$ source /etc/profile.d/rbenv.sh

# ruby-buildインストール
$ git clone git://github.com/sstephenson/ruby-build.git
$ cd ruby-build
$ ./install.sh

# 今回はREE1.8.7をインストール
$ rbenv install ree-1.8.7-2011.12

# REE1.8.7をデフォルトで使用する
$ rbenv global ree-1.8.7-2011.12

と、ここまででrbenvのセットアップは完了。動作確認のために、一度シェルを再起動した上でrubyのバージョンを確認してみる。

$ ruby -v
ruby 1.8.7 (2011-12-28 MBARI 8/0x8770 on patchlevel 357) [i686-linux], MBARI 0x8770, Ruby Enterprise Edition 2011.12

正しくREEが使われることが確認できた。あとは、gemでrailsとunicorn_railsをインストールすればよい。

rbenv上のREEでunicornを起動するには、以下のようなスクリプトを作ればよい。

app_root=/var/www/rails_application
rbenv_bin=/usr/share/rbenv/versions/ree-1.8.7-2011.12/bin
cd $app_root && $rbenv_bin/bundle exec $rbenv_bin/unicorn_rails -c $app_root/config/unicorn.conf.rb -e production $app_root/config.ru

普段はUpstartで自動起動などをしているが、その場合 /etc/init/以下に次のような設定ファイルを作ればいい。これは、アプリケーションごとに1つのファイルになるので、例えばアプリケーション名がRailsAppならばRailsApp.confなどとする。

#!/bin/sh
description "Rails Application"
start on runlevel [2345]

script
  app_root=/var/www/rails_application
  rbenv_bin=/usr/share/rbenv/versions/ree-1.8.7-2011.12/bin
  cd $app_root && $rbenv_bin/bundle exec $rbenv_bin/unicorn_rails -c
  $app_root/config/unicorn.conf.rb -e production $app_root/config.ru >> /tmp/upstart.log 2>&1
end script

respawn

このアプリケーションの起動や終了はLinuxの場合ディストリビューションによって変わるが、大体の場合以下のようなコマンドで実行できるので自分の環境に最適なものを選択する。

# 起動
$ sudo service railsapp start
# または
$ sudo initctl start railsapp

# 終了
$ sudo service railsapp stop
# または
$ sudo initctl stop railsapp

UpstartはUbuntu10.04、CentOS6、Amazon Linuxなどで使える。従来の/etc/init.d/以下にあるようなスクリプトよりだいぶシンプルに書けるので積極的に使っている。他にもいろいろとメリットがあるらしいが、正直よくわからない。

 

Ruby on Rails 3.1.0が正式リリースされたので、早速3.1.0RCで作っていたアプリをアップデートしたところproduction環境でassets pipelineが上手く動かなくなったので。

現象としては、stylesheet_link_tagやjavascript_include_tagで出力されるファイルのURLが、RC6だと

<link href="/assets/application-9bfec9e77b13d0aae452fa8f1b785be2.css" media="screen" rel="stylesheet" type="text/css" />
<script src="/assets/application-773d7f39b6aab9114a3b3b034fe6ac17.js" type="text/javascript"></script>

だったのが、stableだと

<link href="/assets/application.css" media="screen" rel="stylesheet" type="text/css" />
<script src="/assets/application.js" type="text/javascript"></script>

になってしまうため、assets:precompileで予めassetsファイルを作る設定であったproduction環境において、cssファイルもjsファイルも見つからずにかわいそうなことになるわけです。
それどころかrake assets:precompileもコケる事態になってました。

これは、3.1.0RC8あたりでassets pipelineに関する変更がいくつか取り込まれた関係で設定項目が増え、デフォルトの挙動が変更になったからですね。詳細はgithubのrailsリポジトリのissuesを見てください。

肝心の修正方法ですが、config/environments/production.rbの14~17行目あたりを以下のように変えるだけでOKでした。

# Don't fallback to assets pipeline if a precompiled asset is missed
config.assets.compile = true

# Generate digests for assets URLs
config.assets.digest = true

さて、3.1.0がリリースされて2日目ですが、早くもpjax_railsが取り込まれると噂の3.2.0が待ちどおしいです。

 

Rails3からhas_and_belongs_to_manyより柔軟なhas_many thorughというRelationshipができたので試す。ブログの記事を登録する際に、セレクトボックスで複数のカテゴリを選択する場合を想定している。コントローラーやviewについて記述が無い箇所は、scaffoldで生成したコードと同等のもので動くと思う。

まずはmigrationを使ってテーブルを作る。

# ブログ記事
class Posts < ActiveRecord::Migration
  def change
    create_Table :posts do |t|
      t.string :title
      t.text :body
      t.timestamps
    end
  end
end

# カテゴリ
class Categories < ActiveRecord::Migration
  def change
    create_Table :categories do |t|
      t.string :name
      t.timestamps
    end
  end
end

# ブログ記事とタグを繋ぐ中間テーブル
class Categorizations < ActiveRecord::Migration
  def change
    create_Table :categorizations do |t|
      t.references :post, null: false
      t.references :category, null: false
      t.timestamps
    end
  end
end

中間テーブルのカラム名を複数形にしてしまい少しハマッたので注意。次にモデルを定義する。

# ブログ記事のモデル
class Post < ActiveRecord::Base
  has_many :categorizations, dependent: :destroy
  has_many :categories, through: :categorizations
end

# カテゴリのモデル
class Category < ActiveRecord::Base
  has_many :categorizations, dependent: :destroy
  has_many :posts, through: :categorizations
end

# 中間テーブルのモデル
class Categorization < ActiveRecord::Base
  belongs_to :post
  belongs_to :category
end

最後に、ブログ記事の新規登録画面のviewを少し書き換える。

<%= form_for @post do |f| %>
  <%= render '/shared/validation_messages', :model => @product %>
    <div class="field">
      <%= f.label :name %><br/>
      <%= f.text_field :name %>
    </div>
    <div class="field">
      <%= f.label :body %><br/>
      <%= f.text_area :body %>
    </div>
    <div class="field">
      <%= f.label :category_ids %><br/>
      <%= f.select :category_ids, Category.all.map {|c| [c.name, c.id]}, {}, multiple: true %>
    </div>
  </div>
    <div class="actions">
      <%= f.submit 'Launch this product' %>
    </div>
  </div>
<% end %>

これを使うまで知らなかったんだけど、フォームの項目名を複数系にすることでmultipleな入力も受け付けてくれるようなのですごく簡単にこういったことができる。

 

http streamを使ったchunked uploadのとき、webrickとかだとスルーされる。なのでvalums file uploaderとかpluploadとか使ってるとparamsにHttp::UploadedFileが渡されず、ファイル名だけポツンとそこにいて大変寂しい思いをする。

そういうときはrack middlewareを自前で書いてたんだけど、githubでいいのを見つけた。
newbamboo/rack-raw-upload
使い方は簡単で、Gemfileに以下の一行を追加して

gem 'rack-raw-upload', git: 'git://github.com/newbamboo/rack-raw-upload.git'

RAILS_ROOT/config/initializers/rack_raw_upload.rbみたいなものを作って

Rails.application.config.middleware.use Rack::RawUpload

とするだけでよい。

そうするとparams[:file]にHttp::UploadedFileが渡されるようになるので、そのままpaperclipなりcarrierwaveなりが指定するようにparams[:file]をゴニョゴニョするとうまくいく。

ところで今日、Rails3.1RC6がリリースされ、同時にRails3.1のリリース予定日が8/30と発表されたので要チェックです。

 

開発サーバー再構築したらruby+mongoidなアプリが軒並み動かなくなったので。
Ubuntu Server 10.04 LTSにaptから入れるmongodbは1.2.2なんだけど、今rubygemsで最新のmongoid2.0.2はmongodb1.6.0以降にしか対応してないので動くわけがない。
mongodbにはDebian/Ubuntu用の公式aptリポジトリがあるのでそれを使うと簡単です。

Ubuntu and Debian packages – MongoDB

手順は以下の通り。
もともと入ってるmongodbはアンインストールしておきましょう。

$ sudo su -
# apt-key adv --keyserver keyserver.ubuntu.com --recv 7F0CEB10
# echo "deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen" > /etc/apt/source-list.d/10gen.list
# aptitude update
# aptitude install mongodb-10gen

最後にインストールできたか確認する。
2011年7月27日現在だと以下のようになるはず。

$ mongod --version
db version v1.8.2, pdfile version 4.5

Rails3.1RC4でassetsがRoutingError(404 Not Found)になったら

 Rails  コメントは受け付けていません。
7月 262011
 

Rails3.1が楽しすぎて調子にノッてたらちょいハマリしたので。
Rails3.1RC4とSprockets2.0.0.beta.11以降の相性が悪いらしく、これらのバージョンを使うとassets/以下のcssやjsが全てRoutingErrorで404 Not Foundになってしまいます。
sprocketsのissueには上がっていないので不確実ですが、bundle update等してしまうと発症する模様です。
幸いSprockets2.0.0.beta.10以前なら問題なく動きますのでGemfileを以下のようにすれば解決します。

# RAILS_ROOT/Gemfile
gem 'sprockets', '= 2.0.0.beta.10'

Rails3.1楽しすぎるのでみんなやるといいと思う。

 

railsは3からしか触っていないので以前がどうだったか知りませんが、rails3では独自のvalidationを作るのも簡単でよいです。
試しに、入力の書式を検証するFormatsValidatorというクラスを書いてみました。

# RAILS_ROOT/app/validators/formats_validator.rb
class FormatsValidator < ActiveModel::EachValidator

  def validate_each(record,attribute,value)
    begin
      r = __send__ options[:type].to_s, value
    rescue
      raise "Invalid option (type) is specified."
    end

    unless r
      record.errors[attribute] << (options[:message] || I18n.t('activerecord.errors.messages.invalid'))
    end
  end

  private
  # e-mail address
  def email(value)
    # 値を評価する式
  end
end

これをRAILS_ROOT/app/validators以下に配置して、modelから以下のように呼び出す。

class User < ActiveRecord::Base
  validates :mail_addr, :presence => true,
    :length => { :maximum => 255 }, :formats => { :type => :email }
end

modelのvalidatesメソッドの引数optionsはEachValidator::initializeに渡されるので、validate_eachでoptionsを参照することができます。
もとは1validatorにつき1クラスという想定っぽいですが、正規表現1行で使えるようなvalidatorはまとめてしまいたいときは使えそうですね。

 

昨日と逆にrubyでsmtp経由でメールを送信する。

require 'net/smtp'
Net::SMTP.start('smtp.example.com', 587, 'example.com', 'hoge@example.com', 'fugafuga', 'plain') {|smtp|
      smtp.send_mail "test", "hoge@example.com", "to@mail.addr"
}

相変わらず簡単ですね。

通常のsmtp認証の場合は上記でOKです。POP before SMTPの場合は、Net::POPにPOP認証だけ行って接続を切るauth_onlyというメソッドがあるので送信前にそれを実行すればよい。

Net::POP3.auth_only( 'pop.example.com', 110, 'hoge@example.com', 'fugafuga' )
Net::SMTP.start('smtp.example.com', 587, 'example.com') {|smtp|
      smtp.send_mail "test", "hoge@example.com", "to@mail.addr"
}

参考: net/smtp – Rubyリファレンス

 

これだけ。

require 'net/pop'
Net::POP3.start('example.com', 110, 'hoge@example.com', 'fugafuga') do |pop|
  unless pop.mails.empty?
    pop.mails.each do |m|
      # 受信
      p m.pop
      # 削除
      m.delete
   end
  end
end

非常にシンプルでよろしいですね。

実はrubyの公式リファレンスにはこういう簡単なsnippetも載ってたりして調子が良いです。

net/pop – Rubyリファレンスマニュアル

 

rvmには、rvm環境のバイナリをラップして非rvm環境から叩く為の仕組みがある。

解説:

Using RVM with God(本家)

rvm で unicorn(日本語解説)

なので、例えばrvm環境のrails runnerをcronから叩きたい時は、まずrvm環境で以下のようにスクリプトを作っておき、

# rvm wrapper ruby-1.9.2-p180@rails3 rails3 rails

できあがったスクリプトを非rvm環境から普通に叩けばよい。

# $HOME/.rvm/bin/rails3_rails runner "MailReceiver.receive_with_pop" -e production

便宜上$HOME/.rvm以下としてるけど、適宜読み替えてください。

ちなみに私は、rvm環境の何かを叩くためのスクリプトを該当アプリのRAILS_ROOT/script以下に作っておき、そのスクリプトをcronに登録するようにしている。

© 2011 sanojimaru.com Suffusion theme by Sayontan Sinha