僕のYak Shavingは終わらない

車輪の再発明をやめたらそこには壮大なYakの群れが

WEB+DB No.73の記事よりRails 4に入門する 〜その2〜

前回のつづきです。
じゃあスキャフォからで。

お馴染みscaffold

前回入れたsprintgをrailsコマンドの代わりに使えば良い模様。

» spring generate scaffold book title price:integer
Usage: spring COMMAND [ARGS]

Commands for spring itself:

  binstub         Generate spring based binstubs.
  help            Print available commands.
  start           Boot the spring server (this happens automatically when you run a command)
  status          Show current status.
  stop            Stop all spring processes for this project.

Commands for your application:

  cucumber        Execute a Cucumber feature.
  rails           Run a rails command. The following sub commands will use spring: console, runner, generate.
  rake            Run a rake task.
  rspec           Execute an RSpec spec.
  testunit        Execute a Test::Unit test.

あれ、spring {コマンド}のコマンドの部分にむしろrails って入れて、更にサブコマンドとしてgenerateを指定しろってことか・・・。

springのコマンド一覧より

rails console, rails generate, rails runner

These execute the rails command you already know and love. If you run a different sub command (e.g. rails server) then spring will automatically pass it through to the underlying rails executable (without the speed-up).
※springのコマンド一覧より

jonleighton/spring · GitHub

consoleとgenerateとrunner以外もrails *なコマンドは自動でパスを通すから、実行できるけど速度アップはしないよ、ってことでspring rails generateはスピードアップの恩恵に預かれるってことかな。

おそらく最近変わったのだと思われる。

ということで以下。

» spring rails generate scaffold book title price:integer
      invoke  active_record
      create    db/migrate/20130429114819_create_books.rb
      create    app/models/book.rb
      invoke    test_unit
      create      test/models/book_test.rb
      create      test/fixtures/books.yml
      invoke  resource_route
       route    resources :books
      invoke  jbuilder_scaffold_controller
      create    app/controllers/books_controller.rb
      invoke    erb
      create      app/views/books
      create      app/views/books/index.html.erb
      create      app/views/books/edit.html.erb
      create      app/views/books/show.html.erb
      create      app/views/books/new.html.erb
      create      app/views/books/_form.html.erb
      invoke    test_unit
      create      test/controllers/books_controller_test.rb
      invoke    helper
      create      app/helpers/books_helper.rb
      invoke      test_unit
      create        test/helpers/books_helper_test.rb
      invoke    jbuilder
       exist      app/views/books
      create      app/views/books/index.json.jbuilder
      create      app/views/books/show.json.jbuilder
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/books.js.coffee
      invoke    scss
      create      app/assets/stylesheets/books.css.scss
      invoke  scss
      create    app/assets/stylesheets/scaffolds.css.scss

色々つくってくれました〜。

app/controllers/books_controller.rbを覗いてみます。

