Vagrantを再起動したら既存のVMが消えた

現象

Mac上で起動しているVagrantVM内で作業していたところ、バッテリー切れでMacが強制シャットダウンした。 その後、起動して、 Vagrant up でVagrant を起動したところ、作業していたVMではなく、新規VMが立ち上がるようになってしまった。

作業途中のファイルが失われてしまい、とても困った。

復旧する

この方法で以前のVMを起動することができた。

elm-arata.hatenablog.com

VirutalBoxのコマンドラインインターフェース VBoxManage を使って仮想マシンの情報を表示する。

$ VBoxManage list vms
"aaa_1491870543558_52578" {945beac3-3920-4eec-947e-bfce8e017aaa}
"bbb_1493466062889_97138" {462256fa-197f-477d-b1ff-7cc74e401bbb}
"ccc_1505786401940_15041" {9c2a6b5c-7cce-46e3-b60a-76e3e1324ccc}

作成した日時の昇順に表示されるので、一番下が直近に作成したVM。 一番下は、新規作成されたVMなので、1つ前の、bbb_1493… のVMを起動するように設定し直す。

以下のファイルを開き、VMのUID()を置き換える。

$ vi .vagrant/machines/default/virtualbox/id
462256fa-197f-477d-b1ff-7cc74e401bbb

VMを再起動

$ vagrant reload

復旧できた。やったぜ。

vue-cliで作成したアプリケーションをサブディレクトリ上に展開する

vue-cli

github.com

やりたいこと

vue-cliで作成したアプリケーションをサブディレクトリ /test-dir/ に展開したい。

問題

デフォルトの設定でプロダクションビルドし、

$ npm run build

webサーバから {vue-cli-project}/dist/ を参照するようにしたが、このままではパスの問題でアプリケーションが動かなかった。

解決

config/index.js に assetsPublicPath という設定項目がある。この値をvue-cliアプリケーションのルートとなるサブディレクトリパスに書き換えて、 再度ビルドすれば良い。

assetsPublicPath: '/test-dir/',

さらに、vue-router を使ってルーティング設定を行っている場合は、そちらにも設定が必要となる。 コンストラクタオプションの'base' に、サブディレクトリの設定を行う。

ルーターコンストラクタオプション · vue-router

new Router({
  base: '/test-dir/',
  ...
})

Atomでリモートのファイルを直接編集する

先日社内のデザイナの作業を見てたら、ローカルでファイルを編集して、それをVMFTPでアップロードしていたので、 VM上で直接編集したら?っていう話になった。

VimやEで始まる黒い画面はさすがにデザイナにはおすすめできないので、Atomあたり使ってるならプラグインリモートホストに接続できるんじゃないかなーと思い調べてみた。

atom.io

いくつかプラグインを調べて試した感じ、Remote-FTPが一番良かった。 SFTP接続して直接ファイル編集、さらにリモート上のファイルツリーを表示できる。

atom.io

特に導入は難しくない。

まず、Remote-FTPをインストールする。

f:id:tic40:20170907090307p:plain

インストールされた状態。

f:id:tic40:20170907090302p:plain

もしエディタの右下に赤いマークがついていたら、Rebuildが必要なのでRebuildする。

f:id:tic40:20170907090310p:plain

あとは、以下の手順でFTP接続設定を進めればOK。

  1. Open an existing project or create a new one (File -> Open folder…)
  2. Open remote-ftp sidebar (Packages -> Remote-FTP -> Toggle)
  3. Create a configuration file for your project (Packages -> Remote-FTP -> Create (s)FTP config file)
  4. Once connected you should be seeing the content of the remote connection
  5. All basic commands (connect, disconnect, …) are available from the sidebar context menu and the Command Palette

API blueprintでAPIドキュメントを書く

APIを作った際にはAPIドキュメントを必ず書かなくてはならない。 API blueprintでさくっと書くのがおすすめ。

デモ

https://tic40.github.io/api-blueprint/public/v1.html

ソースコード

github.com

書き方

まずAPI blueprint 記法でAPIドキュメントを書く。 API blueprintはMarkdownを拡張したものなので、基本的にMarkdownの記法が使える。敷居は高くない。

