query: tag:merb

dm-is-remixable
は、Commentなどの複数のリソースにまたがって共用されるがちなリソースをDRYにするためのDataMapperプラグインです。

しかし、Validationのためのコードを記述しても、正常に動作しないという問題がありました。これは、以下のようにすれば解決できます。

ruby>>
module Commentable
include DataMapper::Resource
is :remixable, :suffix => "comment"

(..snip..)

def self.included(base)
base.class_eval do
validates_present :message
end
end
end
<<--

See Also

posted by genki genki on Tue 5 May 2009 at 01:54 with 0 comments

Hello, Merbists!

Today, I explain how to develop Merb apps that runs on GAE/J environment by using dm-datastore-adapter.

First of all, here is whole source code of an example app. Please check it out.

After checking it out,
you must edit appengine-web.xml file. Open it by editor and change the application name to yours.
And then you should make war directory by typing this command.

pre>>
MERB_ROOT% jruby -S warble war
<<--

It generates files under tmp/war.

So far, you are ready to deploy this app to GAE/J
Of course, you need an account of GAE/J to do it. Please get it in advance :-)

Let us go to deploy by this command.

pre>>
MERB_ROOT% appcfg.sh update -e {youraccount@gmail.com} tmp/war
<<--

This process takes a time for the first time.
If the log didn't say any errors, you got success!

Now your first Merb app on GAE/J is here at
http://{your app name}.appspot.com/

Congrats!

Further improvement is your home work :-p

APPENDIX

All required gems are being packed into jar file located at lib/merb.jar.
This enables you to pass the limit of which you can upload only 1000 files to GAE.
If you add more gems to the jar file, you can do it like this

  • unpacking it
  • add gems
  • and repack it

Enjoy!

posted by takiuchi takiuchi on Fri 24 Apr 2009 at 07:54 with 2 comments

Merbでconfig/router.rbの中で、リソースのカスタムアクションを追加する方法のメモ。

ruby>>
resource :users,
:collection => {:active => :get},
:member => {:password => :get}
<<--

:collectionで指定したアクションは、resource(:users, :active) などのように参照できて、"/users/active" のような感じになります。

:memberで指定したアクションは、resource(@user, :password) などのように参照できて、"/users/1/password" のようなスタイルになります。

posted by genki genki on Mon 20 Apr 2009 at 14:54 with 0 comments

GAEを使う上での大きな制限として、ファイル数1000までというのがあります。
これを乗り越えるために、unpackしたGemの中から不要なファイルを掃除したりする必要があったのですが、
関連するGem群をjarファイルにまとめる事でこの問題を乗り越えられます。

上記のファイルをlibの下に配置して、config/init.rb
あたりでrequire_fixを読み込みます。
これはjarファイルの中のrbファイルの読み込みに関するバグを
回避するためのものです。

実際に以下のサイトでmerb.jarを使って運用しています。

これによって、事実上ファイル数制限に左右されずにアプリケーションを開発する事が出来るようになります。
ただ、1ファイルのサイズ制限(10MB)が存在するので、jarファイルが大きくなりすぎた場合は分割する必要があります。

posted by genki genki on Sun 19 Apr 2009 at 08:25 with 0 comments

I shipped new dm-datastore-adapter today.

This update is a long jump from previous version. It includes following functions

  • Transaction. It utilizes the DataStore's Transaction API
  • OR conditions
    ruby>>
    Post.all(:id => [1,2])
    <<--
  • NOT conditions
    ruby>>
    Post.all(:id.not => 4)
    <<--

So now we can use most of functions defined in DM by using this adapter.

Enjoy!

posted by takiuchi takiuchi on Fri 17 Apr 2009 at 05:44 with 0 comments

非常にシンプルなのですが、毎回GlobalHelpersに書くのが面倒なので、
Merbから
GRAVATAR
のアイコンを表示するための
merb_gravatarプラグインを作りました。

http://github.com/genki/merb_gravatar/tree/master

インストール方法

pre>>
% sudo gem install merb_gravatar
<<--

使用法

dependencyでmerb_gravatarを指定して、viewで以下のように使います。

html>>
<%= gravatar @user.mail, 16 %>
<<--

第二引数はアイコンのサイズを1~80で指定します。省略すると80が選ばれます。

ss1

GAE/Jでも使えます(See http://watch-me.appspot.com/people)

posted by genki genki on Tue 14 Apr 2009 at 11:05 with 0 comments

Today I shipped a new DataMapper plugin that enables us to easily develop Merb-apps been worked with GAE/J.

By using this plugin, you can seamlessly develop your Merb-apps between local and GAE/J environment.

For example, this site is powered by Merb/DM with the dm-datastore-adapter and running on the GAE/J

(This service is under construction :-p)

As a matter of fact, because it is still being alpha status,
you must treat various issues regarding gem dependencies yet.
I appreciate any kind of feedback and of course patches :-)

Enjoy!

posted by takiuchi takiuchi on Mon 13 Apr 2009 at 05:43 with 11 comments

Bumble
はGoogle App Engine for JavaのDateSoreを利用するためのライブラリですが、Railsで利用する前提で作られているので、
Merbで利用する場合には、以下のような修正を加えると良いようです。

bumble.rb

ruby>>
module Bumble
(..snip..)
def self.included(base)
base.send :include, InstanceMethods
base.send :extend, ClassMethods
Merb::Router.root_behavior =
Merb::Router.root_behavior.identify(Bumble => :key)
end
<<--

これによって、resource(@person) #=> /people/5 のようにidentifyしてくれるようになります。

posted by genki genki on Sun 12 Apr 2009 at 02:19 with 0 comments

2日前ぐらいからいろいろ頑張ってたのですが、ようやくGAEjの本番環境でMerbアプリを動かせました。

http://jmerbist.appspot.com/

ss

使っているgemをfreezeする仕組みがあるフレームワークであれば、どれでも以外と簡単に動きそうな感じがします。

いやー、これは色々面白い事が出来そうですね!

posted by genki genki on Thu 9 Apr 2009 at 17:18 with 0 comments

以前、Merb用のaliasの設定をご紹介しましたが、今度はjruby版です。

