だるろぐ

2009-12-15

DataMapperのdm-validationsでnullを許可するかどうかが nullable => false から required => true になったようだ

| 21:40 | DataMapperのdm-validationsでnullを許可するかどうかが nullable => false から required => true になったようだ - だるろぐ を含むブックマーク

件名で全てを言い終えた。以下は補足。


DataMapperでは、 dm-validations を使う事で、モデルでスキーマの定義をしたときに同時にバリデーションも指定出来る。

例えばmysqlでいう

`name` varchar(255) NOT NULL default '',

は、以下のように表せる。

property :name, String, :nullable => false, :default => '', :length => 0..255

…というのは過去の話。

最近では上記はこう書く。

property :name, String, :required => true, :default => '', :length => 0..255

古い書き方をしていると、怒られる。

{:nullable=>false} is deprecated, use {:required=>true} instead

http://github.com/hirafoo/tiwa をいじってて気が付いた。

2009-11-22

SinatraとDataMapperとERBでサイト作ったのでソースを公開してみる

| 05:11 | SinatraとDataMapperとERBでサイト作ったのでソースを公開してみる - だるろぐ を含むブックマーク

最初に言っておくとrubyは全然分かりません。


また作って晒してみた。

サイトは http://tiwa.hirafoo.net/ 「違いがわかりません」です。

ソースは http://github.com/hirafoo/tiwa に。

以前の sinatraとActiveRecordとERBでBBS作ったのでソースを公開してみる - だるろぐ跡地 とあまり変わりません。


はい解説ゴー。


  • 基本

railsの構成を模したMVCスタイルで作成。要所要所でrailsのそれとは異なる。


人気の軽量wafです。

今回は最初、勉強がてらRackで作ろうとしてましたが、以下の理由により路線変更し、Sinatraを使うことに。

layout、partial機能をRackが備えていない

railssinatraでは、layout、partialと呼ばれる機能が使える。これは、ビューを共通部分と非共通部分に分けて扱える機能と、部分テンプレートを挿入できる機能だ。詳細は省く。

私はこの機能は、てっきりerbやhamlが標準で備えている機能だと思っていたが、実はrailsSinatraが独自実装していたものだった。そしてRackはこれらの機能を持っていない。

この時点で詰んだ。railsSinatraのソースからこれらの機能を持ってこようと考えたが、そんな事をするくらいならハナからそれらのwafを使うべきだ。よってRackの使用を諦めた。

私はこの事実に実に驚いた。rubyテンプレートモジュールであるerb|hamlは、単体での利用を考えられていないのだろうか。これらを使ってサイトを作るときは、wafとの併用が絶対条件とでもされているのだろうか?


さておき、Sinatraはlayout機能は備えているがpartial機能は備えていない。ので、ウノウラボの記事を参考にしつつ独自実装。

  def partial(template, options = {})
    erb "_#{template}".to_sym, options.merge(:layout => false)
  end

ビューの中で呼ぶので、当然ヘルパーとして実装。

なお、erb限定の実装だが、hamlなどを使いたい場合は軽くif文を挟めばよろしいかと。

余談1

perlの標準的なテンプレートモジュールとして位置付けられているTemplate::Toolkitは、モジュール自体がこのlayout、partialに相当する機能を備えている。

当然、wafなどの環境に制限されずこの機能を使うことが出来る。

余談2

実はerb単体でもlayout機能を実現出来る。が、分かりづらい見た目になるのでやめた。


ディスパッチャが無い

Rackの特性上当然と言えば当然だが、ディスパッチャが無い。これもrails/Sinatraは独自実装している。

最初は簡素なディスパッチャを書いていたが、上記と同じ理由によりRackの使用を諦め、窓から投げ捨てた。

rubyのディスパッチャは無いかと軽くググったが、見当たらず。


ハンドラも無い

同上。


とりあえずMとCを読み込む

(Dir::glob("app/{controller,model}/*.rb")).each do |file|
  require file
end

アプリのコアとなるrb(このケースではscript/tiwa.rb)でrequireすれば、任意のM/Cから他のM/Cのメソッドが呼べたりするので楽。

そうなるようにしてるのだから、当然と言えば当然なのだけど。


ビューの位置を変更する

set :views, File.dirname(__FILE__) + '/../app/view'

環境変数でdevelopment/productionを分ける

railsに倣おう。

@env = ENV["TIWA_ENV"] || "development"

この@envは要所要所で使う。例えばdbスキーマの読み込みも

development:
  adaptor: hoge
production:
  adaptor: huga

とでもしておき、

db  = YAML::load_file('config/database.yaml')[@env]

で、環境に応じた設定を読み込む。


ソースの自動再読み込み

Sinatraは0.9.2より、ソースの自動再読み込みがされなくなった。