API Blueprint | API Blueprint

実際にデモ用に書いたソースファイルはこんな感じ

https://raw.githubusercontent.com/tic40/api-blueprint/master/blueprints/v1/apis/tests.md

これだとただのMarkdownで読みにくいので、aglioというレンダーを使って、綺麗なhtmlに書き出そう。

github.com

最後に書き出されたhtmlをWebで共有しておしまい。 業務でExcel管理だったのをこの形式に変えてから、結構評判は良い。

採用試験でコーディングスキルチェックを受けた

先日とある会社の採用試験でオンラインコーディングスキルチェックを受けた。 それがなかなか面白かった。

内容は、サーバーサイド(PHP/Ruby(Rails)/Database) に関する問題で、全9題、試験時間は1時間45分程度、出題は全て英語。

最初の3題ぐらいはPHPでのコーディング(クラス設計)問題。 コーディング問題は、ideone.com のようなUIで、テキストエリアにコードを書いてSubmitするという形式。Submitすると次の問題へ移動する。当然制限時間は各題に設けられており、制限時間がくると強制的に次の問題へ移動する。

以下、コーディング問題の一例。 提示された仕様書を読み取り、コードを作成、用意されたテストケースを全てパスすればOK。 出題内容は伏せるが、だいたいコードから雰囲気でわかると思う。

<?php

class Solution
{
    private $categories = [];

    /**
     * @param string $category
     * @param string @parent
     * @throw InvalidArgumentException
     */
    public function addCategory($category, $parent) {
        if (isset($this->categories[$category])) {
            throw new InvalidArgumentException;
        }
        if ($parent === null) { $parent = ''; }
        $this->categories[$category] = $parent;
    }

    /**
     * @param string @category
     * @return array
     */
    public function getCategory($category) {
        $ret = [];
        if (isset($this->categories[$category])) {
            foreach ($this->categories as $k => $v) {
                if ($v === $category) {
                    $ret[] = $k;
                }
            }
        }
        return $ret;
    }
}

$s = new Solution();
$s->addCategory('A', null);
$s->addCategory('B', 'A');
$s->addCategory('C', 'A');
$s->addCategory('D', 'B');
$s->addCategory('E', 'B');
echo implode(',', $s->getCategory('B')); # D, E

競プロのようなアルゴリズム系、パズル系問題というよりは、実務寄りな内容だった。 地頭の良さ、というより実務寄りな能力を重視しているということだろうか。

PHPが終わると、Ruby(Rails)の問題へ。 表示されているソースコードからバグを見つける、なんていう問題や、Active Recordに関する問題など。

最後の3-4題ぐらいはDB、SQL知識を問う問題。

SQLについては、普段簡単あまりテクニカルなSQLを書くことがないので、慌ててSQLのテスト環境を作ったりして確認しながら確認した。 集計系のSQLが苦しかったので、ちゃんと勉強しとかなきゃなあ。

SQL以外ではDBのパフォーマンスに関する問いが多かった印象。

感想

  • 色々時間がなかったので会社帰りに某チェーンのカフェ*1で受験した。これは失敗だった。ネット途切れる、うるさくて集中できない。ちゃんと家で受けよう。
  • SQLに関する出題はありえるので、手元に実行環境を予め用意しておくと確認が捗る。
  • 英語出題、アルゴリズム問題に関しては、http://hackerrank.com/ あたりで慣れておくと良さそう。

エンジニア転職活動中です。

Ruby on Rails Guides(v5.1.3) Getting Started with Railsを読む#2

これは、最新のRuby on Rails ガイド(v5.1.3) を読んだ際の学習記録です。

#1はこちら Ruby on Rails Guides(v5.1.3) Getting Started with Railsを読む#1 - Do Something

Adding a Second Model

Articleにつづいて、2つめのmodelを追加しよう。

Generating a Model

$ rails generate model Comment commenter:string body:text article:references
Running via Spring preloader in process 19903
      invoke  active_record
      create    db/migrate/20170817114832_create_comments.rb
      create    app/models/comment.rb
      invoke    test_unit
      create      test/models/comment_test.rb
      create      test/fixtures/comments.yml

