« Catalyst+Jifty::DBIの組み合わせを試してみる | メイン | Flickrとのマッシュアップ! (PopBoxとdhtmlxGrid) その2 »

Rails で楽々ソーシャルブックマークの仕組みを作るはてなブックマークに追加 livedoorクリップに追加 Yahoo!ブックマークに追加 del.icio.usに追加 イザ!ブックマーク ニフティクリップに追加

こんにちは。Ruby(とRails)を担当している石原です。

前回に引き続き、ソーシャル「OSを入れた後にインストールする10のアプリケーション」(仮)を作っていきます。以降、サイトの名前は 10 Best Application on New Install から取って 10best とします。

これまでのエントリーはこちら↓

  1. つくるぶガイドブログ: ひとりサービスの雛型をつくる(リキッドレイアウト、GetText、Acts as Authenticated)
  2. つくるぶガイドブログ: Ruby on Rails を使ってひとりでサービスを作ってみよう

10best では、アプリケーションをダウンロードできる場所のURLを登録して、みんなで共有していきます。いわばソーシャルブックマークの、ブックマーク先をアプリケーションのダウンロードページに限定したものです。

今回は以下のような流れで基礎となるソーシャルブックマークの仕組みを作っていきます。

  1. モデルの作成
  2. フォームのエラー処理
  3. ブックマーク作成の処理を整える
  4. データが空のときの画面デザイン

モデルの作成

まずはモデルの作成です。

ソーシャルブックマークでは、最初に誰かがページをブックマークして、別の人が同じページをブックマークすることが考えられます。タイトルなどのページ固有の情報は、そのページを登録したブックマークすべてで共有したいので、ページとブックマークを別々のモデルとし、ページには複数のブックマークがひもづくという構成にしたいと思います。

10best ではページにあたるものがソフトウェアなので、Software と Bookmark という2つのモデルを作成します。

コマンドラインより、

script/generate model Software
script/generate model Bookmark

を実行すると、モデルのクラスファイルや Migration ファイルが作成されます。

Migration ファイルは以下の通り編集します。

== db/migrate/004_create_softwares.rb ==

class CreateSoftwares < ActiveRecord::Migration
  def self.up
    create_table :softwares do |t|
      t.column :title,                    :string
      t.column :url,                      :string
      t.column :created_at,               :datetime
      t.column :updated_at,               :datetime
    end
  end

  def self.down
    drop_table :softwares
  end
end
== db/migrate/005_create_bookmarks.rb ==

class CreateBookmarks < ActiveRecord::Migration
  def self.up
    create_table :bookmarks do |t|
      t.column :software_id, :integer, :null => false
      t.column :user_id, :integer, :null => false
      t.column :created_at, :datetime
      t.column :updated_at, :datetime
    end
  end

  def self.down
    drop_table :bookmarks
  end
end
rake db:migrate

を実行すれば、DB 上に softwares と bookmarks のテーブルが用意されます。

Sotware と Bookmark は 1対多 の関係、User と Bookmark も 1対多 の関係(1人のユーザーが複数のブックマークを持てる)なので、モデルのクラスファイルを以下のように編集します。

== app/models/software.rb ==

class Software < ActiveRecord::Base
  has_many :bookmarks
end
== app/models/bookmark.rb ==

class Bookmark < ActiveRecord::Base
  belongs_to :user
  belongs_to :software
end

Rails のモデルを考えるとき、複数形と単数形がごっちゃになってわかりにくくなることがあります。そういうときは、英語脳で考えるようにして、対象となるものが1個しかないものなのか複数あるものなのかを具体的に考えるようにすると理解しやすいと思います。

has_many の場合は many とあることから次にくるのは複数形とわかりますし、script/generate model のあとに来るモデル名は、モデルは抽象的な名前でモノではないですから、単数形である、といったように考えます。

コードの中の変数名に対しても、それが配列で複数のモノを指すなら複数形を、1個のモノしか入らないのなら単数形を使うようにして、常に複数か単数かを考える癖をつけておくといいと思います。

フォームのエラー処理

エラー処理を書くのは好きですか?

あまり好きだという人を聞いたことがありませんが、フォームのエラー処理が楽に実装できる、というのが Rails の醍醐味の一つではないでしょうか。

まず、フォーム処理の雛型を Scaffold 機能で作ってしまいます。

script/generate scaffold Software

