Ruby on Rails: Merb

Rails 3

On December 23rd, we decided to end the duplication and the paradox of choice. That was the day we declared our intentions of bringing the best ideas of Merb into Rails 3. That was the day we announced our commitment to work together.

Rails3でMerbとマージするそうです。マジかよ!

大丈夫かな?何故かというと、技術的にホットなうちにマージして今と同じ機能を持つ段階に行かないと、コア開発者やサードパーティ開発者、ユーザーも飽きちゃって他の良さそうなのに移ると思うんだよね。

Railsって素早く簡単にアプリ作れるフレームワークって感じだけど実際には機能が多いフレームワークって感じがしてます。

「あの機能が無いんじゃなぁ~」

という感じで他に移れないということが多い。それだけ多くの機能をMerbベースに移行するのを素早くできるのかなというのが心配。

開発者の多さとか、コア開発力(量)がハンパ無く多いからそんな心配は要らないのかもしれない。

むしろ後から見たらプロジェクトの重さと開発力がこれ以上低下する前のベストなタイミングなのかもしれない。

個人的には一つのアプリでRailsのバージョンアップは諦めてるので3で滅茶苦茶変えてもらえると面白い。

プラグインAPIが決まるのは素晴らしいと思います。でも俺自身は面倒臭がりなのでAPI違反モンキーパッチをやり続けちゃうと思います・・・。

passengerでrailsのpaperclipプラグイン使ってて今日ひとつ失敗した。

開発環境(mongrel)では問題なかったのに本番環境(mod_passenger)上でアップした画像が見えない。

理由は、

「画像はアプリの所有者でアップされるのにapacheの権限で見に行こうとするから。」

Phusion Passenger users guide

8.1. User switching (security)

Phusion Passenger solves this problem by implementing user switching. A Rails application is started as the owner of the file config/environment.rb, and a Rack application is started as the owner of the file config.ru.

passengerではUser switchingといって、rootやapacheではなく、railsではconfig/environment.rb、rackアプリではconfig.ruファイルの所有者でプロセスを立ち上げてくれるそうです。

(嫌ならPassengerDefaultUserディレクティブで他のユーザーに指定できる。)

当然、アップするファイルはそのユーザーで保存される。

しかし、アップした画像やページキャッシュなどの静的ファイルはApacheが直接見に行くのでpaperclipのアップ時のデフォルトのパーミッション(0600)だと見れない。(開発環境では個人のユーザーでmongrelを立ち上げるで問題が出なかった。)

対処方法は色々あるけど、やはりpaperclipが0644で保存してくれるのが一番じゃないだろうか。

ファイルの作成をそもそも0644にしようと思ってみてみると、paperclipでは標準ライブラリのtempfileでアップしたファイルを保存し、それをmvしてる。tempfileが0600で作るのでそのままだ。

例によってconfig/initializers以下で上書きする。

config/initializers/paperclip.rb:

module Paperclip
  module Storage
    module Filesystem
      def flush_writes #:nodoc:
        logger.info("[paperclip] Writing files for #{name}")
        @queued_for_write.each do |style, file|
          FileUtils.mkdir_p(File.dirname(path(style)))
          logger.info("[paperclip] -> #{path(style)}")
          FileUtils.mv(file.path, path(style))
          FileUtils.chmod(0644, path(style))
          file.close
        end
        @queued_for_write = {}
      end
    end
  end
end

mvした後でchmodする。(10行目) もっと局所的に書き換えたかったけどメソッドに切り出されてないのでここを変えた。

今回、本番デプロイ時にtagもbrunchも切ってないというミスを犯していたのでこのファイルだけ手でアップするという荒業に出た。反省・・・。

githubって色んな機能がありますが、好きな機能のひとつが、READMEを置くと勝手に表示してくれる機能。(README.rdocとかでもいいみたいです)

github readme

これってRailsプラグインだと”“init.rbにプラグインインストール後にコンソールにREADMEを表示するように書いておく”“なんて慣習もあるので1人3役みたいでお徳な感じです。

プラグインやライブラリを探す時なんかはどうせプログラマー向けなんでREADMEさえ出てれば必要十分で、Wikiすら見なくていいので便利です。

このサイトもAutoPagerizeに対応してみた。

面倒かと思ったらwill_paginateプラグインは最初からrel=”“next”“が設定されてるのでスゴイ楽!やってよかった。

RailsのオススメファイルアップロードプラグインのPaperclipですが、最新版(2.1.4)で嬉しい機能が増えてました。

  • ImageMagick(convertコマンド)に自由にオプションを渡せるようになった。
  • 画像が無いときのパスを指定できるようになった。