そんなときはshotgunを使うのだが、(http://d.hatena.ne.jp/foosin/20090611/1244735821)それはそれで穴があったりする。

shotgunでSinatraアプリを起動させるとどうなるか。

% shotgun ./script/tiwa.rb -p 1000
 - later - 
% netstat -tln | grep 1000
tcp        0      0 127.0.0.1:1000              0.0.0.0:*                   LISTEN

この通り、指定したポートは127.0.0.1バインディングされる。つまり、Sinatraを動かす開発マシンをA(10.0.12.1)と、ブラウザからアクセスするマシンをB(10.0.12.2)とでもすると、Aのマシン上で localhost:1000 にアクセスはできても、マシンBから 10.0.12.1:1000 にはアクセス出来ない。

単一のマシンで開発・確認までしているのなら問題は無いが、今回はそうでなかったので困った。

ちなみにshotgunを使わずに起動すると

% ./script/tiwa.rb -p 1000
 - later - 
% netstat -tln | grep 1000
tcp        0      0 0.0.0.0:1000                0.0.0.0:*                   LISTEN

となるので、マシンBから 10.0.12.1:1000 でアクセスできる。


最初は、

Listen 1001
RewriteEngine On
RewriteRule ^/(.*) http://localhost:1000/$1 [P]

というapacheで代用していたが、postした瞬間に死んだので、大人しくpassengerを使用し、always_restart.txtを配置した。 passengerについては http://d.hatena.ne.jp/foosin/20090619/1245426335 に。

どうせshotgunがソースを再読み込みするのは、ソース変更後にリクエストを受けたときなので、shotgunを使おうがpassengerを使おうが待たされる時間とイライラ感に大差は無かった。


尚、上記のポート説明で使ったポート番号は捏造である。


余談

こんな記事もある。

http://blog.s21g.com/articles/1638

動くっちゃ動くが、ソースを変更して保存した直後にアクセスしても再読み込みされず、少し経ってからだとされる。よく分からん。


  • DataMapper

前回のBBS作成時と違うのはここぐらいである。

ARに不満があるわけではなく、同じ事やっても詰まらないのでDataMapper(以下DM)を使ってみた。


setup

先ほど読み込んだdbを使って

DataMapper.setup(:default, {
  :adapter  => db["adaptor"],
  :database => db["database"],
  :username => db["username"],
  :password => db["password"],
  :host     => db["host"]
})

ページング

DMにはdm-paginationというページングを行うモジュールが存在する。が、どうもorderに渡した値が読まれないようだった。

(一応ソースは追って、DM.allを呼ぶのに使っているoptionをダンプしたところちゃんと値を渡しているようだった。が、原因分からず)

ので、自力実装。

モデルのベースクラスを作り、クラスメソッドとして実装。

      def self.paginate(cond)
        page   = (cond[:page] ? cond[:page] : 1).to_i
        offset = (page == 1) ? 0 : ((page - 1) * 10)
        order  = cond[:order]

        cond.delete(:page)
        cond.delete(:order)

        @result = self.all(cond)
        @result = @result.all(:limit => 10, :offset => offset)
        @result = @result.all(:order => order) if order

        (@prev, @next) = (page == 1) ? ((@result.size < 10) ? [nil, nil] : [nil, 2]) :
                         (@result.size == 10) ? [page - 1, page + 1] :
                         [page - 1, nil]

        return @result, @prev, @next

実に簡素である。

ところでDMも使い方のサンプルは少ない。例えば公式マニュアル

http://datamapper.org/docs/

だが、例えばページングの際のoffsetの指定方法などは載ってない。

DMの基本的な使い方はググれば出るが、ちょっと変わった事をしたくなったら適当にソースを読んだ。

そして、全てが終わったあとに http://www.kuwata-lab.com/book_mdar/index.html を見つけたりする。事前調査はちゃんとしよう。


属性設定、バリデーション

上記ドキュメントか app/model/ 以下参照。説明は不要と思われる。


relation

例えばクラス内で

  belongs_to :entry

などと宣言すればrelationを張ってくれる。が、発行するクエリはjoinしたものではなく、各モデルにそれぞれ非joinのクエリを投げる。

その後、まるでjoinしたかのようなオブジェクトを返してくれる。これはありがたい。

データソースが単一のサーバ上に全て納まっている環境では有り難味は無いのだけれど。


マイグレーション

DMでのマイグレーションARのようにrakeではなく、クラスメソッドを実行する。

DataMapper.auto_migrate!

script/handle_data に簡単なスクリプトを置いておいた。


  • etc

その他。

モデルのベースクラスを作成。

module Tiwa
  module Model
    class Base
      def increment(column)
        self.update(column => (self.__send__(column) + 1))
      end
# 略
    end
  end
end

Tiwa::Model::Baseクラス(厳密にはこの表現は正しくない気がするが、面倒なのでクラスと呼ぶ。どうせ大差無い)をベースとし、他のクラスではこのクラスを継承する。

このincrementメソッドは、受け取った名前のカラムの値を1増やすだけである。これはCでやろうかとも思ったが、

http://twitter.com/hirafoo/status/5762365040

http://twitter.com/kamipo/status/5762395858

http://twitter.com/hirafoo/status/5762459759

http://twitter.com/kamipo/status/5762531208

とのこと。

どっちにしろ、Cにはロジックを極力詰め込まないようにするのが望ましい。

MVCのなんたるかについてはここでは触れない。


そしてこのように、rubyでは任意の名前のメソッドを動的に実行する場合、__send__メソッドを用いる。

perlでは普通に

$obj->$method_name

で呼べる。


railsscript/console が使いたいと思った。/usr/bin/irb のコードに、script/tiwa をrequireさせるだけで使えた。

ruby素晴らしい。


LoadModule env_module modules/mod_env.so
SetEnv TIWA_ENV production

毎度の通り長々と書いたが、やった事はこんなところ。

そして冒頭の通り、私はrubyが全然分からない。pとmethodsにお世話になっている。riもrefeもイマイチ助けにならない。

この記事、そしてtiwaのコードにも嘘や間違いや残念な個所が多分に含まれていると思われる。


事務用品 名刺 デザイン