Yuichiro MASUI <[email protected]>
RailsチュートリアルToDoを作りながら学ぶ
ゴール
Railsが分かったような気になる
M,V,Cの繋がりを把握する
Railsでアプリを組んでみたくなる
Ruby on Railsって?
Rubyで書かれたフルスタックの
MVCフレームワーク
理念
その1
Conventionover
Configuration
設定より規約
デフォルトの動作が規約として多数盛り込まれてる
JavaのフレームワークはXMLで設定する物が多い
XMLの設定=コード
設定ではコード量は減らない
その2
DRY
Don't Repeat Yourself
同じ事は二度するな
DBのO/Rマッピングを行う時
DBのカラム名をコードに書くのはDRY違反
DB変更したらコードの変更もいる
バグの元
Railsならクラス作るだけ
class User < ActiveRecord::Baseend
テーブル定義はDBから参照する
ただ、リレーションだけは設定する必要がある
class User < ActiveRecord::Base has_many :booksend
これだけでusersテーブルのマッピング終わり
Conventionover
Configuration
設定より規約
ActiveRecordの規約
テーブル名の単数形のクラスでActiveRecord::Baseを継承するとO/Rマッパになる
だからSQLでテーブル定義するだけで設定はいらない
規約に沿わない場合は設定が必要
class User < ActiveRecord::Base set_table_name 'user' set_primary_key 'pkey'end
その3
言語重要
Ruby
歴史は長いがキラーアプリがなかった
DHHがRubyを選んだ理由
Rubyは美しいコードを書くことができる,プログラマをハッピーにする言語だと
感じたのです。
ITProインタービューにて
なぜ美しいと思うか
思考との乖離が少ない
頭で考えた物が作れる
言語の柔軟性が非常に高い
本当のオブジェクト指向、演算子のオーバーライド、オブジェクトへのメソッド追加...
RailsではRubyを拡張してDSLっぽく使っている
標準ライブラリなどにも手を入れまくり
Webに特化させてる故に書きやすい
思いついたのを簡単にコードにできたらプログラムは
楽しい
その4
すぐ動く
まず目の前に動く物があると作業がはかどる
動くまで時間がかかるとテンションが続かない
scaffold
足場
コントローラに1行書くだけでCRUD完成
Create - 作成Read - 表示Update - 更新Delete - 削除
DBにテーブル作って空のモデルクラスつくって
コントローラにscaffoldって書くだけで
DB更新アプリ完成
これが10分Railsムービーの内容
あとは足場を頼りに骨組みを作っていけばいい
scaffold以外に色々なジェネレータも出ている
これらを使えば管理画面とかは
コード書かなくても行ける
生成されたコードは勉強にも役に立つ
その5
豊富なプラグイン
なんでもある
画像アップならfile_columnタグはacts_as_taggable
などなど・・・
日本語化はGetText-Ruby
以上5つがRailsの生産性の高さを支えている
Railsのスローガン
that's optimized for programmer happiness
and sustainable
プログラマーの幸福と創造性の継続に最適化されたフレームワーク
よく使われるシステム構成
Rails 1.2.3 ← 最新版Ruby 1.8.6 ← 最新版MySQL 5もしくは4
PostgreSQLも使えるけどちょっと制約もある
WebサーバはRuby+Cで書かれた
Mongrel
apache+fastcgiとかlighttpd+fastcgiがあるけど、mongrelが
今のスタンダード
Railsのプロジェクトを始める
> rails todo> cd todo
なにもしないでとりあえずRails起動
> ruby script/server↓
http://localhost:3000/
http://localhost:3000/
エクスプローラでtodoを開いてみよう
publicの下がDocumentRoot
appがプログラムdbがデータベースの情報
configが設定scriptが各種スクリプト
Railsプロジェクトでまずやること
DBの設定
config/database.ymlがデータベースの設定
development: adapter: mysql database: todo_development username: root password: hostname: localhost
test: adapter: mysql database: todo_test username: root password: hostname: localhost
production: adapter: mysql database: todo_production username: root password: hostname: localhost
開発環境のDB設定
テスト環境のDB設定
製品環境のDB設定
Railsの3つの環境(実行モード)
開発時のdevelopment 詳細なログやエラーメッセージ自動テスト実行test UnitTestなどを実行実稼働のproduction パフォーマンス優先
development: ← Railsの環境 adapter: mysql ← 接続するDB database: todo_development ← DB名 username: root ← ユーザ名 password: ← パスワード hostname: localhost
初期値ではプロジェクト名_development
プロジェクト名_testプロジェクト名_production
の3つのDBが必要
ユーザもrootじゃヤバいので適当なものを作るtodo / todopass
ユーザもrootじゃヤバいので適当なものを作るtodo / todopass
ダメな例
development: ← 3つの環境とも adapter: mysql database: todo_development username: todo ← ユーザ名 password: todopass ← パスワード hostname: localhost encode: utf8 ← 追加
phpMyAdminでテーブルとユーザを作り
権限を与える
todo_developmentとtodo_testデータベースを作る
todoユーザも作りそれぞれにアクセス出来るように
権限付与する
セッションをDBに保存するようにする
PHPと同じようにセッションをDBやファイルに
保存する↓
初期値ではファイルなのでDBに変更する
各種設定はconfig/environment.rb
# 要求するRailsのバージョンRAILS_GEM_VERSION = '1.2.3' unless defined? RAILS_GEM_VERSION
# 起動ファイルrequire File.join(File.dirname(__FILE__), 'boot')
# Railsの初期設定Rails::Initializer.run do |config|
end
config/environment.rb
# 要求するRailsのバージョンRAILS_GEM_VERSION = '1.2.3' unless defined? RAILS_GEM_VERSION
# 起動ファイルrequire File.join(File.dirname(__FILE__), 'boot')
# Railsの初期設定Rails::Initializer.run do |config| config.action_controller.session_store = :active_record_store ← コメントを消すend
config/environment.rb
DBにセッション用のテーブルを作る
rakeコマンド
Rakefileなどに書いてあるレシピを実行させる
> rake -T> rake db:sessions:create
db/migrate/001_add_sessions.rb
class AddSessions < ActiveRecord::Migration def self.up create_table :sessions do |t| t.column :session_id, :string t.column :data, :text t.column :updated_at, :datetime end
add_index :sessions, :session_id add_index :sessions, :updated_at end
def self.down drop_table :sessions endend
db/migrate/001_add_sessions.rb
> rake db:migrate
DBマイグレーション
phpmyadminで確認しよう
↓http://localhost/mysql/
rake db:migrateを実行するとdb/migrate/の中のupメソッドを順番に実行
SQLじゃなくてRubyでテーブル生成
↓DBの構造をバージョン管理
Todoモデルを作る
todoモデルはToDoの内容を書くdescriptionカラムだけ
> ruby script/generate model todo description:text
マイグレーションファイルも自動生成
db/migrate/002_create_todos.rb
> rake db:migrate
Todoモデルクラスapp/models/todo.rb
class Todo < ActiveRecord::Baseend
app/models/todo.rb
AR::Baseを継承してクラスを作るとクラス名の複数形のテーブルをO/Rマッピング
APIリファレンスはhttp://railsapi.masuidrive.jp
> ruby script/console>> t = Todo.new>> t.savephpMyAdminで確認>> t.description = ‘hoge’>> t.savephpMyAdminで確認
phpMyAdminで見ると主キーに勝手にidが割り当てられてるRailsの規約
Todoクラスに定義しなくてもdescriptionカラムを扱える
phpMyAdminで1行適当に追加
>> Todo.find(:all)[#<Todo:0x26efa10 @attributes={"id"=>"1", "description"=>"hoge"}>, #<Todo:0x26ef970 @attributes={"id"=>"2", "description"=>"new todo"}>]
>> t = Todo.find(2)>> t.description“new todo”>> t.description = “test!?”>> t.save
これを操作するコードを書いてToDoアプリを作る
ついにきましたscaffold
モデルに対するCRUDを生成
Create - 生成Read - 表示Update - 更新Destroy - 削除
> ruby script/generate scaffold todo todo
モデル名コントローラ名
TodoモデルをCRUDするTodoコントローラ
app/controllers/todo_controller.rbapp/views/todo/*.rhtml
Railsでコントローラにアクセスするには・・
↓http://サーバ/コントローラ名/アクション名/id
Railsでコントローラにアクセスするには・・
↓http://サーバ/コントローラ名/アクション名/id
省略可能
Railsでコントローラにアクセスするには・・
↓http://サーバ/コントローラ名/アクション名/id
省略可能Railsの規約
ruby script/server
http://localhost:3000/todo/
ToDo完成
と、いう訳にはいかない
まずはscaffoldの流れを追おう
http://localhost:3000/todo↓
Todoコントローラのindexアクションを実行
コントローラのコードを追おう
class TodoController < ApplicationController def index list render :action => 'list' end
# GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) verify :method => :post, :only => [ :destroy, :create, :update ], :redirect_to => { :action => :list }
def list @todo_pages, @todos = paginate :todos, :per_page => 10 end
def show @todo = Todo.find(params[:id]) end
def new @todo = Todo.new end
def create @todo = Todo.new(params[:todo]) if @todo.save flash[:notice] = 'Todo was successfully created.' redirect_to :action => 'list' else render :action => 'new' end end
def edit @todo = Todo.find(params[:id]) end
def update @todo = Todo.find(params[:id]) if @todo.update_attributes(params[:todo]) flash[:notice] = 'Todo was successfully updated.' redirect_to :action => 'show', :id => @todo else render :action => 'edit' end end
def destroy Todo.find(params[:id]).destroy redirect_to :action => 'list' endend
class TodoController < ApplicationController def index list render :action => 'list' end
# GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) verify :method => :post, :only => [ :destroy, :create, :update ], :redirect_to => { :action => :list }
def list @todo_pages, @todos = paginate :todos, :per_page => 10 end
def show @todo = Todo.find(params[:id]) end
def new @todo = Todo.new end
def create @todo = Todo.new(params[:todo]) if @todo.save flash[:notice] = 'Todo was successfully created.' redirect_to :action => 'list' else render :action => 'new' end end
def edit @todo = Todo.find(params[:id]) end
def update @todo = Todo.find(params[:id]) if @todo.update_attributes(params[:todo]) flash[:notice] = 'Todo was successfully updated.' redirect_to :action => 'show', :id => @todo else render :action => 'edit' end end
def destroy Todo.find(params[:id]).destroy redirect_to :action => 'list' endend
短いと言っても長い
順に追ってみましょう
ブラウザRails
mongrel
Controller
Railsアプリの構造
Model
View
Controller
MVCの流れ
Model
View
Controller
MVCの流れ
Model
View
params[]URLやフォームで渡される値
Controller
MVCの流れ
Model
View
params[]URLやフォームで渡される値
URLからコントーラとアクションを決定
Controller
MVCの流れ
Model
View
Model.find,model.save
params[]URLやフォームで渡される値
URLからコントーラとアクションを決定
Controller
MVCの流れ
Model
View
Model.find,model.save
params[]URLやフォームで渡される値
インスタンス変数
URLからコントーラとアクションを決定
Controller
MVCの流れ
Model
View
Model.find,model.save
params[]URLやフォームで渡される値
インスタンス変数
アクション名のテンプレ
URLからコントーラとアクションを決定
class TodoController < ApplicationController ...end
コントローラを定義するには、コントローラ名+ContollerのクラスをActionController::Baseを継承して作る。ApplicationControllerが、ActionController::Baseを継承している。Todoコントローラは、TodoControllerとなる。
def index list render :action => 'list' end
アクションはその名前のpublicメソッドが呼び出される。その為indexアクションはindexメソッドが呼ばれる。ここでは、listメソッドを呼び、ビューをlistアクションのテンプレートを使って実行している。
def list @todo_pages, @todos = paginate({:todos, :per_page => 10})end
第一引数で指定したモデルを:per_pageの件数だけ取得する。第一返値には、Paginator管理オブジェクト、第二返値に取得したモデルデータの配列が返ってくる。URLのパラメータでpageが指定されていた場合、page*per_page件目からのデータが取得される
Railsの便利機能
def index list render :action => 'list' end
デフォルトでは、Viewでアクション名のテンプレートファイルが呼ばれるが、今回はrender命令を使いテンプレートファイルをlistに指定しているので、listアクションのテンプレートが実行される
ビューはどこ?
app/views/コントローラ名/アクション名.rhtml
app/views/コントローラ名/アクション名.rhtml↓
app/views/todo/list.rhtml
<h1>Listing todos</h1><table> <tr> <% for column in Todo.content_columns %> <th><%= column.human_name %></th> <% end %> </tr>
<% for todo in @todos %> <tr> <% for column in Todo.content_columns %> <td><%=h todo.send(column.name) %></td> <% end %> <td><%= link_to 'Show', :action => 'show', :id => todo %></td> <td><%= link_to 'Edit', :action => 'edit', :id => todo %></td> <td><%= link_to 'Destroy', { :action => 'destroy', :id => todo }, :confirm => 'Are you sure?', :method => :post %></td> </tr><% end %></table>
<%= link_to 'Previous page', { :page => @todo_pages.current.previous } if @todo_pages.current.previous %><%= link_to 'Next page', { :page => @todo_pages.current.next } if @todo_pages.current.next %> <br />
app/views/todo/list.rhtml
ブラウザのコードと見比べてみよう
.rhtmlは、ERBと呼ばれる埋め込みRuby
<%~%>の部分が実行され、<%= ~%>は結果が表示される
<h1>Listing todos</h1><table> <tr> <% for column in Todo.content_columns %> <th><%= column.human_name %></th> <% end %> </tr>
<% for todo in @todos %> <tr> <% for column in Todo.content_columns %> <td><%=h todo.send(column.name) %></td> <% end %> <td><%= link_to 'Show', :action => 'show', :id => todo %></td> <td><%= link_to 'Edit', :action => 'edit', :id => todo %></td> <td><%= link_to 'Destroy', { :action => 'destroy', :id => todo }, :confirm => 'Are you sure?', :method => :post %></td> </tr><% end %></table>
<%= link_to 'Previous page', { :page => @todo_pages.current.previous } if @todo_pages.current.previous %><%= link_to 'Next page', { :page => @todo_pages.current.next } if @todo_pages.current.next %> <br />
app/views/todo/list.rhtml
Todoモデルのカラム一覧を取得
paginateしたデータのループ
前後ページへのリンク
データ表示各ページへのリンク
<h1>Listing todos</h1><table> <tr> <% for column in Todo.content_columns %> <th><%= column.human_name %></th> <% end %> </tr>
<% for todo in @todos %> <tr> <% for column in Todo.content_columns %> <td><%=h todo.send(column.name) %></td> <% end %> <td><%= link_to 'Show', :action => 'show', :id => todo %></td> <td><%= link_to 'Edit', :action => 'edit', :id => todo %></td> <td><%= link_to 'Destroy', { :action => 'destroy', :id => todo }, :confirm => 'Are you sure?', :method => :post %></td> </tr><% end %></table>
<%= link_to 'Previous page', { :page => @todo_pages.current.previous } if @todo_pages.current.previous %><%= link_to 'Next page', { :page => @todo_pages.current.next } if @todo_pages.current.next %> <br />
app/views/todo/list.rhtml
Todoモデルのカラム一覧を取得
paginateしたデータのループ
前後ページへのリンク
データ表示各ページへのリンク
リンクを生成するヘルパー
HTMLをエスケープするヘルパー
Showのリンクは要らないので削ってしまおう。
ついでにメッセージも日本語に
文字コードはUTF-8で改行コードは問わず
<h1>ToDo一覧</h1><table> <tr> <% for column in Todo.content_columns %> <th><%= column.human_name %></th> <% end %> </tr>
<% for todo in @todos %> <tr> <% for column in Todo.content_columns %> <td><%=h todo.send(column.name) %></td> <% end %> <td><%= link_to 'Show', :action => 'show', :id => todo %></td> <td><%= link_to '編集', :action => 'edit', :id => todo %></td> <td><%= link_to '削除', { :action => 'destroy', :id => todo }, :confirm => 'ホントにいいの?', :method => :post %></td> </tr><% end %></table>
<%= link_to ’前のページ', { :page => @todo_pages.current.previous } if @todo_pages.current.previous %><%= link_to '次のページ', { :page => @todo_pages.current.next } if @todo_pages.current.next %> <br />
app/views/todo/list.rhtml
でもソースにあった<head>とかは?
アクションのテンプレの前にapp/views/layouts/コントローラ.rhtml
が呼び出される
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"><head> <meta http-equiv="content-type" content="text/html;charset=UTF-8" /> <title>Todo: <%= controller.action_name %></title> <%= stylesheet_link_tag 'scaffold' %></head><body>
<p style="color: green"><%= flash[:notice] %></p>
<%= yield %>
</body></html>
app/views/layouts/todo.rhtml
ここにアクションのテンプレが展開される
編集画面は?
編集リンクを押すと↓
http://localhost:3000/todo/edit/1Todoコントローラのeditアクション、idは1
def edit @todo = Todo.find(params[:id]) end
URLで指定されたidは、params[:id]に入っているのでこれを主キーにして検索し、その結果を@todoに代入@todoはインスタンス変数なので、そのままビューであるapp/views/todo/edit.rhtmlへ渡る
外部からのパラメータ
<h1>Editing todo</h1>
<% form_tag :action => 'update', :id => @todo do %> <%= render :partial => 'form' %> <%= submit_tag 'Edit' %><% end %>
<%= link_to 'Show', :action => 'show', :id => @todo %> |<%= link_to 'Back', :action => 'list' %>
app/views/todo/edit.rhtml
他のテンプレファイルを読み込む:partialで指定された場合は_名前.rhtmlが読み込まれる
<%= error_messages_for 'todo' %>
<!--[form:todo]--><p><label for="todo_description">Description</label><br/><%= text_area 'todo', 'description' %></p><!--[eoform:todo]-->
app/views/todo/_form.rhtml
@todoのエラーを表示
<textarea>を生成して値に@todo.descriptionをセット
formの飛び先はupdateアクション
def update
@todo = Todo.find(params[:id])
if @todo.update_attributes(params[:todo])
flash[:notice] = 'Todo was successfully updated.'
redirect_to :action => 'show', :id => @todo
else
render :action => 'edit'
end
end
URLのパラメータ
フォームのパラメータをモデルに保存
redirect先で表示するメッセージ
error_messages_forでエラーメッセージ表示
CreateとDestroyは省略
さてこのToDoにはなにが足りない?
なにも書かなくてもToDoが登録できる
入力値チェックを行うvalidate
class Todo < ActiveRecord::Base validates_presence_of :descriptionend
app/models/todo.rb
descriptionを必須項目に
saveするとエラーが起こり保存されなくなる
validatorの種類
validates_acceptance_of「この規約に同意しますか?」などのチェックボックスをチェック。これを指定すると、データベースには保存されない仮想的なカラムが自動生成されます。
例: validates_acceptance_of :kiyakuチェックボックスの値をkiyakuカラムに入れると、チェックされてない場合にはエラーが起こる
validates_confirmation_of確認のために同じ値を入力させた場合の同定をチェック。passwordカラムを指定した場合、データベースには保存されないpassword_confirmationカラムができ、passwordカラムとpassword_confirmationカラムの同一性をチェックする。
例: validates_confirmation_of :emailemailカラムと、email_confimationカラムの値を比較して、不一致の場合にエラーが起こる
validates_inclusion_of指定した値に含まれているかチェックvalidates_exclusion_ofとは逆の挙動
例: validates_inclusion_of :year, :in => 1900..2007yearカラムが1900~2007の間に入っていない場合にはエラーが起こる
例: validates_inclusion_of :sex, :in => ['female', 'male']sexカラムが'famale'でも'male'でもなかった場合には、エラーが起こる
validates_exclusion_of指定した値に含まれて居ないかチェックvalidates_inclusion_ofとは逆の挙動
例: validates_exclusion_of :age, :0..19ageカラムが0~19の間に入っていた場合にエラーが起こる
validates_format_of正規表現で指定した書式でチェック
例: validates_format_of :zip, :with => /^\d{3}-\d{4}$/zipカラムが郵便番号のフォーマットに合っていない場合にエラーが起こる
validates_length_ofvalidates_size_of入力した文字数をチェック。日本語などもバイト数ではなく文字数でチェックされます。
例: validates_length_of :phone, :in => 9..11phoneカラムの桁数が9~11桁にない場合にエラーが起こる
validates_numericality_of入力した文字列が、数字かチェック
例: validates_numericality_of :pricepriceカラムに数字以外の物が入っていた場合にはエラーが起こる
例: validates_numericality_of :num, :only_integer => truenumカラムに整数以外の物が入っていた場合にはエラーが起こる
validates_presence_of入力されているかチェック
例: validates_presence_of :mailmailカラムに何も入力されていない場合にはエラーが起こる
validates_uniqueness_ofデータベース内で重複していないかチェック
例: validates_uniqueness_of :mailmailカラムの値が、既にデータベースに登録されている場合にはエラーが起こる
これらをモデルに加えるだけで入力チェックができる
値チェックはモデルの仕事コントローラに書いていませんか?
エラーメッセージが英語・・・
多言語化ライブラリRuby-GetText
RubyGemsで配布PHPのPEARや
PerlのCPANの様なもの
> gem install gettext 1. gettext 1.10.0 (ruby) 2. gettext 1.10.0 (mswin32)~ 中略 ~ 6. Cancel installation> 1 ← 最新の(win32)の番号を入力
config/environment.rbの最後に下記の一行を追加
↓require 'gettext/rails'
app/controllers/application.rbにも下記の一行を追加
↓init_gettext "todo"
classの次の行
優先順位とかカテゴリとか
優先順位モデルやカテゴリモデルとToDoモデルを繋げる
belongs_tohas_many, has_one
もっとAjaxバリバリに
rjs
続きはこれらの本で
日経Linuxでやってる私の連載もよろしく!
続きはこれらの本で
日経Linuxでやってる私の連載もよろしく!
リクエストがあれば続きもやります