Intelligent Technology's Technical Blog

株式会社インテリジェントテクノロジーの技術情報ブログです。

Ruby on Rails4.0を使ってみる

はじめに

こんにちは、櫻です。

気がつけばRuby on Rails4.0がリリースされていたようです。
なんとなく雰囲気は知っていたけれど、きちんと触ったことがなかったというのと、ちょっとRailsとか紹介してよという声が聞こえたので、Getting Startedを見ながら動かしてみたという内容です。

準備

Rails4.0では以下のものが必要なようです。

「環境を揃えるのが楽そう」という理由でUbuntu上で準備をしました。

ruby(とgems) rbenvで2.0.0-p247をインストール
sqlite3 sudo apt-get install sqlite3 libsqlite3-dev
node.js nvmでv0.10.13をインストール

Railsのインストール

Railsのインストールは以下のコマンドを実行するだけで完了します。
簡単ですね。

[sakura@ubuntu13 ~]$ gem install rails

プロジェクト作成

Railsでは以下のコマンドでプロジェクトのテンプレートが生成されます。

[sakura@ubuntu13 ~]$ rails new プロジェクト名

railsのディレクトリ構成に関しては本家のGetting Startedを参考にしてください。
この状態で

[sakura@ubuntu13 ~]$ cd プロジェクト名
[sakura@ubuntu13 blog]$ rails server

と実行すると、テスト用のサーバが3000番ポートで起動します。
ポート番号を変更したい場合は --port=ポート番号 というオプションをつける必要があります。
この時点でJavaScriptの実行環境がインストールされていないと、起動に失敗しました。
CoffeeScriptのコンパイル時にJavaScriptの実行環境が必要になるようです。
http://localhost:3000 を開くと、このような画面が表示されます。

f:id:IntelligentTechnology:20130724183137p:plain

以下、プロジェクト名(Getting Startedと同じようにblog)ディレクトリ内での操作です。

Hello World

まずは動くページを作成してみましょう。
Getting Startedの通りにindexアクション付きのwelcomeコントローラを作成します。

[sakura@ubuntu13 blog]$ rails generate controller welcome index
      create  app/controllers/welcome_controller.rb
       route  get "welcome/index"
      invoke  erb
      create    app/views/welcome
      create    app/views/welcome/index.html.erb
      invoke  test_unit
      create    test/controllers/welcome_controller_test.rb
      invoke  helper
      create    app/helpers/welcome_helper.rb
      invoke    test_unit
      create      test/helpers/welcome_helper_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/welcome.js.coffee
      invoke    scss
      create      app/assets/stylesheets/welcome.css.scss

対応するコントローラとビューが生成されました。
app/views/welcome/index.html.erbの内容を

<h1>Hello, Rails!</h1>

のように書き換えて http://localhost:3000/welcome/index を開くとその内容が表示されます。

f:id:IntelligentTechnology:20130724183149p:plain

リクエストされたURLがどのようにコントローラ/アクションと対応付けられるかはconfig/routes.rbに定義されています。
先ほどのrails generate ...コマンド実行時に

get "welcome/index"

という行が追加されていたので、手動で追加しなくても動作したということです。

ブログ投稿のCRUD作成

Getting Startedではこの後ブログの画面の作成が始まります。

はじめにconfig/routes.rbにresouces :postsという行を追加します。
※resourceと指定すると別のルールになるため、動作が変わってきます。

Blog::Application.routes.draw do
  get "welcome/index"
  resources :posts
end

この1行で良くあるCRUDのルーティング定義がされます。
rake routesというコマンドで現在の定義の一覧が確認できます。

[sakura@ubuntu13 blog]$ rake routes
       Prefix Verb   URI Pattern               Controller#Action
welcome_index GET    /welcome/index(.:format)  welcome#index
        posts GET    /posts(.:format)          posts#index
              POST   /posts(.:format)          posts#create
     new_post GET    /posts/new(.:format)      posts#new
    edit_post GET    /posts/:id/edit(.:format) posts#edit
         post GET    /posts/:id(.:format)      posts#show
              PATCH  /posts/:id(.:format)      posts#update
              PUT    /posts/:id(.:format)      posts#update
              DELETE /posts/:id(.:format)      posts#destroy
[sakura@ubuntu13 blog]$ 

この時点で http://localhost:3000/posts/new を開くとコントローラが無いと怒られます。
f:id:IntelligentTechnology:20130724183823p:plain

後はこの定義に出てくるコントローラ/アクションを作成していく事になります。

新規投稿

