Rubyでは、Module関数を沢山定義する時に便利な Module.module_function というものがあります。以下のように使えます。

   1  module Foo
   2    def foo; puts "foo"; end
   3  module_function
   4    def bar; puts "bar"; end
   5    def baz; puts "baz"; end
   6  end
   7  
   8  Foo.foo # => NoMethodError: undefined method `foo' for Foo:Module
   9  Foo.bar # => bar
  10  Foo.baz # => baz

module_functionの定義を終了したい場合にはどうやるのかな、と漠然と思っていたのですが、以下のようにすれば良いようです。

   1  module Foo
   2  module_function
   3    def bar; puts "bar"; end
   4    def baz; puts "baz"; end
   5  public
   6    def foo; puts "foo"; end
   7  end
   8  
   9  Foo.foo # => NoMethodError: undefined method `foo' for Foo:Module

class関数用のclass_functionもあるのかなと思ったら、無いようです。 classではmodule_functionを使う事も出来ないみたいです。

posted by Png genki on Sat 3 Jan 2009 at 13:33

GitHub上で公開されているgemは、GEM_NAMEにユーザIDがプレフィックスとしてついてしまうので、以下のようにdependency.rbを記述する必要があります。

   1  dependency "genki-merb_babel", :require_as => 'merb_babel'

Railsのconfig.gemの場合の:libオプションに似ていますね。

posted by Png genki on Fri 2 Jan 2009 at 20:58

Railsでいうところの@user.errors.addに相当する機能について、 merb(DataMapper)でも似たような仕組みがあります。

エラー情報を追加するためには、以下のようにします。

   1  @user.errors.add(:login, "login is invalid")
   2  @user.errors.add(:general, "I'm in a bad mood today :-P"

errorsは、 DataMapper::Validate::ValidationErrorsクラス のインスタンスです。特定のプロパティーに関連するメッセージは、第一引数でプロパティを示すシンボルを、それ以外の場合は:generalを指定するようです。

posted by Png genki on Thu 1 Jan 2009 at 22:00

merb-auth-slice-activation は、merbでemailアクティベーションを簡単に実装するための非常に便利なSliceです。 このSliceは、merb-auth-coreが提供するhookである、after_authenticationをフックする事によって、アクティベーションが済んでいないアカウントでのログインをブロックする仕様になっています。該当するコードは以下の通り。

merb-auth-slice-activation/lib/merb-auth-slice-activation.rb

   1      # Initialization hook - runs before AfterAppLoads BootLoader
   2      def self.init
   3        # Actually check if the user is active 
   4        ::Merb::Authentication.after_authentication do |user, *rest|
   5          if user.respond_to?(:active?)
   6            user.active? ? user : nil
   7          else
   8            user
   9          end
  10        end
  11      end

ただ、この実装では、ログインに失敗した場合に何のエラーメッセージも表示されないため、以下のようにExceptionsコントローラにコードを加え、エラーメッセージを表示するようにしてみます。

   1  class Exceptions < Merb::Controller
   2    (snip)
   3    def unauthenticated
   4      request.exceptions.each do |e|
   5        session.authentication.errors.add(:general, e.message)
   6      end if request.exceptions
   7      render :format => :html
   8    end
   9  end

Merbでは、アクションの実行中に例外が発生した場合、Exceptionsコントローラに処理が回ってきます。その場合、発生した例外オブジェクトは、request.exceptionsで参照出来ます。

あとは、view側で

   1  <%= error_messages_for session.authentication %>

を記述すればOK.

posted by Png genki on Thu 1 Jan 2009 at 21:31

merbにはautomigrateやautoupgradeがあるので、Railsほどmigrationを手で書く機会は少ないのですが、それでもデータ移行や、カラムの削除などの破壊的もしくは単純ではないDB操作を行う必要が発生した場合は、Railsと同様のmigrationを行う事になります。

merbの場合、以下のようなコマンドでマイグレーションファイルを作成します。

   1  % merb-gen migration name_of_migration

schema/migrations/001_name_of_migration.rbという感じでマイグレーションファイルが生成されます。中身はこんな感じ。

   1  migration 1, :name_of_migration  do
   2    up do
   3    end
   4  
   5    down do
   6    end
   7  end

See Also

posted by Png genki on Thu 1 Jan 2009 at 17:59

幸か不幸かいままで気がつかなかったのですが、 sqlite3ではDROP COLUMNが使えないらしい。 do_sqlite3レベルでリネーム&コピーによる実装を行うか、 sqlite3のアップデート待ちという事になりますね。

sqlite3はiPhoneアプリでも使ってるので、カラムの削除はなるべくしなくてすむように気をつけよう。

See Also

posted by Png genki on Thu 1 Jan 2009 at 17:49

merbが生成するデフォルトのアプリケーションのひな形には、最初からユーザ認証の仕組みを提供するmerb-auth-slice-passwordというSliceが組み込まれています。

これが提供するデフォルトのログイン画面を変更するためには、 app/views/exceptions/unauthenticated.html.erbファイルを作ればOKです。

posted by Png genki on Wed 31 Dec 2008 at 09:58

Railsで言う所のbefore_saveのようなフィルタは、 Merb(DataMapper)では以下のように使う。

   1  class Post
   2    include DataMapper::Resource
   3  
   4    before :save do
   5      # do some stuff
   6    end
   7  
   8    def foo
   9      puts "foo!"
  10    end
  11  
  12    before :foo, :bar
  13  
  14    def bar
  15      puts "bar!"
  16    end
  17  end

このbeforeメソッドは、ExtLibのhook.rbの中で定義されている。 上記のように、ブロックを取る形式と、イベントをハンドルするメソッド名をシンボルで指定する形式があります。

ExtLibが提供するhookは、saveやvalid?のような特定のイベントだけではなく、全てのメソッドに対して汎用的に利用出来るようです。 ただし、before :foo, :barのように、hookをインストールする時点で、 hookされるメソッド(この場合はfoo)が定義されている必要があります。

クラスメソッドに対するhookも用意されていて、

   1    before_class_method :new do
   2      puts "new!"
   3    end

のように利用出来ます。

Update

beforeフィルターでメソッドの実行を中止する場合は、

   1    before :save do
   2      throw :halt
   3    end

のように、:haltをthrowすればOKです(thanks maiha)。 これは美しい。

posted by Png genki on Tue 30 Dec 2008 at 12:16

merb-1.0.5以降、merbのthor merb:gem:installの挙動が変わったようです。

   1  % thor merb:gem:install

すると、依存するgemファイル一式をMERB_ROOT/gems/以下にインストールする事が出来ます。 同時に、それらを使うためのコマンド類(merbなど)がMERB_ROOT/bin/以下に作られるので、unpackされたgemを使う場合はそれらのコマンドを使う事になります。

以前は、インストールするgem名を引数に取ったり、--cacheなどのオプションが使えたのですが、1.0.5以降はこれが出来なくなっている模様。

   1  % ./bin/merb -i

これを使う事で、edge merbを手元の環境で簡単に使う事が出来ます。

posted by Png genki on Mon 29 Dec 2008 at 21:48

Zshでthor (トール)のタスクを補完するための補完関数を作ってみました。

   1  _thor () {
   2    compadd `thor list | awk "{print \\$1}" | grep "\w" | \ 
   3    ruby -e "puts ARGF.read.gsub /\\e.*?m/,''" | xargs`
   4  }
   5  
   6  compdef _thor thor

See Also

posted by Png genki on Mon 29 Dec 2008 at 17:31