を実行すると、Software を表示、作成、編集、削除するひととおりの画面と処理が出来上がります。たとえば作成画面は以下の通り。

new_software.png

次にエラー処理を追加します。

登録するソフトウェアのページのタイトルと URL は必ず入力するようにし、タイトルは最大100文字、URLは最大 200 文字としておきます。また、URL は重複を許さない、といった制限を Rails で書くと、以下のようになります。

== app/models/software.rb ==

class Software < ActiveRecord::Base
  has_many :bookmarks
  validates_presence_of :title
  validates_length_of :title, :maximum => 100
  validates_presence_of :url
  validates_length_of :url, :maximum => 200
  validates_uniqueness_of :url  
end

以上のコードだけで、この制限にひっかかる値がフォームから入力されたとき、以下のようなエラーメッセージが自動的に表示されるまで実装できてしまいます。

error.png

ブックマーク作成の処理を整える

ソフトウェアのページを登録すると同時に、ブックマークを作成します。すでにそのページが存在している、つまり同じ URL を持つページが他にある場合には、そのページにひもづいたブックマークが作成されるような処理を書きます。

== app/controllers/softwares_controller.rb ==

  def create
    # 同じURLを持った別のページが存在するか?
    @software = Software.find_by_url(params[:software][:url])
    unless @software
      # 存在しなければ新たに作成
      @software = Software.new(params[:software])
    end
    bookmark = Bookmark.new
    if @software.save
      # ソフトウェアのページとブックマークをひもづけ、
      bookmark.software_id = @software.id
      # ログインしているユーザーのブックマークに追加
      current_user.bookmarks << bookmark     
      flash[:notice] = 'Software was successfully created.'
      redirect_to :action => 'list'
    else
      render :action => 'new'
    end
  end

ユーザーのブックマークの一覧ページが必要なので、Bookmark モデルに関連した一覧ページなども Scaffold で作成しておきます。

script/generate scaffold Bookmark

ブックマーク一覧ではログインしたユーザーのブックマークだけが表示されるよう、コントローラーの list アクションは以下のように書きます。

== app/controllers/bookmarks_controller.rb ==

  def list
    @bookmark_pages, @bookmarks = paginate(
      :bookmarks,
      :conditions => ['user_id = ?', current_user.id],
      :order => 'created_at DESC',
      :per_page => 10
    )
  end

/bookmarks/list からブックマークを作成するとき、実際に呼び出すのは、Software モデルの作成画面、つまり /softwares/create になるようビューを変更します。

== app/views/bookmarks/list.rhtml ==

<%= link_to 'New bookmark', :action => 'new' %>

を

<%= link_to 'New software', :controller => 'softwares', :action => 'new' %>

に変更する

そのほか、レイアウトを整えたり、/softwares/create のあと、ブックマーク一覧ページである /bookmarks/list にリダイレクトされるようにしたり、ログイン直後も同様に /bookmarks/list にリダイレクトされるよう、細かな修正をおこないます。

エントリーの一番最後にソースコードへのリンクを掲載しておきますので、実際にコードを読んで確認していただければと思います。

データが空のときの画面デザイン

初回で紹介した Getting RealThe Blank Slate というセクションでは、「データが空の場合の画面デザインを忘れるな」と説いています。

開発環境でテストを続けていると、テストデータをどんどん入力するので、データが実際に入っている状態での画面をいつも見ているわけです。しかしユーザーが最初に見る画面というのは、何もデータが入っていない画面、つまり 10best で言えば、ブックマークが全くない状態の一覧画面になります。

それは次のような画面になります。

top_page.jpg

データが何もない一覧画面というものは味気ないものです。

データが何も入っていない場合には、例えば、サイトのチュートリアルやスクリーンキャストなど、ユーザーにこれから使ってもらうための工夫を施すべきだ、というのが Getting Real の主張です。

試しに、ユーザーにすぐにブックマークを促すメッセージを表示し、文字サイズを大きくして強調してみました。

top_page_blank.jpg

まとめ

モデルを作成し、Scaffold で簡単ですがコントローラーやビューを用意し、エラー処理も整えました。

ここまでのソースコードをアップロードしておきました。

» 10best - Google Code

svn checkout http://10best.googlecode.com/svn/trunk/ 10best

でチェックアウトするか、ブラウザでもリポジトリを閲覧することができます。