2007-02-20
Railsでソーシャルブックマークを作ってみようか(第2回)
説明
Railsアプリを作る「はじめの一歩」としての足がかりになればと思いまとめました。
手順に沿ってコピペしていくといつのまにかアプリケーションが完成するというサンプルです。
第1回のmasuidriveさんベースにRails勉強会@東京第11回での高橋征義さんバージョンとInternet Week 2006でのかずひこさんバージョンをミックスしました。
環境やインストール、趣旨や概要につきましては第1回をご覧ください。
■第1回との相違点
- Internet Week 2006のT24 : はじめよう Ruby on Rails 〜フレームワークで作るWebアプリケーション〜をベースに内容を変更しました。
- 基本的な流れは変わっていませんが、機能/モデルが変更されています。
- 文字コードの設定を先に行うようにしました。
- モデルの定義を先に明示しました。
- モデルの作成にマイグレートを使用するようにしました。
- デザインを『はてブ』に近いものにしました。
- ユーザ認証をLoginGeneratorからacts_as_authenticatedに変更しました。
■今回実装を見送ったもの
今回使用したRailsのバージョン
rails -v
Rails 1.2.2
■バージョンを更新する場合
次のコマンドで最新バージョンに更新します。
gem update rails
※バージョンにはお気を付け下さい。私はバージョンが古いという原因で動作しないサンプルと何日も格闘してしまいました。。。
はてなブックマークっぽいソーシャルブックマークを作ってみよう
■はてなブックマークっぽいソーシャルブックマークってどんなの?
こんな感じです。
ここまではムリですが、それっぽいものを作ります。
■モデリング
ソーシャルブックマークとして最低限持つべき情報は次のようになります
- ○○さんが
- ○○というページに
- ○○というコメントでブックマークする
これをUser,Page,Bookmarkというモデルに割り当てます
※各モデルにはPrimary Keyとして'id'が存在します
■プロジェクトを始める
rails bookmark
自動で生成されるディレクトリは次のようになります(主なものを抜粋)
■文字コードの設定
config/database.ymlを編集(データベースの文字コード)
development: adapter: mysql database: bookmark_development username: root password: **** host: localhost encoding: utf8
config/enviroment.rbを編集(rubyの文字コード)
$KCODE = 'u'
※先頭に追加
app/controllers/application.rbを編集(Webサーバのcharset)
before_filter :set_charset protected def set_charset @headers["Content-Type"] = "text/html; charset=utf-8" end
■データベースの作成
create database bookmark_development default character set utf8;
※MySQL Command Line Clientを使用すると楽です
モデル作成
■pageモデルの作成
db/migrate/001_create_pages.rbを編集
def self.up create_table :pages do |t| t.column :uri, :string, :limit=>1024 t.column :title, :string, :limit=>1024 end end
■acts_as_authenticatedプラグインをインストール
ruby script/plugin install http://svn.techno-weenie.net/projects/plugins/acts_as_authenticated
■userモデル/accountコントローラの作成
■bookmarkモデルの作成
db/migrate/003_create_bookmarks.rbを編集
def self.up create_table :bookmarks do |t| t.column :user_id, :integer, :null => false t.column :page_id, :integer, :null => false t.column :comment, :string, :limit =>1024 t.column :created_at, :datetime end end
■データベースに反映
rake migrate
■モデル間のリレーションを定義
app/models/page.rbを編集(Page<1>----<多>Bookmark<多>----<1>User)
class Page < ActiveRecord::Base has_many :users, :through => :bookmarks has_many :bookmarks, :order => "created_at desc"
app/models/user.rbを編集(User<1>----<多>Bookmark<多>----<1>Page)
class User < ActiveRecord::Base has_many :pages, :through => :bookmarks has_many :bookmarks, :order => "created_at desc"
app/models/bookmark.rbを編集(UserとPageを参照, 同一user_id中のpage_idはユニーク)
class Bookmark < ActiveRecord::Base belongs_to :user belongs_to :page validates_uniqueness_of :page_id, :scope => :user_id
モデル間のリレーションは次のように参照する
■バリデーションを定義
app/models/page.rbを編集
private def validate begin parsed_uri = URI.parse(uri) raise unless parsed_uri.host raise unless %w(http https).include?(parsed_uri.scheme) rescue errors.add(:uri, "invalid URI") end end
■アクセサのオーバライドを定義(titleカラムが空の場合にURIを返す)
app/models/page.rbを編集
def title self[:title].blank? ? self[:uri] : self[:title] end
※self.titleを使用すると無限ループするので注意!
コントローラ作成
■pageコントローラの作成
app/controllers/page_controller.rbを編集(ページのブックマーク一覧の表示)
class PageController < ApplicationController include AuthenticatedSystem before_filter :login_required def show @myuser = current_user @page = Page.find_by_uri(params[:uri]) end end
■userコントローラの作成
app/controllers/user_controller.rbを編集(ユーザのブックマーク一覧の表示)
class UserController < ApplicationController include AuthenticatedSystem before_filter :login_required def show @myuser = current_user @user = params[:id].nil? ? @myuser : User.find(params[:id]) end end
■bookmarkコントローラの作成
app/controllers/bookmark_controller.rbを編集(ブックマークの追加)
class BookmarkController < ApplicationController include AuthenticatedSystem before_filter :login_required def add @myuser = current_user @page = Page.find_by_uri(params[:uri]) || Page.new(:uri => params[:uri]) @page.title = params[:title] @bookmark = Bookmark.new @bookmark.user = @myuser @bookmark.page = @page @bookmark.comment = params[:comment] if request.post? && (@bookmark.save! rescue false) redirect_to :controller => "user", :action => "show", :id => @myuser else render(:action => "add") end end end
ビュー作成
■pageビューを編集
app/views/page/show.rhtmlを作成
<h1><%= link_to(h(@page.title), h(@page.uri)) %></h1> <ul> <% for bookmark in @page.bookmarks -%> <li> <%= bookmark.created_at.strftime("%Y年%m月%d日") %> <%= link_to(h(bookmark.user.login), :controller => "user", :action => "show", :id => bookmark.user) %> <%= h(bookmark.comment) %> </li> <% end -%> </ul>
■userビューを編集
app/views/user/show.rhtmlを作成
<h1><%= h(@user.login) %>さんのブックマーク</h1> <ul> <% for bookmark in @user.bookmarks.find(:all, :include => [:page]) -%> <li> <%= bookmark.created_at.strftime("%Y年%m月%d日") %> <%= link_to(h(bookmark.page.title), :controller => "page", :action => "show", :uri => bookmark.page.uri) %> <%= h(bookmark.comment) %> </li> <% end -%> </ul>
■bookmarkビューを編集
app/views/bookmark/add.rhtmlを作成
<h1>ブックマークの追加</h1> <% if request.post? -%> <%= error_messages_for "page" %> <%= error_messages_for "bookmark" %> <% end -%> <%= form_tag %> <dl> <dt>URI</dt> <dd><%= text_field_tag "uri", @page.uri, :size => 40 %></dd> <dt>タイトル</dt> <dd><%= text_field_tag "title", @page.title, :size => 40 %></dd> <dt>コメント</dt> <dd><%= text_field_tag "comment", @bookmark.comment, :size => 40 %></dd> </dl> <p><%= submit_tag %></p> <%= end_form_tag %>
動かしてみよう
■WEBrickの起動
■サインアップ
■ログイン
■ログアウト
■ブックマークの追加
■ユーザのブックマーク一覧の表示
または、ページのブックマーク一覧からユーザをクリックすると表示します。
■ページのブックマーク一覧の表示
ユーザのブックマーク一覧からページをクリックすると表示します。
完成!
おつかれさまでした。
はてなブックマークっぽい見た目にする
さて、機能は一通り完成しましたが見た目が寂しいですね。
そこではてなブックマークのデザインを拝借してそれっぽくしてみましょう。
■pageビューを編集
public/stylesheets/page.cssを作成
body { background:#FFFFFF none repeat scroll 0%; color:#000000; margin:0px; padding:0px; } #bannersub { background:#5279E7 none repeat scroll 0%; border-bottom:1px solid #06289B; color:#C9D5F8; } #bannersub table { width:100%; } #bannersub td { color:#C9D5F8; font-size:80%; text-align:center; } #bannersub td a { color:#C9D5F8; text-decoration:none; } #bannersub td a.username { text-decoration:underline; } #container { margin-left:10px; margin-right:10px; margin-top:20px; } #body, div.body { margin-left:4%; margin-right:4%; z-index:2; } h2 { font-size:100%; margin-bottom:8px; padding-bottom:5px; padding-left:25px; } h2.entry-title { background:#5279E7; color:#FFFFFF; margin:15px 0pt 0pt; padding:0px; } h2.entry-title span { display:block; padding:7px 5px 5px 9px; } .entry-curve-bottom { clear:both; display:block; font-size:1px; height:8px; padding-bottom:10px; } .caption { font-size:90%; } .bookmarklist ul { background-color:#EDF1FD; border-top:1px solid #5279E7; font-size:90%; line-height:150%; list-style-position:inside; list-style-type:circle; margin:0px; padding:5px; } .bookmarklist ul { font-size:90%; line-height:150%; list-style-position:inside; list-style-type:circle; } span.timestamp { font-size:90%; }
app/views/layouts/page.rhtmlを作成
<head> <title>Page: <%= controller.action_name %></title> <%= stylesheet_link_tag 'page' %> </head> <body> <p style="color: green"><%= flash[:notice] %></p> <%= yield %> </body>
app/views/page/show.rhtmlを編集
<div id="bannersub"> <table cellspacing="0"> <tbody> <tr> <td width="80%" nowrap="nowrap" style="text-align: left;"> ようこそ<%= link_to(h(@myuser.login), :controller => "user", :action => "show") %>さん </td> <td nowrap="nowrap"><%= link_to(h("ログアウト"), :controller => "account", :action => "logout") %></td> </tr> </tbody> </table> </div> <div id="container"> <div id="body"> <h2 class="entry-title"><span><%= link_to(h(@page.title), h(@page.uri)) %></span></h2> <span style="margin-bottom: 15px;" class="entry-curve-bottom"></span> <div class="bookmarklist"> <div class="caption"><a id="comments" name="comments">このエントリーをブックマークしているユーザー</a> (<span title="パブリックモードのブックマーク数" class="public-count"><%= @page.bookmarks.count %></span>) </div> <ul> <% for bookmark in @page.bookmarks -%> <li id="bookmark-user"> <span class="timestamp"><%= bookmark.created_at.strftime("%Y年%m月%d日") %></span> <%= link_to(h(bookmark.user.login), :controller => "user", :action => "show", :id => bookmark.user) %> <span class="comment"><%= h(bookmark.comment) %></span> </li> <% end -%> </ul> </div> </div> </div>
■userビューを編集
public/stylesheets/user.cssを作成
body { background:#FFFFFF none repeat scroll 0%; color:#000000; margin:0px; padding:0px; } #bannersub { background:#5279E7 none repeat scroll 0%; border-bottom:1px solid #06289B; color:#C9D5F8; } #bannersub table { width:100%; } #bannersub td { color:#C9D5F8; font-size:80%; text-align:center; } #bannersub td a { color:#C9D5F8; text-decoration:none; } #bannersub td a.username { text-decoration:underline; } div.body { margin:20px 5% 10px; padding-bottom:1em; } div.header { border-bottom:1px dashed #C9D5F8; margin-bottom:15px; margin-top:15px; padding-bottom:5px; } div.header h2 { display:inline; font-size:120%; margin-bottom:8px; padding-bottom:5px; padding-left:25px; } div.main { display:block; z-index:2; } dl.bookmarklist { display:block; line-height:1.2em; padding:0pt; } dl.bookmarklist dl, dl.bookmarklist dt, dl.bookmarklist dd { display:block; margin:0pt; padding:0pt; } dl.bookmarklist dt.bookmark { display:list-item; font-weight:normal; list-style-type:none; margin:1.2em 0pt 0pt; } dl.bookmarklist dd { margin:0pt 0pt 0pt 1em; } dl.bookmarklist dd.comment { color:green; font-size:80%; margin-top:3px; padding:0px; }
app/views/layouts/user.rhtmlを作成
<head> <title>Page: <%= controller.action_name %></title> <%= stylesheet_link_tag 'user' %> </head> <body> <p style="color: green"><%= flash[:notice] %></p> <%= yield %> </body>
app/views/user/show.rhtmlを編集
<div id="bannersub"> <table cellspacing="0"> <tbody> <tr> <td width="80%" nowrap="nowrap" style="text-align: left;"> ようこそ<%= link_to(h(@myuser.login), :controller => "user", :action => "show") %>さん </td> <td nowrap="nowrap"><%= link_to(h("ログアウト"), :controller => "account", :action => "logout") %></td> </tr> </tbody> </table> </div> <div class="body"> <div class="header"><h2><%= h(@user.login) %>さんのブックマーク</h2></div> <div class="main"> <% for bookmark in @user.bookmarks.find(:all, :include => [:page]) -%> <dl class="bookmarklist"> <dt class="bookmark"><%= link_to(h(bookmark.page.title), :controller => "page", :action => "show", :uri => bookmark.page.uri) %></dt> <dd class="comment"> <span class="comment"> <%= bookmark.created_at.strftime("%Y年%m月%d日") %> <%= h(bookmark.comment) %> </span> </dd> </dl> <% end -%> </div> </div>
■bookmarkビューを編集
public/stylesheets/bookmark.cssを作成
body { background:#FFFFFF none repeat scroll 0%; color:#000000; margin:0px; padding:0px; } #bannersub { background:#5279E7 none repeat scroll 0%; border-bottom:1px solid #06289B; color:#C9D5F8; } #bannersub table { width:100%; } #bannersub td { color:#C9D5F8; font-size:80%; text-align:center; } #bannersub td a { color:#C9D5F8; text-decoration:none; } #bannersub td a.username { text-decoration:underline; } #container { margin-left:10px; margin-right:10px; margin-top:20px; } #body { margin-left:0px; } #body, div.body { margin-left:4%; margin-right:4%; z-index:2; } h2 { font-size:100%; margin-bottom:8px; padding-bottom:5px; padding-left:25px; } h2.ftitle, h2.title { border-bottom:1px dashed #C9D5F8; font-size:120%; } div.info { margin-bottom:10px; margin-left:10px; } .info td { font-size:80%; } td.label { background:#F0F0FF none repeat scroll 0%; color:#000000; font-weight:bold; padding-left:5px; padding-right:5px; } .label { font-size:90%; } .note { font-size:80%; font-weight:normal; }
app/views/layouts/bookmark.rhtmlを作成
<head> <title>Page: <%= controller.action_name %></title> <%= stylesheet_link_tag 'bookmark' %> </head> <body> <p style="color: green"><%= flash[:notice] %></p> <%= yield %> </body>
app/views/bookmark/add.rhtmlを編集
<div id="bannersub"> <table cellspacing="0"> <tbody> <tr> <td width="80%" nowrap="nowrap" style="text-align: left;"> ようこそ<%= link_to(h(@myuser.login), :controller => "user", :action => "show") %>さん </td> <td nowrap="nowrap"><%= link_to(h("ログアウト"), :controller => "account", :action => "logout") %></td> </tr> </tbody> </table> </div> <% if request.post? -%> <%= error_messages_for "page" %> <%= error_messages_for "bookmark" %> <% end -%> <div id="container"> <div id="body"> <h2 class="title">ブックマークの登録</h2> <%= form_tag %> <div class="info"> <table><tbody> <tr> <td nowrap="" class="label">URI</td> <td class="addurl"><%= text_field_tag "uri", @page.uri, :size => 40 %></td> </tr> <tr> <td nowrap="" class="label">タイトル<br/><span class="note">(省略可)</span></td> <td> <%= text_field_tag "title", @page.title, :size => 40 %> <span id="comment_count"/> <div id="candidates_list" style="height: 40px;"/> </td> </tr> <tr> <td nowrap="" class="label">コメント<br/><span class="note">(省略可)</span></td> <td> <%= text_field_tag "comment", @bookmark.comment, :size => 40 %> <span id="comment_count"/> <div id="candidates_list" style="height: 40px;"/> </td> </tr> </tbody></table> </div> <p><%= submit_tag %></p> <%= end_form_tag %> </div> </div>
なんちゃってはてなブックマーク完成!
今度こそ本当におつかれさまでした。
追記
思いがけずたくさんのブックマークをいただき大変嬉しく思います。みなさまありがとうございます。
デモとして使いやすいようにプレーンHTMLを用意しました。(見た目のはてブ化は除いています)
↓のリンク先をローカルに保存してご利用下さい。
http://poohkid.googlepages.com/rails_sbs_02.htm
こんな方に役立つかと思います。
- ネットを参照しながらだと自分が作ったように振る舞いにくい
- Webブラウザからのコピペが大好きなので、テキストファイルで参照したくない
- デモに使いたいが、このサイトの雰囲気は会場のお堅い空気にそぐわない
- デモに使いたいが、会場でネットに接続できなくて困った(acts_as_authenticatedは予めインストールしてね、vendor下のディレクトリコピーで退避できるよ)
id:gomisさん
acts_as_taggable使ってタグ付けまで実装するのはしんどいでしょうか
acts_as_taggableについては全く認識しておりませんでした。
これから調べて可能なら実装するということでお許し下さいませ。
文書で見るより実際に作ってみた方が断然おもしろいので、ぜひチャレンジしてみて下さい!
チャレンジのきっかけが掴めない方は勉強会などに参加すると良いんじゃないかな♪
- http://d.hatena.ne.jp/HiroshiMaruyama/20070221
- http://d.hatena.ne.jp/garyo/20070222
- bookmark
- http://d.hatena.ne.jp/shun262/20070227
- http://d.hatena.ne.jp/kusigahama/20070228
- http://d.hatena.ne.jp/PoohKid/20070301
- http://d.hatena.ne.jp/emergent/20070310
- 只今Ruby勉強中 - 明日のために
- http://d.hatena.ne.jp/emergent/20070421
- http://d.hatena.ne.jp/mokehehe/20070501
- http://d.hatena.ne.jp/niraikanaibird/20070627
- http://d.hatena.ne.jp/niraikanaibird/20070630
- http://d.hatena.ne.jp/makochanz/20070706
- http://d.hatena.ne.jp/d4-1977/20070802
- http://d.hatena.ne.jp/d4-1977/20070809
- http://d.hatena.ne.jp/d4-1977/20070811
- http://d.hatena.ne.jp/d4-1977/20070818
- Ruby on Railsで携帯からファイルのアップロード(+送信者認証)
- opportunity_costの日記
- Webアプリケーション開発日記 - [開発]
- 坊やがゆく - 謹賀新年2008
- タイトル未定 - text_fieldとtext_field_tagの違い
- たばさの - [ruby]rails mysql
- INOHILOG - メモ
- rivertopの日記 - Rails2.0.2をはじめてみた
- 開発Memo - Railsを使ってみる
- 404 http://b.hatena.ne.jp/
- 354 http://www.hatena.ne.jp/
- 235 http://b.hatena.ne.jp/hotentry
- 233 http://secure.ddo.jp/~kaku/tdiary/
- 183 http://d.hatena.ne.jp/
- 123 http://reader.livedoor.com/reader/
- 81 http://b.hatena.ne.jp/add?mode=confirm&title=%u574A%u3084%u304C%u3086%u304F - Rails%u3067%u30BD%u30FC%u30B7%u30E3%u30EB%u30D6%u30C3%u30AF%u30DE%u30FC%u30AF%u3092%u4F5C%u3063%u3066%u307F%u3088%u3046%u304B%uFF08%u7B2C2%u56DE%uFF09&url=http://d.hatena.
- 45 http://www.google.co.jp/ig?hl=ja
- 41 http://www.google.com/reader/view/
- 40 http://b.hatena.ne.jp/entrylist?sort=hot