sh>>
alias jm='PATH=./bin:$PATH jruby -S merb'
alias jmi='jm -i'
alias jmg='PATH=./bin:$PATH jruby -S merb-gen'
<<--

rubyとjrubyを両方使ってると、

pre>>
% jruby -S merb
<<--

のように書く事が多いので、aliasを設定しておくと便利ですね。

posted by genki genki on Thu 9 Apr 2009 at 01:45 with 0 comments

Merb/DataMapperをしばらく使っていたのですが、
少なくともバージョン0.9.10, 0.9.11では、
associationの実装にバグがあり、
レコード数が多いテーブルがあると、aggregation系の処理に時間がかかるという問題がある事が分かりました。

例えば、Post.has n, :comments な関係がある時に、
以下のようなコードを実行すると、このようになります。

ruby>>
?> Post.first.comments.count #=> 188
~ (0.000865) SELECT "id" FROM "posts" ORDER BY "id" LIMIT 1
~ (0.000094) SELECT "id", "post_id" FROM "comments" WHERE ("post_id" IN (1)) ORDER BY "id"
~ (0.000063) SELECT COUNT(*) FROM "comments" WHERE ("post_id" = 1)
<<--

2つ目のSQLは不要なのですが、発行されてしまいます。
例えばCommentのレコード数が多かった場合、Commentオブジェクトを大量に生成しようとしてしまうため、致命的な遅さになってしまいます。

目下この問題の解決策を探しているところですが、
Edge-DMのspec/仕様でerrorが出ている状態なので、なかなか手が付けられない感じです。
とりあえず、dm-coreのassociations/relationship.rbの中の、

ruby>>
# @api private
def get_children(parent, options = {}, finder = :all, *args)
parent_value = parent_key.get(parent)
bind_values = [ parent_value ]

    with_repository(child_model) do |r|
      parent_identity_map = parent.repository.identity_map(parent_model)

      query_values = parent_identity_map.keys
      bind_values = query_values unless query_values.empty?
      query = child_key.zip(bind_values.transpose).to_hash
      collection = child_model.send(finder, *(args.dup << @query.merge(optio

ns).merge(query)))

      return collection unless collection.kind_of?(Collection) && collection.any?

<<--

の最後の collection.any? で件のSQLが実行されているところまでは分かりました。
DataMapper::CollectionはextlibのLazyArrayを継承しているクラスなのですが、どうもそのへんの仕様が変わったのに追従できてないのかな。
モジュールを過度に分散しすぎるのも、整合性を保つのが大変になるという問題がありますね。
注意深く完全なSpecを書く事を心がけていれば防げる問題かもしれないですが。

posted by genki genki on Wed 8 Apr 2009 at 04:52 with 0 comments

桜が散る前に、4/8の夜21時よりMerbJog#2を開催いたします。

詳細・参加申請は以下のサイトをご覧ください。

飛び入り参加もOKです。

よろしくお願いします。

posted by genki genki on Tue 7 Apr 2009 at 14:52 with 0 comments

度々忘れるのでメモしておきます。

ruby>>
Merb::ControllerExceptions.constants.sort #=> ["Accepted", "ActionNotFound", "BadGateway", "BadRequest", "Base", "ClientError", "Conflict", "Continue", "Created", "ExpectationFailed", "Forbidden", "GatewayTimeout", "Gone", "HTTPVersionNotSupported", "Informational", "InternalServerError", "LayoutNotFound", "LengthRequired", "MethodNotAllowed", "MovedPermanently", "MovedTemporarily", "MultiPartParseError", "MultipleChoices", "NoContent", "NonAuthoritativeInformation", "NotAcceptable", "NotFound", "NotImplemented", "NotModified", "OK", "PartialContent", "PaymentRequired", "PreconditionFailed", "ProxyAuthenticationRequired", "Redirection", "RequestEntityTooLarge", "RequestRangeNotSatisfiable", "RequestTimeout", "RequestURITooLarge", "ResetContent", "STATUS_CODES", "SeeOther", "ServerError", "ServiceUnavailable", "Successful", "SwitchingProtocols", "TemplateNotFound", "TemporaryRedirect", "Unauthorized", "UnsupportedMediaType", "UseProxy"]
<<--

posted by genki genki on Sat 4 Apr 2009 at 22:57 with 0 comments

DataMapper#auto_migrate!を実行すると、以下のようなコードが実行される。

ruby>>
self.auto_migrate!(repository_name = nil)
AutoMigrator.auto_migrate(repository_name)
end
<<--

AutoMigrator#auto_migrateは以下のようになっている。

ruby>>
def self.auto_migrate(repository_name = nil, *descendants)
auto_migrate_down(repository_name, *descendants)
auto_migrate_up(repository_name, *descendants)
end
<<--

第二引数以降でDMのクラスリストを指定できる。
省略すると、DataMapper::Resource.decendantsが指定されたものとして動作する。
特定のリソースだけまとめてauto_migrateするには便利そうだ。

posted by genki genki on Sat 4 Apr 2009 at 12:49 with 0 comments

急遽大門駅付近で開催されることになったCouchDB勉強会のレポートです。

参加者: @maiha, @yugui, @yamaz, @takiuchi

そもそもCouchDBは何かというと、
Apacheのプロジェクト
で、分散、耐障害性、スキーマフリー、ドキュメント指向なデータベスで、
RESTfulなAPIを使って制御します。

結構前から存在していたのですが、取りかかるきっかけがなくてスルーしていました。
しかし、dm-couchdb-adapterを使ってMerb/DataMapperで利用可能という事が分かり、にわかに盛り上がってきました。

早速、couchdbをインストールします。
いまのところ、ソースからcouchdb-0.9.0をインストールするのが一番良いようです。
macportsのcouchdb-0.9.0aでは動作が微妙に異なっているようでうまく動きませんでした。

dm-couchdb-adapterは、dm-moreにバンドルされているのですが、
そのままでは一部機能が利用できなかったので、
問題の分析を行いながら、
@maihaさんが改良を加えていきました。
改良版はこちらにあります。
使ってみたい場合は、maiha/dm-moreをcloneしてきて、

pre>>
% cd adapters/dm-couchdb-adapter
% sudo rake install
<<--