これは嬉しい。ソース内のコメントの例では、アップされた画像をlargeとnegativeというstyleに分け、両方にprofileやexif情報を削除する-stripオプションを適応し、negativeには補色で補完する(つまり反転?)-negateオプションを適応してます。

class User < ActiveRecord::Base
  has_attached_file :avatar,
    :styles => {
      :large => "300x300",
      :negative => "100x100"},
    :convert_options => {
      :all => "-strip",
      :negative => "-negate"}
end

RMagickや他のAPIを使わず、直接convert使ってる利点が生かされてますな。 他にも-monochromeオプションでモノクロにしたりとか色々出来そうです。

画像無いときのパス指定は、「あれ?これ指定できないの?」ぐらいだったんで俺がすっきりしただけですが。

以前にも書きましたが、Capistrano利用時にはアップ画像を/public/system以下に置きたいのでオプション指定が必要です。

いちいち指定してたらModelがキモくなるので通常、下記を使ってます。

config/initializers/paperclip.rb:

module Paperclip::ClassMethods
  def has_attached_file_with_default(name, options = {})
    default = {
      :path => ":rails_root/public/system/:class/:attachment/:id/:style/:basename.:extension",
      :url => "/system/:class/:attachment/:id/:style/:basename.:extension",
      :default_style => :normal,
      :default_url => "/images/:class/:attachement/missing_:style.png" 
    }

    has_attached_file_without_default(name, default.update(options))
  end

  alias_method_chain :has_attached_file, :default
end

config/initializers/以下は一番最後(pluginよりあと)に読まれるのでこういうアプリの本質的でない部分の変更には向いてますな。

RMagickインストール地獄にハマってる方はぜひご検討ください! (Debian etch、CentOS 5.2共にimagemagick/ImageMagickパッケージを入れるだけで済みます)

参照: Capistrano利用時のPaperclip

Railsで検索を作る時ちょっと面倒臭いです。

EZ_Where, Where Plugin, CriteriaQueryなど、検索条件を書きやすくするプラグインがいくつかありますが、railswhereというプラグインは記述が簡潔で気に入りました。

timcharper’s railswhere at master ? GitHub

Where clause generator Building a complicated where clause made easy

インストール:

script/plugin install git://github.com/timcharper/railswhere.git

施設(Facility)の検索フォームで、

「都道府県」、「フリーワード」

で検索という場合、

where = Where.new

unless params[:prefecture_id].blank?
  where.and('prefecture_id = ?', params[:prefecture_id])
end

unless params[:word].blank?
  where.and(
    Where.new("name LIKE  ?", "%#{params[:word]}%").
        or("description LIKE ?",  "%#{params[:word]}%")
  )
end

@facilities = Facility.paginate(
  :page => params[:page],
  :conditions => where.to_s)

という感じです。(ちょw%%かよ!はスルーで)

パッと見だけで使い方がわかるのがうれしい。

※間違ってたので修正しました。簡単に条件を入れ子にできるのもいいですね。

fixtureってymlよりcsvの方が書きやすいと思いません? 項目の並びが気に食わなかったり、複雑な置換したり・・・。

っていうのは前に書きましたが、編集にはいちいちsamba経由でOpenOffice Calcを使っていて、なんだか不便に感じてました。

CUIのスプレッドシートアプリ無いのかな? → vimでCSV整形するプラグインあればいいんじゃないのか?

ということでCSVに限らず、高機能なテキスト整形ツールのAlignというプラグインを知りました。

インストール・設定:

http://www.vim.org/scripts/script.php?script_id=294 からスクリプトをダウンロード、解凍する。

解凍したAlign.vbaをvimで開く。

:so %

日本語で丁度良く整形されるように.vimrcに追記。

let g:Align_xstrlen = 3

CSVの整形:

row_fixture

整形したい部分を選択して、:Align (区切り文字)

result_fixture

あらやだ、キレイ。

こう揃ってるとボックスで選択していろいろやりやすくてうれし。

CSVの整形のみやってみましたが、TSVやさまざまなフォーマット、ソースコードの整形など便利な機能がまだまだあるみたいです。

これでCalcで開こうとしてsambaのキャッシュ(?)に騙されたりしなくて済みそうです。

参照: 高性能なテキスト整形ツールAlignの使い方 ? 名無しのvim使い

この間書いた、簡単テストライブラリShouldaのARマクロを(gettextの)日本語で使うプラグインshoulda_jaを下記に置いときます。

ruby script/plugin install http://svn.komagata.org/rails/plugins/shoulda_ja/

参照: 今日のハマリ – shouldaとgettext

