2007年11月28日水曜日

Optimistic Offline Lock(Offline Concurrency Patterns)

仕事でこの辺りのちょっと踏み込んだ議論があったので、他のパターンを飛ばして先にOffline Concurrency Patternsやります。

概要
  • 競合を検出してロールバックさせることで、複数トランザクション間の競合をふせぐパターン。
  • いわゆる楽観的排他制御・楽観的同時実行制御と言われているもの。
  • 競合が滅多に発生しない場合に使われるパターン。競合が頻発する状況では、ビジネストランザクションのロールバックが頻発し、ユーザビリティを損なう。
  • SCM(CVSやSubversion)は、Optimistic Offline Lockの代表的な実装例。マージの対象はソースコードのみなのでシンプルだが、業務システムでもマージ処理を実装することは可能、らしい。
バリエーション
  • バージョンチェック方法としては、UPDATE分のWHERE句でVERSION列を指定する方法が基本。事前にVERSION列を取得して照合することもできるが、SELECT ~ FOR UPDATEで明示的に排他ロックを取得するか、トランザクション分離レベルとREPEATABLE_READかSERIALIZABLEにする必要がある(OracleやSQL Server2005では、スナップショット分離レベルでもOK)。
  • WHERE句の条件に全カラムの値を指定する方法もあるが、VERSION列が不要になるかわりにWHERE句が複雑になる。DB実装によってはパフォーマンスの問題が発生する可能性もあるので考えて使う必要あり。
  • UPDATE/DELTE文によるバージョンチェックだけでは、更新しない場合に対処できず、問題が発生するケースがある。たとえば、読み取ったデータをもとに計算をするような場合、読み取り元データの一貫性が必要となるケースがある。このような場合も、対象データをVERSION列で管理することが必要となることに注意。
使いどころ
  • 同時実効制御が必要な場面では、まずはこちらを選択肢に上げる。理由は、並列処理性能が比較的高い点や、実装が容易な点。
  • ビジネストランザクションの終了時まで競合検出を遅延させるため、システムの並列処理性能を比較的損ないづらい。競合した場合には、ビジネストランザクションをすべてロールバックする必要がある。
  • ビジネストランザクションのロールバックコストが非常に高い場合には、使用は困難。そのようなケースではPessimistic Offline Lockを使う。

Domain Logic Patterns

まとめ・感想
まず、Service Layerは、Domain LogicがTransaction ScriptだろうがDomain ModelだろうがTable Moduleだろうが、共通の視点で適用可否を判別可能なので、これは他の3つとは少し違う位置づけになる気がしている。

で、3つのうちどれを使うかという話になると、(この本を信じるとすると)それは状況次第になるということだ。ただし、実際のところ日本でDomain Modelを適用して開発しているところはかなり少ないように思える。Table Moduleも少なくて、実際はほとんどがTransaction Scriptなんじゃないだろうか。社内外で見聞きする事例を見ても、共通化したり多少変形したりはしているだろうけど、コンセプトとしては Transaction Scriptであることには変わりはない。

これは数年間ずっと感じていることだが、Domain Modelはうまく適用できるような環境になかなか恵まれないし、もしそんな状況になったとしても、Domain Modelで設計すれば変更に強いかどうかもわからない。Domain Modelでも、想定していないような変更にうまく対応できるんだろうか?

Domain Logic Patternは、Domain Modelをどう評価するかに尽きるように思える。このあたりはDomain-Driven Designを読みつつ勉強していきたい。

Service Layer(Domain Logic Patterns)

概要
  • アプリケーション境界を定め、プレゼンテーションや外部システムに対して操作のセットを提供する。このレイヤ自身は、リモートアクセス・レスポンスのとりまとめ・トランザクション境界の管理などを行う。
  • 公開すべきサービス・操作は、ユースケースから識別するといいらしい。

バリエーション
ビジネスロジックの一種としての側面
問題領域に特化した「ドメインロジック」とは別に、システム的なロジック(アプリケーションロジック)を記述する場所としてService Layerを利用することができる。ワークフローなどがあげられている。

実装のバリエーション
Domain FacadeとOperation Scriptというアプローチが書かれている。
Domain Facadeはその名のとおり、Domain ModelのFacadeとして機能し、基本的にロジックは記述しない。クライアント層への操作セットを提供するのが主な役割。
Operation Scriptは、これまたその名のとおり、アプリケーションロジックを実装する(ドメインロジックはDomain Modelに委譲する)。Service層の役割をあらわすクラスとしてLayer Supertypeを持ち、共通の操作が定義されていることが多い。

リモート/非リモート
Service Layerをリモート対応にするには、思った以上にコストがかかるので、安易にリモート化するべきではない。特に、UIがリッチな場合やDomain Modelが複雑な場合は変換やマッピングが面倒くさい。
Fowlerのおすすめは、まずはローカル呼び出し用としてService Layerを作成し、リモート化したければRemote Facadeを追加する、というアプローチ。

いつ使う?
Service Layerのメリットは、多くの種類のクライアントに対して共通の操作セットを提供でき、内部的に呼び出される複数の操作をとりまとめることができる点。 後者は特に、複数リソースをトランザクションとしてまとめる場合などに使われる。よって、これらの要件が存在する場合がService Layerの使いどころになる。
逆に、これらの要件がない(1クライアント/1リソース)のであれば、無用なコストを発生させるだけなので使う べきではない。Page Controllerが直接Data Source層を呼び出す形でレスポンスを構築し、トランザクションをコントロールしてもかまわない、らしい。ここまで言い切ってしまうのが面白い。

雑感
結局、アプリケーションロジックの実装場所、というのが一番理解しやすい。アプリケーションロジックを中立的に実装する場所としてService Layerがある、と考えれば、複数クライアントや複数リソースがないのであればアプリケーションロジックをPage Controllerに実装すればよい、という考えも納得がいく。

Table Modlue(Domain Logic Patterns)

概要
  • テーブル(ビュー)ごとにクラスを作成し、対応するテーブル(ビュー)の全行(実際は処理時に必要なある範囲の行だと思う)に対する処理を配置することで、ドメインロジックを分散配置するパターン。
  • Transaction ScriptとDomain Modelのあいのこのようなパターン。
  • Transaction Scriptと異なり、ドメインロジックはトランザクションごとに構成するのではなく、テーブルに関連づくクラス(Table Module)ごとに構成する。システムで必要な処理は、これらのクラス間の相互作用で実現する(Domain Model的)。
  • Table ModuleはDomain Modelと異なり、レコードごとにインスタンスをもたない。そもそもインスタンスを持たないか、テーブルごと(全レコード)に1つだけインスタンスを持つ(Transaction Script的)。
  • TableModuleがドメインロジックを実行する場合、大抵は対象レコードを指定するためのIDを受け取って動作する。
  • TableModuleを初期化するためのRecord Setは、自身の初期化時に自分で取得しても良いし、呼び出し側から渡されても良い。
  • 実際には、Table状データとみなされるものなら実テーブルでなくてもかまわない。
  • TableDataGatewayとの相性が良い。
  • DataTableを扱うLayer Supertypeを使ったりしてもよさそう。
メリット
  • DBとの親和性が高く、CRUD操作永続化のコストが低い。
  • それなりに意味のある単位でドメインロジックを分散管理できる。Transaction Scriptより重複コードを避けやすい。
デメリット
  • オブジェクト指向プログラミングの恩恵(継承関連)をフルには受けられない。
使いどころ
Record Setのようなデータ構造がUIから永続化まで幅広くサポートされている環境。要は、.NET環境。

参考リンク
実際にはどんな感じで適用しているのかいまひとつわかりづらかったけど、実際に適用された方がデブサミで発表&QAで発言していました。
http://vsug.jp/tabid/63/forumid/72/postid/9190/view/topic/Default.aspx

Domain Model(Domain Logic Patterns)

概要
  • ドメイン(問題領域)のオブジェクトモデルを作成し、そこにドメインロジックと処理対象データを持たせるパターン。
  • 一見したところDBモデルと似ているが、データと処理両方を持っている点が異なる。
  • 条件分岐で処理を実行するかわりに、オブジェクトネットワークをセットアップする。作成されたパスをたどりながら処理を実行することで、目的を達成する。つまり、オブジェクト構造自体がアルゴリズムになる。
メリット
  • 複雑で、変更が絶えず発生するロジックの修正コストが低い。
  • ロジックの重複が少なく、規模は比較的小さくできる?
デメリット
  • トランザクションを多数のオブジェクトの相互作用によって実現するため、多数の開発者による並列開発が困難。
  • (少なくとも、慣れていない人にとっては)設計が難しく、理解も難しい。
  • データベースとのマッピングが困難。
注意
ドメインオブジェクトに置くべきかどうか迷うロジック
ドメインオブジェクトは、ある概念を表したアプリケーション内で1つしか存在しないオブジェクトになるため、そのオブジェクトに関連はしているが、特定の 画面でしか必要のないロジックをすべてオブジェクト内に取り込むと、オブジェクトが肥大化してしまう。こういうロジックはユーティリティクラスや Transaction Scriptとして実装したくなるが、そうするとこの部分の処理が重複しやすくなり、結果的にアプリケーションが複雑になりやすい。
Fowlerの経験上、オブジェクトの肥大化はあまり起こらず、対処も容易なため、こういった操作もドメインオブジェクト内に置くことをすすめている。