すればOKです。
基本的な使い方は上記GitHubリポジトリのREADMEを読めば分かります。

結論として、普通にMerbアプリを作れるようになりました。
試しに作りかけの社内ツール的なものをMerb/DM/CouchDBの構成にしてみました。

http://watch.s21g.com

Future Worksとしては、

  • read_manyでのorderの指定。emitの第一引数を使う。逆順をどうするか。
  • count以外のaggregate (max, min, sum, avg) のサポート

などがありますね。

posted by genki genki on Fri 3 Apr 2009 at 13:41 with 0 comments

あのAsakusa.rbから、dm-paginationバージョン0.3.2がリリースされました。

dm-pagination-0.3.2 is out from Asakusa.rb

最近の改善が取り込まれています。
dm-paginationはMerb with DataMapper用のpaginationを提供するプラグインです。
Rails用の
pagination_scope
の姉妹プラグインです。

See Also

posted by genki genki on Tue 31 Mar 2009 at 10:32 with 0 comments

Passengerは非常に便利なのですが、
通常の設定では、アプリケーションへのアクセスが無い状態が5分程度続くと、プロセスがkillされてしまい、次回にアクセスする時に時間がかかるようになります。

これを回避するためには、httpd.confなどで、以下のように指定します。

pre>>
PassengerPoolIdleTime 0
<<--

これで、プロセス数の限界に達して追い出されるまで、Idle時間によってkillされる事は無くなります。

See Also

posted by genki genki on Sat 28 Mar 2009 at 20:32 with 0 comments

Rails向けのiPhoneサイト作成用プラグインのメモです。

See Also

posted by genki genki on Wed 11 Mar 2009 at 08:18 with 0 comments

@maihaさんを中心として、私やMerb勉強会の参加者によって作り続けていたMerbのRuby-1.9.1対応のパッチ群が、Merb本体に取り込まれました。

ss

http://github.com/wycats/merb/commits/

Merb勉強会での活動が実を結んだわけです。
めでたいです。ありがとうございます。

Merb-1.1では、Ruby-1.9.1に対応したMerbがリリースされそうです。

Merb 1.1 roadmap

posted by genki genki on Wed 11 Mar 2009 at 05:52 with 0 comments

MerbでExpiresやCache-Controlを設定する場合、
アクションメソッドの中で、以下のようにすれば良いようです。

ruby>>
headers['Cache-Control'] = 'public'
headers['Expires'] = 3.days.from_now.utc.rfc2822
<<--

posted by genki genki on Thu 26 Feb 2009 at 18:40 with 0 comments

Merbでリソースに:editのようなカスタムアクションを追加する場合、
以下のように:memberオプションを利用出来ます。

config/router.rb

ruby>>
resources :materials, :member => {:download => :get}
<<--

これによって、GET /materials/:id/downloadが、Materials#downloadにmapされます。

posted by genki genki on Thu 26 Feb 2009 at 01:21 with 0 comments

MerbのCacheはなかなか優れた設計なので面白いのですが、
開発中にログにCache Miss/Hitの具合が分かるように出力してほしかったので、
development環境用のLoggingMemcachedStoreというのを書いてみました。

ruby>>
class LoggingMemcachedStore < Merb::Cache::MemcachedStore
include Extlib::Hook

before :read do |key, params|
if exists?(key, params)
Merb.logger.debug "Cache Hit: #{key}"
else
Merb.logger.debug "Cache Miss: #{key}"
end
end

before :write do |key, data, params, conds|
Merb.logger.debug "Cache Write: #{key}"
end
end
<<--

こんな感じでログに出ます。

pre>>
merb : worker (port 4000) ~ Cache Hit: Plugins#show
merb : worker (port 4000) ~ Cache Write: Plugins#show
<<--

posted by genki genki on Sat 21 Feb 2009 at 01:19 with 0 comments

EC2のサーバ上でruby-1.8.x系で動作しているpassenger(aka mod_rails)
と平行して、ruby-1.9.1を動かす環境を用意するために色々と試行錯誤を繰り返していたのですが、ひとまず良さそうな構成に落ち着いたのでメモしておきます。

構成

入り口から順に並べるとこんな感じです。

  • apache2 (:80)
  • mod_proxy_balancer (:80 -> :4000)
  • swiftiply (:4000 <- :30000)
  • merb cluster with SwiftipliedMongrel adapter (:30000)

apache2はpassengerを使っているので外せないとして、
ruby-1.9.1で動かすサービスはmod_proxy_balancerを使う事にしました。
最初はバックエンドに直接merb clusterを当てていたのですが、
現状のmerb clusterにはgraceful reloadする機能が無いようだったので、
間になにか挟む事にしました。

そこで、以前から目を付けていた
Swiftiply
を試す事にしました。
Swiftiplyはとても面白い設計のウェブサーバで、そのへんは
rakutoさんの記事
[Rails] Swiftiplyのアーキテクチャとベンチマーク
を読むのがわかりやすいでしょう。

merbはbuilt-inでSwiftiply用のアダプター(SwiftipliedMongrel adapter)を備えているので、Swiftiplyを起動する設定をおこなえば、使うのはわりと簡単です。

しかし、例によってswiftiplyが依存しているeventmachineがruby-1.9.1
でインストール出来なかったので、原因を調査。
単純にテストの実行だけがうまく動いてないようだったので、テスト無しでインストールしました。

あとは、swiftiplyの起動スクリプトを用意。

/etc/init.d/swiftiply

ruby>>
#!/usr/bin/env ruby

PID = "/var/run/swiftiply.pid"

case ARGV[0]
when 'start'
if File.exist?(PID)
puts "swiftiply is already started."
exit 1
end
print "Starting swiftiply: "
pid = fork do
Process.setsid
Process.exec "swiftiply", "-c", "/etc/swiftiply.yml"
end
begin
open(PID, "w"){|file| file.write pid}
puts "done"
rescue Exception => e
puts e.message
Process.kill 9, pid
end
when 'stop'
print "Stopping swiftiply: "
begin
pid = open(PID).read.to_i
File.unlink PID
Process.kill 9, pid
puts "done"
rescue Exception => e
puts e.message
end
when 'restart'
system $0, 'stop'
system $0, 'start'
else
puts "usage: {start|stop|restart}"
end
<<--

