Rails 3 + mondoDB + HAML + Rspec + Jquery のインストール - 2

Created on 11 Jul, 2010 | Tag(s): ruby rails mongodb haml rspec

前回 の続き。

主に FactoryGirlMongoMapper の話。基本的に何も考えなくてもそのまま使えるのだけど。

まず、設定。

spec_helper.rb で、

config.use_transactional_fixtures = true

config.use_transactional_fixtures = false

にする。そうしないと、ActiveRecord::TestFixtures が呼ばれてしまうのだけど、ActiveRecordを入れてないので落ちる。

そもそもMongoDBにはトランザクションとかないのでfalseでいい。

後は、ActiveRecordで使う場合と同じように、FactoryGirlの定義の読み込みを spec_helper.rb の中で行う。

Factory.find_definitions

mongoDBにはトランザクションがないので、テストの前にデータを自分で消しておいたほうがいいだろう。

config.before(:each) do
  MongoMapper.database.collections.each {|collection| collection.remove}
end

全体的にはこんな感じ。generatorが作ったものに、上記変更を入れただけ。

Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}

require 'factory_girl'
Factory.find_definitions

RSpec.configure do |config|
  # == Mock Framework
  #
  # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
  #
  # config.mock_with :mocha
  # config.mock_with :flexmock
  # config.mock_with :rr
  config.mock_with :rspec

  # config.fixture_path = "#{::Rails.root}/spec/fixtures"

  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, comment the following line or assign false
  # instead of true.
  config.use_transactional_fixtures = false

  config.before(:each) do
    MongoMapper.database.collections.each {|collection| collection.remove}
  end
end

試しに一個テストを書いてみる。

まず、generatorがEntryクラスのFacotryの雛形も作ってくれているので、それの確認。

File: spec/factories/users.rb

Factory.define :entry do |f|
  f.title "MyString"
  f.body "MyString"
end

簡単なお試し用のテストを書いてみる。(テスト自体に意味はない)

describe Entry do
  it "should create 10 entries" do
    10.times { Factory(:entry) }
    Entry.count.should == 10
  end
end

書いたテストの実行

$ bundle exec rspec spec/models/entry_spec.rb
.

Finished in 0.03308 seconds
1 example, 0 failures

すばらしい!

FactoryGirlで、MongoMapper::Document同士のassociationも問題なく書ける。(ただし、MongoMapper::EmbeddedDocument から 親Documentへのassociationはうまく作ってくれなかった(正格には作ってくれるんだけど、Saveされない))

こんな感じで、Rails 2 + ActiveRecord時代とほとんど変わらない環境が Rails 3 + MongoMapper で、できたかな。



Rails 3 + mongoDB + haml + RSpec + jQuery のインストール - 1

Created on 2 Jul, 2010 (Updated on 11 Jul, 2010) | Tag(s): ruby rails mongodb haml rspec

会社用の、小物Webアプリを作ろうかと思い、せっかくなのでRuby on Rails 3でmongoDB使ってみようかな、と思い、とりあえず環境を作るところまでのメモ。

Rails 3 のインストール

とりあえず Rails 3 のインストール。現在の最新のbeta4を入れる。Bundlerで。Bundler自体のバージョンが0.9.26以上でないとダメみたいなんで、もしそれ未満しか入っていない場合にはBundlerのインストールからする。

プロジェクトのトップディレクトリとなるところを作成し、そこにGemfileを作る。

$ mkdir ~/workspace/hoge_prj
$ cd ~/workspace/hoge_prj

Gemfile

source 'http://rubygems.org'
gem "rails", "3.0.0.beta4"

rails 3のgemのインストール

$ bundle install

railsコマンドでrailsプロジェクトを作成する。

$ bundle exec rails new . --skip-activerecord --skip-testunit

このコマンド自体がGemfileをもう一度作る。上書きしてしまえばいい。MongoMapperを使う予定なので --skip-activerecord 、rspecを使う予定なので --skip-testunit を指定した。

次に、自分が必要なライブラリをGemfileに追加して、もう一回 bundle install する。

今回は、