他のレイヤとの関係
複雑なドメインロジックは変更が頻発するので、修正・ビルド・テストが容易になっていることが重要。そのために、他のレイヤへの依存を最小限にするのが望ましい。

バリエーション
二つのスタイル
シンプルなドメインモデルは、1テーブルに対して1ドメインオブジェクトが対応づくようなスタイル。リッチなドメインモデルは、データベースモデルとは異 なり、継承・StrategiesなどのOOやGoFのパターンが使われおり、細粒度の多数のオブジェクトから成る。リッチなドメインモデルは複雑なドメ インロジックを構成するのに向いているが、DBとの対応づけが困難になる。リッチなドメインモデルにはDataMapperが、シンプルなドメインモデル にはActiveRecordが向いている。

Railsはデフォルトの永続化メカニズムがActiveRecordパターンの ActiveRecordなので、逆に考えるとロジックはシンプルなDomain Modelを指向しているんだろう。名前もActiveRecordの実装クラスはModelフォルダに置かれるし。となると、Railsではみんな、 ControllerじゃなくてModelにドメインロジックを書いてるってこと?Railsが普及すると、日本でも徐々にDomain Modelへの道が開かれるのかな。

使いどころ
 ロジックが複雑で、絶えず変更が発生するような場合。

Transaction Script(Domain Logic Patterns)

Domain Logic Patternsは、ドメインロジック-よくビジネスロジックとか言われているもの-をどのように構成・配置するかについてのパターン。

概要
  • プレゼンテーション層からのリクエストを、ひとつのProcedureとしてまとめる形でドメインロジックを構成するパターン。
メリット
  • シンプルさ!
  • -> 理解しやすい。
    -> メンテナンスしやすい。
  • 他のトランザクション(nearly equal ドメインロジック)のことを気にしなくていい。
  • -> 多人数の並列開発に向いている。
    -> スキルが低い開発者による低品質なコードの影響を局所化できる。
デメリット
  • 同じようなコードがあちこちに出てきやすい。
  • 結果的に、規模が大きくなりやすい。
注意
  • 1メソッド・1クラスとして構成する必要があるわけではなく、必要に応じて構造化する(当たり前)。間違っても常に1メソッドとして作ったりしないこと。
  • レイヤ構造とは独立。上記と関連するが、プレゼンテーションやデータソースとは切り離した方が良い。
バリエーション
Transaction Scriptのクラス構成
もっとも一般的なのは、いくつかのTransaction Scriptを1つのクラスとして構成する方法。ここで作成したTransaction Scriptクラスは、サブジェクトエリアとなる、らしい。サブジェクトエリアって何だ…。ユースケースとか、業務区分みたいなのかな。個人的には、ユー スケースごとにTransaction Scriptクラスを作っていることが多い。Fowlerの見解によると、「たいていの場合はこのアプローチがベスト」らしい。

Transaction ScriptをCommand[GoF]として作成し、Transaction Scriptごとに1クラスを作成する方法もある。自社の社内標準はこちらをすすめていて、この構造でないとツールサポートが受けられなかったりする。 Fowlerの見解では、Transaction Scriptを使うようなシステムで、ここまでする必要性があるケースはめったにないらしい。自社の場合も、FWがうれしいだけで、使っている側にはほと んどメリットがない気がする。あ、SpringのAspectでトランザクション設定が楽になるかな。

使いどころ
ドメインロジックが少なく、シンプルな場合。ロジックが複雑になるにつれ、設計を良い状態に保つのは難しくなる。こういうケースでは、FowlerはDomain Modelを勧めている。

2007年11月18日日曜日

scaffoldが生成するコード

scaffoldでは、Controller、Helper、View(RHTML)が生成される。

Controller
itemモデルから生成した、ItemsController。ApplicationControllerを継承。Railsでは、URIに対応したメソッドが呼ばれる、ということは知っている。たとえば…
  • items/list -> ItemsControllerのlistメソッドが呼ばれる
  • hoge/fuga -> HogeControllerのfugaメソッドが呼ばれる
こんな感じ。で、scaffoldで生成されたメソッドは、以下の8つ。
  • index
  • list
  • show
  • new
  • create
  • edit
  • update
  • destroy
上から順に、一つひとつみていく。

indexメソッド

def index
list
render :action => 'list'
end

コントローラー名のみを指定された場合(items/)に呼び出される。一応、items/index でも呼び出された。
処理内容はというと、listメソッドとrenderメソッドを順番に呼んでいるだけ。listは後で片づけるとして、renderが何をやっているのかがよくわからないので調べてみる。メソッドが定義されているActionController::Baseのコメントを読んでみると、どうやら :action => 'xxx' で指定したactionに対応したテンプレートを使ってレスポンスをレンダリングするらしい。今回の例では、list.rhtmlを使ってレンダリングする。
listメソッドを呼んだら、勝手にlistのテンプレートを使ってレンダリングされるようになってるのかも、と漫然と考えていたが、どうもそうではないらしい。基本的には呼び出されたアクションに対応したテンプレートが使われ、を変更するためにはrenderメソッドを使った明示的な切り替えが必要、ということらしい。
indexメソッドでrenderメソッドを呼び出さなければ、index.rhtmlが使われる、というわけだ。generate scaffoldはindex.rhtmlを生成しないので、動かないが。

verifyメソッド呼び出し

# GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
verify :method => :post, :only => [ :destroy, :create, :update ],
:redirect_to => { :action => :list }

このverifyメソッド、ActionController::Verification::ClassMethodsにあって、リクエストヘッダレベルの検証をやっているらしい。この例では、destroy, create, updateメソッドはPOSTで呼び出されないといけないらしい。GETで呼び出された場合には、listアクションにリダイレクトされる。

やっていることはわかったが、なぜ呼び出せるのかがわからない。コメントには「すべてのControllerのクラスメソッドになる」と書いてあるので、どこかでevalしてるんだろうけど、いつ、どこでやってるのか現時点ではわからない。このあたりは、別途Rails起動時のシーケンスを追う中で明らかにしたい。

listメソッド

def list
@item_pages, @items = paginate :items, :per_page => 10
end

paginateメソッドを呼び出し、インスタンス変数itemsとitem_pagesをセットする。戻り値が配列になっているため、微妙にわかりづらい。paginateメソッドもverifyメソッドと同じく、すべてのControllerのクラスメソッドになっている。
paginateメソッドは、指定されたモデルの今回のページ分のコレクションとPaginatorオブジェクトを返している。Paginatorオブジェクトは、ページング用のプロパティを持っている。

showメソッド

def show
@item = Item.find(params[:id])
end

モデルのfindクラスメソッドにidを渡してモデルオブジェクトを一件取得し、インスタンス変数に保存するだけのシンプルな処理。「show/x」みたいな形で渡されたものがidとみなされるのは、特別扱いだろうか。このあたりも、正常形のシーケンスを追う中で確かめたい。

newメソッド

def new
@item = Item.new
end

モデルオブジェクトのインスタンスを新規作成し、インスタンス変数に保存して終了。

createメソッド

def create
@item = Item.new(params[:item])
if @item.save
flash[:notice] = 'Item was successfully created.'
redirect_to :action => 'list'
else
render :action => 'new'
end
end

渡されたパラメータをもとにItemオブジェクトを新規作成して、保存。保存に成功したらリダイレクトでlistアクションを呼び出す。失敗したら、newアクションのテンプレートを呼び出す。ここでちょっと驚いたのが、何もしていないのにリクエストパラメータが「params[:item]」という風にまとめられていること。どういうルールでまとまっているのかわからなかったので、createアクションを呼び出すHTMLをみてみた。

<p><label for="item_url">Url</label>

<input id="item_url" name="item[url]" size="30" value="" type="text"></p>

<p><label for="item_title">Title</label>

<input id="item_title" name="item[title]" size="30" value="" type="text"></p>

<p><label for="item_description">Description</label>

<textarea cols="40" id="item_description" name="item[description]" rows="20"></textarea></p>

name属性値がすべて、item[xxxxxx]という形になっている。railsはおそらく、こういう形でかかれたリクエストをモデルの属性として扱う仕様なのだろう。

editメソッド

def edit
@item = Item.find(params[:id])
end

IDをもとにモデルオブジェクトを一件検索し、インスタンス変数に保存して終了。showと同じだ。

updateメソッド

def update
@item = Item.find(params[:id])
if @item.update_attributes(params[:item])
flash[:notice] = 'Item was successfully updated.'
redirect_to :action => 'show', :id => @item
else
render :action => 'edit'
end
end

idをもとにモデルオブジェクトを取得し、リクエストパラメータで更新。更新したモデルオブジェクトの保存が成功すれば、リダイレクトでshowアクションを呼び出す。その際、idとしてモデルのインスタンス変数を渡している。保存が失敗した場合は、editアクションのテンプレートを呼び出す。
ここで目をひかれたのは、:idに@itemを入れてるところ。redirect_toは、文字通りリダイレクトするはずなので、次のアクションにオンメモリでItemオブジェクトを渡すことはできない。こうやって渡されたオブジェクトは、次のアクションにどういう形で引き渡されるんだろうか?そもそも、showで必要なのはItemオブジェクト全体じゃなく、idだけのはずだけど。

destroyメソッド

def destroy
Item.find(params[:id]).destroy
redirect_to :action => 'list'
end

idをもとにモデルオブジェクトを取得し、destroyで破棄する。その後、リダイレクトでlistアクションを呼び出す。

Helper