アプリケーションの起動、停止は以下のようにおこないます。

pre>>
cd #{current_path}; #{merb} -K all
cd #{current_path}; #{merb} -e production -a swift -c 1 -p 30000
<<--

これで動きますが、プロセスが死んだりすると不安なので、
monitを使って死活管理をおこなうようにします。

/etc/monit/monitrc

pre>>
check process swiftiply
with pidfile /var/run/swiftiply.pid
group root
start program = "/etc/init.d/swiftiply start"
stop program = "/etc/init.d/swiftiply stop"
if failed port 4000 then restart
if 5 restarts within 5 cycles then timeout

check process merbist
with pidfile /mnt/app/merbist/current/log/merb.30000.pid
group root
start program = "/mnt/app/merbist/shared/script/start"
as uid app and gid app
stop program = "/mnt/app/merbist/shared/script/stop"
as uid app and gid app
if failed port 30000 then restart
if 5 restarts within 5 cycles then timeout
depends on swiftiply
<<--

以上で完了。

現在、http://merbi.st/ は上記の設定で動いています。

See Aslo

追記

しばらく運用してみたのですが、プロセスが数時間に一回ぐらいのペースで落ちる問題があったので、Swiftiplyの代わりにPoundを使う構成に変えました。
Swiftiplyは次のバージョン0.7.0から、クラスタリングのサポートが強化されるようなので、それまで待ってみた方が良いかもしれないですね。

posted by genki genki on Fri 20 Feb 2009 at 16:05 with 0 comments

thinを使ってmerbアプリケーションをrackupする方法のメモです。
以下のようなconfig.ruファイルを用意します。

ruby>>

config.ru

require 'rubygems'
require 'merb-core'Merb::Config.setup(
:merb_root => File.expand_path(File.dirname(FILE)),
:environment => ENV['RACK_ENV'])
Merb.environment = Merb::Config[:environment]
Merb.root = Merb::Config[:merb_root]
Merb::BootLoader.run

use Merb::Rack::Static, Merb.dir_for(:public)
run Merb::Rack::Application.new
<<--

あとは、以下のようなコマンドで起動します。

pre>>
% RACK_ENV=development thin -R config.ru start
<<--

posted by genki genki on Thu 19 Feb 2009 at 02:47 with 0 comments

Merbはコマンドラインから-c 3のように指定する事で、
クラスターとして起動出来ますが、これを設定ファイルの
中から行う場合には、以下のように書けば良いようです。

config/init.rb
ruby>>
Merb::Config.use do |c|
...
 c[:port] = 4001
 c[:cluster] = 2
end
<<--

あとは、普通にmerbコマンドで起動するだけです。
config/environments/production.rbなどで設定しても良いですね。

posted by genki genki on Tue 17 Feb 2009 at 01:02 with 0 comments

Ruby-1.9.1がリリースされて以来、
@maiha
さんと一緒にMerbのRuby-1.9.1対応のための作業を続けてきましたが、
ようやくMerbアプリケーションをRuby-1.9.1で動かす事ができました。

ss
http://merbi.st

決定的に重要だったのは、@ko1_twitterさんが作ってくれた
methoparaです。
これによって、merbが抱えていたmerb-action-argsに関する問題を解決するための道が開かれ、Ruby-1.9.1対応を行うためのモチベーションが高まりました。