gem "mongo_mapper"
gem "bson_ext"
gem "rails3-generators"
gem "haml"
gem "rspec-rails", ">= 2.0.0.beta.13", :require => nil
gem "factory_girl", :require => nil 

を追加した。 rspec / haml / factory_girl は普段も使ってるのであまり考えずにそのまま使ってみる。Rails 3では、rspec 2が必要らしい。

$ bundle install

次に、config/application.rb を開いて、

config.generators do |g|
  g.orm :mongomapper
  g.template_engine :haml
  g.test_framework  :rspec, :fixture => true
  g.fixture_replacement :factory_girl, :dir => "spec/factories"
end

をそれっぽいところに書く。

rspecとhaml、jqueryの関連ファイルを作成する。

$ ./script/rails g rspec:install
$ ./script/rails g haml:install 
$ ./script/rails g jquery:install

mongoDBの接続情報が必要なので、それを config/mongodb.yml とかに書き、それを読ませるinitializerも書く。

今回は Mac上にインストールしたmongoDB を使っている。

config/mongodb.yml

base: &base
  adapter: mongodb
  host: localhost

development:
  <<: *base
  database: hoge-development

test:
  <<: *base
  database: hoge-test

production:
  <<: *base
  database: hoge-production

config/initializers/mongodb.rb

db_config = YAML::load(File.read(Rails.root.join("config", "mongodb.yml")))

if db_config[Rails.env]
  mongo = db_config[Rails.env]
  MongoMapper.setup(db_config, Rails.env, :logger => Rails.logger)
end

これで、ほぼインストール完了。 rails3-generators のおかげで mongo_mapper を使った scaffold を作ることもできる。

$ ./script/rails g scaffold entry title:string body:string
      invoke  mongomapper
      create    app/models/entry.rb
      invoke    rspec
      create      spec/models/entry_spec.rb
      invoke      factory_girl
      create        spec/factories/entries.rb
       route  resources :entries
      invoke  scaffold_controller
      create    app/controllers/entries_controller.rb
      invoke    haml
      create      app/views/entries
      create      app/views/entries/index.html.haml
      create      app/views/entries/edit.html.haml
      create      app/views/entries/show.html.haml
      create      app/views/entries/new.html.haml
      create      app/views/entries/_form.html.haml
      invoke    rspec
      create      spec/controllers/entries_controller_spec.rb
      create      spec/views/entries/edit.html.haml_spec.rb
      create      spec/views/entries/index.html.haml_spec.rb
      create      spec/views/entries/new.html.haml_spec.rb
      create      spec/views/entries/show.html.haml_spec.rb
      create      spec/helpers/entries_helper_spec.rb
      create      spec/routing/entries_routing_spec.rb
      invoke      rspec
      create        spec/requests/entries_spec.rb
      invoke    helper
      create      app/helpers/entries_helper.rb
      invoke      rspec
Plural version of the model detected, using singularized version. Override with --force-plural.
      invoke  stylesheets
      create    public/stylesheets/scaffold.css

とりあえず、コンソールからレコード (mongoDB風に言うとドキュメント)が作成できるか確認してみる。

% ./script/rails console
Loading development environment (Rails 3.0.0.beta4)
irb(main):001:0> Entry.create!(:title => "HOGEHOGE", :body => "Hello")
=> #<Entry created_at: Wed, 30 Jun 2010 22:24:06 UTC +00:00, body: "Hello", title: "HOGEHOGE", updated_at: Wed, 30 Jun 2010 22:24:06 UTC +00:00, _id: BSON::ObjectID('4c2bc406a90e08bf25000001')>
irb(main):002:0> 

簡単!

本当に保存されたか、mongoシェルで確認してみると、

% ~/somewhere/mongodb-osx-x86_64-1.5.3/bin/mongo
MongoDB shell version: 1.5.3
connecting to: test
type "help" for help
> use hoge-development
switched to db blog-development
> db.entries.find()
{ "_id" : ObjectId("4c2bc406a90e08bf25000001"), "created_at" : "Wed Jun 30 2010 23:24:06 GMT+0100 (BST)", "title" : "HOGEHOGE", "body" : "Hello", "updated_at" : "Wed Jun 30 2010 23:24:06 GMT+0100 (BST)" }
> 