空。空です。まわりの説明をみていると、どうやらこのクラスはViewHelperの置き場所として用意されているようで、Viewから呼びたいメソッドを定義したくなったらここに書くらしい。

rhtml
rhtmlで生成されたものは、以下の6つ。
  • items.rhtml
  • list.rhtml
  • show.rhtml
  • new.rhtml
  • _form.rhtml
  • edit.rhtml
こちらも順にみていく。

items.rhtml
すべてのテンプレートを内部にレンダリングする、親テンプレート。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" lang="en" lang="en">
<head>
<meta equiv="content-type" content="text/html;charset=UTF-8">
<title>Items: <%= controller.action_name %></title>
<%= stylesheet_link_tag 'scaffold' %>
</head>
<body>

<p style="color: green"><%= flash[:notice] %></p>

<%= yield %>

</body>
</html>

中身以外の部分が全部書いてある。中身はyieldで差し込まれるようだ。あとは、Controllerで何回か出てきた「flash[:notice]」がある。Controllerでは、保存が成功した際にメッセージを格納していたが、ここに出力されるわけだ。

list.rhtml
listアクションで使われるテンプレート。

<h1>Listing items</h1>

<table>
<tbody><tr>
<% for column in Item.content_columns %>
<th><%= column.human_name %></th>
<% end %>
</tr>

<% for item in @items %>
<tr>
<% for column in Item.content_columns %>
<td><%=h item.send(column.name) %></td>
<% end %>
<td><%= link_to 'Show', :action => 'show', :id => item %></td>
<td><%= link_to 'Edit', :action => 'edit', :id => item %></td>
<td><%= link_to 'Destroy', { :action => 'destroy', :id => item }, :confirm => 'Are you sure?', :method => :post %></td>
</tr>
<% end %>
</tbody></table>

<%= link_to 'Previous page', { :page => @item_pages.current.previous } if @item_pages.current.previous %>
<%= link_to 'Next page', { :page => @item_pages.current.next } if @item_pages.current.next %>


<%= link_to 'New item', :action => 'new' %>


まず、この部分でヘッダを作っている。

<% for column in Item.content_columns %>
<%= column.human_name %>
<% end %>

モデルクラスには、content_columnsなんてものがあるらしい。そして、columnにはfuman_nameという、おそらくは表示用の名前が格納されているようだ。このあたりはActiveRecordをみていく中で確認したい。

次に、テーブル本体。

<% for item in @items %>

<% for column in Item.content_columns %>
<%=h item.send(column.name) %>
<% end %>
<%= link_to 'Show', :action => 'show', :id => item %>
<%= link_to 'Edit', :action => 'edit', :id => item %>
<%= link_to 'Destroy', { :action => 'destroy', :id => item }, :confirm => 'Are you sure?', :method => :post %>

<% end %>

@itemsを順番に取り出した後に、content_columnごとにitemオブジェクトの中身を表示している模様。ここらあたりも、sendメソッドの仕様を確認しないと細かいところはわからない。後で確認する。
最後には、link_toというメソッドで各actionを呼び出すリンクを作成している。

ラスト、ページング。

<%= link_to 'Previous page', { :page => @item_pages.current.previous } if @item_pages.current.previous %>
<%= link_to 'Next page', { :page => @item_pages.current.next } if @item_pages.current.next %>

前ページと次ページのリンクを作っているだけだが、pageパラメータとしてPaginatorオブジェクトのcurrent.previousやcurrent.nextを渡している。Paginate処理はControllerの開始時にフィルタとして処理されており、pageパラメータはPaginatorオブジェクトを復元する際に使われている。
ちなみに、previousやnextプロパティは、それぞれ前ページ・次ページが存在しない場合には存在しないため、リンクも表示されなくなる。
link_toにはactionもURLも書かれていないが、listアクションが呼び出される。action_view -> url_helper.rbのActionView::Helpers::UrlHelperのlink_toをみてみると、実際の変換はActionController::Baseのurl_forがやっているらしい。こちらのコメントによると、actionもurlも指定されていない場合は今と同じURLが使われるらしい。

show.rhtml

<% for column in Item.content_columns %>
<p>
<b><%= column.human_name %>:</b> <%=h @item.send(column.name) %>
</p>
<% end %>

<%= link_to 'Edit', :action => 'edit', :id => @item %> |
<%= link_to 'Back', :action => 'list' %>

モデルのカラムごとに中身を表示している。listとほぼ同じ。

new.rhtml

<h1>New item</h1>

<% form_tag :action => 'create' do %>
<%= render :partial => 'form' %>
<%= submit_tag "Create" %>
<% end %>

<%= link_to 'Back', :action => 'list' %>

form_tagメソッドは、ActionView::Helpers::FormTagHelperで定義されていて、その名のとおりformタグを定義するためのメソッド。
render :partialは、最終的にはActionView::Partials::render_partialが呼ばれており、このメソッドは :partialオプションの値の先頭に「_(アンダースコア)」をつけたテンプレート(rhtml)を使う仕様になっている。

_form.rhtml
new.rhtmlからrender :partial で呼び出されていたテンプレート。

<%= error_messages_for 'item' %>

<!--[form:item]-->
<p><label for="item_url">Url</label>

<%= text_field 'item', 'url' %></p>

<p><label for="item_title">Title</label>

<%= text_field 'item', 'title' %></p>

<p><label for="item_description">Description</label>

<%= text_area 'item', 'description' %></p>
<!--[eoform:item]-->

いくつか知らないメソッドがあるが、みてわかる通りな感じ。text_fieldはテキストフィールドを作成するのだろう。引数の書き方は見慣れない形だが、前から順番にプロパティがたどられるようだ。

edit.rhtml

<h1>Editing item</h1>

<% form_tag :action => 'update', :id => @item do %>
<%= render :partial => 'form' %>
<%= submit_tag 'Edit' %>
<% end %>

<%= link_to 'Show', :action => 'show', :id => @item %> |
<%= link_to 'Back', :action => 'list' %>

new.rhtmlとほとんど同じだ。違うのは、formタグのアクションが「update」んあっており、idとしてitemインスタンスが指定されている点。パラメータにモデルインスタンスを指定すると、HTML上ではいったいどうなるのだろう?実際に表示させてHTMLファイルをみてみると…

<form action="/items/update/1" method="post">

なるほど、という感じだ。リクエストURIとして出力されている。

というわけで、scaffoldが生成した処理はほぼ把握した。

次のステップ
次は、リクエスト発生からレスポンス作成までの流れを一通り追い、Railsの正常形の流れをつかみたい。

2007年11月17日土曜日

generate scaffoldコマンド

generate scaffold実行
generate scaffoldで、scaffoldメソッドが動的に行っていた処理をファイルに書き出してみる。
C:\Projects\OtherProjects\Rails\bookmark>jruby script\generate scaffold item
exists app/controllers/
exists app/helpers/
create app/views/items
exists app/views/layouts/
exists test/functional/
dependency model
exists app/models/
exists test/unit/
exists test/fixtures/
identical app/models/item.rb
identical test/unit/item_test.rb
identical test/fixtures/items.yml
Connection refused

と、MySQLが起動していなかったようだ。もう一回やると…
C:\Projects\OtherProjects\Rails\bookmark>jruby script\generate scaffold item
exists app/controllers/
exists app/helpers/
exists app/views/items
exists app/views/layouts/
exists test/functional/
dependency model
exists app/models/
exists test/unit/
exists test/fixtures/
identical app/models/item.rb
identical test/unit/item_test.rb
identical test/fixtures/items.yml
create app/views/items/_form.rhtml
create app/views/items/list.rhtml
create app/views/items/show.rhtml
create app/views/items/new.rhtml
create app/views/items/edit.rhtml
create app/controllers/items_controller.rb
create test/functional/items_controller_test.rb
create app/helpers/items_helper.rb
create app/views/layouts/items.rhtml
create public/stylesheets/scaffold.css

生成されたファイル(createで始まる行)だけ抜き出してみると、以下のようになる。
  • app/views/items/_form.rhtml
  • app/views/items/list.rhtml
  • app/views/items/show.rhtml
  • app/views/items/new.rhtml
  • app/views/items/edit.rhtml
  • app/controllers/items_controller.rb
  • test/functional/items_controller_test.rb
  • app/helpers/items_helper.rb
  • app/views/layouts/items.rhtml
  • public/stylesheets/scaffold.css
今まではitem_controllerというので処理を受けていたんだけども、作られたものはitemsになっている。Controllerの名前は、単数だろうが複数だろうがどっちでもいいんだろうか?とりあえずは、動作確認。
C:\Projects\OtherProjects\Rails\bookmark>jruby script\server
=> Booting WEBrick...
=> Rails application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with --help for options
[2007-11-17 15:26:10] INFO WEBrick 1.3.1
[2007-11-17 15:26:10] INFO ruby 1.8.5 (2007-11-01) [java]
[2007-11-17 15:26:10] INFO WEBrick::HTTPServer#start: pid=1102920 port=3000
http://localhost:3000/itemにアクセスしてみると、今までどおりの画面。
つづいて、http://localhost:3000/itemsにアクセスしてみる。画面自体はまったく同じだが、よーくみるとタイトルが変更されている。
http://localhost:3000/item -> Scaffolding
http://localhost:3000/items -> Items: index
とりあえずは無事に動いているようだ。

generate scaffoldはどうやって動いている?
ここからは、肝心のgenerate scaffoldを追っていきたい。script\generateの中をみてみると、