今回のRuby-1.9.1対応のために作ったパッチや、修正版のGemなどの多くは
github上に残っています
(http://github.com/maiha, http://github.com/genki)
残りは、http://merbi.st/pluginsで公開されています。

Good luck!

posted by genki genki on Sat 14 Feb 2009 at 14:47 with 0 comments

The ruby-1.9.1 had come to the world, and
several days went by...

@maiha
and I have worked for making the merb to be correspond to the ruby-1.9.1.
And now we are very happy, because we can announce there is the first child of Merb and ruby-1.9.1.

http://merbi.st is the site which is running on merb with ruby-1.9.1.

ss

Our efforts are remaining at several places.
Most of them are in github
(see http://github.com/maiha,
http://github.com/genki).
Others are published on here http://merbi.st/plugins

Good luck!

posted by takiuchi takiuchi on Sat 14 Feb 2009 at 14:43 with 0 comments

GitHubでGemを公開する場合に、バージョン管理をどうするか色々考えていたのですが、良さそうな方法を見つけたのでメモしておきます。

  • 開発時のバージョン番号はリリースしているものより一つ進める
    • 0.1.0をリリースしてる場合、ソースコード中のバージョン番号は、次のリリースバージョン(例えば0.1.1など)にしておく
  • リリース時にgemspecを更新してpushする

これによって、http://gems.github.com をリリースGemリポジトリとして使う事が出来ます。

また、開発中のEdgeGemを配布する場所としては、自分でGemリポジトリを作ってしまうのが良さそうです。
弊社では、http://merbi.st というEdgeGemリポジトリを用意しています。
これは、あらかじめ登録してあるGitHubなどの外部リポジトリから、定期的に最新のコードをfetchしてGem化しています。

posted by genki genki on Mon 9 Feb 2009 at 08:57 with 0 comments

ss

Yay! I am looking forward to it very much!

Merbはmerb-action-argsが無くても動くのだけど、
あの格好良さを知ってしまうとね・・・。

posted by genki genki on Fri 6 Feb 2009 at 05:57 with 0 comments

merbでGeneratorプラグインを作る方法を紹介します。

merbのgeneratorの仕組みは、
templater
という汎用的なGeneratorフレームワークを使って作られています。
なので、基本的にはtemplaterを使ったGeneratorの作り方という事になります。

まずは、Generatorプラグインのひな形を生成します。

pre>>
% merb-gen plugin hello
<<--

Gemの形で提供されるものであれば、merb-pluginの形式である必要は無いですが、簡単にそれを用意してくれるので、merb-gen pluginを使ってみました。

さて、続いて、GEM_ROOTにGeneratorsというファイルを作ります。

ruby>>
scope 'merb-gen' do
dir = File.join(File.dirname(FILE), 'lib', 'generators/')
Merb.add_generators dir + 'hello_generator'
end
<<--

続いて、GEM_ROOT/lib以下のディレクトリ構成を以下のような感じにします。

pre>>
lib
-- generators |-- hello_generator.rb -- templates
-- hello |-- app | -- app以下のファイル群
-- spec -- spec以下のファイル群
<<--

hello_generator.rbが、Railsのgeneratorで言うところの
Generatorマニフェストファイルに相当します。

以下はmerb-mailerのgeneratorファイルの例です。

ruby>>
module Merb::Generators
class MailerGenerator < NamespacedGenerator

def self.source_root
  File.dirname(__FILE__) / 'templates' / 'mailer'
end

desc <<-DESC
  Generates a mailer
DESC

option :testing_framework, :desc => 'Testing framework to use (one of: rspec, test_unit)'

first_argument :name, :required => true, :desc => "mailer name"

template :mailer do |t|
  t.source = 'app/mailers/%file_name%_mailer.rb'
  t.destination = File.join("app/mailers", base_path, "#{file_name}_mailer.rb")
end

template :notify_on_event do |t|
  t.source = 'app/mailers/views/%file_name%_mailer/notify_on_event.text.erb'
  t.destination = File.join("app/mailers/views", base_path, "#{file_name}_mailer/notify_on_event.text.erb")
end

template :controller_spec, :testing_framework => :rspec do |t|
  t.source = 'spec/mailers/%file_name%_mailer_spec.rb'
  t.destination = File.join("spec/mailers", base_path, "#{file_name}_mailer_spec.rb")
end

end

add :mailer, MailerGenerator
end
<<--

あとは、GemをインストールすればOK.

posted by genki genki on Thu 5 Feb 2009 at 05:39 with 0 comments

I made a simple plugin to explain a concept of render-filters.

merb_render_filter

In your controller,

ruby>>
class Posts < Application
before_render :set_title1, :only => :show
before :set_title2, :only => :show

def show(id)
@post = Post.get(id)
display @post
end

private
def set_title1
@title = @post.title # <= you can access to @post here
end

def set_title2
@title = @post.title #=> @post is nil!
end
end
<<--

Without this plugin, you couldn't access to instance variables from before-filters.
Of course you can prepare @post in the first before-filter so that you can access from other before-filters.
But why Merb has the action-args?
They are ignored in such case.
This was the problem I wanted to solve by the plugin.

posted by takiuchi takiuchi on Sun 1 Feb 2009 at 10:08 with 0 comments

merb本体全部対応するのはちょっと厳しかったので、
merb-genを起動する所までで必要なgemをruby-1.9.1対応にしてみました。

これに加えて、merb-gen自体のコードもruby-1.9.1対応させました。

これでmerb-genコマンドでappのひな形を生成する所までは動きます。

posted by genki genki on Sat 31 Jan 2009 at 14:16 with 0 comments

MerbのRakeタスク間の依存関係が知りたかったので、調べてみました。

pre>>
app <= [slices:merb-auth-slice-password:freeze:app_with_gem]
clobber_coverage <= [spec:clobber, spec:coverage]
copy_assets <= [slices:merb-auth-slice-password:install]create <= [db:reset]
db:migrate:up <= [db:migrate]
drop <= [db:reset]
env <= [slices:list]
freeze:app <= [slices:merb-auth-slice-password:freeze]
freeze:views <= [slices:merb-auth-slice-password:patch]
gem <= [slices:merb-auth-slice-password:freeze:app_with_gem]
load <= [db:migrate:down, db:migrate:up]
merb_env <= [audit:actions, audit:controllers, audit:routes, db:automigrate, db:
autoupgrade, db:database_yaml, db:migrate:load, sessions:clear, sessions:create]
migrate <= [db:reset, slices:merb-auth-slice-password:install]
preflight <= [slices:merb-auth-slice-password:install]
setup_directories <= [slices:merb-auth-slice-password:install]
slices:list <= [slices]
spec <= [default]
spec:default <= [slices:merb-auth-slice-password:spec, spec]
spec:explain <= [slices:merb-auth-slice-password:spec]
stubs <= [slices:merb-auth-slice-password:patch]
<<--

右側が左側に依存してる感じです。
以下のようなコードをRakefileの末尾に記述して取得しました。

ruby>>
tree = {}
Rake::Task.tasks.each{|t| t.prerequisites.each{|r| (tree[r] ||= []) << t.name}}
tree.sort_by{|i,j|i}.each{|r,a| puts "#{r} <= [#{a.sort.join(', ')}]"}
<<--

posted by genki genki on Fri 30 Jan 2009 at 03:00 with 0 comments

Merbの良い所を紹介するシリーズ第2回。
今回はMerbのドキュメントViewerを紹介します。
Merbの公式ドキュメントは以下のサイトで見る事が出来ます。

http://merbivore.com/documentation/current/doc/rdoc/stack/index.html

ss

常に最新のドキュメントが公開されていて、
インクリメンタルな検索が出来たりして便利です。

追記

唯一の難点は、URLが覚えにくい事だと思ったので、
短くて覚えやすいショートカットを作ってみました。

上記のURLから、公式のgit-head docにリダイレクトします。

これでいつでも、思い立ったらすぐにドキュメントを参照出来ますね。

posted by genki genki on Wed 28 Jan 2009 at 17:00 with 0 comments

しばらく、Merbの良い所を紹介していこうと思います。

Merbのpartialは、以下のように使います。

html>>
<%= partial :form, :hello => @hello %>
<<--

Railsの場合だとこんな感じ。

html>>
<%= render :partial => "form", :locals => {:hello => @hello} %>
<<--

Merbの方がシンプルなだけでなく、
Controllerの中からも同じように利用出来ます。

posted by genki genki on Tue 27 Jan 2009 at 14:46 with 0 comments

MerbアプリケーションでAtomフィードを配信する方法のメモです。

まずは、config/dependencies.rbに、利用するGemを登録します。

ruby>>
dependency "merb-builder"
dependency "merb_full_url"
<<--

merb_full_url
は、フィード中で埋め込まれる
absoluteなURLを生成するためのプラグインです。
以下のようにインストールできます。

pre>>
% sudo gem install merb_full_url --source http://merbi.st
<<--

次に、コントローラのindexアクションで
atomフィードを提供する事を指示します。

ruby>>
def index
provides :atom
<<--

さらに、config/init.rbで、MIMEタイプとしてatomを登録します。

ruby>>
Merb::BootLoader.before_app_loads do

(..snip..)

Merb.add_mime_type(:atom, :to_atom, %w[application/atom+xml])
end

<<--

続いて、フィードを生成するためのindex.atom.builderファイルを
app/views/{resource_name}の下に作ります。
このあたりはRailsのXmlBuilderと同じような感じです。

ruby>>
xml.instruct! :xml, :version=>"1.0"
xml.feed(:xmlns => "http://www.w3.org/2005/Atom") do |feed|
feed.title @title
feed.link :type => 'text/html', :rel => 'alternate',
:href => full_resource(:posts)

@posts.each do |post|
feed.entry do |entry|
entry.id post.id
entry.title post.title
entry.content post.body, :type => 'text'
entry.issued post.created_at
entry.modified post.updated_at
entry.link :type => "text/html", :rel => "alternate",
:href => full_resource(post)
entry.author do |author|
author.name post.user.login
end
end
end
end
<<--

以上で完了。これで後は/{resource_name}.atom
にアクセスすれば、フィードが表示されます。

ちなみに、Vimでmerb.vimを使っている場合、.builderのfiletype
を認識させるために、.vim/ftdetect/merb.vimの中で、
以下の1行を加えると、正しくファイルタイプを認識してくれます。

pre>>
au BufRead,BufNewFile /app/{mailers/,}views/.builder set ft=ruby.merb_controller
<<--

posted by genki genki on Tue 27 Jan 2009 at 06:17 with 0 comments

Twitter的な動画共有サイトである
Seesmic
では、Merbを使っているようです。

ss

プロダクションで使ってるところも増えてきましたね。

弊社では、PokéDiaの祝日情報共有サービスのバックエンドとなる
Webサービスを、Merbで開発中です。

posted by genki genki on Tue 27 Jan 2009 at 04:35 with 0 comments

Merbを使ってWebアプリケーションを開発している or
しようと考えている方の為に、
お勧めのプラグイン/Slice情報を共有するサービス
Merbist Plugins
をテスト公開いたします。

ss

デザインもScaffoldに毛が生えた程度で、
この手のサービスには欠かせないRatingなどの基本機能が無い状態なのですが、
そもそもMerbのプラグイン情報を共有する場所が無いので、
無いよりはちょっとましかなという事で公開しておきます。

merb_rating, dm-has-rating のようなプラグインを見つけたら、
あるいは作ったら、Rating出来るようになると思います。

ちなみに、新着プラグインのFeedも配信しています。

よろしくお願いします。

posted by genki genki on Sat 24 Jan 2009 at 02:38 with 0 comments

I released the merb_full_url plugin that provides URL which has origin (scheme, host and port)

You can install this gem like this;

pre>>
% sudo gem install merb_full_url --source http://merbi.st
<<--

And you get full URLs by calling full_url/full_resource methods instead of url/resource.

But I think, it is better for merb to become providing such methods in advance :-)

posted by takiuchi takiuchi on Sat 24 Jan 2009 at 02:24 with 0 comments

githubの仕様変更
により、githubでEdgeGem (EdgeのコードをGemにまとめたもの)
を常にフレッシュな状態で公開する事が難しくなってしまったので、
Merbist向けにプラグイン配布用のGemサーバを用意しました。

gems.rubyforge.orgやgems.github.comなどの通常のGemサーバと同様に、以下のようにsourcesに登録して使う事ができます。

pre>>
% sudo gem sources -a http://merbi.st
<<--

仕組みとしては、http://merbi.st/fetch にアクセスされると、
登録されているgithub上のリポジトリから、Edgeのコードがpullされ、
GemとGemサーバ用のインデックスデータを作成します。

現時点では、以下のGemを公開しています。

pre>>
% gem list -r -s http://merbi.st

*** REMOTE GEMS ***

dm-has-versions (0.1.1)
dm-pagination (0.1.1)
merb_babel (0.1.2.2)
merb_component (0.1.1)
merb_recognize_path (0.0.2)
merb_slice-gen (0.0.2)
merb_timezone_select (0.0.2)
pagination_scope (0.0.8)
rttool (1.0.2)
<<--

サーバの負荷の面で不安があるので、
現時点では同期するリポジトリの登録は管理者のみに制限していますが、
http://merbi.st/plugins
よりプラグイン情報を登録していただければ(要アカウント作成)、
問題が無い限り定期的に確認して同期リストに追加いたします。

反応がない場合は@takiuchi
までご一報ください。

posted by genki genki on Fri 23 Jan 2009 at 05:01 with 0 comments

最近書いたNested Resourceに関する話題をまとめておきます。

外部サイトからのリンクバックもまとめ記事作成のための検索対象に含めたら、もっと便利になるかな。

posted by genki genki on Fri 23 Jan 2009 at 04:51 with 0 comments

config/dependency.rbの形式が変わったようなので、メモです。

ruby>>

dependencies are generated using a strict version, don't forget to edit the dependency versions when upgrading.

merb_gems_version = "1.0.8.1"
dm_gems_version = "0.9.9"
do_gems_version = "0.9.10.1"

For more information about each component, please read http://wiki.merbivore.com/faqs/merb_components

dependency "merb-core", merb_gems_version
dependency "merb-action-args", merb_gems_version
dependency "merb-assets", merb_gems_version
dependency("merb-cache", merb_gems_version) do
Merb::Cache.setup do
register(Merb::Cache::FileStore)
end
end
dependency "merb-helpers", merb_gems_version
dependency "merb-mailer", merb_gems_version
dependency "merb-slices", merb_gems_version
dependency "merb-auth-core", merb_gems_version
dependency "merb-auth-more", merb_gems_version
dependency "merb-auth-slice-password", merb_gems_version
dependency "merb-param-protection", merb_gems_version
dependency "merb-exceptions", merb_gems_version

dependency "data_objects", do_gems_version
dependency "do_sqlite3", do_gems_version # If using another database, replace this
dependency "dm-core", dm_gems_version
dependency "dm-aggregates", dm_gems_version
dependency "dm-migrations", dm_gems_version
dependency "dm-timestamps", dm_gems_version
dependency "dm-types", dm_gems_version
dependency "dm-validations", dm_gems_version
dependency "dm-serializer", dm_gems_version

dependency "merb_datamapper", merb_gems_version
<<--

merb_gems_version、
dm_gems_version、
do_gems_version の三つの系列のバージョンに分かれたのですね。

posted by genki genki on Thu 22 Jan 2009 at 19:11 with 0 comments

まずはネストという言葉に関する定義の問題ですが、
諸橋さんが書いているように
、PostsコントローラはComments
コントローラを集約(aggregates)しますが、
内包(compose)する訳ではありません。

たとえば、管理画面に対応するAdminコントローラから、
Commentの削除や修正を行う場合に、AdminコントローラからCommentsコントローラを集約する事を考えると、その必要性が分かりやすいと思います。

PostsコントローラにCommentsを制御するコードを書いてしまう(内包してしまう)と、
Adminコントローラで同じ事をする必要が出来た場合に、
同じようなコードを書く必要が出てきます。

Don't Repeat Yourself!

結果として、メンテナンス性の悪いコードが出来上がります。
この事が、Postsコントローラの中でCommentsリソースを制御する
コードを書く事の問題の本質だと思います。
もちろん、あらかじめCommentsを集約する存在が
Postsしか無いと分かっている場合には問題ありません。

という事で、
続いて検索やCRUD以外の操作はどうするのかという問題について。

この問題を考えるための重要な視点として、最近のRailsやMerbでは、
リソースを制御するためのリソースコントローラと、
それ以外の制御をするための汎用コントローラの役割が分けて考えられるようになってきているという事があります。

上述の話の中で登場するコントローラを当てはめてみると、
以下のようになります。

|caption=コントローラの分類
|
|リソースコントローラ,汎用コントローラ
|
|Posts,Admin
|Comments,Dashboard

リソースコントローラとは、特定のモデルに対してCRUD(create, read, update, delete)操作を行う事に特化したコントローラです。

そう考えると、検索やCRUD以外の操作は、
汎用コントローラが行うのが自然な気がします。
例えば、ブログの記事(Posts)やコメント(Comments)
の検索を行う場合であれば、
SearchコントローラがPostsコントローラやCommentsコントローラ
を集約すればOK.
疑似コード的に表現すると、以下のような感じになります。

pre>>
Search#posts with_scope(query){ Posts#index }
Search#comments with_scope(query){ Comments#index }
<<--

この構造は2年ぐらい前にRailsで使ってみたのですが、
Railsのコントローラの実装がネストに向いていないので、
パフォーマンス上の問題に苦しみました。
Railsのコントローラは、フィルタの実行、アクションの実行だけでなく、レスポンスの作成を担っているので、ネストさせた場合に、
いったん作成したレスポンスを破棄する必要があるなど、無駄が大きいのです(Railsではお馴染みのDoubleRenderErrorが発生するのもこのせいです)

しかし今であれば、Merbを使う事によってこの問題は解決します。
Merbのコントローラはレスポンスの作成をする必要が無く、非常にシンプルなので、ネストさせてもパフォーマンスの問題はほとんどありません。

ということで、Merbを使えばみんなHappyになるよという話でした。

前回の記事の追補

前回の記事では、Aggregatorの#showで、POST, PUT, DELETEを
Aggregatedに委譲するという話を書きましたが、
例えばPostsコントローラがCommentsとTrackbacksの
2つのリソースを集約している場合に、
どちらのリソースに対する操作なのかを判別する方法を用意する必要があるので、どうやって実現するか書いておきます。

pre>>
posts/1 -> Posts#show
posts/1/comments -> Posts#show -> Comments
posts/1/trackbacks -> Posts#show -> Trackbacks
<<--

こんな感じに、posts/1/:resource に対するPOST, PUT, DELETEを、
:resourceに対応するリソースコントローラに委譲する感じですね。

posted by genki genki on Wed 21 Jan 2009 at 09:02 with 0 comments

ちょうどタイミング良く@maihaさんから、@yuguiさんが
近くに来てるという情報をもらったので、
@yamazさんも交えて、ネストしたリソースを扱うコントローラの問題の答えを得るべく、ミーティングをしました。

問題の定義:

モデル層で Post has_many Comment な関係にある時に、
Commentのリストとコメント投稿フォームを含むPosts#show画面(典型的な例としてはブログの一記事表示画面)から、Commentを投稿した場合に、

  1. Comments#createで受け取ると、Commentのsaveに失敗した時に、Post#showを表示したいが、redirect resource(@comment.post) すると、@comment.errorsの情報がロストしてしまう。
  2. Posts#create_commentなどで受け取ると、Commentリソースの処理をPostsコントローラで行う事になって責任の範囲が不明確になり、格好が悪い。

解決策

Merbベースでコンセプトを示します。
まずは、Postsコントローラの中で、以下のような包含関係を宣言するようにします。

ruby>>
class Posts < Application
has_many :comments
end

class Admin < Application
has_many :comments
end
<<--

これにより、コントローラ同士の協調関係を、コントローラ自身が知っているという事になります。
具体的にhas_manyがやることは、以下のようなbeforeフィルターをshowアクションに対してセットする事です。

ruby>>
class Posts < Application
before :only => :show do
controller = Comment.new(request)
@comment = case request.method
when "POST"; controller._dispatch(:create)
when "PUT"; controller._dispatch(:update)
when "DELETE"; controller._dispatch(:destroy)
end
end
<<--

:showアクションに対して、本来は使われない"POST", "PUT", "DELETE"
の各メソッドでのリクエストがあった場合に、Commentsコントローラに処理を回します。"GET"の場合は普通にPosts#showが行われます。
Commentsコントローラ側では、メソッドの返り値としてcommentオブジェクトを返します。

ruby>>
def create(comment)
Comment.create(comment)
end
<<--

作成に失敗した場合は、@comment.errorsにエラー情報が入っているので、
Posts#showの中から利用出来ます。

Posts#show内のComment投稿フォームは以下のような感じで、
Post#showに対してサブミットします。

html>>
<%= form_for @comment, :action => resource(@post) do %>
<%= partial "comments/form" %>
<% end =%>
<<--

コンセプトなので実際に動くかどうかまだ検証してないですが、
こんな感じで良いのではないかと。

MerbだとControllerがresponseの生成を担当していないので、
コントローラをまたいだ処理のネストが高速に行えます。
Railsの場合は、個々のアクションの実行がresponseの生成を伴うので、
この方法だとオーバヘッドが大きくて難しいかもしれません。

posted by genki genki on Tue 20 Jan 2009 at 02:03 with 0 comments

蛇足感がありますが、ちょっと補足しておきます。

Rails勉強会@東京、面白かったんですが。

そもそも何が問題かというと、
Commentsコントローラが担当すべきCommentリソースの処理を、
Postsコントローラで書かなきゃいけないのが格好わるいのでなんとかしたい、という事なんです。

Child belongs_to Parent な関係にあるChildリソースを描画する時は、
多くの場合Parentの描画を伴う事になるので、
そもそもControllerの仕組みが入れ子関係を上手く扱えるようになっているとありがたいのだけど。

View上で階層関係になっているものを、フラットなControllerで処理するという事がそもそも無理があるのかもしれない。
Controller側も階層化させるか、さもなくばセッション中に言ったように、
子供のリソースはAjaxで処理するという形にするのが奇麗かな。

posted by genki genki on Mon 19 Jan 2009 at 20:01 with 0 comments

Merbでは、URLとアクションのマッチングをconfig/router.rbの中で定義しますが、ちょっと複雑なパターンマッチングを行いたい場合は、以下のように正規表現を使ってRouteを定義する事ができます。

ruby>>
match(%r{^/gems/(.*)$}).to(
:controller => 'gems', :action => 'show', :name => "[1]")
<<--

正規表現にマッチしたグループを、パラメータ側から"[1]"のように後方参照する事ができます。

posted by genki genki on Mon 19 Jan 2009 at 10:37 with 0 comments

I am using merb with passenger on our production server.
The passenger is going well in most of cases.
But if dependent gems have been updated, it becomes acting up when it got restarted.
After I encountered such situations several times, I decided to begin investigation about the issue.

Detail of the issue is as follows.

  • My merb app becomes causing errors after restarting of passenger by "touch tmp/restart.txt" if dependency gems were updated and they have no compatibility.
  • Even if the gems were compatible, my merb app kept using old gems until apache is reloaded.
  • pids were changing successfully while restarting.
  • /etc/init.d/apache reload can refresh everything as expected.
  • "touch tmp/restart.txt" can't.

I wonder there is the master of master processes which keeps LOAD_PATH and it is refusing to use new gems.
I am thinking I should investigate the Rack.

Update

Finally, the issue was solved!

The solution is found here

posted by takiuchi takiuchi on Sat 17 Jan 2009 at 22:41 with 0 comments

Mattettiさんから連絡があって、
forkして開発していたmerb_babel

本家に取り込んでもらいました
以下のような機能を追加しています。

  • Merb::Requestからの国コード判別機能
  • YAMLを利用した階層化ローカライゼーション
  • 時刻のローカライゼーション

将来的には、merb-sliceの形にして、ローカライズファイルの
オンライン編集が出来るようにしたい。

posted by genki genki on Sat 17 Jan 2009 at 04:49 with 0 comments

Merbのプラグインを作る場合、merb-gen plugin plugin-name でひな形が生成されますが、現状では生成されるspecがほとんど空っぽなので、
ちゃんとしたspecを書くための足場の作り方を紹介します。

まずは、spec/spec_helper.rb を以下のような感じに準備します
(これはdm-has-versionsの例です)

ruby>>
$:.push File.join(File.dirname(FILE), '..', 'lib')

require 'rubygems'
require 'merb-core'
require 'dm-core'
require "spec"
require 'dm-has-versions/has/versions'
require 'dm-aggregates'

DataMapper::Model.append_extensions DataMapper::Has::Versions
Merb.disable(:initfile)
Merb.start_environment(
:testing => true,
:adapter => 'runner',
:environment => ENV['MERB_ENV'] || 'test',
:merb_root => File.dirname(FILE) / 'fixture',
:log_file => File.dirname(FILE) / "merb_test.log"
)
DataMapper.setup(:default, "sqlite3::memory:")

Spec::Runner.configure do |config|
config.include(Merb::Test::ViewHelper)
config.include(Merb::Test::RouteHelper)
config.include(Merb::Test::ControllerHelper)

DataMapper.auto_migrate!
end
<<--

この例では、DataMapperを使う事を前提としています。
"sqlite3::memory:" を指定することで、テストのための
データベースファイルなどを用意する必要がないので楽です。

テストで利用されるクラス群は、spec/fixture 以下に、
通常のMerbアプリケーションと同様のディレクトリ階層で用意します。

pre>>
% tree spec/fixture [~/project/dm-has-versions:master]
spec/fixture
-- app -- models
|-- comment.rb
`-- story.rb
<<--

posted by genki genki on Fri 16 Jan 2009 at 12:16 with 0 comments

DataMapper用のバージョン管理プラグイン、
dm-has-versions
をリリースしました。

dm-is-versionedというライブラリが既にあるのですが、Railsで慣れ親しんだacts_as_versionedと微妙に挙動が違うのと、revert_toやversion=ができないなど、細かいところが足りない感じがしたので作りました。

USAGE:

以下のコードをご覧の通りです。

ruby>>
class Story
include DataMapper::Resource

property :id, Integer, :serial => true
property :title, String
property :updated_at, DateTime

has_versions :ignore => [:updated_at]
end

Story.auto_upgrade!

story = Story.create(:title => 'hello')
story.version #=> 0
story.update_attributes :title => 'good night'
story.version #=> 1
story.title #=> 'good night'
story.version = 0
story.title #=> 'hello'
<<--

auto_upgrade!は最初に一回だけ必要です。

posted by genki genki on Fri 16 Jan 2009 at 03:37 with 0 comments