まずは新規投稿です。まだコントローラを作成していないので、以下のコマンドで雛形を作成します。

[sakura@ubuntu13 ~]$ rails generate controller posts

ここで再度 http://localhost:3000/posts/new を開くと今度は対応するアクション(new)が無いと怒られます。
f:id:IntelligentTechnology:20130724184356p:plain
コントローラ(app/controllers/posts_controller.rb)にnewメソッドを追加しましょう。

  def new
  end

これでもう一度 http://localhost:3000/posts/new を開くと今度はテンプレートが無いと怒られます。
f:id:IntelligentTechnology:20130724184543p:plain
コントローラの自動生成ではビュー(テンプレート)は生成されていないので、app/vewis/posts/new.html.erbを作成しましょう。

<h1>New Post</h1>

<%= form_for :post, url: posts_path do |f| %>
    <p>
      <%= f.label :title %><br>
      <%= f.text_field :title %>
    </p>

    <p>
      <%= f.label :text %><br>
      <%= f.text_area :text %>
    </p>

    <p>
      <%= f.submit %>
    </p>
<% end %>

これで新規投稿の画面が表示されるようになりました。
f:id:IntelligentTechnology:20130724184931p:plain
このままボタンを押すとまたアクションが無いと怒られるので、コントローラにcreateメソッドを追加します。

  def create
    render text: params[:post].inspect
  end

この状態でボタンを押すと、投稿内容がJSONとして表示されます。
f:id:IntelligentTechnology:20130724185350p:plain

新規投稿用のコントローラとビューが作成できたので、次はモデルです。
コマンド2つでモデルクラス作成、DBのテーブルの作成などが実行できます。

[sakura@ubuntu13 blog]$ rails generate model Post title:string text:text
      invoke  active_record
      create    db/migrate/20130724065248_create_posts.rb
      create    app/models/post.rb
      invoke    test_unit
      create      test/models/post_test.rb
      create      test/fixtures/posts.yml
[sakura@ubuntu13 blog]$ rake db:migrate
==  CreatePosts: migrating ====================================================
-- create_table(:posts)
   -> 0.0012s
==  CreatePosts: migrated (0.0012s) ===========================================

[sakura@ubuntu13 blog]$ 

デフォルトの設定ではdb/development.sqlite3にDBが作成されます。postsテーブルが作成されているのがわかります。

[sakura@ubuntu13 blog]$ sqlite3 db/development.sqlite3 .table
posts              schema_migrations
[sakura@ubuntu13 blog]$

テーブルの作成ができたので、画面から登録できるようにしましょう。
コントローラのcreateメソッドを以下のように書き換えます。

  def create
    @post = Post.new(post_params)

    @post.save
    redirect_to @post
  end

  def show
    @post = Post.find(params[:id])
  end
  
  private
  def post_params
    params.require(:post).permit(:title, :text)
  end

さらに投稿内容表示用のapp/views/post/show.html.erbを作成します。

<p>
  <strong>Title:</strong>
  <%= @post.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @post.text %>
</p>

これで投稿画面のSave Postボタンを押すと、投稿内容がDBに保存され投稿内容が表示されます。
f:id:IntelligentTechnology:20130724185846p:plain

[sakura@ubuntu13 blog]$ sqlite3 db/development.sqlite3 'select * from posts'
3|タイトル1|本文|2013-07-24 09:56:00.516646|2013-07-24 09:56:00.516646
[sakura@ubuntu13 blog]$ 

モデル生成時に指定した項目以外に、ID、登録時刻、更新時刻らしきものが自動で追加されているのが分かります。

投稿一覧

投稿ができるようになったので、次は一覧画面です。
一覧画面の用のアクションはindexになっているので、コントローラにindexメソッドを追加します。

  def index
    @posts = Post.all
  end

同じくテンプレートをapp/views/post/index.html.erbに作成します。

<h1>Listing posts</h1>

<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
  </tr>

  <% @posts.each do |post| %>
      <tr>
        <td><%= post.title %></td>
        <td><%= post.text %></td>
      </tr>
  <% end %>
</table>

これでhttp://localhost:3000/posts/へアクセスすると一覧が表示されます。
f:id:IntelligentTechnology:20130724190241p:plain

画面間のリンク

いくつか画面ができたので、一覧<=>新規投稿の画面間のリンクを作成します。
index.html.erbに

<%= link_to 'New post', new_post_path %>

new.html.erbに

<%= link_to 'Back', posts_path %>

を追加します。

f:id:IntelligentTechnology:20130724190659p:plain f:id:IntelligentTechnology:20130724190648p:plain
バリデーション