#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/generate'

となっている。config/bootは初期設定なので、生成処理自体はcommands/generateにぶん投げられているようだ。というわけで、
%RUBY_HOME%\lib\ruby\gems\1.8\gems\rails-1.2.5\lib\commandsのgenerate.rbを開いてみる。
require "#{RAILS_ROOT}/config/environment"
require 'rails_generator'
require 'rails_generator/scripts/generate'

ARGV.shift if ['--help', '-h'].include?(ARGV[0])
Rails::Generator::Scripts::Generate.new.run(ARGV)
どうやら、さらにRails::Generator::Scripts::Generatorに飛んでいるようだ。Generatorは…
require File.dirname(__FILE__) + '/../scripts'

module Rails::Generator::Scripts
class Generate < command =""> :create
end
end

というわけで結局、Rails::Generator::Scripts::Baseにたどりついた。実際に開いてrunメソッドをみてみると、いろいろ事前チェックはするものの、最終的には

Rails::Generator::Base.instance(options[:generator], args, options).command(options[:command]).invoke!
の部分で生成スクリプトを呼び出している。
Rails::Generator::Baseは、RailsのGeneratorフレームワークを利用する際に継承するための基底クラスになっているようで、実際にScaffoldGeneratorはこのクラス(のサブクラスのNamedBase)を継承している。

RailsのGeneratorフレームワーク
Railsのコード自動生成部分はフレームワーク的な構造になっており、命名規約に従った形で(オプションで変更可能)自動生成スクリプトを作成・配置することで、generateコマンドから呼び出すことができるようになっている。scaffoldコマンドもこのうちの1つという位置づけだ。詳細はRails::GeneratorモジュールのRDocに書いてあるが、実際にGeneratorが格納されているフォルダをながめてみるとだいたいの仕様はわかるはず。Generatorスクリプトは
%RUBY_HOME%\lib\ruby\gems\1.8\gems\rails-1.2.5\lib\rails_generator\generators\components
に配置されていて、「script\generate コマンド名」のコマンド名ごとにサブフォルダが作成されている。自分の環境に存在しているサブフォルダは、
  • controller
  • integration_test
  • mailer
  • migration
  • model
  • observer
  • plugin
  • resource
  • scaffold
  • scaffold_resource
  • session_migration
  • web_service
の12種類。また、これとは別にcomponentsと同レベルにapplicationという名前のディレクトリが存在し、application全体を生成する際にはこれが使われているようだ。
個別スクリプト用のサブディレクトリをみてみると、直下に「コマンド名_generator.rb」という名前でGeneratorフレームワークから呼び出される自動生成のRubyスクリプトが配置されており、templatesサブフォルダにソースのテンプレートが格納される形になっている。

scaffoldコマンドの場合は、components\scaffoldディレクトリの直下にscaffold_generator.rbがあり、templatesサブフォルダ内には
  • controller.rb
  • form.rhtml
  • form_scaffolding.rhtml
  • functional_test.rb
  • helper.rb
  • layout.rhtml
  • style.css
  • view_edit.rhtml
  • view_list.rhtml
  • view_new.rhtml
  • view_show.rhtml
の11種類のテンプレートが格納されている。先ほど生成されたファイルと対応していることが確認できる。

自分で自動生成コマンドを作る時は、これらのルールにあわせてファイルを配置すれば簡単に使えるようになりそうだ。実際の開発に適用するにあたって、もう少しドメインに特化したスクリプトを生成したくなった場合に役立つと思われる。

Controllerの名前
Controllerの命名規約についてだが、USAGEに参考になる情報が載っていた。
The scaffold generator creates a controller to interact with a model.
If the model does not exist, it creates the model as well. The generated
code is equivalent to the "scaffold :model" declaration, making it easy
to migrate when you wish to customize your controller and views.

The generator takes a model name, an optional controller name, and a
list of views as arguments. Scaffolded actions and views are created
automatically. Any views left over generate empty stubs.

The scaffolded actions and views are:
index, list, show, new, create, edit, update, destroy

If a controller name is not given, the plural form of the model name
will be used. The model and controller names may be given in CamelCase
or under_score and should not be suffixed with 'Model' or 'Controller'.
Both model and controller names may be prefixed with a module like a
file path; see the Modules Example for usage.
これによると、scaffoldコマンドにコントローラー名を与えなかった場合には、モデル名の複数形が使われるようだ。コントローラー名を明示的に与えることもできるので、コントローラー名はモデル名となんの関係もなくても良さそうだ。
わざわざこんな場所のUSAGEファイルをみなくても、引数なしでコマンドをたたくとUSAGEが表示されるらしい…最初からそうしておけばよかった。

次のステップ
次は、scaffoldが生成した処理がどういうものなのかを一つずつみていく。

2007年11月15日木曜日

PoEAAを原書で読んでみる。

 以前挫折した、PoEAAを原書で読み直し中。日本語版は斜め読みしてみてちょっとやる気が萎えるような訳が多かったので。読んでいてポイントだと思った部分を書きとめていく。ってか、原書だと読むのに時間がかかりすぎて、何かしらをアウトプットしないと忘れてしまう。ということで、基本は前から順番に。最初はDomain Logic Patternsから。

2007年11月14日水曜日

BluetoothでMindstorms NXTをコントロール(下調べ)

 BluetoothでNXTをコントロールしたい。センサー経由だと、周囲の環境に依存しすぎて思ったとおりの動きをさせづらいので、Bluetoothから明示的に動作命令を出せるとうれしい。が、Bluetoothとかさっぱりわからない。というわけで、まずはざっくりと関連しそうな情報を集めてみた。

Bluetooth全般
・Bluetooth概要
http://ja.wikipedia.org/wiki/Bluetooth

Bluetoothにはいくつかのプロファイルがあって、対応したプロファイル同士でないとやり取りできない模様。

・NXTのBluetooth仕様
http://mindstorms.lego.com/Overview/NXTreme.aspx

ざっと読んでみると、こんなことがわかった。
  • NXTは、Serial Port Profile(SPP)をサポートしている
  • NXTは、Bluetoothチャネルを4本(0~3)持っている
  • NXTは、他のBluetoothデバイスと接続する際に、マスタかスレーブかのどちらかになる
  • 自身がスレーブとなる場合にはチャネル0が使われ、ここにマスタが接続される
  • 自身がマスタとなる場合にはチャネル1~3が使われ、ここにスレーブが接続される
  • PC(LabView)からNXTに接続する場合には、PCがマスタになり、NXTがスレーブになる
マスタとスレーブは同時になることはできないようなので、NXTがマスタになってBluetoothデバイスに接続する場合、PCとの接続は切っておかなければいけないのかもしれない。

接続方法案
■WiiリモコンからBluetoothで指示する
 これができれば、ラジコンみたいに使えて楽しいなぁと思ったのだが、WiiリモコンのBluetooth関連仕様を見ていると、どうも無理っぽい。

・Wiiリモコンの仕様
http://www.wiili.org/index.php/Wiimote

Wiiリモコンでサポートされているプロファイルは、HID/SPD/HCIと書かれている。
SPDはService Discovery Protocol、HCIはHost Controller Interfaceで、プロファイルより下の階層っぽい。実際のプロファイルはHID(Human Interface Device)Profileのみということで、NXTとは通信できそうな気がしない(実際、つないでみてもうまくいかなかった)。
Wiiリモコン経由でNXTを操作するのであれば、WiiリモコンでPCを操作し、PCからSPP経由でNXTを操作するのが現実的な路線か。

■PCからBluetoothで指示する
 PCからBluetoothでNXTを操作する場合、まず候補としてあがるのは、すでにBluetooth接続できており、接続インタフェースらしきものも備えているLabViewからのコントロールだ。
しかし、実際にLabViewをさわってみると、NXTとの接続できているものの、できる操作といえばプログラムの転送以外見当たらない。LabViewのBluetoothブロックでメッセージを送信できそうに見えるが、LabViewで作ったプログラムはPC上で動作させることはできず、あくまでNXTに転送してNXT上で動かすことができるのみ。よって、LabViewからのコントロールは不可能。

というわけで、結局はRobotics Studioを使うのが一番簡単なのかもしれない。

・Microsoft Robotics Studio
http://msdn2.microsoft.com/en-us/robotics/default.aspx

Bluetooth関連情報を調べていて、ついでにJavaでBluetoothデバイスを制御するAPIを発見。

・Java Bluetooth API(JSR-82)
http://today.java.net/pub/a/today/2004/07/27/bluetooth.html

こいつを使えば、JRuby on RailsからNXTをコントロールできるかも?WebのUIをWiiのインターネットチャンネルで表示すれば、Wiiリモコンでも操作できそう。

関連リンク
Mindstorms NXT購入
BluetoothでMindstorms NXTへ接続(BT-Mini2EDRW)

2007年11月13日火曜日

Scaffold処理は何をやっている?

 前回でbookmarkアプリができたわけだが、今はすべての動作が

scaffold :item

で動いているわけで、この中で何が起きているかはよくわからない。今までのRubyの生半可な知識から、「scaffold」はメソッドで、「:item」はシンボルということはわかっている。で、scaffoldはRubyのDSLっぽい機能(動的にクラス定義やメソッド定義を追加できる機能)を使い、リクエストを処理するメソッドを動的に生成しているはずだ。

scaffoldメソッドはどこにある?
 ItemControllerはApplicationControllerのサブクラスなので、scaffoldはApplicationControllerのメソッドなのだろう。…と思ったら、ApplicationControllerも