入ってる!

サーバを起動して、画面でも見てみる。

config/route.rb に、

resources :entries

と、とりあえず書いて、

$ ./script/rails server

ブラウザで http://localhost:3000/entries/ を開いて確認。



なんとも面白くないいつもの scaffold がちゃんとmongoDBで動いているのが確認できる。

とりあえずここまでで、RSpec / factory_girl あたりを 次回

参考:
http://www.mongodb.org/display/DOCS/Rails+3+-+Getting+Started
http://paulbarry.com/articles/2010/01/13/customizing-generators-in-rails-3
http://groups.google.com/group/mongomapper/browse_thread/thread/cd89810b98eb7abf
http://github.com/rspec/rspec-rails
http://stackoverflow.com/questions/3004489/mongodb-initialization-error-in-rails



resque-scheduler (resqueでcronみたいなことをする)

Created on 27 Feb, 2010 (Updated on 28 Feb, 2010) | Tag(s): ruby rails resque scheduler

resqueシリーズ。 前回resque-scheduler について少し触れたのだけど。resque-schedulerのメインであるcron的に使う機能について書いてなかったのでメモ。deployについても書いた。

cronと比較して

*nix系のシステムだと、伝統的に、何かを定期的に実行したいcronを使うのだけど、アプリケーションからcronを使う場合、いくつかの使いづらい点がある。

  • アプリケーション本体から離れた場所でcronの動きを管理しないといけない。通常、/etc/crontab 等をメンテナンスしないといけないのだけど、そこはアプリケーションの外側なので、メンテナンスするときにアプリケーション側で使っている方法を使いづらい(SCMもそうだし、deployとかも別で考えないといけない)。
  • cronから起動する場合、起動する環境について考えないといけない。環境変数が代表例。アプリケーションと同じような環境になるように注意しないといけない。

resque-schedulerを使うと、cronでするようなことをアプリケーション内で完結することできる。crontabのようなものをアプリケーション内に置けるので、アプリケーション本体を管理するSCMを使うこともできるし、deployもアプリケーションと常に同じ方法でできるようになる。

ただ、基本的にはcronの動きを踏襲しているので、cronの以下の(場合によっては)負の特徴も引き継いでいる。

  • cronと同じように、指定した時間にresque-schedulerが立ち上がってない場合、その処理は行われない。
  • cronと同じように、同一のタスクが同時に走ってしまう場合がある。例えば、毎分実行するような設定を書いた場合、その処理に1分以上かかってしまった場合、前の処理が終わる前に次の処理が走ってしまう。それが致命的な場合、自分でロック処理を書く必要がある。
  • 複数のresque-schedulerを走らせることが考えられていない。複数のresque-schedulerを実行すると、同じ時刻に同じタスクが複数回走ってしまう。つまり、「冗長化のために、2台のアプリケーションサーバ上でresque-schedulerをそれぞれ動かす」ということができずに、冗長化した構成が取れない。

最後のは、嫌なので、複数のところで走らせても同一のタスクが複数回走らないようなパッチを書いて作者に投げてある。しかし、「冗長化構成が取れるように」ということには賛同を得たのだけど、そのパッチについてはいくつか課題がありそうなので保留になっている。気が向いたらもうちょっと頑張るつもり。

使い方

resque-scheduler を見るのが早いが、crontabで定義するようことをyamlで定義する。そのドキュメントの例を使わせてもらうと、

queue_documents_for_indexing:
  cron: "0 0 * * *"
  class: QueueDocuments
  args: 
  description: "This job queues all content for indexing in solr"

こんな感じになる。この場合、毎日0時0分に、QueueDocumentsというResqueのJobがQueueに入ることになる。ただし、ResqueのQueueに入るだけで、resque-schdulerは実際の実行はしない、ということに注意。Queueを処理するのはResque自体のWorkerの仕事になる。

deployについて

resqueと同じように、resque-scheduler自身には、起動スクリプトみたいなものがついていないので、godやmonitのようなもので管理するのがいいっぽい。

