Railsを使ったアプリケーション開発 (その1 事前準備編)、
Railsを使ったアプリケーション開発 (その2 基礎編)
ときて、いよいよ3回目である。
いよいよ書籍を管理するアプリケーションを作る。
手動でコントローラ、ビュー、モデルを作成してきたがもっと楽な手段がある。
Scaffolding機能を使う方法である。コマンド一発で、特定のテーブルをCreate、Read、Update、Delete操作できる、簡易アプリケーションが作れてしまう。
この機能を利用して書籍管理アプリケーションを作ってみる。
◆ テーブルの設計
実体と属性
()はrailsが自動で作成する。
赤は主キー、青は非主キーである。
*が外部キーを表す。
books
(id) title price (created_at) (updated_at)
users
(id) name password (created_at) (updated_at)
authors
(id) name (created_at) (updated_at)
reviews
(id) bookd_id* user_id* body (created_at) (updated_at)
books_authors
book_id* author_id*
ER図もどき
books 1 - N reviews N - 1 users
1
|
N
author_books
N
|
1
authors
◆ Scaffoldingを利用して関連ファイルを一括作成
今はここにいる。
# pwd
/var/www/rails/bookapp
モデルクラス名は単数なので注意を。複数形にするのはテーブルだけである。
# rails generate scaffold book title:string price:integer
# rails generate scaffold user name:string password:string
# rails generate scaffold author name:string
# rails generate scaffold review book:references user:references body:text
# rails generate model authors_book book:references author:references
author_booksだけscaffoldで作らなかった。純粋な中間テーブルをCRUDに従った操作をする必要はないと判断したためである。もちろん作ってもかまわない。
◆ マイグレーションファイルによるテーブルの作成
自動でマイグレーションファイルはできているが、主キー列、created_at列、updated_at列が不要な中間テーブルの作成前には一部編集が必要である。主キーを無効化し、かつタイムスタンプ列を削除する。
# vi db/migrate/20111113040505_create_author_books.rb
def change
create_table :author_books, :id=>false do |t|
t.references :book
t.references :author
t.timestamps ←削除
end
add_index :author_books, :book_id
add_index :author_books, :author_id
end
テーブルを作成する。
# rails db:migrate RAILS_ENV=development
◆ テストデータの流し込み
自動でテストデータが作られている。流し入れるデータを変えたければymlファイルを編集すればよい。
# vi test/fixtures/books.yml
book1:
title: book1
price: 1000
book2:
title: book2
price: 2000
book3:
title: book3
price: 3000
上記のように記載していくと手間がかかるためブロックを使った式をymlファイルに記載してもよい。
<% 1.upto(3) do |n| %>
book<%= n %>:
title: book<%= n %>
price: <%= n * 1000 %>
<% end %>
# vi test/fixtures/users.yml
<% 1.upto(3) do |n| %>
user<%= n %>:
name: name<%= n %>
password: password<%= n %>
<% end %>
# vi test/fixtures/authors.yml
<% 1.upto(3) do |n| %>
author<%= n %>:
name: name<%= n %>
<% end %>
reviwesテーブルはカラムにbookd_idとuser_idをもち、それぞれbooksテーブル、usersテーブルの外部キーになっているはずである。しかしbook_id、user_idにidを入れて参照元のテーブルと紐づけるのは手間である。そこでラベル名で参照先を識別させることもできる。
# vi test/fixtures/author_books.yml
one:
book: book1
author: author2
two:
book: book3
author: author1
上と同じく、book_id、user_idは使わずラベル名を利用する。
# vi test/fixtures/reviews.yml
one:
book: book1
user: user2
body: fantastic
two:
book: book2
user: user3
body: interesting
ymlの試験データを元にデータをテーブルへ流し込む。
# rails db:fixtures:load FIXTURES=books RAILS_ENV=development
# rails db:fixtures:load FIXTURES=users RAILS_ENV=development
# rails db:fixtures:load FIXTURES=authors RAILS_ENV=development
# rails db:fixtures:load FIXTURES=reviews RAILS_ENV=development
# rails db:fixtures:load FIXTURES=author_books RAILS_ENV=development
同じコマンドを2回打っても追記はされない。ymlファイルに記載されているものだけが挿入される。既に存在している行があってもymlになければその行は削除される。
データベースを初期化するにはこうである。
# rails db:reset
◆ ルーティングの作成
ルーティングも自動で入っている。
# cat config/routes.rb
(略)
resources :reviews
resources :authors
resources :users
resources :books
(略)
# rails routes
ヘルパー名 HTTPメソッド URLパターン ルートパラメータ
(略)
books GET /books(.:format) {:action=>"index", :controller=>"books"}
POST /books(.:format) {:action=>"create", :controller=>"books"}
new_book GET /books/new(.:format) {:action=>"new", :controller=>"books"}
edit_book GET /books/:id/edit(.:format) {:action=>"edit", :controller=>"books"}
book GET /books/:id(.:format) {:action=>"show", :controller=>"books"}
PUT /books/:id(.:format) {:action=>"update", :controller=>"books"}
DELETE /books/:id(.:format) {:action=>"destroy", :controller=>"books"}
(略)
ヘルパーとはテンプレートファイルを記述する際に役立つメソッドの総称である。
パスヘルパー 得られるパス
books_path /books/
book_path(id) /books/:id
new_book_path /books/new
edit_book_path(id) /books/:id/edit
例
<%= link_to 'Edit', edit_book_path(book) %>
<%= link_to 'New', new_book_path(book) %>
XXX_path 部分を XXX_url に変えると相対パスから、絶対パスになる。
さて、RESTfulなインターフェースになっていることに気がつくだろうか。RESTfulであるとは一意なURLに対して、CRUD(Create、Read、Update、Delete)を使ってアクセスできるということである。CRUDをHTTPプロトコルにあてはめると、POST、GET、PUT、DELETEになる。URLを一意にすることでインターフェースを統一し、プロトコルにしたがってルートパラメータでリソースを制御するのである。
◆ 確認
テーブルを作成し、テストデータを流し込んだので以下にアクセスして確認してみる。
Create、Read、Update、Delete操作ができるので触ってみてほしい。
http://x.x.x.x/books
http://x.x.x.x/users
http://x.x.x.x/authors
http://x.x.x.x/reviews
ついでに検証機能もいれておこう。
◆ モデルファイルの編集(検証機能の設定)
bookモデルに検証機能を入れる。
タイトルは必須、また長さは1~100。
価格はintegerのみで価格は100000以下。
# vi app/models/book.rb
validates :title,
:presence => true,
:length => { :minimum => 1, :maximum => 100 }
validates :price,
:numericality => { :only_integer => true, :less_than => 100000 }
再度アクセスして、書籍を新規に登録してみよう。
http://x.x.x.x/books
検証機能に違反する操作を行うとエラーが出るはずである。
これはどうやってエラーを表示させているのであろうか。
# cat app/views/books/new.html.erb
<%= render 'form' %>
部分テンプレートになっている
# cat app/views/books/_form.html.erb
<% if @book.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@book.errors.count, "error") %> prohibited this book from being saved:</h2>
<ul>
<% @book.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
CSSを変えたければ以下のファイルのfield_with_errorsを編集すればよい。
# cat ./app/assets/stylesheets/scaffolds.css.scss
.field_with_errors {
(略)
}
最後に、複数のオブジェクトモデル(リレーショナルテーブル)を作ったので
それらを連携させたアプリケーションを作成する。
http://x.x.x.x/store/search
にアクセスして書籍を検索できるサイトを作る。
◆ モデルファイルの編集(アソシエーションの設定)
"◆ テーブルの設計"で作成したER図を見ながら考える。
再掲
books 1 - N reviews N - 1 users
1
|
N
author_books
N
|
1
authors
# vi app/models/book.rb ※下記コードを挿入
has_many :authors
has_many : author_books
has_many :authors, through: :author_books
# vi app/models/user.rb ※下記コードを挿入
has_many :reviews
has_many :books, through: reviews
# vi app/models/author.rb ※下記コードを挿入
has_many :author_books
has_many :books
# vi app/models/review.rb ※下記コードを挿入
# booksテーブルとusersテーブルのM:Nひもづけ用中間テーブル
belongs_to :book
belongs_to :user
純粋な中間テーブル用のモデルは不要である
# rm app/models/books_author.rb
◆ コントローラとビューの作成
# rails generate controller store
# vi app/views/store/search.html.erb
<%= form_tag :action => 'scan' do %>
<div class="field">
<%= label_tag 'title', 'book title:' %><br />
<%= text_field_tag 'title' %>
</div>
<%= submit_tag 'search' %>
<% end %>
# vi app/controllers/store_controller.rb ※下記コードを挿入
def scan
@books = Book.where('title = ?', params[:title])
render 'list'
end
<% @books.each do |book| %>
<h1>TITLE : <%= book.title %></h1>
<hr />
<ul>
<li>PRICE : <%= book.price %></li>
<% book.authors.each do |author| %>
<li>AUTHOR : <%= author.name %> </li>
<% end %>
</ul>
<h1>REVIEW</h1>
<hr />
<ul>
<% book.reviews.each do |review| %>
<li><%= review.body %>(<%= review.updated_at %>)</li>
<% end %>
</ul>
<% end %>
◆ ルーティングの編集
"http://localhost/bookapp/store/search"としてアクセスさせたい。RESTfulインターフェースにはsearch、scan、listのアクションはないので、自前で用意する。
# vi config/routes.rb
resources :store do
collection do
get 'search'
post 'scan'
get 'list'
end
end
# rails routes
search_store_index GET /store/search(.:format {:action=>"search", :controller=>"store"}
scan_store_index POST /store/scan(.:format) {:action=>"scan", :controller=>"store"}
list_store_index GET /store/list(.:format) {:action=>"list", :controller=>"store"}
collectionは複数のオブジェクトを扱うが、単一のオブジェクト、例えば、GET時に、/store/:id/hoge のようなルーティングを作りたければ、memberブロックを使う。
member :store do
member do
get 'hoge'
end
end
◆ サービス確認
書籍名に"book1"などを入れて検索結果が出てくれば成功である。
http://x.x.x.x/store/search
Next ⇒ Railsを使ったアプリケーション開発 (その4 応用編 認証機能の追加)