簡単テストライブラリShouldaを使うと、RailsのModelのテストが楽だ。

class RegionTest &lt; ActiveSupport::TestCase
  should_require_attributes :name
  should_require_unique_attributes :roman
end

豊富なマクロのおかげでこんな感じに宣言的にテストが書ける。

でも単体でテストを実行したとき(ruby test/units/region_test.rb)はOKなのにrakeで実行すると(rake test:units)エラーになる。

 33) Failure:
test: Region should require name to be set. (RegionTest)
(略)
/blank/ not found in ["名前を入力してください。"] when set to nil.
&lt;nil&gt; is not true.

 34) Failure:
test: Region should require unique value for roman. (RegionTest)
(略)
/taken/ not found in ["ローマ字はすでに存在します。"] when set to "hokkaidou-touhoku".
&lt;nil&gt; is not true.

うーん・・・。

def should_require_attributes(*attributes)
  message = get_options!(attributes, :message)
  message ||= <strong>/blank/</strong>
  (略)
  end
end

ソースを見てみると、should_require_attributesメソッドはエラーメッセージを/blank/という正規表現で決め打ち比較してるみたい。(validates_presence_ofのデフォルトが”“can‘t be blank”“だから)

そういういい加減なの、嫌いじゃない。

environments読み込むとgettextがエラーメッセージを日本語化するのでエラーになっちゃうのか。

でも、

class RegionTest &lt; ActiveSupport::TestCase
  should_require_attributes :name, 
    :message =&gt; "名前を入力してください。" 
  should_require_unique_attributes :roman, 
    :message =&gt; "ローマ字はすでに存在します。" 
end

いちいちこんな風にテスト書かなきゃいけないんでは面倒過ぎてアメリカに亡命したくなる。

プラグインより後に何かを読み込むにはどうすりゃいいんだろう?

% tree vendor/plugins/shoulda_ja
vendor/plugins/shoulda_ja
|-- init.rb
`-- lib
    `-- shoulda
        `-- active_record_helpers.rb

良い方法とは思えないけど、プラグインはアルファベット順に読まれるはずなのでshoulda_jaというプラグインを置いて上書きしてみた。

init.rb:

require 'shoulda'
require File.dirname(__FILE__) + '/lib/shoulda/active_record_helpers'

lib/shoulda/active_record_helpers.rb:

module ThoughtBot
  module Shoulda
    module ActiveRecord
      def should_require_attributes(*attributes)
        message = get_options!(attributes, :message)
        message ||= <strong>/入力してください/</strong>
    (略)
      end

      def should_require_unique_attributes(*attributes)
        message, scope = get_options!(attributes, :message, :scoped_to)
        scope = [*scope].compact
        message ||= <strong>/すでに存在します/</strong>
        (略)
      end
    end
  end
end

うほ、動いた。

・・・・・・・・・アドホック!!!

月曜日なので一心不乱にコード(仕事の)。

画像アップするプラグインを前に目を付けていたPaperclipに変えてみた。

file_columnと同じでファイルに画像を保存する形式。で、attachment_fuと違って専用のテーブルを作らないタイプ。

うーん、ファイルに保存方式だとcapistranoでdeployしてるとき困るんだよな~。そんでsystem以下に保存するように変えたり。

とりあえず使ってみた。

class User < ActiveRecord::Base
  has_attached_file :avatar, 
    :styles => { :medium => "300x300>",
      :thumb => "100x100>" }
end

モデルにhas_attached_fileを定義。

class AddAvatarColumnsToUser < ActiveRecord::Migration
  def self.up
    add_column :users, :avatar_file_name, :string
    add_column :users, :avatar_content_type, :string
    add_column :users, :avatar_file_size, :integer
  end
  (略)
end

DBは専用のじゃなくて普通にそのモデルのテーブルに上記3種を追加。

<% form_for :user, :html => { :multipart => true } do |form| %>
  <%= form.file_field :avatar %>
<% end %>

アップするフォームはこんな感じ。

<%= image_tag @user.avatar.url %>
<%= image_tag @user.avatar.url(:medium) %>
<%= image_tag @user.avatar.url(:thumb) %>

画像表示もこんな風に深く考えなくてもいいようになってる。

あとImageMagickだけでRMagickが要らないっぽいです。そこは嬉しい。

で、file_columnでちょっと面倒だったファイルの保存場所関係はhas_attached_fileのオプションで結構柔軟っぽい設定ができそうでした。

:url => "/:attachment/:id/:style_:basename:extension" 
:url => "http://some.other.host/stuff/:class/:id_:extension"

シンプルなところが気に入りました。使ってこ。

参照: thoughtbot: Paperclip File Attachments