自分はmonitを使って、こんな感じのwrapperを書いて、monitではそのwrapper経由で、起動、停止をしている。



なぜMongoDBなのか

Created on 25 Feb, 2010 | Tag(s): mongodb ruby rails

ここを見てもらってる人に、「MongoDBって何がいいの?」と改めて聞かれてしまって、ああ、そっか、そういうこと書いてなかったな、と思ったので、なぜ自分がMongoDBに興味を持っているのか、ということを書いてみた。いざ自分の思いを書いてみたらRails中心の話になってしまったけど、モダンなフレームワークならそんなに話は変わらないのかな、と思っている。


そもそものきっかけは、ここ半年間くらいRuby on Rails(以下RoR)で開発していることにある。

ここ半年弱ほどRoRで開発をして、それなりに満足しているのだけど、ActiveRecordに関しては色々とひっかかるところがあった。

「ActiveRecordがRoRの素晴らしいところそのものだ」と評価している人もいるが、自分の中では逆で、ActiveRecordはRoRの中でもかなりいまいちな部分。

いや、ActiveRecordというよりも、O/Rマッパーの限界なのかな。実際、ActiveRecordはO/Rマッパーの中では、いい方だと思う。今まで、自分用O/Rマッパーを作ったり、それなりの数のO/Rマッパーを見てきたけど、そういうのに比べても、確かにActiveRecordはよくできている。

でも、RoRがよくできたフレームワークなだけに、O/Rマッパー経由RDBMSを使うことに、色々な無駄を感じてしまう。

たとえば、1つのテーブルから同じ条件で検索し、複数のインスタンスを作るために、primary key(id)を検索条件として、何度も検索するとか。「そこはSQL一発で持ってこいよ」と思ってしまう。またそこからlazyに外部キーで他のModel(テーブル)のオブジェクトを持ってこようとすると、またprimary keyでの検索が走る。

一人のユーザが一つの画面を開くだけでSQLを何十回も発行することも普通に起こる。これはどう考えてもSQLの使い方として間違っている。SQLはそういう風に作られていないので、結局(RoR自体を含めた)アプリケーション側の別のレイヤーでインスタンスをCacheしたりと、どんどん複雑になっていく。

そういうのが嫌で、RDBMSとして正しく使おうと、find()で:includeとか:joinsとか使いだすと、今度はどんどんRoR(Ruby)のコードのSQL成分が増していく。全然楽しくない。

つまり、

  1. SQLっぽく書かずに、Ruby(ActiveRecord)っぽくきれいに書くように頑張る
  2. でも、ロジック的/パフォーマンス的に頑張るのが限界だった場合に、SQLっぽいコードをRubyに書くことになる
  3. そうこうしているうちにRubyらしい可読性の高いコードは消えSQLっぽいコードにまみれる

この流れにうんざりする。そもそも1.の段階でも脳内では普通にSQLのことを考えている。だったら最初からSQL書けよ、と思ってしまう。

結局、O/Rマッパーで、きれいに書けるところは、検索した結果でオブジェクトを作る、または、primary *key*指定で持ってきてそれを元にオブジェクトを作る。これだけ書けて、十分な性能が出るなら、RDBMSでなくても、(そこそこの機能を持った) Key Value Storageで十分なんじゃないのかな、とぼんやりと思っていた。

もう一つActiveRecordで嫌いなのが、Modelのコードにカラムの情報を持たない、というとこと。DRYのためだかなんだか知らないが、これはどうみても欠点だと思っている。Modelのコードを見ただけでどういう情報が入っているかわからない。migrationのコードはツギハギだらけで後から見るには向いていない。どういうModelか、ということを知るためには、動いているデータベースにアクセスしてスキーマ情報を見るしかない。かっこ悪い。


ってなことを、漠然と思っていた頃に、タイミングよくNoSQL(KVS)ブームが到来。「ああ、これだ」と思って、ブームに乗っかって、色々なKVSを試している中で、

「これなら最近のWeb開発の多くの部分でRDBMSを捨ててもいけるんじゃないかな」