class ApplicationController < session_key =""> '_bookmark_session_id'
end

こんな感じなので、ActionController::Baseにあるのではないかと思われる。このクラス自体は空だが、アプリケーション全体のフィルタ的処理を書く場所として生成されているようだ。詳細は不明だが、セッションの設定もされている模様。で、ActionController::Baseはというと…

$:.unshift(File.dirname(__FILE__)) unless
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))

unless defined?(ActiveSupport)
begin
$:.unshift(File.dirname(__FILE__) + "/../../activesupport/lib")
require 'active_support'
rescue LoadError
require 'rubygems'
gem 'activesupport'
end
end

require 'action_controller/base'
require 'action_controller/deprecated_redirects'
require 'action_controller/request'
require 'action_controller/deprecated_request_methods'
require 'action_controller/rescue'
require 'action_controller/benchmarking'
require 'action_controller/flash'
require 'action_controller/filters'
require 'action_controller/layout'
require 'action_controller/deprecated_dependencies'
require 'action_controller/mime_responds'
require 'action_controller/pagination'
require 'action_controller/scaffolding'
require 'action_controller/helpers'
require 'action_controller/cookies'
require 'action_controller/cgi_process'
require 'action_controller/caching'
require 'action_controller/verification'
require 'action_controller/streaming'
require 'action_controller/session_management'
require 'action_controller/components'
require 'action_controller/macros/auto_complete'
require 'action_controller/macros/in_place_editing'

require 'action_view'
ActionController::Base.template_class = ActionView::Base

ActionController::Base.class_eval do
include ActionController::Flash
include ActionController::Filters
include ActionController::Layout
include ActionController::Benchmarking
include ActionController::Rescue
include ActionController::Dependencies
include ActionController::MimeResponds
include ActionController::Pagination
include ActionController::Scaffolding
include ActionController::Helpers
include ActionController::Cookies
include ActionController::Caching
include ActionController::Verification
include ActionController::Streaming
include ActionController::SessionManagement
include ActionController::Components
include ActionController::Macros::AutoComplete
include ActionController::Macros::InPlaceEditing
end

こんな感じになっており、大量のModuleをincludeしているだけ。ということは、scaffoldメソッドはこれらモジュールのどれかで定義されているはず、だとすると…

require 'action_controller/scaffolding'
include ActionController::Scaffolding

ここにあるとしか考えられない。

scaffoldメソッドの処理内容
 「action_controller\scaffolding.rb」を見てみると、予想通りにscaffoldメソッドを発見。RDocを見てみると、こんな風にメソッドが追加されるらしい。この、Entryの部分がModelのクラスになるのだろう。

class WeblogController < method =""> :post, :only => [ :destroy, :create, :update ],
:redirect_to => { :action => :list }

def index
list
end

def list
@entries = Entry.find(:all)
render_scaffold "list"
end

def show
@entry = Entry.find(params[:id])
render_scaffold
end

def destroy
Entry.find(params[:id]).destroy
redirect_to :action => "list"
end

def new
@entry = Entry.new
render_scaffold
end

def create
@entry = Entry.new(params[:entry])
if @entry.save
flash[:notice] = "Entry was successfully created"
redirect_to :action => "list"
else
render_scaffold('new')
end
end

def edit
@entry = Entry.find(params[:id])
render_scaffold
end

def update
@entry = Entry.find(params[:id])
@entry.attributes = params[:entry]

if @entry.save
flash[:notice] = "Entry was successfully updated"
redirect_to :action => "show", :id => @entry
else
render_scaffold('edit')
end
end
end

ソースを見てみると、最終的にActionController::BaseはClassMethodsモジュールをextendすることになっているようだ。

module ActionController
module Scaffolding
def self.included(base)
base.extend(ClassMethods)
end

module ClassMethods

def scaffold(model_id, options = {})

scaffoldメソッドの第一引数model_idは、modelの名前だ(bookmarkアプリの場合は、item)。で、こんな風に変数をいくつか初期化して…

singular_name = model_id.to_s
class_name = options[:class_name] || singular_name.camelize
plural_name = singular_name.pluralize
suffix = options[:suffix] ? "_#{singular_name}" : ""

その後、module_evalでメソッド定義を追加しまくっている。追加されているメソッドは、RDocに書かれているものと同じだ。メソッドを動的に変更するため、ところどころにさきほど初期化した変数が埋め込まれているのがわかる。

