Ruby on Rails 基礎

Railsアプリケーションの作成

Railsアプリケーションを作成します。

rails new first-app

Railsアプリケーションは以下のような構成になっています。

first-app
├── Gemfile # ライブラリを管理するファイル
├── Gemfile.lock
├── README.rdoc
├── Rakefile
├── app # アプリケーションのコードが配置される
│   ├── assets
│   │   ├── images
│   │   ├── javascripts
│   │   │   └── application.js
│   │   └── stylesheets
│   │       └── application.css
│   ├── controllers
│   │   ├── application_controller.rb
│   │   └── concerns
│   ├── helpers
│   │   └── application_helper.rb
│   ├── mailers
│   ├── models
│   │   └── concerns
│   └── views
│       └── layouts
│           └── application.html.erb
├── bin
│   ├── bundle
│   ├── rails
│   ├── rake
│   ├── setup
│   └── spring
├── config # アプリケーションの設定ファイル
│   ├── application.rb
│   ├── boot.rb
│   ├── database.yml
│   ├── environment.rb
│   ├── environments
│   │   ├── development.rb
│   │   ├── production.rb
│   │   └── test.rb
│   ├── initializers
│   │   ├── assets.rb
│   │   ├── backtrace_silencers.rb
│   │   ├── cookies_serializer.rb
│   │   ├── filter_parameter_logging.rb
│   │   ├── inflections.rb
│   │   ├── mime_types.rb
│   │   ├── session_store.rb
│   │   └── wrap_parameters.rb
│   ├── locales
│   │   └── en.yml
│   ├── routes.rb
│   └── secrets.yml
├── config.ru
├── db
│   └── seeds.rb
├── lib
│   ├── assets
│   └── tasks
├── log
├── public # 静的なファイル
│   ├── 404.html
│   ├── 422.html
│   ├── 500.html
│   ├── favicon.ico
│   └── robots.txt
├── test
│   ├── controllers
│   ├── fixtures
│   ├── helpers
│   ├── integration
│   ├── mailers
│   ├── models
│   └── test_helper.rb
├── tmp
│   └── cache
│       └── assets
└── vendor
    └── assets
        ├── javascripts
        └── stylesheets

Railsアプリケーションを起動します。

cd first-app
rails server

ここからさきでは、first-appのディレクトリをRAILS_ROOTと呼びます。

scaffoldによるコードの自動生成

Railsではさまざまなコードを自動生成できます。scaffoldでアプリケーションの雛形を作ります。 scaffoldは一覧、詳細、追加、削除、変更のコードを自動生成します。

ここでは bookmark を管理する機能を作ります。RAILS_ROOTで以下のコメンドを実行します。

rails generate scaffold bookmark title:string description:text url:string

DBを更新して、アプリケーションを起動します。RAILS_ROOTで以下のコメンドを実行します。

bin/rake db:migrate
rails server

http://localhost:3000/bookmarksで動作確認ができます。

データの追加、削除、変更などを行ってみましょう。

scaffoldで生成されるファイル

scaffoldで生成されたファイルを確認していきます。scaffoldではこのような実行結果が出力されます。

% rails generate scaffold bookmark title:string description:text url:string
      invoke  active_record
      create    db/migrate/20151129091144_create_bookmarks.rb
      create    app/models/bookmark.rb
      invoke    test_unit
      create      test/models/bookmark_test.rb
      create      test/fixtures/bookmarks.yml
      invoke  resource_route
       route    resources :bookmarks
      invoke  scaffold_controller
      create    app/controllers/bookmarks_controller.rb
      invoke    erb
      create      app/views/bookmarks
      create      app/views/bookmarks/index.html.erb
      create      app/views/bookmarks/edit.html.erb
      create      app/views/bookmarks/show.html.erb
      create      app/views/bookmarks/new.html.erb
      create      app/views/bookmarks/_form.html.erb
      invoke    test_unit
      create      test/controllers/bookmarks_controller_test.rb
      invoke    helper
      create      app/helpers/bookmarks_helper.rb
      invoke      test_unit
      invoke    jbuilder
      create      app/views/bookmarks/index.json.jbuilder
      create      app/views/bookmarks/show.json.jbuilder
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/bookmarks.coffee
      invoke    scss
      create      app/assets/stylesheets/bookmarks.scss
      invoke  scss
      create    app/assets/stylesheets/scaffolds.scss