生成された app/models/comment.rb を見てみよう。

class Comment < ApplicationRecord
  belongs_to :article
end

belongs_to :article という記述がある。これはアクティブレコードの関連付けを表している。db:migrateをコマンドを実行してみよう。

$ rails db:migrate

db/schema.rb を見てみよう。以下のように変更されている。

ActiveRecord::Schema.define(version: 20170817114832) do

  create_table "articles", force: :cascade do |t|
    t.string "title"
    t.text "text"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "comments", force: :cascade do |t|
    t.string "commenter"
    t.text "body"
    t.integer "article_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["article_id"], name: "index_comments_on_article_id"
  end

end

Associating Models

Active Record Associationを使用すると、2つのモデル間の関係を簡単に宣言できる。 コメントや記事の場合は、このように関係を書き出すことができます。

  • Each comment belongs to one article.
  • One article can have many comments.

app/models/article.rb へも関係を表すための記述を追加する必要がある。以下のようにhas_manyを追加しよう。

class Article < ApplicationRecord
  has_many :comments
  validates :title, presence: true,
                    length: { minimum: 5 }
end

Adding a Route for Comments

config/routes.rb を開き、以下のように編集する。

resources :articles do
  resources :comments
end

Generating a Controller

$ rails generate create controller Comments
      create  app/controllers/comments_controller.rb
      invoke  erb
      create    app/views/comments
      invoke  test_unit
      create    test/controllers/comments_controller_test.rb
      invoke  helper
      create    app/helpers/comments_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/comments.coffee
      invoke    scss
      create      app/assets/stylesheets/comments.scss

app/views/articles/show.html.erb

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>
 