module_eval <<-"end_eval", __FILE__, __LINE__ verify :method => :post, :only => [ :destroy#{suffix}, :create#{suffix}, :update#{suffix} ],
:redirect_to => { :action => :list#{suffix} }


def list#{suffix}
@#{singular_name}_pages, @#{plural_name} = paginate :#{plural_name}, :per_page => 10
render#{suffix}_scaffold "list#{suffix}"
end

def show#{suffix}
@#{singular_name} = #{class_name}.find(params[:id])
render#{suffix}_scaffold
end

def destroy#{suffix}
#{class_name}.find(params[:id]).destroy
redirect_to :action => "list#{suffix}"
end

def new#{suffix}
@#{singular_name} = #{class_name}.new
render#{suffix}_scaffold
end

def create#{suffix}
@#{singular_name} = #{class_name}.new(params[:#{singular_name}])
if @#{singular_name}.save
flash[:notice] = "#{class_name} was successfully created"
redirect_to :action => "list#{suffix}"
else
render#{suffix}_scaffold('new')
end
end

def edit#{suffix}
@#{singular_name} = #{class_name}.find(params[:id])
render#{suffix}_scaffold
end

def update#{suffix}
@#{singular_name} = #{class_name}.find(params[:id])
@#{singular_name}.attributes = params[:#{singular_name}]

if @#{singular_name}.save
flash[:notice] = "#{class_name} was successfully updated"
redirect_to :action => "show#{suffix}", :id => @#{singular_name}
else
render#{suffix}_scaffold('edit')
end
end

private
def render#{suffix}_scaffold(action=nil)
action ||= caller_method_name(caller)
# logger.info ("testing template:" + "\#{self.class.controller_path}/\#{action}") if logger

if template_exists?("\#{self.class.controller_path}/\#{action}")
render :action => action
else
@scaffold_class = #{class_name}
@scaffold_singular_name, @scaffold_plural_name = "#{singular_name}", "#{plural_name}"
@scaffold_suffix = "#{suffix}"
add_instance_variables_to_assigns

@template.instance_variable_set("@content_for_layout", @template.render_file(scaffold_path(action.sub(/#{suffix}$/, "")), false))

if !active_layout.nil?
render :file => active_layout, :use_full_path => true
else
render :file => scaffold_path('layout')
end
end
end

def scaffold_path(template_name)
File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml"
end

def caller_method_name(caller)
caller.first.scan(/`(.*)'/).first.first # ' ruby-mode
end
end_eval
end

というわけで、RDocの完成版ソースと比べてみてみれば、scaffoldメソッドがやっていることは一目瞭然。少し複雑な部分があるとすれば、ビュー部分だろう。この部分はさすがにActionControllerの中では完結しないため、(自前で作成したrhtmlがない場合には、)事前に用意されたテンプレートファイルが使われるようになっている。細かい点でわからないところはあるが、生成されるメソッドの内容自体については、実際にScaffold処理を静的に生成してから見ていくことにする。

クラス名からのソースファイルの探し方
 Javaの場合は、基本的に(内部クラスやpublicでないクラスを除いて)1クラス->1ソースファイルになるため、ソースファイルを探すのは容易だ。
Rubyの場合は、1つのクラスが複数のモジュールから成っていることが多く、結果として1クラスの定義が複数のソースファイルにまたがっている場合が多くなり、ソースを追うのが多少面倒になってくる。
RDocをインストールしているのであれば、RDocのクラスページに

Class ActionController::Base
In: lib/action_controller/base.rb
lib/action_controller/cgi_process.rb
lib/action_controller/deprecated_redirects.rb
lib/action_controller/test_process.rb
Parent: Object

このような形で表示されているため、参考にしていきたいところ。Web上にもRDocは存在するが、ファイルの種類は参考になるが、ファイルの位置はローカル環境とは異なっているため、参考にならない。
Gemでインストールした場合、RDocのファイル表示のトップディレクトリは「%RUBY_HOME%\lib\ruby\gems\1.8\gems\ライブラリ名(actionpack-1.13.5など)\」になる。

次のステップ
 次は、scaffoldメソッドで動的に生成されていた各種の処理を、script\generateを使って実際のRubyスクリプトとして生成し、その中を見ていく。

JRuby で Ruby on Rails

Rails難民脱却を目指す
 10分で作る~シリーズでbookmarkアプリだけは何回も作ったけど、そこから先へはいっこうに進めないRails難民と化してはや1年。JRubyが話題になったのを受けて、目先を変えるべく JRubyでRuby on Railsを動かしていくことにする。
自分のレベルは今こんな感じ。
  • しょぼいRubyアプリ(ファイル処理とか)は一応作れる
  • Javaはそこそこわかる
  • Railsはbookmarkアプリ+αレベル
  • RubyのDSLとか、一応どうやってるか知ってる
  • Railsのプラグインとか全然わかりません
  • Railsでどうやって開発するか、ちょっと難しくなると想像つきません
とりあえずは、こういう感じのことを知りたい。
  • 日本語化(必要?不要?)
  • Javaとの連携
  • 複数テーブルのJoin
  • 複雑なSQL
  • メジャーなプラグインの種類と使い方
    • Ajax
    • ログイン
    • DB分散
  • 2.0との関連
  • どういう状況(ビジネス・技術制約や開発プロセス・文化などなど)だとおいしいのか
  • JavaでどうやってRuby処理系を実装しているか
JRubyインストール
 とりあえずはインストールから。まずはJRubyの公式サイトからアーカイブをダウンロード。CodeHausだったとは、今まで知らなかった。今回は、リリースバージョンで最新の、1.0.2をダウンロード。いつまでも1.0にならないと思っていたら、もう1.0.2が出ていたとは。

ダウンロードしたアーカイブは単なるZipなので、展開して適当なディレクトリに配置。うれしいことに(?)、Railsの鬼門(?)である、GemはJRubyの配布アーカイブに含まれているらしい。

自分の場合は、いつも使っている「C:\ProgramFiles\Java」に「JRuby」フォルダを作り、展開したフォルダごと移動。
これから先、%RUBY_HOME%\binのコマンドにはお世話になるはずなので、Javaにならって環境変数に「RUBY_HOME」を登録、Pathに「%RUBY_HOME%\bin」を追加してみる。これで、コマンドプロンプトから「jruby」が動くようになるはず。
C:\Projects\OtherProjects\Rails\bookmark>jruby
You must set JAVA_HOME to point at your Java Development Kit installation
指定されたバッチ ラベルが見つかりません - cleanup
'""' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

む…JRubyは環境変数「JAVA_HOME」がいるのか。さすがJRuby。Javaを感じる。というわけで、環境変数「JAVA_HOME」を設定。どうやら、JAVA_HOMEが指す先はメッセージどおり「JDK」でないといけないらしく、「JRE」では同様のエラーになる。JREしかインストールされていない場合は、JDKをインストールして環境変数に「JAVA_HOME」を指定。
C:\Projects\OtherProjects\Rails>jruby -v
ruby 1.8.5 (2007-11-01 rev 4810) [x86-jruby1.0.2]
よしよし。動く動く。
irbは…
C:\Projects\OtherProjects\Rails>irb
'irb' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。
ないらしい。%RUBY_HOME%\binを見ると、irbはjirbという名前らしい。ややこしいから全部オリジナルと同じにしてくれればよかったのに。
C:\Projects\OtherProjects\Rails>jirb
irb(main):001:0> p "test"
"test"
=> nil
irb(main):002:0> exit

よしよし。次はRailsのインストールだ。

Railsのインストール
 Gemを使ってRailsをインストール。なんか、もっとオプションを色々つけた方がいいのかもしれないけど、面倒くさいからとりあえず知ってるコマンドを叩く。
gem install rails
長々とメッセージが出て、あっさりインストール完了。もっとトラブるかと思ったけど、意外と良さそう。

Bookmarkアプリ作成
 あの、有名すぎるBookmarkアプリを作って動作確認してみる。
そう、masuidriveさんの「10分で作るRailsアプリ for Windows」だ。

「C:\Projects\OtherProjects\Rails」フォルダでさっそく、

C:\Projects\OtherProjects\Rails>jruby rails bookmark
Error opening script file: rails (指定されたファイルが見つかりません。)
指定されたファイルが見つかりません。どうも、jrubyからは%RUBY_HOME%\binのrailsは見つからない様子。パス指定しないと動かないのか。
C:\Projects\OtherProjects\Rails>jruby %RUBY_HOME%\bin\rails bookmark
create
create app/controllers
create app/helpers
create app/models
create app/views/layouts
create config/environments
create components
create db
create doc
create lib
create lib/tasks
create log
create public/images
create public/javascripts
create public/stylesheets
create script/performance
create script/process
create test/fixtures
create test/functional
create test/integration
create test/mocks/development
create test/mocks/test
create test/unit
create vendor
create vendor/plugins
create tmp/sessions
create tmp/sockets
create tmp/cache
create tmp/pids
create Rakefile
create README
create app/controllers/application.rb
create app/helpers/application_helper.rb
create test/test_helper.rb
create config/database.yml
create config/routes.rb
create public/.htaccess
create config/boot.rb
create config/environment.rb
create config/environments/production.rb
create config/environments/development.rb
create config/environments/test.rb
create script/about
create script/breakpointer
create script/console
create script/destroy
create script/generate
create script/performance/benchmarker
create script/performance/profiler
create script/process/reaper
create script/process/spawner
create script/process/inspector
create script/runner
create script/server
create script/plugin
create public/dispatch.rb
create public/dispatch.cgi
create public/dispatch.fcgi
create public/404.html
create public/500.html
create public/index.html
create public/favicon.ico
create public/robots.txt
create public/images/rails.png
create public/javascripts/prototype.js
create public/javascripts/effects.js
create public/javascripts/dragdrop.js
create public/javascripts/controls.js
create public/javascripts/application.js
create doc/README_FOR_APP
create log/server.log
create log/production.log
create log/development.log
create log/test.log
できたできた。MySQLにitemsテーブルを作って…bmuserを作って…
C:\Projects\OtherProjects\Rails>cd bookmark

で、Modelを作ってみる。
C:\Projects\OtherProjects\Rails\bookmark>jruby script\generate model item
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/item.rb
create test/unit/item_test.rb
create test/fixtures/items.yml
create db/migrate
create db/migrate/001_create_items.rb
なんか自動生成重い…?JRubyのが重いのかなぁ。続いてController。
C:\Projects\OtherProjects\Rails\bookmark>jruby script\generate controller item
exists app/controllers/
exists app/helpers/
create app/views/item
exists test/functional/
create app/controllers/item_controller.rb
create test/functional/item_controller_test.rb
create app/helpers/item_helper.rb
重いけど、とりあえず両方できた。「config\database.yml」の設定を変えて…
development:
adapter: mysql
database: bookmark
username: bmuser
password: bmuser
host: localhost
これでよし。
C:\Projects\OtherProjects\Rails\bookmark>jruby script\server
=> Booting WEBrick...
=> Rails application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with --help for options

で、「http://localhost:3000/」は動作していることを確認。「http://localhost:3000/item」は、

Unknown action

No action responded to index

というわけで、動作せず。コントローラー何も書いてないから当然。「app\controllers\item_controller.rb」を開いて、scaffoldで動くように書き換える。
class ItemController <>もう一回「http://localhost:3000/item」にアクセスすると…うおっ、エラーが出た。

Mysql::Error in ItemController#index

#28000Access denied for user 'root'@'localhost' (using password: NO)

RAILS_ROOT: ./script/../config/..

Application Trace | Framework Trace | Full Trace

Request

Parameters: None

Show session dump

Response

Headers: {"cookie"=>[], "Cache-Control"=>"no-cache"}
データベース接続のユーザー設定がおかしいぞ…?と思ったら、変更したdatabase.ymlを保存していなかったことに気づく。保存してリロード…しても、メッセージ変わらず。いくらRailsといっても、環境設定は起動時に一度しか読まないのね。それじゃあWEBRickを再起動。
C:\Projects\OtherProjects\Rails\bookmark>jruby script\server
=> Booting WEBrick...
=> Rails application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with --help for options
[2007-11-13 22:29:18] INFO WEBrick 1.3.1
[2007-11-13 22:29:18] INFO ruby 1.8.5 (2007-11-01) [java]
[2007-11-13 22:29:19] INFO WEBrick::HTTPServer#start: pid=1102920 port=3000
もう一度「http://localhost:3000/item」にアクセスすると…ちゃんと表示された。
JRubyで目先変えて新鮮な気持ちで頑張ろうと思ったけど、意味ないと思えてくるぐらい、Rubyと変わらず普通に動いてしまった。拍子抜けですね。

次のステップ
 次のステップとしては、ちゃんとしたアプリを作る前にscaffoldをスクリプトとして出力して、出力されたソースをながめたり見た目を変えたりしてみる。

2007年11月8日木曜日

ソフトウェアの舶来信仰

 ひがさんの日記->404 Blog Not Foundとつながっていたので、自分の考えもメモしておく。ひがさんの日記は、こんな感じ。

これには、明確な理由があります。日本人の特にソフトウェアにおける「舶来信仰」をぶちこわすためです。

自分たちであまり考えることなく、海外ではやっていたらそれをそのまま受け入れる傾向が、日本人には強いように思えます。それっていいことだとはあまり思えません。

 半人前ながら、一応プロの開発者としての自分は、舶来だろうが日本製だろうが、良いプロダクトがあれば迷わずそちらを選択する。「日本製」だからといってそちらを優先的に使うことこそが、プロの開発者としての自分を裏切ることになる。
 コミュニティに参加していない開発者の大半は、自分と同じような考えなんじゃないだろうか?だとしたら、Seasarプロジェクトのやるべきことは、「開発者が選ばざるを得ないプロダクトを作ること」
であって、「舶来信仰をやめろと言うこと」ではないのではないだろうか。

それにしても…ちょっと自分の考えを書いただけでこんなに波紋が広がるとは。ひがさんの影響力というのは絶大だなぁ。大変だ。

2007年11月5日月曜日

BluetoothでMindstorms NXTへ接続(BT-Mini2EDRW)

Bluetoothなんて標準が決まってるんだから、バージョンさえあわせればなんでもつながるだろと考えて、ビックカメラでPlanex(PCi)のBT-Mini2EDRW(Class2/Blootooth ver2.0+EDR)を買ってみた。
試してみると、奇妙な経過を辿って…最終的には失敗した。

まず、付属のCD-ROM内のソフトをインストールせず、いきなりUSBスロットにさしてLabVIEW(プログラム開発ソフト)を立ち上げ、デバイスを検索してみたところ、普通にNXTを認識でき、接続もできた。
その後、CD-ROM内のソフト(東芝のBluetoothスタックとBluetoothデバイスのマネージャ)をインストールしたところ、デバイス検索にまったくひっかからなくなってしまった。
ソフトをアンインストールして再度LabVIEWからデバイス検索すると、今度はデバイスとしては発見できるものの、接続時にエラーが出るようになってしまった。LabVIEWでのパスキー入力画面の後、NXT側でパスキーを入力した直後に、LabVIEW側でエラーとなってしまう。何度やっても同じで、結局あきらめた。

初期の(接続に成功していた)段階でプログラムの転送まで試さなかったためなんとも言えないが、BT-Mini2EDRWは付属ソフトをインストールしなければ普通に(少なくともMindstorm NXT用としては)使えるのかもしれない。しかし、自分の今の環境では使えないことは間違いない。

最終的にはMicrosoft Robotics Studioを使ってのプログラミングを考えているので、Bluetoothで接続できないとかなり厳しい。近いうちに、NXTと接続できると評判のELECOMのドングルを試してみたい。

追記:
WindowsのBluetoothデバイス管理ソフト「コントロールパネル->Bluetoothデバイス」からはNXTが認識できており、Microsoft Robotics Studioではこの状態になっていればやり取りできる、とのことだったので、ダメもとでもう1度LabViewから試してみたところ、今回はなぜかすんなりつながった。というわけで、「BT-Mini2EDRW」でも一応OKのよう。プログラムの転送までできることを確認済み。


関連リンク
Mindstorms NXT購入
BluetoothでMindstorms NXTをコントロール(下調べ)

2007年11月4日日曜日

モンシロチョウの幼虫がついてきた

 実家から送られてきた救援物資のレタスに、モンシロチョウの幼虫がついてきた。あんまりにも懐かしいので、飼いはじめてみた。


綿棒の空き箱に、レタスをつめただけ…。下には水を入れてみたけど、水漏れしたので受け皿で受けてみた。


レタスの穴の中にいます。かわいい。さなぎになる時期が近いらしく、はげしく動き回ってる。

Mindstorms NXT購入


 ついに(ようやく?)Mindstorms NXT(Retail版)を買ってしまった。ビックカメラにも売ってたけど、ポイント含めてもAmazonの方が安かったので結局Amazonで購入。
 買った人のBlogでも常に書かれているように、パッケージはかなりしょぼい。うすーい厚紙でできた箱の中に、ビニール小袋に小分けされたパーツが入っているような感じ。箱は、持ち上げるとぐにゃりとゆがむ。というわけで、パーツ用のケースを買ってきて中身はすべてそちらに移し替えた。

 ちなみに、購入時にレゴブロックはいくつかの小袋に仕分けされているが、この仕分け方法が微妙で、パーツの種類ごとに分かれているわけではない。このため、ロボット組み立てをはじめてパーツを探す際にはいろいろな小袋を探しまわってかなり苦労した。なので、最初のロボットを組み立てはじめる前に、パーツは種類ごとに分類しておくことをおすすめする。
以下は、空になった箱の写真。かなりへなへなしていて保存にはあまり向いていない。

Mindstorms NXT(Retail版)の箱
left

パーツ用のケースは、細かい部品用と大きめの部品用の二つを購入。ちなみに、両方新宿の東急ハンズで売っていた。レジの裏くらいね。部品を入れてみると、こんな感じ。

細かい部品用ケース

これは、仕切の間隔を自分で調整できるパーツ入れ。RING STARというメーカーの「Super Pitch(SP-3000S)」という製品で、楽天でも買えるみたいなので、興味があればどうぞ。

大きめの部品用

これは、MEIHO(明邦化学工業株式会社)の「Million Box」のLサイズ。買って帰ってくると、既に家で薬箱として同じものが使われていた。つまり、使わなくなっても薬箱に使える模様。ネットだと、Yahoo!ショッピングで発見。

中身は、ざっとこんな感じ。
  • インテリジェントブリック(昔のゲームボーイみたいなので、プログラムを走らせる本体)
  • 各種センサー(タッチセンサー/光センサー/音センサー/超音波センサー)
  • レゴブロック
  • センサー<->インテリジェントブリック接続用ケーブル
  • PC<->インテリジェントブリック接続用USBケーブル
  • ユーザーガイド
  • テスト用シート(光センサーで感知できるようにラインがひいてある紙)
  • クイックスタートキット(手っ取り早く簡単な車を作るための部品&説明書のセット)
  • CD-ROM(プログラム作成用ソフト)
電池は入っていないので注意。6本も必要なので、あらかじめ気をつけて用意しておかないと、やる気はあっても何もできない。自分はここで、Wiiリモコンの電池をひっこ抜いてしのいだ。

購入後の流れは?
 購入後は、まずはクイックスタートキットで簡単な車を作成。その後は、順番に
  1. TriBot(アーム付き自動車、クイックスタートキットで作った車を拡張)
  2. RoboArm T-56(アーム制御ロボット)
  3. Spike(サソリロボット)
  4. Alpha Rex(二足歩行ロボット)
のようなロボットを製作していくのが基本。プログラム開発ソフト内に親切なチュートリアルがあり、それぞれのロボットについてレゴブロックの組み立て方・プログラムの作り方から動作確認の方法まで、すべてイラストつきで丁寧に解説されているので、上記4種類のロボットを作るのであれば、特に迷うことなく誰でもできる。

Mindstorms NXTのプログラム開発ソフト
left
右はしのロボットの絵をクリックすると、ロボット作成の手順が表示される。


Spikeを動かしてみた

本当は、青いボールに当てたかったんだけど…。


関連リンク
BluetoothでMindstorms NXTへ接続(BT-Mini2EDRW)
BluetoothでMindstorms NXTをコントロール(下調べ)

2007年11月3日土曜日

効率の良いポイントの稼ぎ方

換金までが遠いポイント獲得サービス
 以前からネットでは、広告クリックやアンケートでポイントを付与し、一定ポイントたまれば換金できたり、商品をもらえたりするサービスが数多く存在している。しかし、これらのサービスが発行するポイントは、基本的にはそのポイントの換金(商品交換)ポイントに達するまで地道にポイントを貯めるしかなく、ショッピングやクレジットカードの申込など、二の足を踏むような行動をとらない限りは劇的にポイントを獲得することが難しかった。こうして、なかなか換金ポイントに達しないままいつの間にかやめてしまう人が多かった。

ポイント同士の提携で使い勝手が向上
 しかし、ここ最近のブロードバンドや電子マネーの普及も追い風となってポイント市場が急拡大し、メジャーなポイント(航空会社のマイルやクレジットカードのポイント)も含めたポイントの相互交換が可能になったことで、少額のポイントもマイルなどに合算することで無駄なく使い切れるようになってきた。
 実際に今自分は、毎日5分程度を使って、月に平均で1000~2000円程度を得ている。クレジット・電子マネー・その他リアル店舗のポイントもほぼ無駄なく使い切ることができるようになったため、実質的にはもう少し効果が高いだろう。それでもあまり大きな額ではないのだが、ネットの合間にちょこちょこ作業をしてこれくらいになるのであれば、やってみる価値はあるんじゃないだろうか?何より、ポイントをうまく使いきること自体、結構楽しいのだ。

空いた時間で効率良くポイントを稼ぐには
 それでも、仕事を続けている身では毎日アンケートや広告クリックに精を出すわけにもいかないため、
「できる範囲で・かけた手間が無駄にならない」ようなやり方を実践する必要がある。ここ2~3年にわたって、数十のサービスを利用してみた経験をもとに、効率的なポイントを稼ぐための方法を書いていきたい。

実際にやってみて重要だと感じたのは以下の4点を徹底すること。
  1. 効率の良いサービスを選択する
  2. できるだけ多くのサービスに登録する
  3. 作業の優先順位を設定し、実践する
  4. ポイントの合算で期限切れを回避する
以下、それぞれについて具体的に書いていこう。

1. 効率の良いサービスを選択する
 効率が良いサービスを選ぶ際には、何よりもまず「ポイントのたまりやすさ」と「換金効率」が重要となる。ポイントを貯めるためのサイトは多数あり、1日5分程度で2~3ヶ月ごとに1000~2000円たまるようなサービスもあれば、2年以上毎日ひたすらクリックしても一度も換金できないようなサービスもある。
そのため、効率の良いサービスの選択は重要だ。さらに、効率の良いサービスにうまく時間を使うためには、「効率の悪いサービスからは早期に退会する」ことも同程度重要と言える。時間は限られているのだ。
  • ポイントの換金効率が異常に悪い
  • メールの数が異常に多く、メールボックスを占拠してしまう
  • 作業すべき内容がわかりづらく、メールのどこをクリックすればいいかわからない
  • ポイント獲得作業の一つ一つに時間がかかり、複雑
このような「効率の悪い」サービスに登録し続けた場合、大量のメールによってメールボックスは破壊され、いつまで経っても換金できないポイントの獲得に延々と時間を使い続け、効率が良いサービスに時間を割くこともできないまま疲れ果ててやめてしまう可能性が高い。
 実際に多数のサービスを試してみた経験をもとに、おすすめのサービス(または登録してはいけないサービス)をエントリ末尾にリストしておいたので、参考にして欲しい。

2. できるだけ多くのサービスに登録する
 これは特にリサーチモニター系に対して言えることだが、できるだけ多くのサービスに登録することで効率は格段にあがる。実際にやってみるとわかるのだが、 アンケート依頼の数は思ったより少ないため、登録サービスが少ない状況ではなかなかポイントがたまらない。効率良く換金し続けるために は、できるだけ多くのサービスに登録するのがおすすめだ。
 また、複数のサービスからまったく同じアンケート依頼が来ることが多く、その場合にはいつもより簡単に多数のアンケート依頼をこなすことができる。たまに出現する、異常にポイント獲得数が多いアンケート依頼を捕まえる(うまくいけば複数同時にこたえる)可能性も高くなる。

3. 作業の優先順位を設定し、実践する
 ポイントがもらえる作業には有効期限が設定されており、アンケートは有効期限が短く、回答者数が予定人数に達した時点で終了になるものも多い。反面、広告クリックは有効期限が長いものが多い。
また、アンケートの特徴として、以下のような点がある。
  1. アンケートの報酬ポイントはばらつきが大きく、まれに簡単なアンケートでかなりのポイントがもらえるものがある
  2. 有効期限が短い(募集人数が少ない)ものほどもらえるポイントが多い傾向がある
1. のようなアンケートに募集人数制限があった場合、あっという間に締め切られてしまうことがほとんどだ。また、2のようなアンケートは、比較的短期間でチェックしておかないと見逃してしまう。
 これらのことから、作業はアンケートを優先にすべきである(広告クリックは最悪捨ててもかまわない)ことがわかるだろう。自分の処理のパターンは、以下のようになっている。
  • 平日に空いた時間があれば、アンケートに答える。この際、「緊急!」などと書かれているものを優先的に処理する。
  • 平日にさらに時間がとれれば、広告クリックも処理しておく。
  • 週末にまとめて、広告クリックのメールを処理する。
4. ポイントの合算で期限切れを回避する
 マイルやクレジットカードなどのメジャーなポイントにはたいてい有効期限が設定されており、期限内に使わなかったポイントは消えてしまうが、ネットのポイントの中には有効期限が設定されていないものの方が多い。さらに、最近ではポイントの相互連携が進み、どんなポイント同士でもなんらかのルートで変換できるようになっているので、有効期限が切れそうな(かつ換金可能ポイント数に達していない)ポイントに余っているポイントを加算して期限切れ前に換金したり、期限切れ直前のポイントを有効期限が設定されていないポイントに退避させることで、有効期限切れを回避することができるようになった。
 有効期限を管理し、期限切れ前に確実に上記のような行動を取ることで、効率よくポイントを稼ぐことができるようになる。

退避先として使いやすい(有効期限がなく、変換先が多い)サービスは、以下のようなサービスだ。
  • Gポイント
  • PeX
  • ポイントオン
ただし、ポイント同士を交換する際には手数料がかかり、実質的に何パーセントかポイントをひかれてしまう事が多い。交換ルートによってレートがかなり異なってくるため、最適なルートで交換するようにしよう。ポイント同士の最適交換ルートは、ポイント探検倶楽部を使えばすぐにわかる。

ツール
ポイント付与対象の作業(クリック・くじ・アンケート)を効率良くこなすためには、以下のツールは必須だ。
  • 大容量ストレージのWebメール(GMailがおすすめ)の利用
  • タブブラウザ(IE7・Firefox・Operaなど)の利用
また、GポイントのMONEY MAPや、ポイント探検倶楽部のポイント自動管理を使えば、かなりの種類のポイントを一元管理できる。興味があれば使ってみてもいいだろう。

サイト一覧

:おすすめ
:普通
×:登録してはいけない

広告クリック系(PC)
○:お財布.comhttp://osaifu.com/
 有効期限なし。メールは少ない。会員登録やネットショップの買い物をお財布.com経由にすることでポイントを貯められる。対応ショップ・サービスが多いので、ネットを使うときはのぞいてみると良い。
○:Infoseek メールdeポイントhttp://pointmail.rakuten.co.jp/
 有効期限はあるが、楽天ポイントとしてたまるのでネットショップで使う機会は多く、無駄になりにくい。メールはかなり多い。
○:Gポイントhttp://www.gpoint.co.jp/
 有効期限あり。メールは普通。交換先が多い。単体ではあまりたまらないが、ポイント退避・交換先として有用。
○:ちょびリッチhttp://www.chobirich.com/
 有効期限なし。メールは多め。まぁまぁたまる。
○:HESO-CLICKhttp://www.heso-click.com/
 有効期限なし。メールの数は少ない。
○:MyPointhttp://www2.mypoint.com/ja/MemberHome
 有効期限なし。HESO-CLICKと似ている。
○:PeXhttp://pex.jp/
 有効期限なし。メールはほぼない。ポイント交換専用。ECナビと組み合わせて使うと良い。
○:ECナビhttp://ecnavi.jp/
 そこそこ貯まるが、有効期限が短い。メールは多め。PeXに交換できる。
△:フルーツメールhttp://www.fruitmail.net/
 有効期限なし。なかなかたまらない。メールは少ない。ポイントの使い勝手があまり良くない。
△:CMサイトhttp://www.cmsite.co.jp/
 有効期限なし。 なかなかたまらない。メールは少ない。ポイントの使い勝手があまり良くない。
△:予想.nethttp://www.yosoo.net/top.php
 有効期限なし。メールは多い。クリックポイント(コイン)を、ベット(賭け)でポイントに変換しなくては使えないのが面倒。そこそこたまる。
△:eBethttp://ebet.jp/
 有効期限なし。なかなかたまらない。メールは普通。
×:ポイントタウンhttp://www.pointtown.com/
 有効期限なし。 メールは異常に多い。ポイント換金効率は異常に悪い。
×:ふくびき.comhttp://www.fukubiki.com/
 有効期限なし。メールは異常に多い。ポイント換金効率は異常に悪い。ポイントタウン以上の地雷。

広告クリック系(モバイル)
○:ポイントオンhttp://www.p-on.co.jp/
 有効期限あり。メールは普通。クリック単価が高く、よく貯まる。
○:お財布.comhttp://osaifu.com/
 PCサイトと共通。メールは普通。
△:キリバンhttp://kiriban.jp/
 有効期限なし。おそらく、お宝メール・スロットメール等と同じ運営。クリック時の番号がキリバン(ぞろ目など)であれば、お金がもらえる。なかなか換金可能な額までたまらない。
×:お宝メールhttp://web.otakaramail.jp/
 広告クリック後、一方的に当たりはずれが通知される。あたったことは一度もない。
×:スロットメールhttp://www.slotmail.jp/
 広告クリック後、スロットをクリックして止める。あたったこと一度もない。

リサーチモニター系(PC)
○:InfoQhttp://infoq.jp/
 有効期限なし。メールは多め。そこそこたまる。
○:mpackhttp://member.mpack.ne.jp/front/pc/index.do
 有効期限なし。メールは普通。かなりたまる。3ヶ月ごとくらいに1000円程度換金している気がする。
○:マクロミルhttp://monitor.macromill.com/
 有効期限なし。メールはかなり多め。本調査にあたらないかぎり、あまりたまらない。
○:JMAhttp://www.jma-net.com/jp/topics/monitor/
 有効期限なし。メールは少なめ。存在感薄いが、いつの間にかそこそこたまっている。
○:MyVoice.comhttp://www.myvoice.co.jp/voice/
 有効期限なし。メールは少なめ。存在感薄いが、いつの間にかそこそこたまっている。
○:スコープnethttps://www.scope-net.ne.jp/sn/index.jsp
 有効期限なし。メールは普通。存在感薄いが、いつの間にかそこそこたまっている。
○:CiM-Nethttp://www.cim-net.jp/
 有効期限なし。メールは普通。存在感薄いが、いつの間にかそこそこたまっている。
○:ゲームエイジ総研http://www.gameage.jp/
 有効期限なし。ゲーム専門。500円分たまると勝手にギフトカードが送られてくる。半年に一回くらい送られてきてる印象。
△:Yahoo! リサーチ・モニターhttp://monitor.research.yahoo.co.jp/monitor/toroku_monitor.html
 メールは普通。抽選で規定人数が数十円~数百円もらえる。たいてい、毎月数百円ずつJNBに振り込まれている。
△:Yahoo! リサーチ・モニター・ライトhttp://monitor.research.yahoo.co.jp/lite/toroku_lite.html
 有効期限あり。メールは普通。あまりたまらない。
△:楽天リサーチアンケートhttp://research.rakuten.co.jp/monitor/
 有効期限あり。楽天ポイントなので、無駄になりづらい。が、あまりたまらない。
△:日本リサーチセンターhttp://www.nrc.co.jp/monitor/cyber201.html
 メールは少ない。たまーーにギフト券が送られてくる。一年に一回くらいか?
×:CREST生活アンケートhttps://survey.japancrest.com/entry/entry.asp
 「食日記」というのがメイン。メールは少ない。異常に書くのに時間がかかる。仕事してる人は無理。
×:DIMSDRIVEhttp://www.dims.ne.jp
 抽選でギフト券があたるが、人数がとても少なく、あたったことがない。メールは少ない。面倒なアンケートが多い。