BackgrounDRbを使ってみた(第8回Rails勉強会@東京ログ)
第8回Rails勉強会@東京において、この前調べておいた記事を実際にやってみた。
http://d.hatena.ne.jp/walf443/20060720/1153400540
動かす際に色々とハマりどころがあったようなのでうまくやるコツのようなものを書いておく。
rakeのタスクは基本的にproduction環境のときに使うコマンドが多い。BackgrounDRbのrakeにインストールされるタスクは基本的にproduction用。development環境の際は、script/backgroundrb/(start|stop)を使う。rakeのタスクはデーモン化し、scriptを使うとデーモン化しない。デーモン化するとlogを見る以外には反応しないのでプロセスをうっかり2・3個立ち上げたりするとうまくいかなくなってしまう。
workerを編集したら実行する前にbackgroundrbサーバーを再起動させること。RailsがWEBrickにやっているみたいなトリック(開発時には再起動しなくても良いようにする)がBackgrounDRbサーバーにもできないかなとか思ったりした。
DRbの使い方を詳しく知らなくてもタスクに対してWorkerクラスにdo_work(args)メソッドを一つ書いておけば良いというのは便利だと思いました。
最近のアップデートだと、期限付きでWorkerを生成できるようになったようです。
http://brainspl.at/articles/2006/07/02/backgroundrb-updated
BackgrounDRbについて調べてみた
このページの情報は既にかなり古いです。私は現在あまり使っていませんし、追ってもいないのでこのページの情報が更新されることはあまり期待できないのでなるべく本家の情報なりを見た方がよいでしょう。2009年8月現在だと http://d.hatena.ne.jp/tech-kazuhisa/20090816/1250432286 とかで似たような情報をあつかってるみたいです。検索しても結構上にまだくるっぽいのでいちおう追記しておきました。
次回の第8回Rails勉強会@東京でセッション案に上がっていたBackgrounDRbというやつがなにやら面白そげなので調べてみた。
調べてみた感じ以下のページがよくまとまっている。
http://www.infoq.com/articles/BackgrounDRb
以下、この内容を大雑把に要約してみる。
Ruby on Railsは素晴らしいフレームワークですが、Webアプリケーションをどんどん拡張していくと、バックグラウンドで長い時間処理するようなタスクが必要になってくることがあります。このような処理をControllerとかに書いてしまうとWebサーバーの動作の妨げになります。Webサーバーをタイムアウトさせずにこれらの処理を手軽に行なえるようにしたRailsのプラグインがBackgrounDRbです。
BackgrounDRbサーバーはMiddleManオブジェクトを配信します。このオブジェクトはworkerクラスを管理します。このオブジェクトは
- @jobs = {job_key => running_worker_object}
- @timestamps = {job_key => timestamp }
というインスタンス変数を持ちます。MiddleManオブジェクトはDRbサーバーとRailsアプリケーションの仲立役として機能します。(ここら辺にある図は見ておいた方が良さそう。)
インストールは次のように行います。
script/plugin install svn://rubyforge.org//var/svn/backgroundrb
Pluginをインストールすると、generatorにworkerジェネレータが使えるようになります。
$ script/generate worker Foo class FooWorker < BackgrounDRb::Rails def do_works(args) end end
MiddleManオブジェクトを通じてRailsからFooWorkerオブジェクトがインスタンス化されると、do_worksメソッドが自動的にRailsとは独立したスレッドで動作します。そのため、Railsはdo_worksメソッドが終了するのを待たずに先に進めます。
BackgrounDRbを使えば、Ajaxリクエストを使うと新しいworkerオブジェクトを生成します。このとき、Viewではperiodically_call_remoteメソッドを利用すればジョブの進行状況を取得し、どうなっているか表示することが出来ます。FooWorkerオブジェクトを生成し、RailsのControllerから進行状況を表示するコードを以下に示します。
class FooWorker < BackgrounDRb::Rails attr_reader :progress def do_work(args) @progress = 0 calculate_the_meaning_of_life(args) end def calculate_the_meaning_of_life(args) while @progress < 100 # ここで計算します @progress += 1 end end end
コントローラは以下のようにします。
class MyController < ApplicationController # FooWorkerを起動するクラス def start_background_task session[:job_key] = MiddleMan.new_worker(:class => :foo_worker, :args => "do_workメソッドに渡される引数") # 追記 「)」が抜けておりました。お試しになった方申し訳ありません。 end # 進行状況を表示するクラス def get_progress if request.xhr? progress_percent = MiddleMan.get_worker(session[:job_key]).progress render :update do |page| page.call('progressPercent', 'progressbar', progress_percent) page.redirect_to(:action => 'done') if progress_percent >= 100 end else redirect_to :action => 'index' end end # タスクが終わったときに表示するメソッド def done render :text => "FooWorker task has completed" MiddleMan.delete_worker(session[:job_key]) end end
start_background_task.rhtmlはpage.call('progressPercent', 'progressbar', progress_percent)が実行されたときのViewを指定します。*1
<html> <head> <style type="text/css"> .progress{ width: 1px; height: 16px; color: white; font-size: 12px; overflow: hidden; background-color: #287B7E; padding-left: 5px; } </style> <%= javascript_include_tag :defaults %><!-- 本家にもないので注意! --> <script type="text/javascript"> function progressPercent(bar, percentage) { document.getElementById(bar).style.width = parseInt(percentage*2)+"px"; document.getElementById(bar).innerHTML= "<div align='center'>"+percentage+"%</div>" } </script> </head> <body> <div id='progressbar' class="progress"></div> <%= periodically_call_remote(:url => {:action => 'get_progress'}, :frequency => 1) %> </body> </html>
MiddleMan.new_workerメソッドはランダムなジョブキーを持つジョブを作成し、あとからセッションに格納します。ジョブキーに特定の名前を付けたい場合は以下のようにします。
MiddleMan.new_worker(:class => :foo_worker, :job_key => :my_worker, :args => "do_workerメソッドの引数") MiddleMan.get_worker :my_worker
BackgrounDRbの設定はRAILS_ROOT/config/backgroundrb.ymlで行います。load_railsオプションをtrueに設定すると、workerクラスにActiveRecordオブジェクトを使うことが出来ます。BackgrounDRbサーバーを起動する際にdatabase.ymlの情報を読んでデータベースに接続します。
このプラグインはActiveRecordのオブジェクトを含む巨大なオブジェクトをキャッシュする機能があります。レンダリングされたViewや巨大なクエリもMarshallダンプできるオブジェクトであれば何でもキャッシングすることが出来ます。以下のようにして行います。
# キャッシュするには次の2通りのやり方がある @posts = Post.find(:all, :include => :comments) MiddleMan.cache_as(:post_cache, @posts) @posts = MiddleMan.cache_as :post_cache do Post.find(:all, :include => :comments) end # 次のようにしてキャッシュを取り出す @posts = Middleman.cache_get(:post_cache) @posts = MiddleMan.cache_get(:post_cache){ Post.find(:all, :include => :comments) }
MiddleMan.cache_getがブロックをとるかとらないかは:post_cacheキーが存在しないときにnilを返すか、ブロックを実行するかである。
現在の実装だと役目を終えたworkerオブジェクトやキャッシュは手動で無効にしてやる必要がある。これにはMiddleMan.delete_worker(:job_key)とMiddleMan.delete_cache(:cache_key)を使う。Timeオブジェクトを取り、それよりもタイムスタンプが古ければ削除するというMiddleMan.gc!メソッドもある。次の例は、30分前のジョブは無効にするスクリプトである。これをcronで定期的に実行することで残った不要なジョブを自動的に消すことが出来る
#!/usr/bin/env ruby require 'drb' DRb.start_service MiddleMan = DRbObject.new(nil, "druby://localhost:22222") MiddleMan.gc!(Time.now - 60 * 30)
将来的にはオブジェクトを作成したときにGCが実行される時間をスケジュールしておけるようにするつもりのようです。
BackgrounDRbサーバーを起動したり停止したりするRake タスクも自動的にインストールされます。
$rake backgroundrb:start
$rake backgroundrb:stop
次のような用途にBackgrounDRbは使われているそうです。
- RSSアグリゲータとしてRSSフィードをダウンロードしてキャッシュする。
- (2番目はちょっとよく分からず)(追記: はてなスクリーンショットみたいなやつ)
- (これもよく分からない)(追記: Xenの仮想サーバーをWeb上から起動するみたいな?)
- Hyper Estraierのインデックスをバックグラウンドで作成したり、検索したりする
- IRCボットとRailsをつなぐ
多少端折りましたが、重要なところはだいたい網羅できてると思います。なんか変なところとかあればコメントとかTBでどうぞ。
追記
yamazさんに具体例の訳を手伝っていただきました。
*1:ちょっとマズいところがあったので本家のソースと差し替えて少し書き加えました
Rails::Initializerを読む。
目次はこちらです。
まずは、rails-stable/railties/lib/initializer.rbから読みます。このファイルではRails全体の初期化を行なっています。
File.join, File.dirname, __FILE__
require File.join(File.dirname(__FILE__), 'railties_path')
Rubyのライブラリではよく出てくる表現なので一応解説しておく。
File.joinはディレクトリ区切り文字で引数を連結した文字列を返す。この場合、Unixであれば"#{File.dirname(__FILE__)}/railties_path"、Windowsであれば"#{File.dirname(__FILE__)}\railties_path"を返す。
File.dirname(filename)はfilenameの一番後ろのスラッシュより前の文字列を返す。もしスラッシュがなければカレントディレクトリ(つまり".")を返します。
__FILE__はRubyの組み込み変数で現在のソースファイル名です。似たようなので$0というのもありますがこちらは実行中のスクリプトファイルを表します。Rubyではよく出てくるので違いをしっかり覚えておきましょう。
つまり、ここのコードでは、このファイル(initializer.rb)と同じディレクトリにあるrailties_path.rbをrequireしています。
RAILS_ENVの設定
RAILS_ENV = (ENV['RAILS_ENV'] || 'development').dup unless defines?(RAILS_ENV)
定数RAILS_ENVが定義されていなければ定義する。環境変数RAILS_ENVがセットされていればそれを用い、それがなければdevelopmentをセットする。
Object#dupを使って複製しているのがちょっとよく分からない。複製していないとRails起動中にENV['RAILS_ENV']が代えられてしまった場合、内部動作に整合性が保てなくなってしまうからだろうか。なかなかどういうときにdupすればよいかというのは難しそうだ。というよりうっかり書き忘れてしまいそうだ。
Rails::Initialize.run
def self.run(command = :process, configuration = Configuration.new) yield configuration if block_given? initializer = new configuration initializer.send(command) initializer end
ブロックでRailsの設定が出来ます。ConfigurationクラスはRailsの設定に関わるクラスです。Railsアプリケーションの作成時にはconfig/environment.rbでこれらの設定を行います。
この中の
initializer = new configuration
というのがビックリする。Javaっぽいけどこれはどういうことなのだろうか?
クラスメソッドの中なので、同じクラスメソッドのnewのレシーバが省略できるらしい。
つまり、
initializer = self.new configuration
ということのようだ。
ここでInitializerにせずselfにしたのはひょっとするとクラス名を変更したい、あるいはaliasしたいといった場合があるからである。クラス定義文の中ではなるべくクラス名は書かずにselfとしておいた方が継承したときなどにいろいろ厄介ごとがなくて済む。
個人的にはどこで定義されているのか混乱してしまうのでクラスメソッドのレシーバ省略はあまりしたくないのですが、省略する方が一般的なのでしょうか?やっぱり派閥に分かれているのかも。まぁできるだけ省略したくないと思うのはそれだけ使い込んでないということなんだろうと思う。今回すぐにクラスメソッドの省略が見抜けなかったのも経験不足ゆえだろう。
次の行はInitializerの任意のインスタンスメソッドをObject#sendを使って実行させている。デフォルトではprocessメソッドが実行されるらしい。
確か1.8以降だとObject#__send__を使わないと警告が出るんだったような気がする。ここらあたりはどうやって回避しているのだろうか。もしかすると再定義されたメソッドなのかもしれない。自分で探してみた感じどうもそれっぽいのが分からないのですがどうなんでしょうか?
Configurationクラス
ここで先にConfigurationクラスを呼んだ方が理解しやすいので一旦Configurationクラスの基本的なところを押さえておく。
default_load_paths
500行目:
paths = ["#{root_path}/test/mocks/#{environment}"]
カッコが省略されているのでわかりづらいがそれ以前にroot_pathやenvironmentといったローカル変数が定義されていないのでこれはメソッドだ。ということで探してみると案の定あった。
def root_path ::RAILS_ROOT end def environment ::RAILS_ENV end
定数を参照しているだけなのにメソッドを作ってるのは何故?
RAILS_ROOT = ::RAILS_ROOT
とかだといけないのか?
503行目:
paths.concat(Dir["#{root_path}/app/models/[_a-z]*"])
書き方からしてDir.glob()のaliasのようだなと思って探してみたらRuby標準で定義されてた。ActiveSupport拡張だと思ってた。今まで知らずに全部Dir.globとか書いてたよ…。ワンライナーとか全部Dirに変更だな。
というより考えてみたら当たり前なのだけど、クラスメソッドに(args)といったメソッドを定義すれば組み込みメソッドみたいに使えるのね。最初はDirといった定数にハッシュを入れておいてそれにアクセスだとか、Kernel#Dir(array)といったメソッドを定義しておいてそれのカッコの省略なんじゃないかと疑った。
default_log_level
log_levelはinfo、debugという2種類のモードがあるようだ。development, testのときはdebugモードで作動する。
default_controller_paths
controllerは"#{root_path}/app/controllers/"だけではなく、"#{root_path}/components/"や"#{RAILTIES_PATH}/builtin/controllers/" に置くことが出来る。
RAILTIES_PATHはrailties_path.rbで定義されている。
railties_path.rb:
RAILTIES_PATH = File.expand_path(File.join(File.dirname(__FILE__), '..'))
File.expand_pathは相対パスの文字列を絶対パスへ展開する。その際、第2引数を基準にする。第2引数が無ければカレントディレクトリとみなす。~の展開もしてくれるらしい。
つまり、Railsライブラリの置いてあるディレクトリのbuiltin/controllers/にコントローラを置くことも出来る。
普通は"app/controllers"を使うけど、"components/"とbuiltin/controllers/はどう使い分けるのだろうか?
他のメソッドは今はちょっとよく分からないので置いておく。
コンストラクタの最後では、フレームワークそれぞれのアクセサメソッドにOrderedOptionsクラスのインスタンスを渡している。
OrderedOptions, OrderedHash
OrderedOptions、OrderedHashは実はActiveSupportでも定義されている。ActiveSupportをrequireする前に必要であるためにDRYじゃないがこちらでも定義せざるを得なかったようだ。OrderedOptionsやOrderedHashを別ライブラリにするという手もあったのではないかと思ってみる。
そうしてOrderedOptionsクラスを見てみると、OrderedHashクラスを継承している。ということでOrderedHashクラスを先に読む。
OrderedHashクラスはArrayを継承している。名前どおり順番を保持したHashを実装している。実装は特に難がなさそうなので飛ばす。順序つき配列を使いたいケースは結構あるので一応こんなのがあったと頭にとめておくと参考に出来る。
ということでOrderedOptionsを読む。
ここで注意することは、OrderedOptionsがキーとして文字列を取るのに対し、OrderedHashはキーとしてシンボルをとる。ActiveSupportをまだrequireしていないのでシンボル・文字列間の自動変換はやってくれない。
method_misingを用いてキーのアクセサっぽくアクセスできるようにしている。
def method_missing(name, *args) if name.to_s =~ /(.*)=$/ self[$1.to_sym] = args.first else self[name] end end
このテクニックは色んなところで使えそうかも。
Initializer#process
ようやくInitializerクラスへ戻ってこれた。次は、Initializer#processを読む。
def process check_ruby_version set_load_path set_connection_adapters require_frameworks load_environment initialize_database initialize_logger initialize_framework_logging initialize_framework_views initialize_dependency_mechanism initialize_breakpoints initialize_whiny_nils initialize_temporary_directories initialize_framework_settings load_environment add_support_load_paths load_plugins initialize_routing after_initialize end
メソッド名が分かりやすいので何の処理を行なっているかが読んだだけで大体は分かる。分かりにくいところ・気になるところだけ以下補足。
check_ruby_version, ruby_version_check.rb
check_ruby_versionはruby_version_check.rbをrequireしているだけなので、ruby_version_check.rbをみてみる。
どうもRubyの1.8.2と1.8.4以降ではRailsは動作して、1.8.3は動かないらしい。どうしてかよく分からない。安定版と開発版が交互だったのだろうか。でもここ最近は1.9.1が開発版だし、1.8.3がそんな前とも思えない。
load_environment
def load_environment silence_warnings do config = configuration constants = self.class.constants eval(IO.read(configuration.environment_path), binding) (self.class.constants - constants).each do |const| Object.const_set(const, self.class.const_get(const)) end end end
silence_warningsというメソッドが定義されている。名前だけでも十分機能を想像できるが一応読んでおく。
silence_warningsはactivesupport/core_ext/kernel/reporting.rbで定義されている。
def silence_warnings old_verbose, $VERBOSE = $VERBOSE, nil yield ensure $VERBOSE = old_verbose end
多重代入を用いて$VERBOSEを一旦old_verboseに非難しておいて$VERBOSEにnilを代入する。今まであまり多重代入は使ってこなかったけど、こういうケースだと意味も明確になるので良さそうな気がする。
このメソッドの難点は警告が使っている間全く表示されないことだろう。Rubyのバージョンが変わって警告が出るようになっても気づかない恐れもある。警告は煩わしいが、出すには出すなりの理由があると思う。
例えば、先ほども出てきたObject#sendは使うと警告が出る。何故かといえば、sendはよくありそうなメソッド名だからだ。だからObject#__send__を推奨するようにメッセージが出てくる。うっかりユーザがsendメソッドを追加してしまったらどこかでハマりになる恐れがあるような気もする。
load_environmentへ戻る。
(self.class.contants - constants).each do |const| Object.const_set(const, self.class.const_get(const)) end
Array#- は集合の差を返すメソッドである。2行前でconstをself.class.constantsとしたあとに、"config/environment.rb"を読み込んでいることから、"config/environment.rb"はInitializer::定数名を定義することを期待されているようだ。変更されたものだけ定数をセットする。こんな回りくどいやり方をしているのは定数に再代入しようとしたら例外が起こるからだろう。
evalを使っても出来るが、Module.const_getやModule.const_setを使った方が作法としては良いだろう。
initialize_logger
RAILS_DEFAULT_LOGGERが定義されていたら、何もせずに抜けるようだ。つまり独自のloggerを使いたければコンストラクタでRAILS_DEFAULT_LOGGERを定義しておけば良いようです。でも、その処理はどこに書けば良いのだろう・・・。
initialize_dependency_mechanism
Configuration#cache_classesの値によって毎回読み込むか、一度のみ読み込むかを操作しているらしい。
development環境だと:loadモードでproduction環境だと:requireモードになるようだ。
これによりブラウザをリロードするだけでコードの変更を反映できるという開発の快適性を実現している。
Dependenciesクラスはaction_controller/dependencies.rbで定義されている。
詳しく読むのはまた次の機会にする。
initialize_whiny_nils
珍しくメソッド名から意味がよく分からない。activesupportのwhiny_nil.rbをrequireしているのでそちらへ飛ぶ。
このファイルをのぞいてみると、NilClassを拡張している。コメントを読むと、どうやらnilに伴うエラーの際にデバッグしやすくするための拡張のようだ。
WHINERS = [ ::ActiveRecord::Base, ::Array ] @@method_class_map = Hash.new WHINERS.each do |klass| methods = klass.public_instance_methods - public_instance_methods methods.each do |method| @@method_class_map[method.to_sym] = klass end end
まず、あるクラスからObjectクラスに定義されているパブリックインスタンスメソッドの配列の差集合を取ることで独自に定義されているメソッド名を割り出している。
その後、メソッド名をキー、クラス名を値としたハッシュとしてキャッシュしておくことでメソッドを呼び出そうとしたがnilクラスのためメソッドが定義されてないと例外を発生させる以上のことをすることが出来る。
method_missingでraise_nil_warning_forを呼び出すことでユーザがどのクラスを使おうとしていたのかということまで教えてくれる。
ActiveRecord::BaseとArrayに対してこの機能を有効にしているのだけど、これが拡張できるとうれしいケースが他にもあるかもしれない。(ちょっと今は思いつかないけど)
initialize_temporary_directories
セッションとキャッシュを保存するディレクトリを初期化する。
initialize_framework_settings
それぞれのフレームワークの設定を初期化する。
def initialize_framework_settings configuration.frameworks.each do |framework| base_class = framework.to_s.camelize.constantize.const_get("Base") configuration.send(framework).each do |setting, value| base_class.send("#{setting}=", value) end end end
frameworkはシンボルなので定数形式に変換し、〜::Baseとする。(:active_record → ActiveRecord::Base)
Object#__send__を利用してそれぞれのアクセサにアクセスする。それぞれのアクセサの値には前に見たようにOrderedOptionsクラスのインスタンスがセットされているので、eachを使うことが出来る。
eachの内部でまたObject#__send__を使っているが、OrderedOptionsクラスには引数のようなメソッドはなく、method_missingで処理を振り分けていることを思い出して欲しい。
after_initialize, after_initialize_block
Configurationクラスのafter_initializeメソッドで登録したブロックをコールバックする。
Configurationクラスのafter_initializeメソッドをみてみる。
def after_initialize(&after_initialize_block) @after_initialize_block = after_initialize_block end def after_initialize_block @after_initialize_block end
Rubyでコールバック関数を定義するテクニックっぽい。
定義部分で
def method_name(&block)
と受けることでブロックはProcオブジェクトへ変換される。(yieldで受けた場合は変換されない。そのためyieldを使う方が若干速いという説がある)
ブロックは単なるProcオブジェクトになったのでインスタンス変数に代入できる。その結果、変数名.callするだけでコールバックすることが可能。(コールバックの意味合いがちょっと違うような気もする。)
これでとりあえずinitializer.rbはこれで終了。色々なテクニックが駆使されていてかなり面白かった。さすがにこれだけの量の文章を書くのは大変だったけど。
次はどこを読むかまだ決めていない。何かリクエストかお勧めがあればコメント・TBで教えてくださいな。
はじめに・目次
はじめに
Railsのソースコードを読みながら個人的に引っかかったところをメモしていきます。コメント・TB大歓迎です。
飽きっぽい性分のためいつまで続くか分かりませんが頑張ってみようかと思います。
いちおう大げさなタイトルを付けて自分にプレッシャーをかけておきました。
とはいえ卒論やら、期末レポートやらいろいろやらなければならないことに追われていますので比較的マイペースに進めていく予定です。
目次は随時追加していきます。
解説しているソースについて
ここで解説しているコードはbrancheのstableです。同じコードを読みたい方は以下のようにしてソースコードをダウンロードしてください。
svn co -r 4519 http://dev.rubyonrails.org/svn/rails/branches/stable rails-stable
環境としては以下のような感じです。
- Rails 1.1.4
- ActiveRecord 1.14.3
- ActionPack 1.12.3
- ActionWebService 1.1.4
- ActionMailer 1.2.3
このドキュメントに書かれているものはあくまで私個人の解釈であり、正しさを保障できるものではない点をご了承ください。
したがってこのドキュメントのことでRuby on Rails開発者の方に直接問い合わせることは避けてください。
また[RailsHackingGuide]カテゴリのドキュメントに出てくるソースコードのライセンスはすべてRailsの開発者に帰属し、MITライセンスです。
第7回Rails勉強会@東京
第7回Rails勉強会@東京へ行ってきました。
第2回以来の久々の参加でした。何人か顔を覚えてくださっていた方もいらっしゃって前回よりも緊張せずに済みました。
パフォーマンスに関してのページを読む。
セッションの提案が滞っていたので一応他の人の意見を聞いてみようかなと印刷してきていたやつをセッションとして提案してしまいました。
InfoQ - Tracking change and innovation in the enterprise software development community
http://www.infoq.com/articles/Rails-Performance
Railsアプリで遅くなりがちな点について次のようなものがあげられています
- choosing a slow session container
- doing things on a per request basis, which could have been done once at startup
- repeating identical computations during request processing
- reading too often and too much from the database (especially in conjunction with associations)
- relying too much on inefficient helper methods
- 遅いセッションコンテナを使っている
- アプリを立ち上げた際に出来ることをリクエストのたびに行なっている
- リクエストの処理中に同じようなことを何度もやっている
- DBへのアクセスの回数が多すぎる
- helperの使いすぎで遅い
遅いセッションコンテナを使っている
- 速いセッションコンテナといえばSQLSessionStoreとMemCacheStoreがある。
- SQLSessionStoreを使えばActiveRecordStoreのトランザクションやcreate_on、update_atといったイベントを避けることが出来る。
- MemCacheStoreは30%ほどSQLSessionStoreよりも速い
- ただしEric Hodelさん作のmemcache cliantを使う必要がある。(Ruby-Memcacheは遅すぎる)
- このページの作者の人は開発の際にMemcacheは大げさだし色々めんどくさいのでDBベースのセッションストレージを使っているそうです。
アプリを立ち上げた際に出来ることをリクエストのたびに行なっている
- 恋塚さんによれば、ここで出ているコード見たいな感じのやつをlib/以下に書いておけばRailsプロセスを立ち上げた際に読むだけなので良いとのことです。Helper依存しすぎはよくないと。
リクエスト処理中に同じようなことを何度もやっている
- 例として挙げられているdetecting installed Ruby classes/modules using ObjectSpace.eachの意図がちょっと不明
クエリの最適化
Helper使いすぎ
- デフォルトのルーティングは最適化の余地があるのでよく使うものほど上におく方が少し速くなるそうな。
ActiveController::Routing::Routes.draw do |map| map.connect '', :controller => 'welcome" map.connect ':controller/sevice.wsdl', :action => 'wsdl' map.connect ':controller/:action/:id' end
- link_toは遅いのでHTMLで直接書いた方が良いよ。
- メンテコストが高くなるしBKくさいのでやらないよなぁ。
といった感じでした。id:ogijunさんによれば、こういうコードの最適化よりはスケーラビリティの面でのパフォーマンス云々の話の方が重要ということです。
自分がセッションを提案したのに全然オーナーとして知識も経験も不足していて参加された方には申し訳なかったです。
ARモデリング入門
セッションがなかなか出なかったので、Railsは使いやすいけど経験があまりないので実際どのようにモデルを作るかわからないと言ったらセッションになりました。
Yuguiさんとid:moroさんによるABDによるモデリング入門と言った感じでした。題材としてはソーシャルブックマークをモデリングしてみようという話でした。
まずPageとUserがいてそれらはリソース系のモデル。UserがPageをBookmarkするのでその間にイベント系のモデルBookmarkが出来る。テーブルの定義はこんな感じ。
create_table :users do |t| end create_table :pages do |t| t.column :url, :string, :null => false t.column :title, :string end create_table :bookmarks do |t| t.column :page_id, :integer, :null => false t.column :user_id, :integer, :null => false t.column :create_on, :timestamp, :null => false end
モデルは次のようになる。
class User < ActiveRecoard::Base has_many :pages, :through => :bookmarks has_many :bookmarks end class Page < ActiveRecoard::Base has_many :users, :through => :bookmarks has_many :bookmarks end class Bookmark < ActiveRecoard::Base has_many :users, :pages end
追記(06/06/20 08:17)
上の定義に誤りがありました。訂正します。
class User < ActiveRecord::Base has_many :pages, :through => :bookmarks has_many :bookmarks end class Page < ActiveRecord::Base has_many :users, :through => :bookmarks has_many :bookmarks end class Bookmark < ActiveRecord::Base belongs_to :user belongs_to :page end
まずリソース系のモデルを作成してテストをし、テストが通ったらイベント系のモデルを作成して実装していくといった感じにすると良いそうです。たしかにどこをどう組み立てたら良いのかが少しクリアになった気がするし、この書き方であれば一つのテーブルの定義は比較的単純で済むので拡張しやすいのかなと思いました。
ちょっと頭をひねったのが自己参照をする場合。UserがUserをお気に入りに加えるといった場合、Userの間にイベント系のFavoriteモデルを置いて、それをRailsで書くときにどうするのってことになりました。確かhabtm使うしかないんじゃないか的な話になったような気がするんですが、詳しいところは他の人の調べを待とうかと思います。
一次会
たまたま自分の卒論のテーマと似たようなことをやろうとしてる方がいてその人と色々話をしました。高橋さんの執筆経験の話はそこらへんにとても参考になりました。やっぱりモチベーションを保つことが重要で、それにはシステムだけにはどうしようもないところがあるよねってことのようです。
それから国内外でRails本ラッシュが起きそうだけど、実際初心者の人がそれを取って色々アプリを使っても簡単に動かせる環境がないんじゃあダメだよね。と話をして、国内のRailsホスティングサービスがやっぱりほしいなぁという話になりました。
二次会
先延ばしにされたはぶさんへの返答をみんなで考えました。いろいろネタが出ましたが、そこらへんはid:yad-ELさんの感想を参照してください。
http://d.hatena.ne.jp/yad-EL/20060618/1150648909
全体を通してのまとめ
- Keep
今回自分は積極的に関われたと思う
- Problem
なかなかセッションが上がらない。セッションを提案した人がオーナーになってしまうみたいなところが提案しづらいところではないか?
- Try
もうちょっとRailsを使い倒してネタを持っていけるようになる。ネタについていけるようになる。
RJS Templates for Rails
というのがPDF本であるらしい。
http://www.oreilly.com/catalog/rjsrails/
日本の人で誰か読んだ人いるかなー。今度のRails勉強会@東京で聞いてみよう。
ひさびさにRailsをやる
ちょっと卒論で作ろうと思ってるシステムをRailsで作りたいなぁと思ってひさびさにRailsをやる。
題材は[Ruby][Rails] Ruby on RailsでWiki
http://d.hatena.ne.jp/muscovyduck/20060511#1147307789
以下少し違ったところと要点をまとめる。
開発&実行環境
walf443@ruribita:railswiki% script/about
About your application's environment
Ruby version 1.8.2 (i386-linux)
RubyGems version 0.8.11
Rails version 1.1.2
Active Record version 1.14.2
Action Pack version 1.12.1
Action Web Service version 1.1.2
Action Mailer version 1.2.1
Active Support version 1.3.1
Application root /home/walf443/railswiki
Environment development
Database adapter mysql
Database schema version 2
データベース周り
自分はMySQL4.0を使った。
後々FrontPageがないとハマることになるので、自分はマイグレーションのスキーマを更に次のように変更した。
# db/migrate/002_initial_schema.rb class Contents < ActiveRecord::Base end class InitialSchema < ActiveRecord::Migration def self.up Contents.create :title => 'FrontPage', :body => 'This is FrontPage.' end def self.down frontpage = Contents.find_by_title 'FrontPage' frontpage.destroy end end
Contentsのアイテムを作成するので事前にクラスだけ定義しておくのがポイント。
$rake migrate
でスキーマを上げたり戻したり出来るのでファイルは分けておいた方が良いと思われる。
self.upメソッドには今回追加する処理を記述。self.downメソッドには前回のスキーマに戻すためのメソッドを記述する。
Helper
Helperにはscript/generate helperとかないのかな。うまく動作しなかった。
題材のページでは一つのファイルにHelperを記述しているけど、こちらでは動かず。ということで以下のようにした。
walf443@ruribita:railswiki% cat app/helpers/railswiki_helper.rb module RailswikiHelper end walf443@ruribita:railswiki% cat app/helpers/railswiki_helper/to_html_with_links.rb require 'rdoc/markup/simple_markup' require 'rdoc/markup/simple_markup/to_html' module RailswikiHelper class ToHtmlWithLinks < SM::ToHtml def handle_special_WIKIWORD(special) %Q|<a href="/railswiki/show/#{special.text}">#{special.text}</a>| end def handle_special_HTTP(special) %Q|<a href="#{special.text}">#{special.text}</a>| end def handle_special_BRACKET(special) %Q|<a href="/railswiki/show/#{special.text[2..-3]}">#{special.text[2..3]}</a>| end end end walf443@ruribita:railswiki% cat app/helpers/railswiki_helper/railswiki_template.rb require 'rdoc/markup/simple_markup' require 'rdoc/markup/simple_markup/to_html' module RailswikiHelper class RailswikiTemplate def initialize(view=nil) @view = view end def render(template, assigns) markup = SM::SimpleMarkup.new markup.add_special /\b([A-Z][a-z]+[A-Z]\w+)/, :WIKIWORD markup.add_special %r|(https?://\s+)|, :HTTP markup.add_special /(\[\[.+?\]\])/, :BRACKET markup.convert template, ToHtmlWithLinks.new end end end
SM::Simpleがなかなか認識されない(Const missing)ので仕方なく両方のファイルにrequireした。こういうときはどこにrequireするものなのだろうか。
Controller・View
hoge :action => ... とかいう感じのメソッドってどちらかというとSymbolを渡したくなるのは自分だけだろうか。特に:actionはメソッド名を指しているのでSymbolの方がそれっぽい気がする。エディタ的には色がつかないので文字列の方が見やすいのだけど。
その他
WEBrickでサーバを起動したとき、.irbrcでプロンプトの設定をしているとActiveSupportの拡張によりエラーが発生するようです。具体的には以下のような記述。
IRB.conf[:PROMPT][:SIMPLEST] = { :PROMPT_I => "", :PROMPT_S => "", :PROMPT_C => "", :RETURN => " #=> %s\n\n" }
エラーメッセージを頼りにunless IRB.conf[:PROMPT].nil? .. end で囲むと大丈夫になった。
まとめ
簡単にできると思っていたが割といろんな場面でハマった。というわけで色々作りながら覚えていこうかと思う。