<h2>Add a comment:</h2>
<%= form_for([@article, @article.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br>
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br>
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>
 
<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>

create in app/controllers/comments_controller.rb

class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end
 
  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end
end

app/views/articles/show.html.erb

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>
 
<h2>Comments</h2>
<% @article.comments.each do |comment| %>
  <p>
    <strong>Commenter:</strong>
    <%= comment.commenter %>
  </p>
 
  <p>
    <strong>Comment:</strong>
    <%= comment.body %>
  </p>
<% end %>
 
<h2>Add a comment:</h2>
<%= form_for([@article, @article.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br>
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br>
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>
 
<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>

Refactoring

app/views/articles/show.html.erb をpartialsファイルを作成することで簡潔にする。

Rendering Partial Collections

app/views/comments/_comment.html.erb を作成

<p>
  <strong>Commenter:</strong>
  <%= comment.commenter %>
</p>
 
<p>
  <strong>Comment:</strong>
  <%= comment.body %>
</p>

app/views/articles/show.html.erb

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>
 
<h2>Comments</h2>
<%= render @article.comments %>
 
<h2>Add a comment:</h2>
<%= form_for([@article, @article.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br>
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br>
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>
 
<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>

これでコメント毎にapp / views / comments / _comment.html.erb をレンダーされる。

Rendering a Partial Form

app/views/comments/_form.html.erb を作成する。

<%= form_for([@article, @article.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br>
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br>
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>

app/views/articles/show.html.erb

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>
 
<h2>Comments</h2>
<%= render @article.comments %>
 
<h2>Add a comment:</h2>
<%= render 'comments/form' %>
 
<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>

Deleting Comments

スパムコメントの削除機能。

app/views/comments/_comment.html.erb

<p>
  <strong>Commenter:</strong>
  <%= comment.commenter %>
</p>
 
<p>
  <strong>Comment:</strong>
  <%= comment.body %>
</p>
 
<p>
  <%= link_to 'Destroy Comment', [comment.article, comment],
               method: :delete,
               data: { confirm: 'Are you sure?' } %>
</p>

app/controllers/comments_controller.rb へ destroyアクションを追加する。

class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end
 
  def destroy
    @article = Article.find(params[:article_id])
    @comment = @article.comments.find(params[:id])
    @comment.destroy
    redirect_to article_path(@article)
  end
 
  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end
end

Deleting Associated Objects

記事が削除されるとき、関連しているコメントも削除する必要がある。 以下のように dependentオプションを利用する。

app/models/article.rb

class Article < ApplicationRecord
  has_many :comments, dependent: :destroy
  validates :title, presence: true,
                    length: { minimum: 5 }
end

Security

Basic Authentication

Rails はHTTP認証システム備えている。 Railsのhttp_basic_authenticate_withメソッドを使用する。

app/controllers/articles_controller.rb

http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]

これで、index, show 以外のアクションを実行する際に、basic認証が表示されるようになる。 destroyだけに認証をかける場合は以下のようにする。

http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy

Getting Started with Rails は以上。

Ruby on Rails Guides(v5.1.3) Getting Started with Railsを読む#1

これは、最新のRuby on Rails ガイド(v5.1.3) を読んだ際の学習記録です。

Getting Started with Rails — Ruby on Rails Guides

#1 Start Here

以下を学ぶ

  • Railsインストール、Railsアプリケーションの作成、そしてアプリケーションをデータベースへ接続する方法
  • 一般的なRailsアプリケーションのレイアウト
  • MVC, RESTfulデザインの基本原則
  • Railsアプリケーションを素早く生成する方法

What is Rails?

RailsRubyで書かれたWebアプリケーションフレームワークRailsには以下の2つの重要な原則がある。

  • Don’t Repeat Yourself

Don't repeat yourself - Wikipedia

Don’t repeat yourself (DRY) あるいはSingle Source of truth(英)[要出典]は、特にコンピューティングの領域で、重複を防ぐ考え方である。この哲学は、情報の重複は変更の困難さを増大し透明性を減少させ、不一致を生じる可能性につながるため、重複するべきでないことを強調する。

  • Convention Over Configuration

設定より規約 - Wikipedia

設定より規約(せっていよりきやく、英: convention over configuration)とは、開発者の決定すべきことを減少させ、単純にするが柔軟性は失わせないというソフトウェア設計パラダイム

Creating a New Rails Project

Creating the Blog Application

‘blog’ アプリケーションを生成する。

$ rails new blog

blog配下には以下のファイルを生成される。

  • app/ Contains the controllers, models, views, helpers, mailers, channels, jobs and assets for your application. You’ll focus on this folder for the remainder of this guide.
  • bin/ Contains the rails script that starts your app and can contain other scripts you use to setup, update, deploy or run your application.
  • config/ Configure your application’s routes, database, and more. This is covered in more detail in Configuring Rails Applications. config.ru Rack configuration for Rack based servers used to start the application.
  • db/ Contains your current database schema, as well as the database migrations.
  • Gemfile
  • Gemfile.lock These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see the Bundler website.
  • lib/ Extended modules for your application.
  • log/ Application log files.
  • public/ The only folder seen by the world as-is. Contains static files and compiled assets.
  • Rakefile This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing Rakefile, you should add your own tasks by adding files to the lib/tasks directory of your application.
  • README.md This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.
  • test/ Unit tests, fixtures, and other test apparatus. These are covered in Testing Rails Applications.
  • tmp/ Temporary files (like cache and pid files).
  • vendor/ A place for all third-party code. In a typical Rails application this includes vendored gems.
  • .gitignore This file tells git which files (or patterns) it should ignore. See Github - Ignoring files for more info about ignoring files.

Hello, Rails!

Starting up the Web Server

web serverを起動する。

$ cd blog
$ rails server
#shorthand: rails s 

Say “Hello”, Rails

Railsで"Hello" を表示させるために、viewとcontrollerを生成する。

‘Welcome’ controllerの'index' アクションを生成する。

$ rails generate controller Welcome index

上記のgenerateコマンドで以下のファイル群が生成される。

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
invoke  assets
invoke    coffee
create      app/assets/javascripts/welcome.coffee
invoke    scss
create      app/assets/stylesheets/welcome.scss

controllerは、app/controllers/welcome_controller.rb, viewはapp/views/welcome/index.html.erb へ生成された。

app/views/welcome/index.html.erb のh1タグを以下のように修正する。

<h1>Hello, Rails!</h1>

Setting the Application Home Page

Railsのroot を設定する。

config/routes.rb へ以下を追加する。

  root 'welcome#index'

http://localhost:3000 へアクセスすると、Hello, Railsが表示される。

Getting Up and Running

CRUD操作を実現するためのresourceを定義する。

以下をconfig/routes.rb へ追加。

  resources :articles

rails routes コマンドを実行すると、標準のRESTfulアクションが定義される。

$ rails routes
       Prefix Verb   URI Pattern                  Controller#Action
welcome_index GET    /welcome/index(.:format)     welcome#index
     articles GET    /articles(.:format)          articles#index
              POST   /articles(.:format)          articles#create
  new_article GET    /articles/new(.:format)      articles#new
 edit_article GET    /articles/:id/edit(.:format) articles#edit
      article GET    /articles/:id(.:format)      articles#show
              PATCH  /articles/:id(.:format)      articles#update
              PUT    /articles/:id(.:format)      articles#update
              DELETE /articles/:id(.:format)      articles#destroy
         root GET    /                            welcome#index

Laying down the groundwork

生成した、http://localhost:3000/articles/new へアクセスしてみよう。'Routing Error'が出る。 これはrouteは生成したが、まだcontroller、viewが生成されてないため。

コントローラーを生成する。

$ rails generate controller Articles

生成した、app/controllers/articles_controller.rb を見てみよう。

class ArticlesController < ApplicationController
end

ApplicationController を継承した、ArticlesControllerが定義されているが、まだ中には何も定義されていない。

この状態で先程のURIへアクセスしてみよう。

http://localhost:3000/articles/new

‘Unknown action’ エラーが出る。

app/controllers/articles_controller.rb を以下のように更新する。

class ArticlesController < ApplicationController
  def new
  end
end

この状態で先程のURIへアクセスしてみよう。

http://localhost:3000/articles/new

ActionController::UnknownFormat in ArticlesController#new’ 今度は先程と別のエラーが出る。 viewがないためこのようなExceptionがなげられているため、次のviewテンプレートを新規作成する。

app/views/articles/new.html.erb

<h1>New Article</h1>

これでlocalhost:3000/articles/new へアクセスすると正常にページが表示される。

The first form

Railsのフォームビルダーは form_for と呼ぶヘルパーメソッドで提供される。

form_for: ActionView::Helpers::FormHelper

app/views/articles/new.html.erb へ以下を追加する。

<%= form_for :article 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 %>

生成されたhtmlを見ると、formのアクション属性が /articles/new を指している。 これを、/articles へ移動するように変更するため、form_forを以下のように書き換える。

<%= form_for :article, url: articles_path do |f| %>

ここで、rails routes コマンドでRailsのアクションを振り返ってみる。

$ rails routes
       Prefix Verb   URI Pattern                  Controller#Action
welcome_index GET    /welcome/index(.:format)     welcome#index
     articles GET    /articles(.:format)          articles#index
              POST   /articles(.:format)          articles#create
  new_article GET    /articles/new(.:format)      articles#new
 edit_article GET    /articles/:id/edit(.:format) articles#edit
      article GET    /articles/:id(.:format)      articles#show
              PATCH  /articles/:id(.:format)      articles#update
              PUT    /articles/:id(.:format)      articles#update
              DELETE /articles/:id(.:format)      articles#destroy
         root GET    /                            welcome#index

POST /articles のアクションは create となっているため、画面のsave articleボタンを押すと ‘Unknown action’ エラーとなる。

Creating articles

‘Unknown action’ エラーを取り除くため、create アクションをコントローラへ追加する必要がある。

app/controllers/articles_controller.rb

class ArticlesController < ApplicationController
  def new
  end
 
  def create
  end
end

このままだと 204 No Content エラーが出てしまう。 フォームがsubmitされるとパラメータがRailsに送信されるので、以下のようにして表示してみよう。

def create
  render plain: params[:article].inspect
end

これで再度画面の'save submit' を押すと以下が表示される。title, text はformに入力した値が反映される。

<ActionController::Parameters {"title"=>"First Article!", "text"=>"This is my first article."} permitted: false>

Creating the Article model

次のコメンドでArticleモデルを生成する。モデルには単数形が使われる。

$ rails generate model Article title:string text:text
Running via Spring preloader in process 12909
      invoke  active_record
      create    db/migrate/20170815123358_create_articles.rb
      create    app/models/article.rb
      invoke    test_unit
      create      test/models/article_test.rb
      create      test/fixtures/articles.yml

このコマンドは、RailsにArticleモデルを共にstring型のtitle, text型のtextの属性を生成する。 これらの属性は自動的にarticlesテーブルに追加され、さらにArticleモデルにマッピングされる。

Active Recordは列名をモデル属性に自動的に対応させる。つまり、Railsモデル内で属性を宣言する必要はない。これは、Active Recordによって自動的に行われる。

Running a Migration

生成された db/migrate/20170815123358_create_articles.rb

class CreateArticles < ActiveRecord::Migration[5.0]
  def change
    create_table :articles do |t|
      t.string :title
      t.text :text
 
      t.timestamps
    end
  end
end

次のコマンドで、articles テーブルを実際にデータベースへ生成する。

$ rails db:migrate

Saving data in the controller

ArticlesControllerへ戻り、submitしたデータをデータベースへ保存するためにcreateアクションを変更する。

app/controllers/articles_controller.rb

def create
  @article = Article.new(params[:article])
 
  @article.save
  redirect_to @article
end

実際に画面から'save article'を押すと ‘ActiveModel::ForbiddenAttributesError in ArticlesController#create’ エラーが表示される。 Railsが備えているセキュリティ機能が働いているため、コントローラーにどのパラメータが許可されているか伝える必要がある。

title と text を許可してみよう。

app/controllers/articles_controller.rb

def create
  @article = Article.new(article_params)
 
  @article.save
  redirect_to @article
end
 
private
  def article_params
    params.require(:article).permit(:title, :text)
  end

article_params は外部から意図せず呼ばれることを防ぐため、基本はprivateメソッドとして定義される。

Showing Articles

再度画面から、'Save Article'を押してみよう。すると、show アクションがないため以下のエラーが表示される。

Unknown action
The action 'show' could not be found for ArticlesController

rails routes で確認。

$ rails routes
...
article GET    /articles/:id(.:format)      articles#show

show アクションをコントローラへ追加しよう。

app/controllers/articles_controller.rb

  def show
    @article = Article.find(params[:id])
  end

続いてshowアクションのためのviewを追加する。

app/views/articles/show.html.erb

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

再度画面から'Save Article'を押してみると、以下が画面に表示される。

Title: test title

Text: test Text

Listing all articles

記事リストページを追加する。

$ rails routes
articles GET    /articles(.:format)          articles#index

app/controllers/articles_controller.rb

  def index
    @articles = Article.all
  end

app/views/articles/index.html.erb

<h1>Listing articles</h1>

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

  <% @articles.each do |article| %>
    <tr>
      <td><%= article.title %></td>
      <td><%= article.text %></td>
      <td><%= link_to 'Show', article_path(article) %></td>
    </tr>
  <% end %>
</table>

Adding links

記事のcreate, show, list が作成できたので、今度は記事へナビゲートするためのリンクを追加してみよう。

app/views/welcome/index.html.erb

<h1>Hello, Rails!</h1>
<%= link_to 'My Blog', controller: 'articles' %>

link_to メソッドはRailsのview helperの1つ。articles へのリンクを生成する。

app/views/articles/index.html.erb の

タグの上に以下を追加してみよう。

<%= link_to 'New article', new_article_path %>

上記は新しい記事作成ページへのリンクとなる。

次に indexアクションへのbackリンクを追加してみよう。

app/views/articles/new.html.erb

<%= form_for :article, url: articles_path do |f| %>
  ...
<% end %>
 
<%= link_to 'Back', articles_path %>

最後にshowページにもbackボタンを追加してみよう。 app/views/articles/show.html.erb

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>
 
<%= link_to 'Back', articles_path %>

Adding Some Validation

app/models/article.rb ファイルを見てみよう

class Article < ApplicationRecord
end

Article クラスは ApplicationRecord を継承している。 さらにApplicationRecordクラスは、ActiveRecord::Baseを継承しており、様々な機能が使えるようになっている。

Railsはモデルに送信するデータのバリデーションを行うメソッドを備えている。以下のようにarticle.rbモデルを変更してみよう。

class Article < ApplicationRecord
  validates :title, presence: true,
                    length: { minimum: 5 }
end

この変更により、すべてのArticleのタイトルが5文字以上になるようになります。

不正なタイトルで@article.saveを呼び出すと、falseが返されるようになるので、 @article.save の結果をチェックするコードを追加しよう。

app/controllers/articles_controller.rb

def new
  @article = Article.new
end
 
def create
  @article = Article.new(article_params)
 
  if @article.save
    redirect_to @article
  else
    render 'new'
  end
end
 
private
  def article_params
    params.require(:article).permit(:title, :text)
  end

上記の変更で@article.save がfalseを返す場合は、 new アクションへ遷移するようになった。 ただ、これだけでは不十分なので、app/views/articles/new.html.erb を修正して、ユーザにエラーを通知しよう。

app/views/articles/new.html.erb

<%= form_for :article, url: articles_path do |f| %>
 
  <% if @article.errors.any? %>
    <div id="error_explanation">
      <h2>
        <%= pluralize(@article.errors.count, "error") %> prohibited
        this article from being saved:
      </h2>
      <ul>
        <% @article.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', articles_path %>

ArticlesControllerに@article = Article.newを追加した理由は、@articleがビューでnilとなり、@article.errors.any?でエラーが発生するため。

Updating Articles

CRUDのreadとcreateについては学んだので、次のはupdateを行う。

edit メソッドをコントローラーへ追加する。

app/controllers/articles_controller.rb

def edit
  @article = Article.find(params[:id])
end

editテンプレートファイルを作成する。

app/views/articles/edit.html.erb

<h1>Edit article</h1>
 
<%= form_for(@article) do |f| %>
 
  <% if @article.errors.any? %>
    <div id="error_explanation">
      <h2>
        <%= pluralize(@article.errors.count, "error") %> prohibited
        this article from being saved:
      </h2>
      <ul>
        <% @article.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', articles_path %>

app/controllers/articles_controller.rb

def update
  @article = Article.find(params[:id])
 
  if @article.update(article_params)
    redirect_to @article
  else
    render 'edit'
  end
end

app/views/articles/index.html.erb

<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
    <th colspan="2"></th>
  </tr>
 
  <% @articles.each do |article| %>
    <tr>
      <td><%= article.title %></td>
      <td><%= article.text %></td>
      <td><%= link_to 'Show', article_path(article) %></td>
      <td><%= link_to 'Edit', edit_article_path(article) %></td>
    </tr>
  <% end %>
</table>

app/views/articles/show.html.erb

<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>

Using partials to clean up duplication in views

partialファイルを作成し、コードの重複を排除する。 partialファイルには慣例として、ファイル名の先頭に ‘_’ をつける。

app/views/articles/_form.html.erb

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

app/views/articles/new.html.erb ファイルを更新してみよう。

<h1>New article</h1>
 
<%= render 'form' %>
 
<%= link_to 'Back', articles_path %>

app/views/articles/edit.html.erb も同様に更新する

<h1>Edit article</h1>
 
<%= render 'form' %>
 
<%= link_to 'Back', articles_path %>

Deleting Articles

articleを削除するDELETEメソッドを作成する。

DELETE /articles/:id(.:format)      articles#destroy

app/controllers/articles_controller.rb

def destroy
  @article = Article.find(params[:id])
  @article.destroy
 
  redirect_to articles_path
end

app/views/articles/index.html.erb

<h1>Listing Articles</h1>
<%= link_to 'New article', new_article_path %>
<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
    <th colspan="3"></th>
  </tr>
 
  <% @articles.each do |article| %>
    <tr>
      <td><%= article.title %></td>
      <td><%= article.text %></td>
      <td><%= link_to 'Show', article_path(article) %></td>
      <td><%= link_to 'Edit', edit_article_path(article) %></td>
      <td><%= link_to 'Destroy', article_path(article),
              method: :delete,
              data: { confirm: 'Are you sure?' } %></td>
    </tr>
  <% end %>
</table>