scaffoldでは以下のようなファイルを生成しています。

  • active_record
    • テーブルの作成
    • モデルの作成
  • routeの設定
  • controller
    • viewの作成
    • helperの作成
    • jbuilder(jsonテンプレート)の作成
  • assets
    • CoffeeScriptの作成
    • Scssの作成

実際にファイルを開いて確認して見ましょう。

migration

db/migrate/20151129091144_create_bookmarks.rb

class CreateBookmarks < ActiveRecord::Migration
  def change
    create_table :bookmarks do |t|
      t.string :title
      t.text :description
      t.string :url

      t.timestamps null: false
    end
  end
end

routes

config/routes.rb

Rails.application.routes.draw do
  resources :bookmarks
  # The priority is based upon order of creation: first created -> highest priority.
  # See how all your routes lay out with "rake routes".

  # You can have the root of your site routed with "root"
  # root 'welcome#index'

  # Example of regular route:
  #   get 'products/:id' => 'catalog#view'

  # Example of named route that can be invoked with purchase_url(id: product.id)
  #   get 'products/:id/purchase' => 'catalog#purchase', as: :purchase

  # Example resource route (maps HTTP verbs to controller actions automatically):
  #   resources :products

  # Example resource route with options:
  #   resources :products do
  #     member do
  #       get 'short'
  #       post 'toggle'
  #     end
  #
  #     collection do
  #       get 'sold'
  #     end
  #   end

  # Example resource route with sub-resources:
  #   resources :products do
  #     resources :comments, :sales
  #     resource :seller
  #   end

  # Example resource route with more complex sub-resources:
  #   resources :products do
  #     resources :comments
  #     resources :sales do
  #       get 'recent', on: :collection
  #     end
  #   end

  # Example resource route with concerns:
  #   concern :toggleable do
  #     post 'toggle'
  #   end
  #   resources :posts, concerns: :toggleable
  #   resources :photos, concerns: :toggleable

  # Example resource route within a namespace:
  #   namespace :admin do
  #     # Directs /admin/products/* to Admin::ProductsController
  #     # (app/controllers/admin/products_controller.rb)
  #     resources :products
  #   end
end

rake routes で定義されいているroutesを確認できます。

% rake routes
       Prefix Verb   URI Pattern                   Controller#Action
    bookmarks GET    /bookmarks(.:format)          bookmarks#index
              POST   /bookmarks(.:format)          bookmarks#create
 new_bookmark GET    /bookmarks/new(.:format)      bookmarks#new
edit_bookmark GET    /bookmarks/:id/edit(.:format) bookmarks#edit
     bookmark GET    /bookmarks/:id(.:format)      bookmarks#show
              PATCH  /bookmarks/:id(.:format)      bookmarks#update
              PUT    /bookmarks/:id(.:format)      bookmarks#update
              DELETE /bookmarks/:id(.:format)      bookmarks#destroy

models

app/models/bookmark.rb

class Bookmark < ActiveRecord::Base
end

controllers

app/controllers/bookmarks_controller.rb