class BooksController < ApplicationController
  # 一番下のprivateメソッドのところにあるset_bookを
  # show, edit, update, destroyを処理するときだけ
  # 事前に実行する
  before_action :set_book, only: [:show, :edit, :update, :destroy]

  # GET /books
  # GET /books.json
  def index
    # 本のレコード全部を表示する
    @books = Book.all
  end

  # GET /books/1
  # GET /books/1.json
  def show
    # id指定されたものを表示する
    # なんと何も書かなくていいとは!
  end

  # GET /books/new
  def new
    @book = Book.new
  end

  # GET /books/1/edit
  def edit
  end

  # POST /books
  # POST /books.json
  def create
    @book = Book.new(book_params)

    respond_to do |format|
      # saveの成功判定をして分岐
      if @book.save
        format.html { redirect_to @book, notice: 'Book was successfully created.' }
        format.json { render action: 'show', status: :created, location: @book }
      else
        format.html { render action: 'new' }
        format.json { render json: @book.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /books/1
  # PATCH/PUT /books/1.json
  def update
    respond_to do |format|
      if @book.update(book_params)
        format.html { redirect_to @book, notice: 'Book was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: 'edit' }
        format.json { render json: @book.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /books/1
  # DELETE /books/1.json
  def destroy
    @book.destroy
    respond_to do |format|
      format.html { redirect_to books_url }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_book
      # idの指定があれば値が入る
      @book = Book.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def book_params
      params.require(:book).permit(:title, :price)
    end
end

DB作成

scaffoldでつくった定義をDBに反映する

spring rake db:migrate

できたか確認して見ましょう。

» sqlite3 db/development.sqlite3
sqlite> .schema

CREATE TABLE "books" (
    "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 
    "title" varchar(255), 
    "price" integer, 
    "created_at" datetime, 
    "updated_at" datetime
);

CREATE TABLE "schema_migrations" (
    "version" varchar(255) NOT NULL
);

CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version");

sqlite> .exit
» 

※見やすいように勝手にインデントしました。

ちゃんとできてるみたいですね!

テストを実行

チュートリアルもおもむろにテストを実行していたので実行してみます!

spring rake

結果はおもしろくなかったので割愛。

サーバー起動!

これはspringじゃないんですね!!

» rails s
=> Booting WEBrick
=> Rails 4.0.0.beta1 application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
[2013-04-29 21:22:19] INFO  WEBrick 1.3.1
[2013-04-29 21:22:19] INFO  ruby 2.0.0 (2013-02-24) [x86_64-darwin11.4.2]
[2013-04-29 21:22:19] INFO  WEBrick::HTTPServer#start: pid=86260 port=3000

以下にアクセス!
http://localhost:3000/
f:id:kazuph1986:20130429212414p:plain
はい、TOPはつまんないっすね。


次行きましょう。
http://localhost:3000/books
↓↓↓↓↓↓
f:id:kazuph1986:20130429212808p:plain
↓↓↓↓↓↓
f:id:kazuph1986:20130429212814p:plain
↓↓↓↓↓↓
f:id:kazuph1986:20130429212820p:plain
↓↓↓↓↓↓
f:id:kazuph1986:20130429212825p:plain
はい、CRUD全部できますね!

TOPページをbooksに

» vi  config/routes.rb
Tetsuzine::Application.routes.draw do
  resources :books
  root 'books#index'
 # 中略
end

はいこれで、http://localhost:3000/ にアクセスしてもbooksのページに行くはずです。

カラム追加を行う

booksにpublished_onを追加します。
railsコマンドを挟むのを忘れずに。

» spring rails generate migration add_published_on_to_books published_on:date
      invoke  active_record
      create    db/migrate/20130429123541_add_published_on_to_books.rb

続けてマイグレーションを実行します。

» spring rake db:migrate
==  AddPublishedOnToBooks: migrating ==========================================
-- add_column(:books, :published_on, :date)
   -> 0.0174s
==  AddPublishedOnToBooks: migrated (0.0175s) =================================

確認しましょう!

» sqlite3 db/development.sqlite3
sqlite> .schema
CREATE TABLE "books" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar(255), "price" integer, "created_at" datetime, "updated_at" datetime, "published_on" date);

出来てますね!

今度はViewをいじってpublished_onを追加できるようにしましょう。
おそらく"title"というワードが入ってるファイルは全部編集した方がよさそうでね。

# grepで検索(rはサブディレクトリも含め再帰で検索、lはファイルのリストだけ表示にする)
» grep -rl 'title' app/views/books/
app/views/books/_form.html.erb
app/views/books/index.html.erb
app/views/books/index.json.jbuilder
app/views/books/show.html.erb
app/views/books/show.json.jbuilder

以外にいっぱいありますね(^q^)

# 大文字のoオプションで立て区切りで複数ファイルを開いて編集する
» vi -O `grep -rl 'title' app/views/books/`

htmlの方だけ画像で変更箇所を貼っておきます。
ブルーでハイライトされている部分だけ編集しています。
f:id:kazuph1986:20130429215028p:plain

またcontrollerにあるStrongParametersの部分にもpublished_onを追加しないとですね。
(これなしで実行するとフォームはあっても値の入力ができないらしい)

» vi app/controllers/books_controller.rb
    def book_params
      params.require(:book).permit(:title, :price, :published_on)
    end

実際に投稿してみましょう。
f:id:kazuph1986:20130429215646p:plain
デリミタ無しで続けて年月日を入力します。

f:id:kazuph1986:20130429215710p:plain
やった!ちゃんとDATE型で保存できているようです!

ということで今回はここまでにしましょう!

残すはActiveModelとAjaxです!

つづく