単純なバリデーションはモデルで以下のように指定できます。

class Post < ActiveRecord::Base
  validates :title, presence: true,
            length: { minimum: 5 }
end

バリデーションに失敗した際に入力画面へ戻る様にコントローラを書き換えます。

  def new
    @post = Post.new
  end

  def create
    @post = Post.new(post_params)

    if @post.save
      redirect_to @post
    else
      render 'new'
    end
  end

更に入力画面でエラーを表示するようにします。

<%= form_for :post, url: posts_path do |f| %>
    <% if @post.errors.any? %>
        <div id="errorExplanation">
          <h2><%= pluralize(@post.errors.count, "error") %> prohibited
            this post from being saved:</h2>
          <ul>
            <% @post.errors.full_messages.each do |msg| %>
                <li><%= msg %></li>
            <% end %>
          </ul>
        </div>
    <% end %>

    <p>
      <%= f.label :title %><br>
      <%= f.text_field :title %>
    </p>

    <p>
      <%= f.label :text %><br>
      <%= f.text_area :text %>
    </p>

    <p>
      <%= f.submit %>
    </p>
<% end %>

これでタイトルが5文字未満の場合はエラーになり、登録できなくなります。
f:id:IntelligentTechnology:20130724190952p:plain

更新画面

CRUDのCRができるようになったので、次はUです。

まずアクションの定義です。コントローラに以下のメソッドを定義します。editが編集画面表示のアクション、updateが更新時のアクションです。

  def edit
    @post = Post.find(params[:id])
  end

  def update
    @post = Post.find(params[:id])

    if @post.update(params[:post].permit(:title, :text))
      redirect_to @post
    else
      render 'edit'
    end
  end

次に編集画面のビュー(app/views/posts/edit.html.erb)を作成します。
Getting Startedをそのままコピーしたところ、シンタックスエラーになったため、form_forの行の"}"を削除しています。

<h1>Editing post</h1>

<%= form_for :post, url: post_path(@post.id), method: :patch do |f| %>
<% if @post.errors.any? %>
    <div id="errorExplanation">
      <h2><%= pluralize(@post.errors.count, "error") %> prohibited
        this post from being saved:</h2>
      <ul>
        <% @post.errors.full_messages.each do |msg| %>
            <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
<% end %>
<p>
  <%= f.label :title %><br>
  <%= f.text_field :title %>
</p>

<p>
  <%= f.label :text %><br>
  <%= f.text_area :text %>
</p>

<p>
  <%= f.submit %>
</p>
<% end %>

<%= link_to 'Back', posts_path %>

そして、一覧画面から更新画面へのリンクを作成します。ここも微妙に調整しています。(post_pathに引数を追加)

  <% @posts.each do |post| %>
      <tr>
        <td><%= post.title %></td>
        <td><%= post.text %></td>
        <td><%= link_to 'Show', post_path(post) %></td>
        <td><%= link_to 'Edit', edit_post_path(post) %></td>
      </tr>
  <% end %>

これで投稿内容の変更ができるようになりました。

投稿の削除

最後に削除機能です。
削除のアクションdestroyをコントローラに追加します。

  def destroy
    @post = Post.find(params[:id])
    @post.destroy

    redirect_to posts_path
  end

そして、一覧画面に削除のリンクを作成します。

  <% @posts.each do |post| %>
      <tr>
        <td><%= post.title %></td>
        <td><%= post.text %></td>
        <td><%= link_to 'Show', post_path(post) %></td>
        <td><%= link_to 'Edit', edit_post_path(post) %></td>
        <td><%= link_to 'Destroy', post_path(post), method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
  <% end %>
data: { confirm: 'Are you sure?' }

の指定をすることで確認のダイアログが表示されます。

f:id:IntelligentTechnology:20130724191430p:plain
f:id:IntelligentTechnology:20130724191443p:plain

これで無事削除機能も追加できました。

scaffold

ここまででCRUD操作が一通り作成できました。
実はこの内容のほとんどは

[sakura@ubuntu13 blog]$ rails generate scaffold post title:string text:text
[sakura@ubuntu13 blog]$ rake db:migrate

のコマンドで作成できます。
慣れればこちらの方が速いかもしれませんが、初めて利用した場合は何が起こるのか理解しづらいのでばらばらに生成しているのかもしれません。

まとめ

Getting Startedの内容を試してみました。
リンク先ではこの後has_manyの関連が続いています。
動くものを速く作成したい場合にはやはり強力なフレームワークですね。