buildって何だろう?

ActiveRecordのアソシエーションを扱うメソッドで「build」というのを初めて知りました。

class Group < ActiveRecord::Base
  has_many :users
end

class User < ActiveRecord::Base
  belongs_to :group
end

というアソシエーションがあったときに、

@group.users.build params

みたいな感じで書くことができます。

@group.users << user

みたいなコードは save まで実行されてしまうので、場合によっては build の方がいいかと。

belongs_to についての build なら

@user.group.build params

だけじゃなくて

@user.build_group params

という書き方もできるようです。

参考:
Rails new vs build http://vinhboy.com/blog/2009/01/15/rails-new-vs-build/

ActionViewとActionControllerでurl_forの挙動が違います

URLを出力するためにつかう「url_for」というメソッドがあります。
root_path, root_url, XXXX_path, XXXX_urlなどroutes.rbで定義したURLを呼び出すメソッドでも内部的に使っています。

このurl_forやXXXX_urlですが、Controllerの中で呼ぶときとViewの中で呼ぶときで挙動が違います。
Viewで呼ぶ時はURLがエスケープされたものが返ってくるのです。

Controllerでは

http://localhost:3000/controller/action?a=1&b=2

となるところがViewだと

http://localhost:3000/controller/action?a=1&amp;b=2

となります。

普通にブラウザを相手にする場合は問題ないですが、
ActionMailerで送信するメールの文面の場合は不具合の素になるので注意が必要です。
url_forの引数に「:escape => false」を渡すとエスケープされずに返ってきます。

file_columnで受信メールの添付画像を保存する方法

前提
Statusモデルにimageというフィールドがあって、そこがfile_columnを採用している。
メールを受信したら添付ファイルをstatus.imageに格納したい。


(1)file_columnの一部を拡張します。
vendor/plugins/file_column/lib/file_compat.rb
を以下のように変えます。
(- で始まる行が元々のコード、+ ではじまるのが変更後のコードです。)

   module FileCompat # :nodoc:
     def original_filename
-      File.basename(path)
+      @original_filename || File.basename(path)
     end
+
+    def original_filename=(name)
+      @original_filename = name
+    end


(2)StatusMailerというActionMailerを作る
メールを受信するためのクラスです。
中身はこんな感じ。

class StatusMailer < ActionMailer::Base  
  def receive(email)
    Status.parse_mail(email)
  end
end

メールを受信するための.forwardファイルは下記のような感じ。

RAILS_ROOT/script/runner -e production 'Status.receive(STDIN.read)'
(3)Statusモデルの中に本体を実装
  def self.parse_mail(email)

    # email.to を解析して 対象の status を割り出す。
    status = ....

    # 添付画像を取得する
    if email.multipart?
      email.parts.each do |m|
        if m.main_type == 'image'
          # file_columnへの引き渡し
          status.save_image_from_mail m
          break
        end
      end
    end
    status
  end

  def save_image_from_mail(mail_part)
    # ランダムな文字列に拡張子を付けた形でファイル名を作る
    tmp_filename = Digest::SHA1.hexdigest( rand.to_s ).slice(1..10) + '.' + mail_part.sub_type

    # Tempfileを作って、データを格納する
    tmp = Tempfile::new tmp_filename
    tmp.puts mail_part.body

    # file_columnへの引き渡し準備
    tmp.extend FileColumn::FileCompat
    tmp.original_filename = tmp_filename

    self.image = tmp
    self.save

    # 後片付け
    tmp.close
  end
(4)動作確認メーラーの適当なメールを.emlとかテキストフォーマットで保存する。 ・script/consoleを起動 ・以下を実行
$ f = open '.emlファイルのパス'
$ StatusMailer.receive(f.read)

QRコードを画像で書き出す

rmagickがインストール済みを前提。

$ gem list | grep rmagick
rmagick (2.12.2)

QRコードのもとになる配列を作ってくれるgemを入れる。

gem install rqrcode

QRコードの配列を画像にしてくれるモジュールを入れる。