class BookmarksController < ApplicationController
  before_action :set_bookmark, only: [:show, :edit, :update, :destroy]

  # GET /bookmarks
  # GET /bookmarks.json
  def index
    @bookmarks = Bookmark.all
  end

  # GET /bookmarks/1
  # GET /bookmarks/1.json
  def show
  end

  # GET /bookmarks/new
  def new
    @bookmark = Bookmark.new
  end

  # GET /bookmarks/1/edit
  def edit
  end

  # POST /bookmarks
  # POST /bookmarks.json
  def create
    @bookmark = Bookmark.new(bookmark_params)

    respond_to do |format|
      if @bookmark.save
        format.html { redirect_to @bookmark, notice: 'Bookmark was successfully created.' }
        format.json { render :show, status: :created, location: @bookmark }
      else
        format.html { render :new }
        format.json { render json: @bookmark.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /bookmarks/1
  # PATCH/PUT /bookmarks/1.json
  def update
    respond_to do |format|
      if @bookmark.update(bookmark_params)
        format.html { redirect_to @bookmark, notice: 'Bookmark was successfully updated.' }
        format.json { render :show, status: :ok, location: @bookmark }
      else
        format.html { render :edit }
        format.json { render json: @bookmark.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /bookmarks/1
  # DELETE /bookmarks/1.json
  def destroy
    @bookmark.destroy
    respond_to do |format|
      format.html { redirect_to bookmarks_url, notice: 'Bookmark was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_bookmark
      @bookmark = Bookmark.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def bookmark_params
      params.require(:bookmark).permit(:title, :description, :url)
    end
end

helpers

app/helpers/bookmarks_helper.rb

module BookmarksHelper
end

views

app/views/bookmarks/index.html.erb

<p id="notice"><%= notice %></p>

<h1>Listing Bookmarks</h1>

<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Description</th>
      <th>Url</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @bookmarks.each do |bookmark| %>
      <tr>
        <td><%= bookmark.title %></td>
        <td><%= bookmark.description %></td>
        <td><%= bookmark.url %></td>
        <td><%= link_to 'Show', bookmark %></td>
        <td><%= link_to 'Edit', edit_bookmark_path(bookmark) %></td>
        <td><%= link_to 'Destroy', bookmark, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Bookmark', new_bookmark_path %>

app/views/bookmarks/_form.html.erb

<%= form_for(@bookmark) do |f| %>
  <% if @bookmark.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@bookmark.errors.count, "error") %> prohibited this bookmark from being saved:</h2>

      <ul>
      <% @bookmark.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </div>
  <div class="field">
    <%= f.label :description %><br>
    <%= f.text_area :description %>
  </div>
  <div class="field">
    <%= f.label :url %><br>
    <%= f.text_field :url %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

app/views/bookmarks/index.json.jbuilder

json.array!(@bookmarks) do |bookmark|
  json.extract! bookmark, :id, :title, :description, :url
  json.url bookmark_url(bookmark, format: :json)
end

console

rails console で対話的に操作できます。RAILS_ROOTで以下のコメンドを実行します。

% rails console
Loading development environment (Rails 4.2.4)
irb(main):001:0>

Bookmark.last で最後に登録したBookmarkを取得します。

irb(main):001:0> Bookmark.last
  Bookmark Load (0.2ms)  SELECT  "bookmarks".* FROM "bookmarks"  ORDER BY "bookmarks"."id" DESC LIMIT 1
=> #<Bookmark id: 1, title: "sadah", description: "My portfolio site.", url: "https://sadah.github.io", created_at: "2015-12-02 00:26:31", updated_at: "2015-12-02 00:26:31">
irb(main):002:0>

このようにデータを作成することもできます。

irb(main):002:0> bookmark = Bookmark.create(title: "test", description: "test bookmark", url: "http://exapmle.com")
   (4.0ms)  begin transaction
  SQL (5.1ms)  INSERT INTO "bookmarks" ("title", "description", "url", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["title", "test"], ["description", "test bookmark"], ["url", "http://exapmle.com"], ["created_at", "2015-12-03 00:04:59.802792"], ["updated_at", "2015-12-03 00:04:59.802792"]]
   (1.0ms)  commit transaction
=> #<Bookmark id: 2, title: "test", description: "test bookmark", url: "http://exapmle.com", created_at: "2015-12-03 00:04:59", updated_at: "2015-12-03 00:04:59">
irb(main):003:0>

Active Recordのクエリについてはこちらがわかりやすいです。

debug

pryはirb(標準のRubyコンソール)に代わる、パワフルなコンソールです。

Gemfileにpryのgemを追加します。

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug'
  gem 'pry'
  gem 'pry-doc'
  gem 'pry-byebug'
  gem 'pry-rails'
  gem 'awesome_print'
end

bundle installで、gemをインストールします。

bundle install

Railsサーバが起動済みであれば、一度停止してください。

Railsサーバを起動すると、pryが有効になります。

rails s

それぞれのgemはこのような機能を持ちます。

gem
pry pry本体
pry-doc ドキュメントやメソッドを表示する
pry-byebug デバッグをできるようにする
pry-rails Railsのコンソールをpryにする
awesome_print データ構造などをわかりやすく表示する

Railsコンソールを立ち上げると、pryが起動していることがわかります。RAILS_ROOTで以下のコメンドを実行します。

% rails c
Loading development environment (Rails 4.2.4)
[1] pry(main)>

show-docでドキュメントが表示されます。

[1] pry(main)> show-doc String.nil?

From: object.c (C Method):
Owner: Kernel
Visibility: public
Signature: nil?()
Number of lines: 4

Only the object nil responds true to nil?.

   Object.new.nil?   #=> false
   nil.nil?          #=> true

show-methodでメソッドが表示されます。

[2] pry(main)> show-method String.nil?

From: object.c (C Method):
Owner: Kernel
Visibility: public
Number of lines: 5

static VALUE
rb_false(VALUE obj)
{
    return Qfalse;
}

pryでデバッグしていきます。gemを追加したのでrailsを再起動します。

app/controllers/bookmarks_controller.rb に binding.pry を追加します。

def index
  @bookmarks = Bookmark.all
  binding.pry
end

http://localhost:3000/bookmarks を表示します。railsを起動したターミナルでデバッグができます。

% rails s
=> Booting WEBrick
=> Rails 4.2.4 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
[2015-12-04 07:43:32] INFO  WEBrick 1.3.1
[2015-12-04 07:43:32] INFO  ruby 2.2.3 (2015-08-18) [x86_64-darwin14]
[2015-12-04 07:43:32] INFO  WEBrick::HTTPServer#start: pid=35325 port=3000


Started GET "/bookmarks" for ::1 at 2015-12-04 07:43:42 +0900
  ActiveRecord::SchemaMigration Load (0.5ms)  SELECT "schema_migrations".* FROM "schema_migrations"
Processing by BookmarksController#index as HTML

From: /Users/sada/git/gs/first-app/app/controllers/bookmarks_controller.rb @ line 8 BookmarksController#index:

6: def index
7:   @bookmarks = Bookmark.all
8:   binding.pry
=> 9: end

インスタンス変数などを表示できます。

[1] pry(#<BookmarksController>)> @bookmarks
  Bookmark Load (1.2ms)  SELECT "bookmarks".* FROM "bookmarks"
[
    [0] #<Bookmark:0x007feb883cbfb0> {
                 :id => 1,
              :title => "sadah",
        :description => "My portfolio site.",
                :url => "https://sadah.github.io",
         :created_at => Wed, 02 Dec 2015 00:26:31 UTC +00:00,
         :updated_at => Wed, 02 Dec 2015 00:26:31 UTC +00:00
    },
    [1] #<Bookmark:0x007feb883cbc90> {
                 :id => 2,
              :title => "test",
        :description => "test bookmark",
                :url => "http://exapmle.com",
         :created_at => Thu, 03 Dec 2015 00:04:59 UTC +00:00,
         :updated_at => Thu, 03 Dec 2015 00:04:59 UTC +00:00
    }
]

helpと入力するとpryの使い方が表示されます。

~/.pryrc にこんな感じの設定を書いておくと便利です。

# http://qiita.com/Linda_pp/items/d75d7c3953faa34a1f0e
begin
  require "awesome_print"
  AwesomePrint.pry!
rescue LoadError => err
  puts "no awesome_print :("
end

# http://morizyun.github.io/blog/pry-tips-rails-ruby/
# pry-debuggerのショートカット
Pry.commands.alias_command 'c', 'continue'
Pry.commands.alias_command 's', 'step'
Pry.commands.alias_command 'n', 'next'

またエラーが発生した場合、エラーが表示された画面でデバッグを行うこともできます。

課題

今週の課題です。

  • link_toを使ってURLをリンクにしてみましょう。
  • タイトルとURLを必須項目にしましょう。

さらに頑張りたい方はbookmarkにコメントできるようにしましょう。 ざっくりとした流れはこんな感じです。

  • comment(user_name, content, bookmark_id)をscaffoldで作成する
  • has_many, belongs_toで関連をつける
  • bookmark#showでコメントを表示する
  • bookmark#showでコメントを新規登録できるようする