- [What will be covered (取り上げる内容)](#what_will_be_covered_)
- [What will be covered (この節で取り扱う内容)](#what_will_be_covered_)
- [What to test?](#what_to_test_)
- [Introduction](#introduction)
- [Contributing](#contributing)
This book is work in progress, your help is requested @ github
この本はまだ執筆中です。協力いただけるなら github まで。
(TODO) the foreword
This is a collaborative effort to document the features of Merb and DataMapper,
while also providing example Merb applications.
これは、Merb と DataMapper の機能をドキュメント化しようという、協力的な取り組みです。 また Merb アプリケーションのサンプルも提供します。
Who is this for? (対象読者)
The target reader is someone who has some experience with writing Ruby on Rails applications, and is looking to try out a new framework. So if your looking for the AWDR equivalent for Merb, you may be disappointed as this is a work in progress.
対象読者は、Ruby on Rails のアプリケーションを書いたことがあり、新しいフレームワークを試してみたい人です。 もし Merb における RailsによるアジャイルWebアプリケーション開発 相当のものを探しているなら、これはまだ執筆中なので、がっかりするかもしれません。
Copyright © 2008 Respective Authors.
This work is licensed under a Creative Commons Attribution-Noncommercial 2.0 UK: England & Wales License.
本著作物は、Creative Commons Attribution-Noncommercial 2.0 UK: England & Wales License のもとでライセンスされています。
Source code of the applications are dual licensed under the MIT and GPL licenses:
アプリケーションのソースコードは、MIT ライセンスと GPL ライセンスのデュアルライセンスです:
If you're not living on the edge, you're taking up too much room. - Alice Bartlett
もし端っこに住んでいないなら、あなたはたくさんの部屋を専有しすぎている。〔訳注: ???〕- Alice Bartlett
Merb, DataMapper and RSpec are all open source projects that are great for building kick-ass web applications. They are all in active development and although it can be hard, we'll try our best to keep up-to-date.
Merb と DataMapper と RSpec はどれも、すごい Web アプリケーションを構築するのにとても役に立つオープンソースプロジェクトです。 3 つとも活発に開発中であり、そのため最新版についていくのは大変なのですが、最大限努力してみます。
Merb is a relatively new web framework with an initial 0.0.1 release in October
2006. Ezra Zygmuntowicz is Merb's creator, and
continues to actively develop Merb along with a dedicated development team at
Engine Yard and many other community contributors.
Merb は比較的新しい Web アプリケーションフレームワークであり、最初のバージョン 0.0.1 がリリースされたのは 2006 年 10 月です。 Ezra Zygmuntowicz が Merb の作者であり、Engine Yard の熱心な開発チームや他のコミュニティメンバとともに、活発な開発を継続しています。
Merb has obvious roots and inspiration in the Ruby on Rails web framework. If you know Ruby and have used Rails you're likely to get the hang of Merb quite easily.
Merb は明らかに Ruby on Rails を源流とし、また影響を受けています。 もし Ruby を知っていて Rails を使ったことがあるなら、Merb のコツを得るのも簡単でしょう。
While there are similarities, Merb is not Ruby on Rails. There are core
differences in design and philosophy. In many areas that Rails chooses to be
opinionated, Merb is agnostic - with respect to the ORM, the JavaScript library
and template language. The Merb philosophy also disbelieves in having a monolithic
framework. Instead, it consists of a number of gems: merb-core, merb-more and
merb-plugins. This means that it is possible to pick and choose the
functionality you need, instead of cluttering up the framework with non-essential
features.
両者に似た点はあるものの、Merb は Ruby on Rails ではありません。
設計と哲学に、明確な違いがあります。
Rails には意固地な (opinionated) 点が多々ありますが、Merb は不可知論者 (agnostic) です〔訳注: すべてを知りうることはできないという考えのこと〕 - たとえば ORM や、JavaScript ライブラリや、テンプレート言語についてがそうです。
Merb の哲学では、モノリシック (一枚岩) なフレームワークに対すして不信感を持っています。
かわりに、Merb は複数の gems から構成されています: merb-core と merb-more と merb-plugins です。
つまり、本質的でない機能でフレームワークをごちゃごちゃにするのではなく、自分が必要とする機能を取捨選択することができるというわけです。
The merb gem installs both merb-core and merb-more; all you need in order to
get started straight away. The benefit of this modularity is that the framework
remains simple and focused with additional functionality provided by gems.
merb gem をインストールすると、merb-core と merb-more もインストールされます;
すぐに開始するのに必要なものはすべてインストールされます。
このモジュール性の高さによる利点は、付加的な機能は gems で提供することで、フレームワークをシンプルかつ〔訳注: 必要な機能だけに〕集中した状態に保たれることです。
Thanks to Merb's modularity, you are not locked into using any particular libraries. For example, Merb ships with plugins for several popular ORMs and provides support for both Test::Unit and RSpec.
Merb のモジュール性の高さのおかげで、どの特定のライブラリにも縛られることはありません。 たとえば、Merb は複数の人気のある ORM 用プラグインを同梱しており、また Test::Unit と RSpec の両方をサポートしています。
merb-core alone provides a lightweight framework
(a la camping) that can be used to
create a simple web app such as an upload server or API provider where the
functionality of an all-inclusive framework is not necessary.
merb-core はそれ自体で軽量なフレームワーク (camping 流の) であり、たとえばアップロードサーバや API プロバイダのような、必ずしもすべての機能を備えたフレームワークを必要としないような、シンプルな Web アプリを作成するのに使うことができます。
DataMapper is an Object-Relational Mapper (ORM) written in Ruby by Sam Smoot. We'll be using DataMapper with Merb. As previously mentioned, Merb does not require the use of DataMapper. You can just as easily use the same ORM as Rails (ActiveRecord) if you prefer.
DataMapper は、Sma Smoot によって作られた、Ruby で実装されたオブジェクト-リレーショナルマッパー (ORM) です。 本稿では、DataMapper を Merb で使います。 前述したように、Merb は必ずしも DataMapper を必要としません。 Rails と同じ ORM (ActiveRecord) のほうが好きなら、そちらを使うのも簡単にできます。
We have chosen to use DataMapper because of it's feature set and performance. One
of the differences between it and ActiveRecord that I find useful is the way
database attributes are handled. The schema, migrations and attributes are all
defined in one place: your model. This means you no longer have to look around in
your database or other files to see what is defined.
本稿では、機能セットとパフォーマンスを考慮し、DataMapper を選択しました。 DataMapper と ActiveRecord の違いのうち私が有用だと思ったことのひとつは、データベースの属性の扱いについてです。 〔訳注: DataMapper では、〕スキーマとマイグレーションと属性は一カ所で管理されます: つまり、自分のモデルクラスです。 これにより、データベースや他のファイルを見て何が定義されているかを調べる必要がなくなります。
While DataMapper has similarities to ActiveRecord, we will be highlighting the differences as we go along.
DataMapper は ActiveRecord に似ていますが、本稿では両者の違いを強調するようにします。
RSpec is a Behaviour Driven Development framework for Ruby. It consists of two main pieces, a Story framework for integration tests and a Spec framework for object tests. Both these components are implemented as Domain Specific Languages which help to make the stories and specs created more readable.
RSpec は、Ruby 用の振る舞い駆動開発フレームワークです。 RSpec は主に、統合テストのためのストーリーフレームワークと、オブジェクトテスト〔訳注: 単体テストのことと思われる〕のための仕様フレームワークの、2 つの部分から構成されています。 これら両方のコンポーネントが問題領域特化言語 (DSL) で実装されており、これによりストーリーと仕様がより読みやすくなります。
Merb currently supports the Test::Unit and RSpec testing frameworks. Both Merb and Datamapper use the RSpec testing frameworks and so we will be covering some aspects so that you may use it for your own applications.
Merb は現在、Test::Unit と RSpec の両方のテスティングフレームワークをサポートしています。 Merb と DataMapper は両方とも RSpec テスティングフレームワークを使っているので、自分自身のアプリケーションで RSpec を使うためのいくつかの側面について、本稿でも取り上げています。
[Merb is] Harder, Better, Faster, Stronger, to quote Daft Punk - Max Williams
Daft Punk を引用するには、[Merb は] より堅くて、より良くて、よりくて、より堅牢である〔訳注: ???〕 - Max Williams
So what's the big deal? We have Ruby on Rails and that's enough, isn't it? There is little doubt that Ruby on Rails has rocked the web application development world. You have to give credit where credit's due, and Ruby on Rails is definitely a great web framework. However, there is no such thing as a one-size fits all solution. Ruby on Rails is opinionated software which provides many benefits such as Convention over Configuration. On the other hand, this also means that Ruby on Rails can be unforgiving if you don't want to do things 'the Rails way'.
大きな違いは何だろうか? すでに Ruby on Rails があるし、それで十分じゃね? Ruby on Rails が Web アプリケーション開発の世界を揺るがしていることはほぼ間違いありません。 人は、賞賛すべきものはきちんと賞賛すべきであり、そして Ruby on Rails は間違いなくすばらしい Web フレームワークです。 しかし、それ 1 つですべての問題を解決してしまうような万能なものはありません。 Ruby on Rails は、たとえば設定より規約 (Convention over Configuration, CoC) のように、たくさんの利点を提供するけど頑固な (opinionated) ソフトウェアです。 一方で、'Rails 流' 以外の方法でなにかしたいとき、Ruby on Rails はそれを許してはくれません。
Where Rails is opinionated, Merb is agnostic. For example, you can easily use
your favourite ORM (ActiveRecord, DataMapper, Sequel) or none at all.
Similarly, you can choose the Javascript library and template language that you
are most comfortable with, or that best meets the requirements of your specific
project.
Rails は頑固 (opinionated) であり、Merb は不可知論者 (agnostic) です。 たとえば、もし自分の好きな ORM (ActiveRecord, DataMapper, Sequel) を使いたい、あるいは何も使いたくないと思ったら、簡単にできます。 同様に、JavaScript ライブラリやテンプレート言語でも、自分が使いたいものを、あるいはプロジェクトの要件に最もよく合うものを選ぶことができます。
If performant were a word, Merb would be it. One of Merb's design mantras is "No code is faster than no code". Merb has super-fast routing and is thread-safe. The core functionality is kept separate from the other plugins and it uses less Ruby 'magic', making it easier to understand and hack.
もしパフォーマンスが神の言葉であれば、Merb はきっとそれに違いありません〔訳注: ???〕。 Merb の設計におけるスローガンのひとつに、『何も書かない (no code) よりも速いコードはない (No code)』というものがあります。 Merb は非常に高速なルーティングを持ち、かつ Merb はスレッドセーフです。 コア機能は他のプラグインから分離されており、Ruby の「魔法」はほぼ使ってないため、理解してハックするのは簡単でしょう。
Rails (and consequently Ruby) has received a lot of criticism for not being suitable for large scale web applications, which isn't necessarily true. Merb has been built from the outset to prove that Ruby is a viable language for building fast and scalable web applications.
Rails (だけでなく Ruby も) は、大規模な Web アプリケーションには向かないという批判を受けてきました。 が、これらは必ずしも正しくはありません。 Merb は、Ruby が高速でスケーラブルな Web アプリケーションを構築できるだけの能力を持った言語であることを証明するための発端となるよう、構築されています。
At the end of the day it's about choice. There are many new Ruby frameworks springing up, undoubtedly encouraged by the success of Rails. In our opinion, Merb shows the most promise of these.
最終的には、こういったことは選択です〔訳注: ???〕。 明らかに Rails の成功に刺激され、Ruby 用の新しいフレームワークがたくさん現れてきています。 私たちの意見としては、Merb はこれらの中では最も有望です。
If you'd like to take a look at some other frameworks these links should get you started:
もし他のフレームワークを見てみたいなら、下のリンクが参考になるでしょう:
i'm going to become rich and famous after i invent a device that allows you to stab people in the face over the internet - <[SA]HatfulOfHollow>
The internet is a scary place, but fortunately the Ruby community is very friendly. All these open source projects rely on the contributions from the community, if something needs fixing consider helping out.
インターネットは実に恐ろしい場所ですが、幸運にも Ruby コミュニティはたいへんフレンドリーです。 なにか助けを必要とするような修正があったときは〔訳注: ???〕、これらオープンソースプロジェクトのすべてが、コミュニティによる貢献に依存しています。
These are the first places to go for help. Check out the API documentation and see if you can find your answer there.
困ったときに最初に見るべき場所があります。 API ドキュメントをチェックし、求める回答があるかどうか探しましょう。
If you can't find what you were looking for in the API docs then you could join the respective IRC channel on FreeNode and ask your question in there, you may need to wait for a response.
もし探しているものが API ドキュメントに見つからない場合は、FreeNode にあるそれぞれの IRC チャネルに参加して、そこで質問することができます。 返事をもらえるにはしばらく待つ必要があるかもしれません。
The mailing lists are another good way to get help, the response time isn't as fast as asking in an IRC channel but it can be useful to do a search to see if someone else has had your problem before.
メーリングリストは、助けを求めるためのもうひとつの方法です。 答えが返ってくる時間は IRC チャネルほど早くはありませんが、自分と同じ問題に直面した人が以前にいたかどうか調べることができます。
Your problem may or may not be a known bug. Search the bug trackers and submit a ticket if it's not there already (don't forget to include a description and test cases, or better yet: a patch!). You may find the ticket is solved in the edge version.
もしかしたら、あなたが直面した問題は未発見のバグかもしれません。 バグトラックを検索し、同じ問題が見つからなければチケットを発行しましょう (詳細な説明と、テストケースを含めるのを忘れないでください。 よりよいのは、パッチも含めることです!)。 開発中の最新バージョンでは、チケットが解決済みになるかもしれません。
〔訳注: ここで説明されている方法はお勧めしません。Ruby 1.8.X と MySQL と RugyGems 1.3.1 をインストールして、which mysql_config でパスが通っていることを確認したあと、gem install merb --development を実行してください。なお Ruby 1.9 や古い RubyGems ではうまく動きません。〕
Before we get started I'm going to assume you have the following installed:
始めるまえに、みなさんは以下のものをインストールしていることを確認してください:
Ruby 〔訳注: 現在のMerb は 1.9 では動きません。1.8.X を使ってください〕
The basic directory structure for the framework
Merb と DataMapper と RSpec のインストール
If you're on a *nix operating system then keeping up to date with all the edge versions of these gems can be made really easy by using the Sake tasks.
もし *nix 系のオペレーティングシステムを使っているなら、Sake タスクを使うことで、これらの gems のバージョンを最新のものに保つのが実に簡単にできます。
Merb sake tasks can be found in merb-more repository under tools directory. Sake tasks for DataMapper are in dm-dev repository at http://github.com/dkubb/dm-dev/.
Merb の sake タスクは、merb-more リポジトリの tools ディレクトリ下にあります。 DataMapper 用の Sake タスクは、http://github.com/dkubb/dm-dev/ にある dm-dev リポジトリの中にあります。
To install Sake tasks run sake -i PATH where PATH is path to Sake tasks file on your local machine. For example,
Sake タスクをインストールするには、自分のローカルマシンで sake -i PATH を実行します (ここで PATH は Sake タスクファイルへのパスです)。
sake -i ~/dev/opensource/merb/merb-more/tools/merb-dev.rake
To do a fresh clone of all repositories use sake dm:clone and merb:clone, respectively. And then to keep up to date you just need to execute:
すべてのリポジトリについてフレッシュクローンを行うなら、それぞれ sake dm:clone と merb:clone を実行してください。 そのあと、Merb と DataMapper の gems を最新版に更新するには、
sake dm:update
and
と
sake merb:update
to update Merb and DataMapper gems.
を実行してください。
But what you really want is probably to wipe out Merb and DM gems before update, do the update and install new updated gems. Use sake merb:gems:refresh and dm:gems:refresh to do so.
しかし、本当に必要としているのはおそらく、Merb と DM の gems を更新するより前にきれいに消し去り、それから最新版の gems をインストールすることだと思います。 そのためには、sake merb:gems:refresh と dm:gems:refresh を実行してください。
If you have an older version of Merb (<0.9.2) you should remove all merb and
datamapper related gems before continuing. Use gem list to see your installed
gems. The following command will uninstall the gem you specify:
もし古いバージョン (<0.9.2) の Merb をインストールしているなら、関連するすべての merb と datamapper の gems を事前に削除しておく必要があります。
gem list を実行して、自分がイントールした gems を確認してください。
次のコマンドを使うと、指定した gem をアンインストールすることができます。
sudo gem uninstall the_gem_name
Installing the merb gems should be as simple as:
merb gems をインストールするのは非常に簡単です:
sudo gem install merb --source http://merbivore.org
or for JRuby:
または、JRuby を使っているなら:
jruby -S gem install merb mongrel
Unfortunately we are living right on the edge of development so we'll need to get down and dirty with building our own gems from source. Luckily this is much easier than it sounds...
残念ですが、本稿では最新の開発版を使っているため、ソースから自分の gems を構築するという汚れ仕事をしなければなりません。 幸運にも、これは思った以上にずいぶんと簡単です...
Start by installing the gem dependancies:
まずは依存するものを gem でインストールします:
sudo gem install rack mongrel json erubis mime-types rspec hpricot \
mocha rubigen haml markaby mailfactory ruby2ruby
or for JRuby: または JRuby を使っているなら:
jruby -S gem install rack mongrel json_pure erubis mime-types rspec hpricot \
mocha rubigen haml markaby mailfactory ruby2ruby
Then download the merb source:
そして merb のソースをダウンロードします:
git clone git://github.com/sam/extlib.git
git clone git://github.com/wycats/merb-core.git
git clone git://github.com/wycats/merb-plugins.git
git clone git://github.com/wycats/merb-more.git
Then install the gems via rake: それから rake を使ってこれらの gems をインストールします:
cd extlib ; rake install ; cd ..
cd merb-core ; rake install ; cd ..
cd merb-more ; rake install ; cd ..
cd merb-plugins; rake install ; cd ..
Note that Merb and DataMappers share Extlib library since after 0.9.3 release of DM.
The json_pure gem is needed for merb to install on JRuby (Java implementation of a Ruby Interpreter), otherwise use the json gem as it's faster.
注意事項ですが、Merb と DataMapper は、DM 0.9.3 リリース以降は Extlib ライブラリを共有します。
Merb を JRuby (Ruby インタプリタの Java 実装) にインストールするときは、json_pure gem が必要になります。
それ以外では、より高速な json gem を使いましょう。
Merb is ORM agnostic, but as the title of this book suggests we'll be using DataMapper. Should you want to stick with ActiveRecord or play with Sequel, check the Merb documentation for install instructions.
Merb では ORM を自由に選べますが、本稿のタイトルが示すように、ここでは DataMapper を使います。 もし ActiveRecord に固執したり、Sequel で遊んでみたいという場合は、Merb documentation を読んでインストール手順を確認してください。
DataMapper has spit into the gems dm-core and dm-more, the old datamapper
gem is now outdated.
DataMapper は、dm-core と dm-more という、2 つの gems に分かれています。
datamapper という gem は古いので、使わないでください。
If you have an older version of datamapper, data_objects, or do_mysql,
merb_datamapper (< 0.9) you should remove them first.
もし datamapper や data_objects や do_mysql や merb_datamapper (< 0.9) といった古いバージョンをインストールしているなら、まず最初にこれらを削除してください。
We will use MySQL in the following example, but you can use either sqlite3 or PostgreSQL, just install the appropriate gem. You will also need to ensure that MySQL is on your system path for the gem to install correctly.
本稿では以降のサンプルに MySQL を使用しますが、適切な gem をインストールすれば、sqlite3 や PostgreSQL を使うことも可能です。 また gem が正しくインストールされるために、MySQL が自分のシステムパスに存在することを確認する必要があります。
To get the gems from source:
ソースから gems を構築するには:
git clone git://github.com/sam/extlib.git
git clone git://github.com/sam/do.git
cd extlib
rake install ; cd ..
cd do
cd data_objects
rake install ; cd ..
cd do_mysql # || do_postgres || do_sqlite3
rake install
git clone git://github.com/sam/dm-core.git
git clone git://github.com/sam/dm-more.git
cd dm-core ; rake install ; cd ..
cd dm-more
rake install
To update a gem from source, run git pull and rake install again.
ソースから gem を更新するには、git pull と rake install を再度実行してください。
The rspec gem was installed in the Merb section above. However, if you want
to grab the source, run one of the following commands:
rspec の gem は、先の Merb セクションでインストールされています。
しかし、もしソースからインストールしたいなら、次のコマンドのうちどちらかを実行してください:
gem install -r rspec
# or
git clone git://github.com/dchelimsky/rspec.git
cd rspec
rake gem
sudo gem install pkg/rspec-*.gem
One of the best ways to become familiar with a framework is to jump in and get your hands dirty. So now that we've got everything installed, it's time to roll up your sleeves and create a test Merb application.
フレームワークに慣れる最もよい方法のひとつは、自分の手で実際に試してみることです。 必要なものはすべてインストールしたはずですので、袖をまくって Merb アプリケーションをひとつ作ってみましょう。
Merb-more comes with a gem called merb-gen, this gives you a command line
tool by the same name which is used for all of your generator needs. You can
think of it as script/generate. Running merb-gen from the command line with
no arguments will show you all of the generators that are available.
Merb-more をインストールすると、merb-gen という gem もインストールされます。
この gem は、ジェネレータとして必要なものをすべて備えた、同名のコマンドラインツールを提供します。
ちょうど〔訳注: Ruby on Rails における〕 script/generate のようなものだと思ってください。
コマンドラインから merb-gen を引数なしで実行すると、利用可能なすべてのジェネレータが表示されます。
Merb follows the same naming convention for projects as rails, so 'my_test_app' and 'Test2' are valid names but 'T 3' is not (they need to be valid SQL table names).
プロジェクトでは、Merb は Rails と同じ命名規約に則っています。 たとえば 'my_test_app' や 'Test2' は有効な名前ですが、'T 3' はそうではありません (これらは SQL において有効なテーブル名である必要があります)。
merb-gen app test
This will generate an empty Merb app, so lets go in and take a look. You'll notice that the directory structure is similar to Rails, with a few differences.
このコマンドを実行すると、空の Merb アプリが作成されます。 中を自由に見てみてください。 2、3 の違いはあるものの、ディレクトリ構造は Rails によく似ていることがわかるでしょう。
# expected output
RubiGen::Scripts::Generate
create log
create gems
create app
create app/controllers
create app/helpers
create app/views
create app/views/exceptions
create app/views/layout
create autotest
create config
create config/environments
create public
create public/images
create public/stylesheets
create spec
create app/controllers/application.rb
create app/controllers/exceptions.rb
create app/helpers/global_helpers.rb
create app/views/exceptions/internal_server_error.html.erb
create app/views/exceptions/not_acceptable.html.erb
create app/views/exceptions/not_found.html.erb
create app/views/layout/application.html.erb
create autotest/discover.rb
create autotest/merb.rb
create autotest/merb_rspec.rb
create config/rack.rb
create config/router.rb
create config/init.rb
create config/environments/development.rb
create config/environments/production.rb
create config/environments/rake.rb
create config/environments/test.rb
create public/merb.fcgi
create public/images/merb.jpg
create public/stylesheets/master.css
create spec/spec.opts
create spec/spec_helper.rb
create /Rakefile
Before we get the server running, you'll need to edit the init.rb file and
un-comment the following line (this is only necessary if you need to connect
to a database, which we do in our case):
サーバを起動する前に、init.rb ファイルを編集して次の行のコメントを外す必要があります
(これはデータベースに接続する場合のみ必要です。本稿でもデータベースに接続するので必要となります)。
config/init.rb
use_orm :datamapper
Typing merb now in your command line will start the server.
ここまできたら、コマンドラインで merb とタイプすればサーバが起動されます。
Loaded DEVELOPMENT Environment...
No database.yml file found in /Users/work/merb/example_one/config, assuming database connection(s) established in the environment file in /Users/work/merb/example_one/config/environments
loading gem 'merb_datamapper' ...
Compiling routes...
Using 'share-nothing' cookie sessions (4kb limit per client)
Using Mongrel adapter
As you can see, however, we did not yet configure the database. Let's create the database.yml file that merb is looking for:
しかし見ての通り、データベースの設定構成をまだしていませんでした。 Merb が探している database.yml というファイルを作成しましょう:
config/database.yml
# This is a sample database file for the DataMapper ORM
development:
adapter: mysql
database: test
username: root
password:
host: localhost
socket: /tmp/mysql.sock
Don't forget to specify your socket, if you do not know it's location, you can find it by typing:
ソケットを指定するのを忘れないようにしてください。 もしソケットがどこにあるのかわからない場合は、次の方法で探すことができます:
mysql_config --socket
Starting Merb again shows that everything is running okay.
再度 Merb を起動すると、すべてが順調に動きます。
The following command will give you access to the Merb interactive console:
次のコマンドを使うと、Merb のインタラクティブコンソールにアクセスできます:
merb -i
You'll notice Merb runs on port 4000, but this can be changed with flag
-p [port number]. More options can be found by typing:
Merb はポート 4000 番で実行されますが、-p [port number] フラグを使えば変更できます。
オプションの詳細については以下を実行してください:
merb --help
You can even run Merb with any application server that supports rack (thin, evented_mongrel, fcgi, mongrel, and webrick):
Merb は、Rack がサポートされていればどのアプリケーションサーバ上でも動作します (thin、evented_mongrel、fcgi、mongrel、webrick など)。
merb -a thin
If you see a 500 error with the following error message when trying to navigate to localhost:4000 in your browser:
ブラウザで localhost:400 にアクセスしたときに、もし 500 エラーが発生して以下のようなエラーメッセージが表示されたら:
undefined method `match' for Merb::Router:Class - (NoMethodError)
This means Merb has been started outside of your applications root directory.
これは Merb が自分のアプリケーションのルートディレクトリ以外の場所から起動されたことを意味します。
The directory structure of the project created should look like the following. We'll give brief overview of the framework here and go into further details of each component in subsequent chapters.
作成されたプロジェクトのディレクトリ構造は、次のようになっています。 ここではフレームワークの簡単な概要を説明し、次節以降で各コンポーネントの詳細を説明します。
test
|--> app
|--> autotest
|--> config
|--> log
|--> public
`--> spec
The app folder contains your models, views, controllers and helpers. It also
has Parts, they inherit from AbstractController and similar to the old Rails
components, but are lightweight and are useful for sidebars, widgets etc.
app フォルダの下に、models、views、controllers、helpers〔訳注: の各フォルダ〕があります。
また app フォルダは Parts も含んでいます。
これは AbstractController を継承しており、古い Rails コンポーネントに似ていますが、軽量で、サイドバーやウィジットなどに役立ちます。
Mailers, which also inherit from the AbstractController have their own
folder where the controllers and views live.
Mailers もまた AbstractController を継承しています。
Mailers は自身のフォルダを持っており、コントローラとビューをそこに置いています。
app
|--> controllers
|--> models (generated with a model)
|--> helpers
|--> mailers (generated with a mailer)
|--> helpers
|--> parts (generated with a parts controller)
`--> views
The config folder has all the configuration files and environments. It's
important to edit the init.rb and database.yml files in here before
running Merb.
config フォルダには、設定構成ファイルと環境のすべてが収められています。
このうち、init.rb と database.yml のファイルは Merb を起動する前に編集する必要があります。
The Merb router, which maps the incoming requests to the controllers is also
here. The rack.rb file is the rack handler and you can pass options to
merb -a to change rack adapter.
Merb のルータは、受信したリクエストをコントローラに対応づける役目を持っていますが、それもここにあります。
rack.rb ファイルは Rack ハンドラであり、merb -a にオプションを渡して Rack アダプタを変更することができます。
config
`--> environments
RSpec specs can be found in the spec folder.
RSpec による仕様 (スペック) は、spec フォルダに置かれます。
spec
In addition to these folders you can have a gem directory, which stores
frozen gems (see Freezing Gems for more info), and a lib folder to store
other ruby files.
これらのフォルダに加え、凍結した gems (詳細は『Gems を凍結する』を参照してください) を置くための gem ディレクトリや、他の Ruby ファイルを置くための lib フォルダを持つことができます。
merb_helpersConfiguring and sending emails
サンプルとなるブログアプリケーションの設定
merb_helpers で定義されているビューヘルパのいくつかIn the examples, we'll be developing a small blogging application. It's a good idea to grab the source code from http://github.com/deimos1986/book_mdar/tree/master/code, so you can follow along with the examples.
サンプルでは、小さいブログアプリケーションを作成します。 ソースコードは http://github.com/deimos1986/book_mdar/tree/master/code から取ってこれますので、サンプルに沿って試すことができます。
First of all, let's define some of the functionality we would expect from any blogging application.
まず最初に、ブログアプリケーションとして考えられる機能をいくつか定義しましょう。
Authentication
投稿を公開する
We're going to call our app golb. Think of it as a backward blog. Feel free
to change the name of your app, but if you do, remember to replace the word
golb with the name of your app.
このアプリケーションは、golb という名前にすることにします。
これは、blog の逆読みです。
アプリの名前は自由に変更して構いませんが、変更した場合は golb という単語が出てきたら変更後の名前に置き換えてください。
To make a new app we'll use the command
新しいアプリを作るには、次のコマンドを使います。
merb-gen app golb
Set up the configuration files for your application, this lets Merb know what gems to load for plugins and generators.
自分のアプリケーションの設定構成ファイルを設定してください。 そうすることで、プラグインやジェネレータでどの gems を読み込むのか、Merb は知ることができるようになります。
config/init.rb
use_orm :datamapper
use_test :rspec
dependencies "dm-validations"
Now add a config/database.yml file with the following:
また config/database.yml ファイルを次のような内容で作成してください:
---
# This is a sample database file for the DataMapper ORM
development: &defaults
# These are the settings for repository :default
adapter: mysql
database: golb
encoding: utf8
username: root
password:
host: localhost
# Add more repositories
# repositories:
# repo1:
# adapter: postgresql
# database: sample_development
# username: the_user
# password: secrets
# host: localhost
# repo2:
# ...
test:
<<: *defaults
database: golb_test
# repositories:
# repo1:
# database: sample_development
production:
<<: *defaults
database: golb_production
# repositories:
# repo1:
# database: sample_development
---
Note: DataMapper has a rake task to generate a default database.yml file:
注意: DataMapper には、デフォルトの database.yml ファイルを生成する rake タスクが用意されています。
dm:db:database_yaml
You can also put a database URI in development.rb (or other environments) just as easily:
また development.rb (または他の環境設定ファイル) にデータベース URI を設定することも簡単です:
Merb::BootLoader.after_app_loads do
DataMapper.setup(:default, 'mysql://user:pass@localhost/database')
end
Now we're ready to rock and roll ...
ここまできたら、ロックンロールする準備はできました...
(TODO) - rewrite for DM 0.9 almost done, just need to finish it!
Building a model with Merb and DataMapper requires generating a model, specifying attributes (properties), and running a migration to create the database table and all the properties. Generating a model is similar to Rails, as is running a migration. But unlike ActiveRecord, DataMapper does not use migration files to define the model.
Merb と DataMapper を使ってモデルを構築するには、モデルを生成し、属性 (プロパティ) を指定し、マイグレーションを実行してデータベーステーブルとすべてのプロパティを生成する必要があります。 モデルを生成するのとマイグレーションを実行するのは Rails に似ていますが、ActiveRecord と違い、DataMapper はモデルを定義するためのマイグレーションファイルは使いません。
Instead, properties are defined in the model itself. This allows you to easily see how your models map to the database and removes the headache of trying to use separate migration files (when you have conflicting or irreversible migrations).
そうではなくて、プロパティはモデルそのものに定義されます。 これにより、自分のモデルがどうデータベースに対応付けされるかが簡単にわかるので、マイグレーション用の別ファイルを使うことによる頭痛のタネ (マイグレーションが衝突したり取り消せなかったり) をなくすことができます。
DataMapper has a model generator just as Rails does:
DataMapper は、Rails と同じようなモデルのジェネレータを備えています:
merb-gen model post
This will make a post model for you, provided that you have defined an ORM and the database golb, in the previous steps.
これを実行すると、前の手順で定義した ORM とデータベース golb を使って、post モデルが生成されます。
Note: Sometimes you might prefer to directly create a resource (Model, Controller, View) instead of calling the generator tree times:
注意: リソース (モデル、コントローラ、ビュー) を生成する場合は、ジェネレータを 3 回呼び出すよりも、直接リソースを生成したほうがいいでしょう:
merb-gen resource post
So DataMapper models differ a bit from ActiveRecord models as previously
stated. Defining the database columns is achieved with the property method.
前述したように、DataMapper のモデルは ActiveRecord のそれとは少し違います。
データベースカラムの定義は property メソッドを実行することで行います。
app/models/post.rb
property :title, String, :lazy => false
This is the title property of the post model. As we can see, the parameters
are the name of the table column followed by the type and finally the options.
これは post モデルにおける title プロパティです。
見ればわかるように、パラメータはテーブルカラムの名前であり、そのあとに型が続き、最後にオプションが指定されます。
Note: We could have also directly set the properties when we called the generator:
注意: ジェネレータを呼び出すときに、プロパティを直接指定することもできます:
merb-gen model post title:string
By default, the lazy attribute is set to false for everything except text fields.
デフォルトでは、テキストフィールド以外を除き、遅延 (lazy) 属性は false に設定されます。
Some of the available options are: (TODO) - cover more properties
利用可能なオプションの一部: (TODO) - より多くのプロパティをカバーすること
:public, :protected, :private, :accessor, :reader, :writer,
:lazy, :default, :nullable, :key, :serial, :field, :size, :length,
:format, :index, :check, :ordinal, :auto_validation, :validates, :unique,
:lock, :track, :scale, :precision
:key - Set as primary key
:serial - auto-incrementing key
:lazy - Lazy load the specified property (:lazy => true).
:default - Specifies the default value
:field - Specifies the table column
:nullable - Can the value be null?
:index - Creates a database index for the column
:accessor - Set method visibility for the property accessors. Affects both
reader and writer. Allowable values are :public, :protected, :private.
:reader - Like the accessor option but affects only the property reader.
:writer - Like the accessor option but affects only the property writer.
:protected - Alias for :reader => :public, :writer => :protected
:private - Alias for :reader => :public, :writer => :private
:public, :protected, :private, :accessor, :reader, :writer,
:lazy, :default, :nullable, :key, :serial, :field, :size, :length,
:format, :index, :check, :ordinal, :auto_validation, :validates, :unique,
:lock, :track, :scale, :precision
:key - 主キーとして設定する
:serial - 自動採番キー
:lazy - 指定したプロパティを遅延 (lazy) 読み込みにする (:lazy => true).
:default - デフォルト値を指定
:field - テーブルカラムを指定
:nullable - 値が NULL を取ることができるか?
:index - そのカラム用にデータベースインデックスを作成する
:accessor - プロパティアクセッサ用のメソッドの可視性。読み書き両方に作用。利用可能な値は :public、:protected、:private。
:reader - accessor オプションと同じだがプロパティの読み出しのみに作用。
:writer - accessor オプションと同じだがプロパティの書き込みのみに作用。
:protected - :reader => :public, :writer => :protected と同じ
:private - :reader => :public, :writer => :private と同じ
〔訳注: 一意制約は :unique_index => true を指定します。複数のプロパティにまたがる一意制約は、:unque_index => :u1 をそれぞれのプロパティに指定します (ここで :u1 は任意の Symbol)。〕
(TODO) - talk about accessors and overriding them
(TODO) - アクセッサとそれらの上書きについて追記する
DataMapper supports the following properties in the core:
DataMapper は以下のプロパティをコア機能でサポートします:
Class (datastore primitive is the same as String. Used for Inheritance)
TrueClass, Boolean
(TODO) - creating your own custom properties
(TODO) - 独自のカスタムプロパティを作成する
Before a new record is created, be sure you have syncronized your model with the database. In order to do this, load the merb console with:
新しいレコードを作成するまえに、自分のモデルとデータベースとを同期させましょう。 そのためには、merb コンソールをロードします:
merb -i
Then migrate your Post model with:
そして、次のようにして Post モデルをマイグレートします:
Post.auto_migrate!
To create a new record, just call the method create on a model and pass it your attributes.
新しいレコードを作成するには、モデルに対して属性データつきで create メソッドを呼び出すだけです。
@post = Post.create(:title => 'My first post')
Or you can instantiate an object with #new and save it to the repository later: または、#new を使ってオブジェクトのインスタンスを生成したあと、リポジトリ層でセーブする方法でもいいです:
@post = Post.new
@post.title = 'My first post'
@post.save
There is also an AR like method to find\_or\_create which attempts to find an
object with the attributes provided, and creates the object if it cannot find it:
AR ライクな find\_or\_create メソッドも用意されています。
このメソッドは、与えられた属性を使ってオブジェクトを検索し、見つからなければオブジェクトを作成します:
@post = Post.first_or_create(:title => 'My first post')
There are a couple of different ways to set attributes on a model:
モデルの属性を設定するには、いくつか異なる方法があります。
@post.title = 'My first post'
@post.attributes = {:title => 'My first post'}
@post.attribute_set(:title, 'My first post')
Find out if an attribute has been changed (aka is dirty):
属性が変更された (つまりダーティである) ことを見てみましょう
@post = Post.first
@post.dirty?
=> false
@post.attribute_dirty?(:title)
=> false
@post.title = 'Changing the title'
@post.dirty?
=> true
@post.attribute_dirty?(:title)
=> true
@post.dirty_attributes
=> Set: {#<Property:Post:title>}
The syntax for retrieving data from the database is clean an simple. As you can see with the following examples.
データベースからデータを抽出するための文法は、次の例で見ればわかりますが、きれいでかつシンプルです。
Finding a post with one as its primary key is done with the following:
主キーを使って post を検索するのは、次のようにします:
# will raise a DataMapper::ObjectNotFoundError if not found
# use #get to just return nil if not record is found
Post.get!(1)
# 見つからない場合は例外 DataMapper::ObjectNotFoundError が発生します。
# 見つからない場合は単に nil を返してほしいなら、#get を使ってください。
Post.get!(1)
To get an array of all the records for the post model:
post モデルの全レコードを配列として取り出すには:
Post.all
To get the first post, with the condition author = 'Matt':
author = 'Matt' という条件で検索し、最初の post だけを取り出すには:
Post.first(:author => 'Matt')
When retrieving data the following parameters can be used:
データを取り出すときには、次のパラメータが利用できます:
# Posts.all :order => 'created_at desc' # => ORDER BY created_at desc
# Posts.all :limit => 10 # => LIMIT 10
# Posts.all :offset => 100 # => OFFSET 100
# Posts.all :includes => [:comments]
If the parameters are not found in these conditions it is assumed to be an attribute of the object.
もしこれらに含まれないパラメータが指定された場合は、それはオブジェクトの属性〔訳注: テーブルのカラム名〕だと見なされます。
You can also use symbol operators with the find to further specify a condition, for example:
また検索するときに、Symbol の演算子を使ってより詳しい条件を指定することもできます。
Posts.all :title.like => '%welcome%', :created_at.lt => Time.now
This would return all the posts, where the tile was like 'welcome' and was created in the past.
この例では、すべての post のうち、タイトルに 'welcome' を含み、かつ過去に作成されたものをすべて返します。
Here is a list of the valid operators:
こちらが利用可能な演算子の一覧です:
in - will be used automatically when an array is passed in as an argument
gt - より大きい
TODO: execute sql via the adaptor.
TODO: アダプタ経由で SQL を実行する
Updating attributes has a similar syntax to ARs update_attributes:
属性を更新するのは、AR の update_attributes と似たような文法でできます:
@post.update_attributes(:title => 'Opps the title has changed!')
You can also just set attributes and then save:
単に属性を設定してから保存してもいいです:
@post = Post.first
@post.title = 'New Title!'
@post.save
You can destroy database records with the method destroy, this work much like AR.
データベースレコードを削除するには destory メソッドを使います。
これも AR と同じです。
bad_comment = Comment.first
bad_comment.destroy
Like ActiveRecord, DataMapper has associations which define relationships
between models. There is a difference in syntax but the underlying idea is the
same. Continuing with the Post model we can see a few of the associations
defined:
ActiveRecord と同じように、DataMapper はモデル間の関係を定義する関連づけを行うことができます。
文法こそ違うものの、下敷きとなる考え方は同じです。
引き続き Post モデルを使って、関連づけの定義を見てみましょう:
has n, :comments
belongs_to :author, :class => 'User', :child_key => [:author_id]
The has n syntax is a very flexible way to define associations and the
standard way in DataMapper > 0.9. It can be used to model all of ActiveRecord
associations plus more. The types of associations currently in DataMapper are:
has n という文法は、関連づけを定義するうえでたいへん柔軟な方法であり、DataMapper 0.9 以降では標準の方法です。
これを使うと、ActiveRecord で定義できる関連付けのすべてが定義でき、またそれ以上のことも可能です。
DataMapper での関連の種類は、現在のところ以下のようになっています:
# DataMapper 0.9 | ActiveRecord
has n, :things # has_many :things
has 1, :thing # has_one :thing
belongs_to :item # belongs_to :item
many_to_one :item # belongs_to :item
has n, :items, :through => Resource # has_and_belongs_to_many :items
has n, :gizmos, :through => :things # has_many :gizmos, :through => :things
The has n syntax is more powerful than above, since n is the cardinality of
the association, it can be an arbitrary range. Some examples:
has n という文法はこれより強力です。
なぜなら、n が関連づけにおける基数 (cardinality) を表し、任意の範囲を設定できるからです。
例をお見せします:
has 0..n #=> will have a MIN of 0 records and a MAX of n
has 1..n #=> will have a MIN of 1 record and a MAX of n
has 1..3 #=> will have a MIN of 1 record and a MAX of 3
has 0..n #=> 0 個以上のレコードを持つ
has 1..n #=> 1 個以上のレコードを持つ
has 1..3 #=> 1 個以上 3 個以下のレコードを持つ
Pretty straight forward. A few things you should note however, you do not need to specify the foreign key as a property if it's defined in the association.
実にわかりやすいですね。 注意しなければいけないこととしては、もし外部キーが関連づけの中で定義されてあれば、プロパティとして外部キーを指定する必要はありません。
You also don't have to specify a relationship at all if you don't want to, as models can have one way relationships.
また望まないのであれば、関連づけを指定する必要は一切ありません。 その場合、〔訳注: 相手の〕モデルは一方向の関係を持つことになります。 <-- またモデルが一方向の関係を持つことができるように、望まないのであれば関連づけを指定する必要は一切ありません。 -->
(TODO) -polly assoc
(TODO) 多相関連づけ
has\_many :through?! (私のかわいい has\_many :through はどこ?!)DataMapper > 0.9 now supports has_many :through. For example, if you have a Post model that has many Categories through the Categorization model you would define these associations:
DataMapper 0.9 以降では、has_many :through をサポートするようになりました。 たとえば、Categorization モデル経由で Post モデルが複数の Category を持っているような場合、次のようにしてこれらの関連づけを定義できます:
class Post
include DataMapper::Resource
has n, :categorizations
has n, :categories, :through => :categorizations
end
post = Post.first
post.categorizations #=> []
post.categories #=> []
# to attach a category to a post:
post.categorizations << Categorization.new(:category => Category.first)
# or you could just create a Categorization object passing in both category and post:
Categorization.create(:post => post, :category => Category.first)
A has n :through relationship is useful, especially when the join model itself
has a lot of information on it. Perhaps a subscription which contains the join
date and the users rating for the feed it tracks. Sometimes, however, the join
model is very simple, just a table with two id columns.
has n :through 関係は役に立ちます。
ジョインモデル自体が多くの情報を持っているような場合は特にそうです。
もしかしたら購読 (subscription) は、ジョインした日付と、購読対象のフィードに対してつけられた、ユーザによる採点 (rating) を含んでいるかもしれません。
しかしジョインモデルは、テーブルの id カラムを 2 つ持つだけのような、とてもシンプルな場合もあります。
〔訳注: Subscription モデルは、Feed モデルと User モデルを N : M で関連づけるためのジョインモデルであり、feed id と user id を含んでいる。それ以外に、subscription モデルデータが作成された日付 (= ジョインした日付) と、ユーザによる rating もデータとして持つことが考えられる。それらが上記の『ジョインモデルが (持つ) 多くの情報』にあたる。〕
For this, DataMapper offers an alternative to :through => :models, which is
:through => Resource. The use of Resource tells DataMapper to automatically
create a join table. So to revisit the previous example:
このために DataMapper は、:through => :models の代替となる :through => Resource を提供しています。
リソースの使用を DataMapper に教えることで、ジョインテーブルを自動生成します。
これを使うと、前の例は次のようになります:
class Post
include DataMapper::Resource
has n, :categories, :through => Resource
end
post = Post.first
post.categories #=> []
post.categories << Category.first
post.save
post.categories #=> [Category.first]
The join table this would create be called posts_categorizations which would
contain the two keys of each post-categorization pair.
これによって生成されるジョインテーブルは posts_categorizations という名前になり、Post と Categorization の組における 2 つのキーを含みます。
(TODO) - still needs 0.9 love - mostly done now, I think
(TODO) - 0.9 への愛がまだ必要 - ほとんどできたとは思ってる
It's a known fact that users will enter invalid, blank or malicious data into your web app.
よく知られた事実ですが、ユーザは正しくないデータや、空欄や、悪意のあるデータをアプリに入力します。
We need to guard against user error by validating anything that we need to save out to our persistence layers. Sometimes that means guarding against hack attempts, but most of the time it means guarding against invalid data and accidents.
永続化層 (persistence layer) 〔訳注: データベースのこと〕にセーブする必要のあるデータはすべて、バリデーションをすることで、ユーザエラーを防がなくてはなりません。 バリデーションはハッキング防止の意味を持つこともありますが、たいていは妥当でないデータや事故を防止することを意味します。
Both ActiveRecord and DataMapper have a concept called Validations, which is ultimately a set of callbacks which fire right before an object gets saved out to our persistence layer and interrupt things when it detects something awry. To use them in DataMapper, all we have to do is require the gem dm-validations.
ActiveRecord と DataMapper の両方とも、バリデーションの概念を持っています。 それは突き詰めると、コールバックの集合といえます。 このコールバックは、オブジェクトが永続化層に保存される直前に呼び出され、エラーがあれば処理を中断させます。
require 'dm-validations'
class Post
include DataMapper::Resource
property :id, Integer, :serial => true
property :title, String, :length => 0..255
property :body, Text
property :original_uri, String, :length => 0..255
property :created_at, DateTime
property :can_be_displayed, Boolean, :default => false
validates_present :body
end
How many validations do we have on the content of the post class? To someone
familiar with ActiveRecord, the answer is obviously one. We have a validation
that the body must contain something - that it is present. In fact DataMapper,
through dm-validations, has set up four validations for us. When we declare
properties like :length => 0..255 as well as declaring the maximum length for
the field, it also adds a validation to check that the supplied values will fit
within that field. So when we validate our model DataMapper will check we ...
どれだけたくさんのバリデーションが Post クラスにあるかわかりますか?
ActiveRecord に慣れている人なら、答えは明らかに「1」でしょう。
body が何らかのデータを含んでいなければならない - つまり body が存在してないといけない、というバリデーションだけがあるからです。
しかし DataMapper の場合、dm-validations によって、 4 つ のバリデーションが設定されます。
たとえばプロパティで :length => 0..255 と宣言した場合、これはフィールドの最大長を宣言しただけでなく、入力された値がこのフィールド内に収まるかどうかをチェックするようなバリデーションも追加されるのです。
そのため、モデルをバリデーションすると、DataMapper は以下のことをチェックします...
have a body, which contains ... something, at least
body があるかどうか、また少なくとも何かデータが入力されているかどうか
And also, without us having to type anything, that we ...
また、特に何も追加することなく、以下のこともチェックしてくれます...
title, with a length somewhere between 0 and 255 charactersoriginal_url, with a length somewhere between 0 and 255 characters.have a value for can_be_displayed which is true or false (but not nil)
title があるかどうか、また長さが 0 から 255 文字以内であるか
original_url があるかどうか、また長さが 0 から 255 文字以内であるかcan_be_displayed に値があってそれが true または false (ただし nil ではない) であるか We can test this by calling valid? on one of our posts:
Post モデルに valid? メソッドを呼び出すことで、これらを確認することができます:
@post = Post.new
@post.valid?
=> false
@post.title = "A cool story!"
@post.body = "It was a dark and stormy ..."
@post.valid?
=> true
If an object isn't valid, you can access its the errors by calling its errors method.
もしオブジェクトのデータが正しくない場合、errors メソッドを呼び出すことでエラーにアクセスすることができます。
@post.errors
=> #<DataMapper::Validate::ValidationErrors:0x2537e40 @errors={:body=>["Body must not be blank"]}>
A problem arises when your website has users creating content and content being created automatically from scrapers or some sort of automated background process (be it from RSS feeds, an FTP server or a web service). No idiots are involved in the creation of content when it's imported into the system and you likely really want that content to appear in your system. This is where context specific validations come into play.
自分の Web サイトに、コンテンツを作成するユーザと、スクレイパ (scraper) あるいはある種の自動化されたバックグラウンドプロセスによって (たとえば RSS フィードや FTP サーバや Web サービスから) 自動的に作成されたコンテンツがある場合、問題が発生します。 コンテンツ作成において、それがシステムに取り込まれるときにミスをするような人間はおらず、またそのコンテンツがシステム内に現れるのをおそらく本当に望んでいます〔訳注: ???〕。 これは、文脈によって異なるバリデーションが作用している場面です。
Contexts let you control which validations run when you perform a particular operation. You might want to make sure that a user enters the title for a blog post in your system, but you don't really want such a check for when that blog post comes in off of your RSS scraping system. Maybe you'd send those imported blog posts into a holding pen somewhere so that they can be rescued later, rather than preventing their save and never importing them in at all.
文脈によって、ある操作を実行するときにどのバリデーションを実行するかを制御することができます。 システムにおいて、ユーザがブログへ投稿するときにはタイトルが入力されているかを確かめたいという場合もありますし、RSS スクレイピングシステムからのブログの投稿ならそういったチェックはしたくないという場合だってあります。 これらの読み込んだブログの投稿を、保存しないようにして一度に読み込むことがないようにするよりも、あとから救出可能なように留置所かどこかに送信したいと思うかもしれません〔訳注: ???〕。
With ActiveRecord, if you declare a validates\_presence\_of on :title,
that's it - game over. The only way to bypass that validation is to
save\_without\_validations and that skips all of your validations, rather
than just this one.
ActiveRecord では、validates\_presence\_of を :title に宣言してしまうと、そこでおしまいです〔訳注: それ以上のことはできない〕。
唯一の方法は、save\_without\_validations を使ってバリデーションを回避することですが、それだと今度はそのバリデーションだけをスキップするのではなく、すべてのバリデーションをスキップしてしまいます。
But with DataMapper and dm-validations , you can check for the validity of an object depending on the circumstance you're in. Here's what that blog post model would look like if we wanted to validate blog posts by idiots, but not from our not-so-idiotic scrapper:
しかし DataMapper と dm-validations を使えば、状況によってオブジェクトのバリデーションを使い分けることができます。 以下に示すブログの Post モデルでは、ユーザがブログの投稿をしたときは間違った入力をする可能性があるのでバリデーションを行い、スクレイパからの投稿であればエラーの可能性がないのでバリデーションをしないようにしています。
require 'dm-validations'
class Post
include DataMapper::Resource
property :id, Integer, :serial => true
property :title, String, :length => 0..255, :auto_validation => false
property :body, Text
property :original_uri, String, :length => 0..255, :auto_validation => false
property :created_at, DateTime
property :can_be_displayed, Boolean, :default => false
# user creation
validates_present :title, :when => [:default, :display]
validates_present :body, :when => [:default, :display, :import]
# automated import
validates_length :original_uri, :in => 0..255, :when => :import
# a callback to set can_be_displayed appropriately (more on these later)
before :save do
self.can_be_displayed = true if self.valid? :display
end
end
Running quickly through my sample here, you'll spot a few things. The first
is the :auto_validation => false on the title and the original_uri.
Because we want to define custom contexts for when we need these properties to
be checked, we have to override the ones dm-validations adds by default. The
second are the :when => [...] following some of our validations. These define
in what situation (or context) these validations will be applied.
このサンプルを実行してみたとき、2、3 のことに気づくでしょう。
1 つめは、title と original_uri につけられた :auto_validation => false です。
これらのプロパティがいつチェックされる必要があるかを表すための文脈を自分で定義したいので、dm-validations がデフォルトで付加するオプションを上書きする必要があります。
2 つめは、いくつかのバリデーションに :when => [...] が追加されていることです。
これらは、どのような状況で (つまりどのような文脈で) これらのバリデーションが適用されるかを定義します。
To check if a post is valid in a particular context, we pass the context as an
argument to valid?. For example @post.valid? :display tells us if the post
is valid for displaying. These contexts are also honoured by the save method,
allowing us to call @post.save :import after our RSS scrapper has parsed the
RSS feed and assigned our variables.
ある文脈で、投稿にエラーがないかどうかをチェックするには、valid? メソッドの引数として文脈を渡します。
たとえば、@post.valid? :display は表示する場合においてその投稿にエラーがないかどうかを表します。
このような文脈は save メソッドでも効果を持ち、たとえば RSS スクレイパが RSS フィードを構文解析して必要なデータを取り出したあとで、@post.save :import を呼び出すといったことができます。
You'll notice that I gave :body a validates\_present for all my contexts.
This means that, no matter what, that validation callback will kick in. At
present there doesn't appear to be a meta "all" context, which will fire under
any circumstances.
また、:body に対してはすべての文脈において validates\_present が有効になっていることに気づいていることと思います。
これはつまり、どんな場合でも、そのバリデーションのコールバックが呼び出されることを意味します。
現在では、どんな状況でも呼び出されるような、「すべて」を表すようなメタな文脈を指定することはできません。
Also of note is the can\_be\_displayed boolean and the before :save manual
callback I defined. Here, I'm helping myself out later on so that it's easy to
pull out valid blog posts that can be displayed without worrying about nil field
values and such:
また、can\_be\_displayed が真偽値を返すことと、before :save という手動でのコールバックを定義していることにも注目しましょう。
ここでは、エラーのないブログ投稿データを引き出すのを簡単にすることで、あとで私自身楽をしようと思っています。
そうしておけば、フィールド値が nil であるかどうかを気にすることなくブログ投稿データを表示することができます。
たとえばこれが:
@posts = Post.all(
:title.not => nil,
:slug.not => nil,
:order => :created_at.desc,
:limit => 10
)
Becomes…
このようになります…
@posts = Post.all(
:can_be_displayed => true,
:order => :created_at.desc,
:limit => 10
)
Pretty sexy, no? I can't off-hand think of a way to get this functionality from ActiveRecord objects without a lot of fuss and bother - perhaps using single-table inheritance and with the validations on the subclasses?
これってすごくないですか? これと同じ機能を ActiveRecord オブジェクトで実現しようと思ったら、私は多くの不平やイライラなしではいられないでしょう - 単一テーブル継承と、サブクラスでのバリデーションを使えば、もしかしたらできるかも?
With the proper use of validation contexts, you end up saving yourself a lot of headache and work later on down the line, as well as supporting different scenarios where a post might be valid or might not -- all without having to hack-around. How enterprise-y!
文脈ごとのバリデーションを適切に使えば、最後には頭痛と労働から自分自身を解放することができます〔訳注: "later on down the line" って何???〕。 また投稿がエラーを含むべきでなかったり、または含んでいてもいいというような、異なるシナリオに対応しなければならなくても -- ハックする必要がまったくありません。 なんというエンタープライジー (enterprise-y) !
Another very powerful feature in dm-validations is validates\_with\_method.
Think of it as like overloading valid? only with the full power of real
validations still there too.
dm-validations が持っているもうひとつの強力な機能は、validates\_with\_method です。
これはちょうど、バリデーションの強力さはそのままに、valid? をオーバーロードしたようなものです。
Say, for example, you've got an Event model that needs to make sure the
end\_date for the event is greater than the start_date. Wouldn't want to
break the laws of physics, so we'd do something like:
たとえば Event というモデルがあったとし、これの end\_date が start_date より大きいことを確認しておきたいとします。
物理法則を無視することはしたくないので、たとえば次のようにします:
class Event < ActiveRecord::Base
def valid?
start_time < end_time
end
end
Yup, it's pretty simple with ActiveRecord. Just toss in our own valid? method and we're done. With DataMapper, things are a touch more complicated, but not difficult, and buy you the full power of dm-validations:
ええ、ActiveRecord では実にシンプルです。単に自分で valid? メソッドを定義するだけであり、事実その通りにできました。 DataMapper の場合は、より複雑になりますが、難しくはなく、また dm-validations の力をすべて享受できます。
class Event
include DataMapper::Resource
# properties here
validates_with_method :check_times
def check_times(context = :default)
if start_time < end_time
return true
else
return [false, 'End time must be after start time']
end
end
end
So, a couple of things are going on here. First, we declare that we're going to
use our custom validation method check_times for the model. Then comes the
method itself. It's a pretty simple method. If our start\_time is before
our end\_time, return true as we're valid. Otherwise, it return an array. The
first entry in the array is false, which lets DataMapper know the validation
has failed. The second entry is a string, which is added to @event.errors so
the user has some idea what has gone wrong.
ここでは、2 つのことが行われています。
最初に、モデルに対して check_times というバリデーション専用のメソッドを使うことを宣言します。
続いて、メソッド自身が定義されています。
これは実にシンプルなメソッドです。
もし start\_time が end\_time より前であれば、エラーがないことを表す true を返します。
そうでなければ、配列を返します。
配列の最初の要素は false であり、これによって DataMapper はバリデーションが失敗したことを知ります。
2 番目の要素は文字列であり、ユーザに何が間違っていたかを知らせるために、この文字列が @event.errors に追加されます。
Of course, this custom validator can also be applied only in certain contexts,
just by adding a :when => [...] on the validates_with_method line. This
brings us a lot of flexibility, and as we're validating with a ruby method, we
can get as complex as we need to specify our behaviour. Much nicer than just
overriding valid. It's this functionality which requires the context to be
passed in (Although your method can feel free to ignore it).
もちろん、この特注のバリデータは、ある特定の文脈でのみ適用させるようにすることができます。
そのためには validates_with_method の行に :when => [...] を追加します。
これにより、多大な柔軟性を得ることができます。
Ruby のメソッドを使ってバリデートしているので、自分の好きなだけ振る舞いを指定できます。
単に valid メソッドをオーバーライドするよりもずっといいです。
この機能こそ、文脈を指定することが必要とされるものです (もちろん文脈を無視してもいっこうに構いません)。
There is a rake task to migrate your models, but be warned these are currently destructive!
自分のモデルをマイグレートするための rake タスクが用意されています。 しかし、現時点ではこれらは破壊的であると警告されます!
rake dm:db:automigrate # Automigrates all models
rake dm:db:autoupgrade # Perform non destructive automigration
rake dm:db:automigrate # 全モデルについて自動マイグレーションを実行
rake dm:db:autoupgrade # 非破壊的な自動マイグレーションを実行
You can also create databases from the Merb console (merb -i)
データベースは Merb コンソール (merb -i) で作成することもできます。
Post.auto_migrate!
or
または
Post.auto_upgrade!
This does the same job as the rake task migrating all your models.
これは、自分のすべてのモデルについてマイグレーションを行う rake タスクと同じことを行います。
DataMapper.auto_migrate!
Why the two commands? They both do slightly different things.
なぜ 2 つのコマンドがあるのでしょうか? 両者は少しだけ違うことを行います。
The first, auto_migrate!, works by dropping the table (if it exists) and all
of its data then working out which columns need to exist from the model
definition, before finally rebuilding the table in the database. This includes
any constraints imposed. For example, :nullable => false will add a NOT NULL
to the column definition.
最初の auto_migrate! のほうは、テーブルとデータを削除し (もしあれば)、それからモデル定義からどのカラムが必要かを判断し、最後にテーブルをデータベースに作成します。
また、制約もすべて考慮されます。
たとえば、:nullable => false が指定されていればカラム定義に NOT NULL が付加されます。
auto_upgrade! on the other hand, creates the table from nothing only if the
table isn't there already. If it is there, then it compares the current table
to the model. If there are properties in the model not defined as columns in
the table, it will add them to the table. It does have some limitations though.
It doesn't delete columns, and it can't detect renaming them.
一方、auto_upgrade! のほうは、テーブルがまだ存在していない場合にのみテーブルを作成します。
もしすでに存在している場合は、現在のテーブルとモデルとをを比較します。
テーブルのカラムにないプロパティがモデル中にあれば、それをテーブルに追加します。
ただし、いくつか制限があります。
カラムは削除しませんし、カラム名の変更も検出できません。
Whilst the preceding commands and tasks can keep the database schema in
perfect sync with the models, they can also wipe out any data you might have in
the database, or fail to remove columns which are no longer needed. To avoid
this, AR style migrations are also supported. These are stored in schema/migrations
and are ruby files.
先ほど紹介したコマンドとタスクは、データベーススキーマとモデルとを完全に同期させるものでしたが、データベース中のデータをすべて消し去ってしまったり、もう必要のなくなったカラムが削除されないという問題がありました。
これを避けるために、AR スタイルのマイグレーションもサポートされています。
これらは Ruby ファイルであり、schema/migrations に格納されます。
migration(1, :add_homepage_to_comments ) do
up do
modify_table :comments do
add_column :homepage, String, :length => 100, :nullable => true
end
end
down do
modify_table :comments do
drop_column :homepage
end
end
end
The first line of the file is what identifies the migration, and there are two
components to it. The more important one is the name, :add_homepage_to_comments,
which must be unique across all the migrations applied to the database. The
other parameter, 1 in this case, is the level or order and migrations are
applied. This number doesn't have to be unique, although a migration mustn't
have a higher number than a migration it depends on. You shouldn't define
modifications to a table to happen before that table is made, for example.
ファイルの最初の行は、マイグレーションを一意に特定するためのものであり、2 つのコンポーネントから構成されています。
〔訳注: 2 つのうち〕より重要なのは :add_homepage_to_comments という名前のほうであり、これはデータベースに対して適用されたすべてのマイグレーションにおいて一意である必要があります。
数字のほうは一意である必要はありませんが、そのマイグレーションが依存する別のマイグレーションより高い〔訳注: 小さい〕値であってはいけません。
たとえば、あるテーブルに対する修正を、そのテーブルが作成されるより前に定義することはできません。
The up and down blocks describe the actual behaviour of the migration. up
is what happens when the migration is applied, and down happens when the
migration is 'undone'. This might not mean undone in the literal sense - if you
migrate to remove a column, and add it back in the down migration, while the
column will be there, all the data will be lost.
up と down のブロックには、マイグレーションの実際の動作を記述します。
up のほうはマイグレーションが適用されるときに実行され、down のほうはマイグレーションを 「元に戻す」ときに実行されます。
これは、文字通りの意味で元に戻されるわけではありません - もしカラムを削除するようなマイグレーションを作成し、かつそれを追加し直すような down マイグレーションを作成した場合、〔訳注: down マイグレーションで元に戻そうとしても〕そのカラムのデータは失われてしまうでしょう。
In this case, as the name suggested, we add a homepage column to comments.
It's specified much like a property (and should match up with the relevant
property in the model.rb file). It also takes many of the same options -
essentially all those which are just database features: :length, :nullable
are valid, but :private, which is a pure ruby option is not allowed. The
down migration is the opposite - it removes the column.
今回の場合、名前を見ればわかりますが、comments テーブルに homepage カラムを追加しています。
これはプロパティによく似た感じで指定されます (また model.rb ファイルの中で関係のあるプロパティと一致しなければいけません)。
カラムの追加にはたくさんの〔訳注: プロパティと〕同じオプションを指定可能です - 本質的にはデータベースの機能であるものすべてが使用可能です: たとえば :length と :nullable が使用可能ですが、純粋に Ruby のオプションである :private は使用できません。
down マイグレーションは逆であり、カラムを削除します。
To apply the migrations, there are a couple of rake tasks available through merb_datamapper
マイグレーションを適用するために、merb_datamapper によって 2 つの Rake タスクが用意されてます。
rake dm:db:migrate:up # migrates the database up
rake dm:db:migrate:down # migrates the database down
〔訳注: これは古いタスク名です。Merb 1.0 以降は dm:db:xxxx のかわりに db:xxxx を使ってください。〕
Which apply or remove all the migrations in turn. Sometimes, you don't want to
go all the way up (or down) and so you can also specify a level to migrate to,
via VERSION=2 or invoking a task like rake dm:db:migrate:up[2]. For both up
and down migrations, the version determines the highest order that will be
reflected in the table, either by applying up migrations until the level is
complete or applying all the down migrations greater than the given level.
これらはすべてのマイグレーションを順に適用するか、削除します。
すべての up マイグレーション (または down マイグレーション) を適用したくはない場合もあるでしょう。
その場合、〔訳注: Rake コマンドに〕VERSION=2 を追加するか、rake dm:db:migrate:up[2] のようにしてタスクを起動するかしてください。
up と down のマイグレーションの両方において、テーブルに対して反映されるいちばん高いレベル番号が、バージョンによって決定されます〔訳注: ???〕。
up マイグレーションの場合はそのレベルに達するまで、また down マイグレーションの場合は指定されたレベルより大きいマイグレーションが、すべて適用されます。
There are a couple of generators to make migrations
マイグレーションを作成するためのジェネレータが 2 つ用意されています。
merb-gen migration name_of_migration # an empty migration
merb-gen resource_migration Post # a migration for the post class
The first creates an empty migration stub with the name defined and an up and
down block. The second loads up the class in question from app/models and
does it's best to construct the appropriate migration from the properties of the
model. It currently doesn't generate anything to do with relationships, however.
最初のほうは、マイグレーションのスタブを up と down のブロックが空の状態で作成し、それに指定された名前をつけます。
2 番目のほうは、該当するクラスを app/models から読み込み、そのモデルのプロパティから適切なマイグレーションを構築するよう最大限努力します。
ただし現在のところ、関連づけについては何も生成してくれません。
Callbacks in DataMapper > 0.9 are very powerful. In any DataMapper::Resource you can set before and after callbacks on any instance/class method. There are a couple of different ways to define callbacks:
DataMapper 0.9 以降では、コールバックが非常に強力です。 どの DataMapper::Resource のどのインスタンスメソッド/クラスメソッドに対しても、呼び出し前と後のコールバックを設定することが可能です。 コールバックを設定する方法は 2 つあります:
class Post
include DataMapper::Resource
property :id, Integer, :serial => true
property :title, String, :length => 200
# before save call the instance method make_permalink
# save メソッドを呼び出す前に、インスタンスメソッド make_permalink を呼び出す
before :save, :make_permalink
def make_permalink
self.title = PermalinkFu.permalink(self.title)
end
#callbacks can be defined for any method
# コールバックはどのメソッドに対しても定義できる
after :publish, :send_message
def publish
# do some publishing here
end
def send_message
# email someone here
end
# defining a callback on a class method, passing in a block to run before its created.
# 作成前に実行されるブロックを渡すことで、コールバックをクラスメソッドに定義する。
before_class_method :create do
# do something before a record is created
end
end
Sometimes, you have to operate on a large number of records at once, to do
exactly the same thing to each of them. The example earlier for deleting old
posts via each. It involved several SELECTs and then lots of DELETEs, potentially
hundreds, depending on the size of the database. Wouldn't it be nice if you
could just go Comments.all(:date.lt => Date.today - 20).destroy! and it would
produce an appropriate query to do it in one operation and without loading all
those posts which are about to be deleted?
ときどき、一度に大量のレコードに対して、まったく同じ操作をしなければならない場合もあります。
先の例では、古い投稿を each で消していました。
この場合、SELECT 文を何回かと、大量の DELETE 文が実行されました。
この DELETE 文は、データベースの大きさによっては数百回実行される可能性が潜在的にあります。
もし Comments.all(:date.lt => Date.today - 20).destroy! みたいなことができて、それが適切なクエリーが発行されることによって 1 回の操作で済み、かつ削除すべきデータをすべて読み込むようなことを避けるようになってくれてたらいいと思いませんか?
Well, that's what happens. Collections are 'lazily evaluated', which is to
say, they don't do anything until they've been 'kicked'. .each, mentioned
earlier, is a kicker method. It issues a SELECT appropriate to the conditions.
.destroy! is another one, except it issues a DELETE. The other bulk method is
update!, which looks like (example taken from the DataMapper source)
ええ、その通りに動作します。
Collections は「遅延評価」されます。
これはいうなれば、「開始する (kick)」までは〔訳注: データベースに対して〕何もしないということです。
前に出てきた .each は「開始する」メソッドであり、条件に対して適切な SELECT 文を発行します。
.destroy! は別の「開始する」メソッドであり、〔訳注: SELECT 文のかわりに〕DELETE 文を発行すること以外は同じです。
〔訳注: こういったメソッドは bulk メソッドといい、そして〕他の bulk メソッドとしては update! があり、これは次のように使います (DataMapper のソースから持ってきた例です):
Person.all(:age.gte => 21).update!(:allow_beer => true)
This command would update the allow_beer attribute of all people aged 21 or
older in the database, all in one UPDATE statement.
このコマンドは、データベースにある 21 歳以上の人たち全員の allow_beer 属性を、1 回の UPDATE 分で更新します。
Note: ActiveRecord has a well known Model.delete_all class method to erase all table entries. In DataMapper to delete all instances of an Object in the database, you would do Model.all.destroy!
注意: ActiveRecord は、よく知られているように Model.delete_all クラスメソッドがあり、これでテーブルのエントリをすべて消去することができます。
DataMapper では、データベース中のオブジェクトのインスタンスをすべて削除するには Model.all.destroy! を使います。
DataMapper by default does not provide aggregator methods, but dm-aggregates
in dm-more does. After adding dependency "dm-aggregates" to your merb init.rb
file, your resource model will have aggregator methods including count, min,
max, avg, and sum. You can pass conditions to any of these aggregator
methods the same as Resource.first or Resource.all
DataMapper は、デフォルトでは集計用メソッドを提供していません。
しかし dm-more に含まれる dm-aggregates がそれらを提供してくれます。
Merb の設定ファイル init.rb に dependency "dm-aggregates" を追加すると、モデルクラスで count, min, max, avg, sum という集計用メソッドが使えるようになります。
これらの集計用メソッドには、Resource.first や Resource.all と同じように、条件を渡すことができます。
Post.count :title.like => "%hello world%"
# you can also do a count on an association:
@post.comments.count
Post.avg(:reads_count)
Post.sum(:comments_count)
Each works like like expected iterating over a number of rows and you can pass
a block to it. The difference between Comments.all.each and Comments.each
is that instead of retrieving all the rows at once, each works in batches
instantiating a few objects at a time and executing the block on them (so is less
resource intensive). Each is similar to a finder as it can also take options:
Each は、たくさんの行に対して繰り返しを行ないます〔訳注: "like like" ってなんじゃ???〕。
また each にブロックを渡すことができます。
Comments.all.each と Comments.each の違いは、前者が一度にすべての行を取り出すのに対し、後者はオブジェクトを少しずつインスタンス化してブロックを実行するという点です (そのため後者のほうがリソースの消費が劇的に少なくて済みます)。
Each はオプションを取ることができるので、finder に似ています:
Comments.all.each(:date.lt => Date.today - 20).each do |c|
c.destroy
end
NB: This isn't currently working in DataMapper. It instead fetches all the records. However, it will be reimplemented soon.
NB: これは、まだ DataMapper では動作しません。 かわりにすべてのレコードをフェッチします。 しかしながら、まもなく再実装される予定です。
You can set the name of the database table in your model if it is called something different by overriding a method in the class:
モデルの中で、データベーステーブル名を設定することができます。 そのためには、クラスの中でメソッドをオーバーライドします:
def default_storage_name
'list_of_posts'
end
This is only necessary if you are using an already existing database. If you
have a lot of tables to rename, consider instead a NamingConvention, detailed
later.
これは、既存のデータベースを使う場合にのみ必要です。
もし名前を変更しなければいけないテーブルがたくさんある場合は、次に説明する NamingConvention を使うことを検討してください。
TODO: Write NamingConventions section.
TODO: NamingConventions の章を書く。
Routing in Merb is similar to Rails, if you take a look at your router.rb file
自分の router.rb ファルを見ればわかりますが、Merb におけるルーティングが Rails のそれと似ています。
(TODO) - Defining routes, and resources (TODO) - Nested routes (TODO) - Namespaces (TODO) - Show routes, merb.show_routes in merb's irb console (merb -i)
(TODO) - filters, how the chaining works and :throw
Merb filters are quite powerful, etc..
Merb のフィルタは実に強力です。たとえば...
In Rails:
before_filter :find_post
after_filter
In Merb:
before :login_required, :exclude => [:index, :show]
after :send_email, :only => :create
skip_before is used to skip a before filter
skip_before は before フィルタを飛ばすのに使われます。
NB: it's exclude not except
(TODO) - how params get passed in controllers (TODO) - Exception Controller (TODO) - explain provides (TODO) - usecase for a part, explain what they are (possibly comments?) - or a side bar of some sorts (TODO) - Admin controller (TODO) - specify a layout (TODO) - rest (TODO) - content_type (TODO) - flash?
(TODO) - form helpers (TODO) - mention you can use other template languages
Use the partial method to render a partial from the current directory. If you pass a hash as the second argument the contents will be made available as local variables in the partial.
カレントディレクトリでの部分テンプレートを描写するには、partial メソッドを使います。 もし第 2 引数としてハッシュを渡すと、その値は部分テンプレート内ではローカル変数として使用可能です。
partial :post, {:comments => @post.comments}
To display the latest posts on our blog's front page, we use the :with and :as arguments to render a collection.
自分のブログのフロントページで最新の posts を表示するために、:with と :as を使ってコレクションを描写します。
partial :post, :with => @posts, :as => post
(TODO) - sending mail (TODO) - mail templates in /views
(TODO) - Rolling your own (TODO) - integrating RESTful auth (merbful)
(TODO) - attachment_pu (TODO) - image resize/crop (TODO) - downloading
RSpec is a testing framework which uses a Domain Specific Language or DSL to provide more human readable test code.
RSpec はテスティングフレームワークであり、問題領域専用言語 (Domain Specific Language、DSL) を使うことで人間にとって読みやすいテストコードが書けるようにしているのが特徴です。
When using stubs with RSpec you can roughly categorise the methods you are
going to use into two categories. On one side you have the stub! and
should_receive methods which refine what methods you expect to be called
with what parameters and potentially what they should return in the case of
the test being run. On the other side you have assertions which test the output
and value or variables. The should method is primarily used when asserting
specific results.
RSpec でスタブを使うときは、使うメソッドを大まかに 2 つにカテゴリ分けできます。1 つめは stub! と should receive メソッドであり、これらは実行するテストケースにおいて、どのメソッドが呼び出されることを期待しているのか、どんな引数を伴うのか、そしてどんな値を返すべきかを定義します〔訳注: "refine" は "define" の間違い?〕。もう 1 つはアサーションであり、出力と変数または値をテストします。特定の結果をアサーションで確かめるときは、主に should メソッドが使われます。
(TODO) - how to write good test and what should just trust works
(TODO) - finish stories section
RSpec Stories are use to replace the specification phase in requirements gathering, in the form of scenarios. So we have both a spec and a integration tests.
RSpec のストーリーは要件収集における仕様策定フェーズを、シナリオの形で置き換えます。 そのため、仕様と統合テストの両方を手に入れることができます。
Add this line to your app's test environment:
自分のアプリの test 環境に次の行を追加してください:
dependency "merb_stories"
Now generate your story:
次に自分のストーリーを生成します:
merb-gen story mystory
Now run your story:
続いてストーリーを実行しましょう:
MERB_ENV=test rake story[mystory]
Yes, you must include the square brackets.
角カッコは必要ですので注意してください。
Now fill out your story. There are some differences to Rails' versions. The best places to look for help are in the Merb code itself:
ここで、ストーリーを埋めてしまいましょう。 Rails の場合とは少し違う点があります。 いちばん参考になるのは、Merb のコード自身です:
spec/public/test/controller _matchers _spec.rb
lib/merb-core/test/helpers
lib/merb-core/test/matchers
To start you off, here are the steps for a simple integration test:
steps_for(:homepage) do
When("I visit the root") do
@mycontroller = get("/")
end
Then("I should see the home page") do
@mycontroller.should respond_successfully
@mycontroller.body.should contain("Hello")
end
end
(TODO) - How to spec models, use example merb/dm test talk through them, mocking
(TODO) - What they should test
For more information, check Merb's wiki
Testing controllers typically involves stubbing out some methods, making a fake request and then ensuring the right variables are assigned, exceptions are raised and views rendered.
コントローラのテストは、典型的にはいくつかのメソッドのスタブを含み、ダミーのリクエストを生成します。 そして、変数が正しく代入されたことや、必要であれば例外が発生したことや、ビューが描写されたことを確かめます。
A good start is testing the show action in our Posts controller.
さきほどの Posts コントローラにおける show アクションのテストをしてみましょう。
class Posts < Application
provides :html
def show
@post = Post.get!(params[:id])
render @post
rescue DataMapper::ObjectNotFoundError
raise NotFound
end
end
Our first test will ensure that Post.get!(1) is called when /posts/1 is visited, and when the post exists the response code is 200 OK.
最初のテストでは、/posts/1 にアクセスがあったときに Post.get!(1) が呼び出されることを確かめ、また Post が存在しているならレスポンスコードが 200 OK であることを確かめます。
describe Posts, "show action" do
it "should find post and render show view" do
Post.should_receive(:get!).with("1")
controller = get('/posts/1') do |controller|
controller.stub!(:render)
end
controller.should be_successful
end
end
The first should_receive ensures that Post.get!(1) is called, we could mock out a Post instance to return here, but in this case we're only interested in it being called and not raising an exception.
最初の should_receive では、Post.get!(1) が呼び出されることを確認しています。 Post インスタンスを作って返すこともできますが、今回の場合はそのメソッドが呼ばれて例外を発生させないことがわかれば十分です。〔訳注: "we could mock out a Post instance to return here" がよくわからん〕
Next we use the get method to make a request to the controller. The get method yields the controller, allowing us to stub out the render method, as we're not interested in how that behaves. Anything inside the get method's block will be executed before the request is dispatched.
次に、get メソッドを使ってコントローラにリクエストを送信します。 get メソッドを使ってコントローラを呼び出し、render メソッドが呼び出させるように設定しますが、それらがどう振る舞うかは知る必要はありません。 get メソッドのブロックの中で行なわれていることは、リクエストがディスパッチされる前に実行されます。
After the request has been dispatched, it returns the controller. Several methods are available to examine the results from the request: body, status, params, cookies, headers, session, response and route.
リクエストがディスパッチされたあと、get メソッドはコントローラオブジェクトを返します。 リクエストからの戻り値を調べるためのメソッドがいくつか利用可能です: body, status, params, cookies, headers, session, response, route があります。
This test was fairly simple, and it's likely you won't need to such tests if your controllers are as simple as ours. But once you have more than a few lines in your controller, simple response status checks can be useful for ensuring the overall integrity of your app.
このテストは極めてシンプルであり、みなさんのコントローラがこれほどシンプルでない限りは、たぶんみなさんはこのようなテストを必要としないでしょう。 しかし一度コントローラが 2、3 行程度には収まらないようになれば、レスポンスステータスの単純なチェックでも、自分のアプリ全体の完全性を確かめるときに、役に立つでしょう。
A more important test would be ensuring that a 404 is returned when the post cannot be found in the database. When Datamapper cannot find a record it raises Datamapper::ObjectNotFoundError. Merb has several useful exception classes which will set the correct status and then call the relevant action in your Exceptions controller. Raising NotFound will set the status to 404 and then call the not_found action, which can return a much nicer.
より重要なテストは、データベースに Post が見つからなかった場合に 404 が返されることを確認することでしょう。 DataMapper はレコードを見つけられなかった場合、DataMapper::ObjectNotFoundError を返します。 Merb は役に立つ例外クラスをいくつか持っており、それらは正しいステータスを設定して例外ハンドラの適切なアクションを呼び出してくれます。 NotFound 例外が発生した場合、ステータスには 404 が設定され、not_found アクションが呼び出されます。 not_found アクションは実にすばらしいレスポンスを返します。
it "should return 404 if post doesn't exist" do
Post.should_receive(:get!).with("1").and_raise(DataMapper::ObjectNotFoundError)
controller = get('/posts/1')
controller.status.should == 404
end
Unlike the last test there was no need for us to stub the render method because DataMapper::ObjectNotFoundError is raised before it is reached.
最後のテストと違い、render メソッドのスタブを作る必要はありません。 なぜなら、render メソッドを呼び出すまえに DataMapper::ObjectNotFoundError が発生するためです。
(TODO: Make and example of uploading assets in the simple blog)
The multipart_post method allows you to include files in a fake request. There must however be an actual file to be opened and submitted. If you put the file in the same directory as your spec, use File.dirname(FILE) to ensure the full path is used.
multipart_post メソッドを使うと、ダミーのリクエストにファイルを含めることができます。 ファイルは実際に開くことができて送信できる必要があります。 ファイルを spec ファイルと同じディレクトリに置いた場合は、File.dirname(FILE) を使ってフルパスが使われていることを確かめます。
If you are going to open the tempfile which is uploaded, remember to stub out File.open. Watch out though, if you use simply open instead of File.open it won't be the File.open you stubbed out. The other issue here is within the spec we have no way of knowing what the filename of the tempfile is, so we have to assume it's correct and use an_instance_of(String) so any filename is accepted.
もしアップロードされた tempfile を開く場合は、File.open のスタブを作成するようにしてください。 あとで見るように、もし File.open ではなく単に open を使う場合は、それはスタブを作成した File.open とは違うので注意してください。 他に、spec の中では tempfile のファイル名を知る方法がないという問題があります。 そのため、アップロードされた内容が正しいと仮定し、どのファイル名でも受け付けるように an_instance_of(String) を使う必要があります。〔訳注: わかんねー〕
(TODO: test code)
describe Posts, "create action" do
it "should receive file" do
File.should_receive(:open).with(an_instance_of(String))
multipart_post("/posts", {:image => File.open(File.join( File.dirname(__FILE__), "picture.jpg"))})
controller.assigns(:filename).should == "picture.jpg"
end
end
Your controller would look something like this.
コントローラ自身はこんな感じになるでしょう。
class Posts < Application
def create
fp = File.open(params[:image][:tempfile].path)
@filename = params[:image][:filename]
end
end
There are several other ways to dispatch a request in your test. Look at Merb's Wiki for more information
テストにおいてリクエストをディスパッチする方法は、ほかにもあります。Merb's Wiki を参照してみてください。
(TODO) - session cache (TODO) - Query/Mem cache
Check Merb's wiki for more information.
| The Rails way | The Merb way |
|---|---|
| script/server | merb |
| script/console | merb -i |
| script/generate | merb-gen |
| redirect_to blog_path(@blog) | redirect url(:blog, @blog) |
| respond_to | provides :xml, :js, :yaml |
| format | content_type |
| format.html | only_provides :html |
| render :xml => @post | render @post |
| render :file => 'public/404.html', :status => 404 | raise NotFound |
| logger | Merb.logger |
| before_filter | before |
| render :partial | partial |
| f.text_field :name | text_control :first_name |
| RAILS_ENV | Merb.environment |
As Merb is spilt up into various gems, and it's hard to keep update with each one it's a good idea to freeze them into your application, so an update to one gem doesn't break your app.
Merb は複数の gems に分かれており、それぞれを更新し続けるのは困難です。 そのため、それらを自分のアプリケーションに凍結 (freeze) するのはいい考えです。
The easiest way to freeze a gem is to add -i gems as a command line option to specify the location for the installed gem. And then add the gem as a dependency in your init.rb.
Gem を凍結するのに最も簡単な方法は、-i gems をコマンドラインオプションとして付けて、インストール済みの gem の場所を指定することです。それから init.rb の中で gem を依存物として追加します。
gem install aquarium -i gems
When running this command from the root of your merb application, it will install the gem inside the gem directory
Merb アプリケーションのルートディレクトリでこのコマンドを実行すると、gem ディレクトリの中に gem がインストールされます。〔訳注: "gems" ディレクトリの間違い?〕
If you want to freeze the version of the gem that you have installed which is from trunk, you'll need to find where your gems are located and pass that parameter to the gem install command.
〔訳注: Git リポジトリの〕trunk からインストールした gem のバージョンを凍結したい場合、自分の gems がどこにあるのかを調べ、それを gem install コマンドのパラメータに渡してやります。
gem environment gemdir
As I have installed Ruby via port my gem folder is located at /opt/local/lib/ruby/gems/1.8.
To freeze the aquarium gem I have from trunk I would need to run:
筆者の場合、port〔訳注: MacPorts のこと〕で Ruby をインストールしたので、gem フォルダは /opt/local/lib/ruby/gems/1.8 になっています。
たとえば trunk から持ってきた aquarium gem を凍結したいときは、次を実行する必要があるでしょう:
gem install /opt/local/lib/ruby/gems/1.8/cache/aquarium-0.4.1/ -i gems
If you want to freeze merb itself you need to add this to your init.rb, then run the following:
Merb それ自体を凍結したい場合は、次〔訳注: "require 'merb-freezer'" のこと〕を init.rb に追加する必要があります。
またそのあと、次〔訳注: の rake コマンド〕を実行してください。
require 'merb-freezer'
rake freeze:core
rake freeze:more
rake freeze:plugins
Once the merb gem is frozen, you can run merb with frozen-merb. If you want to update your frozen gem version, pass the update parameter to the rake task:
いったん merb gem が凍結されたあとは、frozen-merb コマンドで merb を実行します。
凍結した gem のバージョンを更新したいときは、更新のためのパラメータを rake タスクに渡してやります:
rake freeze:core UPDATE=true
(TODO) - DM / AR diffs
http://www.gweezlebur.com/2008/2/1/so-you-want-to-contribute-to-merb-core-part-1
(TODO) - example patch
(TODO) - where to send diffs
(TODO) - doc convention
(TODO) - write specs
(TODO) - Hacking Merb
##
# Build the framework paths.
#
# By default, the following paths will be used:
# application:: Merb.root/app/controller/application.rb
# config:: Merb.root/config
# lib:: Merb.root/lib
# log:: Merb.root/log
# view:: Merb.root/app/views
# model:: Merb.root/app/models
# controller:: Merb.root/app/controllers
# helper:: Merb.root/app/helpers
# mailer:: Merb.root/app/mailers
# part:: Merb.root/app/parts
#
# To override the default, set Merb::Config[:framework] in your initialization file.
# Merb::Config[:framework] takes a Hash whose key is the name of the path, and whose
# values can be passed into Merb.push_path (see Merb.push_path for full details).
#
# ==== Note
# All paths will default to Merb.root, so you can get a flat-file structure by doing
# Merb::Config[:framework] = {}
#
# ==== Example
# {{[
# Merb::Config[:framework] = {
# :view => Merb.root / "views"
# :model => Merb.root / "models"
# :lib => Merb.root / "lib"
# }
# ]}}
#
# That will set up a flat directory structure with the config files and controller files
# under Merb.root, but with models, views, and lib with their own folders off of Merb.root.
class Merb::BootLoader::BuildFramework < Merb::BootLoader
class << self
def run
build_framework
end
# This method should be overridden in merb_init.rb before Merb.start to set up a different
# framework structure
# DOC: Yehuda Katz FAILED
def build_framework
unless Merb::Config[:framework]
%w[view model controller helper mailer part].each do |component|
Merb.push_path(component.to_sym, Merb.root_path("app/#{component}s"))
end
Merb.push_path(:application, Merb.root_path("app/controllers/application.rb"))
Merb.push_path(:config, Merb.root_path("config"), nil)
Merb.push_path(:environments, Merb.dir_for(:config) / "environments", nil)
Merb.push_path(:lib, Merb.root_path("lib"), nil)
Merb.push_path(:log, Merb.log_path, nil)
Merb.push_path(:public, Merb.root_path("public"), nil)
Merb.push_path(:stylesheet, Merb.dir_for(:public) / "stylesheets", nil)
Merb.push_path(:javascript, Merb.dir_for(:public) / "javascripts", nil)
Merb.push_path(:image, Merb.dir_for(:public) / "images", nil)
else
Merb::Config[:framework].each do |name, path|
Merb.push_path(name, Merb.root_path(path.first), path[1])
end
end
end
end
end
"Yes, Rails scales just like everything else scale." - Ezra Zygmuntowicz
The most satisfying experience of building a web application is having others use it. Implementing a robust deployment plan is essential to ensure each release of your project goes off with out a hitch.
Version control is an essential piece of any software development cycle. There are several options available, including CVS, Git, Subversion, and many more. This guide assumes you have either a Subversion or Git repo that holds your application.
A proxy is required to handle incoming HTTP requests. Here there are several options including Apache, Lighttpd, Swiftiply, and many more. A favorite in the community for performance and simplicity has become Nginx, which we'll use for this example. Developed in Russia by Igor Sysoev, the goal of Nginx is to provide a lightweight, high performance web server.
Merb deployment's base lies with Capistrano. Originally developed to ease the process of pushing Rails applications into production, it has been improved upon to more generally provide automating tasks via SSH on remove servers. It can be used for software installation, application deployment, configuration management, and much more.
For our merb instances, there are a variety of Ruby web servers. The de facto standard has been Mongrel, which has also spawned improved stacks such as Thin and Swiftiply. It is extremely easy to change, so examples for all 3 will be provided.
The stable version of Nginx at the time of writing is 0.5.35 with the latest development of 0.6.29. There is also a branch that supports a newer balancer for handing out requests. For this example we'll compile the latest development version.
./configure --prefix=/usr/local --with-http_ssl_module
Create a configuration file, this example has been maintained by Ezra and is availble at http://brainspl.at/nginx.conf.txt
# user and group to run as
user ez ez;
# number of nginx workers
worker_processes 6;
# pid of nginx master process
pid /var/run/nginx.pid;
# Number of worker connections. 1024 is a good default
events {
worker_connections 1024;
}
# start the http module where we config http access.
http {
# pull in mime-types. You can break out your config
# into as many include's as you want to make it cleaner
include /etc/nginx/mime.types;
# set a default type for the rare situation that
# nothing matches from the mimie-type include
default_type application/octet-stream;
# configure log format
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# main access log
access_log /var/log/nginx_access.log main;
# main error log
error_log /var/log/nginx_error.log debug;
# no sendfile on OSX
sendfile on;
# These are good default values.
tcp_nopush on;
tcp_nodelay off;
# output compression saves bandwidth
gzip on;
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_proxied any;
gzip_types text/plain text/html text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript;
# this is where you define your mongrel clusters.
# you need one of these blocks for each cluster
# and each one needs its own name to refer to it later.
upstream mongrel {
server 127.0.0.1:4000;
server 127.0.0.1:4001;
server 127.0.0.1:4002;
}
# the server directive is nginx's virtual host directive.
server {
# port to listen on. Can also be set to an IP:PORT
listen 80;
# Set the max size for file uploads to 50Mb
client_max_body_size 50M;
# sets the domain[s] that this vhost server requests for
# server_name www.[engineyard].com [engineyard].com;
# doc root
root /data/ez/current/public;
# vhost specific access log
access_log /var/log/nginx.vhost.access.log main;
# this rewrites all the requests to the maintenance.html
# page if it exists in the doc root. This is for capistrano's
# disable web task
if (-f $document_root/system/maintenance.html) {
rewrite ^(.*)$ /system/maintenance.html last;
break;
}
location / {
# needed to forward user's IP address to rails
proxy_set_header X-Real-IP $remote_addr;
# needed for HTTPS
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect false;
proxy_max_temp_file_size 0;
# If the file exists as a static file serve it directly without
# running all the other rewite tests on it
if (-f $request_filename) {
break;
}
# check for index.html for directory index
# if its there on the filesystem then rewite
# the url to add /index.html to the end of it
# and then break to send it to the next config rules.
if (-f $request_filename/index.html) {
rewrite (.*) $1/index.html break;
}
# this is the meat of the rails page caching config
# it adds .html to the end of the url and then checks
# the filesystem for that file. If it exists, then we
# rewite the url to have explicit .html on the end
# and then send it on its way to the next config rule.
# if there is no file on the fs then it sets all the
# necessary headers and proxies to our upstream mongrels
if (-f $request_filename.html) {
rewrite (.*) $1.html break;
}
if (!-f $request_filename) {
proxy_pass http://mongrel;
break;
}
}
error_page 500 502 503 504 /500.html;
location = /500.html {
root /data/ez/current/public;
}
}
# This server is setup for ssl. Uncomment if
# you are using ssl as well as port 80.
server {
# port to listen on. Can also be set to an IP:PORT
listen 443;
# Set the max size for file uploads to 50Mb
client_max_body_size 50M;
# sets the domain[s] that this vhost server requests for
# server_name www.[engineyard].com [engineyard].com;
# doc root
root /data/ez/current/public;
# vhost specific access log
access_log /var/log/nginx.vhost.access.log main;
# this rewrites all the requests to the maintenance.html
# page if it exists in the doc root. This is for capistrano's
# disable web task
if (-f $document_root/system/maintenance.html) {
rewrite ^(.*)$ /system/maintenance.html last;
break;
}
location / {
# needed to forward user's IP address to rails
proxy_set_header X-Real-IP $remote_addr;
# needed for HTTPS
proxy_set_header X_FORWARDED_PROTO https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect false;
proxy_max_temp_file_size 0;
# If the file exists as a static file serve it directly without
# running all the other rewite tests on it
if (-f $request_filename) {
break;
}
# check for index.html for directory index
# if its there on the filesystem then rewite
# the url to add /index.html to the end of it
# and then break to send it to the next config rules.
if (-f $request_filename/index.html) {
rewrite (.*) $1/index.html break;
}
# this is the meat of the rails page caching config
# it adds .html to the end of the url and then checks
# the filesystem for that file. If it exists, then we
# rewite the url to have explicit .html on the end
# and then send it on its way to the next config rule.
# if there is no file on the fs then it sets all the
# necessary headers and proxies to our upstream mongrels
if (-f $request_filename.html) {
rewrite (.*) $1.html break;
}
if (!-f $request_filename) {
proxy_pass http://mongrel;
break;
}
}
error_page 500 502 503 504 /500.html;
location = /500.html {
root /data/ez/current/public;
}
}
}
Install Capistrano.
gem install capistrano
Navigate to your Merb repository directory and run the capify command to create the skeleton for your deployment recipe.
$ capify .
[add] writing `./Capfile'
[add] writing `./config/deploy.rb'
[done] capified!
Tailor your deploy.rb to meet the requirements of your application
set :application, "YOUR_APPLICATION_NAME"
# Set the path to your version control system (Subversion assumed)
set :repository, "http://something.com/svn/yourapplication/trunk"
# Set your SVN and SSH User
set :user, "your_ssh_user"
set :svn_user, "your_svn_user"
#Set the full path to your application on the server
set :deploy_to, "/PATH/TO/YOUR/#{application}"
#Define your servers
role :app, "your.appserver.com"
role :web, "your.webserver.com"
role :db, "your.databaseserver.com", :primary => true
desc "Link in the production extras and Migrate the Database ;)"
task :after_update_code do
run "ln -nfs #{shared_path}/config/database.yml #{release_path}/config/database.yml"
run "ln -nfs #{shared_path}/config/merb.yml #{release_path}/config/merb.yml"
run "ln -nfs #{shared_path}/log #{release_path}/log"
#if you use ActiveRecord, migrate the DB
#deploy.migrate
end
desc "Merb it up with"
deploy.task :restart do
run "cd #{current_path};./script/stop_merb"
run "cd #{current_path};env EVENT=1 merb -c 3"
# If you want to run standard mongrel use this:
# run "cd #{current_path};merb -c 4"
end
#Overwrite the default deploy.migrate as it calls:
#rake RAILS_ENV=production db:migrate
#desc "MIGRATE THE DB! ActiveRecord with Merb Love"
#deploy.task :migrate do
# run "cd #{release_path}; rake db:migrate MERB_ENV=production"
#end
Use Capistrano to initiate the environment, setting up the necessary directories on the server.
$ cap deploy:setup
Next, install the Gems you need on the Production server.
#Ensure you have the latest version of the gem system
sudo gem update --system
sudo gem install merb
sudo gem install rspec
# For ActiveRecord
sudo gem install merb_activerecord
# For DataMapper
sudo gem install datamapper
sudo gem install do_mysql
# For Evented Mongrel
sudo gem install swiftiply
# For Standard Mongrel
sudo gem install mongrel
# For Thin
sudo gem install thin
Create the directories and files that will be linked in.
mkdir /YOURDEPLOYPATH/shared/config
touch /YOURDEPLOYPATH/shared/config/database.yml
touch /YOURDEPLOYPATH/shared/config/merb.yml
Edit the .yml files to your liking and then be sure to create your database in MySQL.
Deploy your app:
cap deploy
You should now have your application deployed to your server with 3 Mongrel instances running being proxied to by Nginx.
So you've come this far and are feeling pretty confident about your Merb abilities. This chapter is all about sharing some top tips and code examples that you may find to be time savers in real life situations.
Most of the examples here are taken from real world projects or blog posts. We are always looking for contributions so if you have something to share let us know.
Sometimes you will need to load up a Merb environment (and it's frozen gems) for things such as RSpec stories or cron tasks. This little snippet does just that.
env = ENV['MERB_ENV'] || 'test'
require 'rubygems'
Gem.clear_paths
Gem.path.unshift(File.join(File.dirname(__FILE__), "gems"))
require 'merb-core'
Merb.load_dependencies(:environment => env)
require 'spec'
Merb.start_environment(:testing => true, :adapter => 'runner', :environment => env)
Here we have assumed that the script is running from the root of your app. In practice though you will most likely want it in a different location so be sure to adjust the Gem.path accordingly.
This recipe was contributed by Michael Klishin
One thing Merb community gets right is gems bundling. config/init.rb in Merb apps has the following magic line that shows idea of independency of the application from environment it runs in is baked into the core:
Gem.path.unshift(Merb.root / "gems")
Yay, no need to reinvent Gem plugin. It is that simple. A note here: to actually get up and running with Merb and Edge ActiveSupport and ActiveRecord bundled under /gems directory you have to specify installation directory with -i option:
sudo gem install -i ~/dev/workspace/some-merb-application/gems
This sets up a directory structure RubyGems' custom require expects to see.
Another thing Merb community gets right is freezing of Merb itself. I want to run this app on Edge Merb, I want to be independent from what Merb gems are on the box I deploy to. Rails does it by exporting a tarball since it moved to Git so you absolutely cannot track the tree you currently use.
In Merb there is a nice plugin merb-freezer. What it does is using either gems unpack strategy or Git submodules strategy if you use Git for your Merb application. This is very cool. Git submodules is like Subversion's externals but adapted to distributed nature of Git and packed with features Subversion lacks.
With git modules freezing you can track what commit hash app is frozen to, what recent log messages say, update it one by one or all at once, use the branch you want from repository as a submodule, see meaningful submodules state summary. Compare this to tarballs management.
To use merb freezer all you have to do is to install merb-freezer from merb-more repo and include a line
require 'merb-freezer'
into your config/init.rb. Then run
rake freeze:core
if you want to use Git submodules or
MODE=gems rake freeze:core
if you want to go with installed gems.
freeze:more and freeze:plugins do freezes of merb-more and merb-plugins, respectively.
If you choose submodules, make sure you start with a clean branch. Submodules meta information file (.gitmodules) and frameworks directory where Merb is frozen to have to be commited after run of Rake task that does the freeze.
To update Merb use the same Rake task with UPDATE env variable set to true. To see what commits application is frozen to, use
git submodule status
To see N recent commits in Merb core installed as a submodule use
git submodule summary -n <N> frameworks/merb-core
In your usual merb application, most of your assets (images, stylesheets etc.) can cheerfully sit in the public folder of the application, ready to be served up quickly and efficiently by nginx (or whatever webserver you're using) without having to trouble mongrel or thin or whatever is running the merb part of the application. There's no need for merb to see the request, because they're public files, ready to be given to everybody.
Right?
Most of the time, that's fine, but sometimes, you want to protect some premium content. Perhaps premium users get more themes, or perhaps you have to register before you can see some images or download pdfs. Now requests like those are going to have to hit merb, so that you can check the user has sufficient privileges to do so. These might be as simple as 'is a user' or as complex as you can imagine them to be.
Just use send_file. I'm assuming in this example there are some pdf reports,
which only appropriately authenticated users can access, which is what the
before filter does. All the important stuff happens in the download_report
action, so I'm ignoring the rest of the controller. I'm also assuming the
appropriate MIME type for pdfs has been set up.
class Reports < Application
before :check_authentication, :only => :download_report
def download_report
only_provides :pdf
@report = Report.first(:name => params[:name])
send_file(@report.file_path, :type => 'application/pdf')
end
end
Assuming that full_path returns the full path to the report file, perhaps
located in the private/reports subdirectory of the merb application (NOT in
the public folder) then the file will be sent to the client with the appropriate
headers and they'll download it. Since you're probably using mongrel or thin,
this won't actually tie the whole application up, it will be run in a separate
thread or EventMachine connection. Everything's good, right?
Well, not entirely. First, there's a small delay whilst the file is read into memory. Second, there's the fact that the file is read into memory and has to remain there whilst it's sent. Not so much of an issue for a 50kb image, but when you start to get a couple of dozen users wanting to download a 10 or 20mb pdf report, all that memory usage starts to become quite noticeable.
This is where nginx comes in. Nginx offers a facility which it calls X-Accel-Redirect, which works quite simply. When appropriate headers are set by the application, nginx will read files outside the normal web root, and send them to the user.
There are two parts to this. The first is a section in the nginx config file.
location /private/ {
internal; # only the server can make requests here, a client will get a 404
alias /path/to/merb/root/private/; # note trailing slash
}
This snippet goes inside the nginx server block. internal is the directive
which gives us our protection, since a direct request to the url will just give
a 404 error, not serve the file.
For the second part, we re-write our action from earlier:
def download_report
only_provides :pdf
@report = Report.first(:name => params[:name])
headers['Content-Type'] = ''
nginx_send_file(@report.file_path)
end
We have to (at the time of writing) set the content type to blank, to let nginx
set it appropriately, although we could also set it accurately ourselves. All
that's left is to make sure the file path is correct. This path should begin
with /private/ and should be the path of the file in the private directory.
Something like /private/reports/secret_report.pdf. The nginx_send_file method
takes care of setting the X-Accel-Redirect header appropriately.
Assuming the file exists, nginx will send it to the client, without merb needing to load it into memory and with nginx's trademark speed and efficiency.
(TODO) - Go over some cool merb plugins