git clone git://github.com/hal99/qrimage.git lib/qrimage
mv lib/qrimage/qrimage.rb lib/qrimage.rb
rm -rf lib/qrimage

そのままだとwarningが大量に発生したので(他の箇所でもrmagickを使っているから?)
qrimage.rbの中で

require 'rmagick'

require 'RMagick'

に書き変えた。(なぜこれでwarningが消えるかは謎。)

例えばコントローラーに下記のアクションを作っておくと、QRコードを動的に作って返す。

  def qr_image
    image = QRImage::create_qr root_url, :size => 6
    send_data image, :type => 'image/jpeg', :filename => 'qr_image.jpg'
  end

special thanks : RubyでQRコードを作る(画像編)

acts_as_taggable_on_steroidsの使い方

インストールする(githubに引っ越してるから注意)

ruby script/plugin install git://github.com/jviney/acts_as_taggable_on_steroids.git
ruby script/generate acts_as_taggable_migration
rake db:migrate

Manualモデルというのをtaggableにするとして

Manualモデルには

  acts_as_taggable

と書く。

routes.rbには

  map.resources :manuals do |manual|
    manual.resources :tags, :collection => { :remove => :delete }
  end

と書く。
(/manuals/:manual_id/tags のURLで受け入れアクションが生成される。)

コントローラーはこんな感じ。

class TagsController < ApplicationController
layout nil

  before_filter :load_manual

  def index
    @tags = @manual.tag_list
  end

  def create
    keys = params[:name].gsub(/ /," ").scan(/\S+/)
    @tags = @manual.tag_list.add(keys)
    @manual.save
    render :action => 'index'
  end

  def remove
    @tags = @manual.tag_list.remove(params[:name])
    @manual.save
    render :action => 'index'
  end

private

  def load_manual
    @manual = Manual.find(params[:manual_id])
  end

end

views/tags/index.html.erbはこんな感じ。

<div id="tag">
  <% form_remote_tag :update => "tag", :url => manual_tags_url(@manual) do %>
    <h3>キーワード</h3>
    <div><%= text_field_tag 'name' %><%= submit_tag 'キーワードをつける' %></div>
  <% end %>
  <ul>
    <% @tags.each do |tag|%>
      <li><%=h tag %>
      <%= link_to_remote( '[削除]',:update => "tag",
        :url => remove_manual_tags_url(@manual,:name => tag), :method => :delete ) %>
      </li>
    <% end %>
  </ul>
</div>

以上です。

PostgreSQL + Ludia + PostGIS な環境作り

MacPortsだと Ludia の環境が PostgreSQL 8.1 しか見つからず、PostGISは 8.2 or 8.3 しかありませんでした。
PostGISよりLudiaの方が手動でインストールするのが面倒だったので、Ludia を port install して PostGISの方は手動でインストールすることにしました。


PostgreSQL 8.1 + Ludia

インストール

sudo port install postgresql81-ludia

DBの領域作り

sudo mkdir -p /opt/local/var/db/postgresql81/defaultdb
sudo chown postgres:postgres /opt/local/var/db/postgresql81/defaultdb
sudo su postgres -c '/opt/local/lib/postgresql81/bin/initdb -D /opt/local/var/db/postgresql81/defaultdb'

サーバ起動

sudo su postgres -c '/opt/local/lib/postgresql81/bin/pg_ctl -D /opt/local/var/db/postgresql81/defaultdb start'

データベースにLudia用の関数とか作成

psql81 -f /opt/local/share/postgresql81/pgsenna2.sql -U postgres project_development


PostGIS

インストール

wget http://postgis.refractions.net/download/postgis-1.3.6.tar.gz
tar zxvf postgis-1.3.6.tar.gz
cd postgis-1.3.6
./configure --with-pgsql=/opt/local/lib/postgresql81/bin/pg_config
make
sudo make install

PostGIS用の関数などをデータベースに定義

createlang plpgsql -U postgres project_development
psql81 -f /opt/local/share/lwpostgis.sql -U postgres project_development