と感じさせてくれたのがMongoDBだった。

数あるKVSの中でMongoDBの何がよかったのかと言うと、

  1. RoRのModelにぴったりと合いそう。上にも書いたように、KeyとValueの関係が、そのままModelのIdとInstanceの関係になりそう。
  2. Valueの部分はスキーマフリー&ダイナミックに作成可能なので、Ruby側のコード主導でカラムの追加をすることができる。Rubyのコードでのカラムの定義が「正」になる。もちろんDRYも守られたまま。
  3. SQL-likeなこともできる。where句(のようなもの)を指定したり、maxとかminとかgroup byも使えるし、やろうと思えばストアドプロシージャ(Javascript)のようなこともできる。この辺は、SQL脳の人(チーム)にとっては移行するためのハードルが低そう。
  4. 上記を踏まえた上で、KVSらしく、RDBMSでは厄介なスケールさせることも考えられていて、Shardみたいなこともデフォルトで用意されている。

とまあ、こんなところ。

つまり、最近のリッチなフレームワークの中では、DB側にそれほどリッチな機能はいらない。RDBMSはそのいらないリッチさがあり、そのせいでスケールさせるのが難しくなったりもしている。だったら、軽量でコンパクト、でもそれなりの機能を持って、そしてスケールしやすいMongoDBで十分なんじゃないのかな、と思った。

もしMongoDB始めてみようって人がいたら、こちらをどうぞ

追記: CouchDB編もできたみたいです。私は、Railsは好きです。念のため。



resqueとRails

Created on 19 Jan, 2010 (Updated on 16 Feb, 2010) | Tag(s): ruby rails resque

前回 の続き(だがあんまり書くことなかった)

Railsとの話の前に、前回書き忘れてしまったのだけど、resqueには、1日1回実行する、と言ったスケジューリングの機能はない。スケジュール機能は別のそういう機能を持ったソフトウェアに任せる(代表例: cron)か、自分で作る必要がある。また、resque-scheduler といresqueのプラグインタイプの物もある。現在どの方法が良さそうかか評価中なのでそのうち書く。

さて、Railsとの連携だが、resque自体がそもそもGitHubのRailsシステム用に作られたという経緯から、もちろん非常に親和性が高い。たとえば、worker毎にRailsのEnvironmentが一回ロードされるだけなので余計な資源を食わなかったり、RailsアプリのWeb UIから非同期な処理の扱いなども簡単にできる。

さて、インストール

./script/plugin install git://github.com/defunkt/resque

gemでインストールする方法ある。詳しくは http://github.com/defunkt/resque 参照。

設定

これも特に難しいところはない。前回も書いたように必要になるのは Redis サーバを指定するところのみ。作るファイルは3つ。全部上記READMEに書いてある。

  1. config/initializers/resque.rb
  2. lib/tasks/resque.rake
  3. config/resque.yml

Workerの起動

インストールが完了するとRailsのrake taskに以下の二つが追加される。

  • rake resque:work                           # Start a Resque worker
  • rake resque:workers                        # Start multiple Resque workers

このコマンド経由で実行するとRailsの環境を読み込んだWorkerが起動し、Jobの中でRailsとまったく同じ環境でプログラミングすることができる。

optionとかは単体での起動と同じ。

例: QUEUE=default rake resque:work

非同期処理

Web系アプリではレスポンスの速さがとても大事なので、処理に時間がかかる部分は、とりあえずレスポンスだけユーザに返してしまって、残りの処理は裏でやるということがよく行われる。resqueを使うとこの処理を簡単に行うことができる。と言ってもresque側で何か特別な非同期用の仕組が用意されているわけではなく、Web側のアプリでは、単に Resque.enqueue を呼ぶだけでの話。そして、Jobの中で再度元のクラスのmethodを呼ぶということで簡単に実現できる。この辺、変に凝った仕組ではなくとても簡単でいい。

まとめ

なんか色々なことが簡単にできてしまって、何かを見落としているのではないかという気分になるresque。まだ実戦投入してないので、その後見えてくることも多いと思うので、解り次第また何か書く。

追記: 実戦投入した