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

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

Ruby on Rails Guides

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>

第222回TOEIC結果

結果

score: 765

  • listening: 405 (+0)
  • reading: 360 (-5)

スコア推移

f:id:tic40:20170814210304p:plain

感想

あかn

次回受験

2017/09/10 第223回

Cybozu Groon API から当日の予定を取得するPHPスクリプトを書いた

Cybozu Garoon API

Cybozu Garoon を社内で使っているんだけど、APIが公開されていることをつい最近知った。

developer.cybozu.io

サイボウズ上の当日の予定をAPI経由で取得できるので、 毎朝チームのチャットグループに自分のスケジュールを自動投稿する、とか面白いんじゃないかな。

スクリプト

サイボウズのGaroon APISOAP方式で実装されている。XML辛い…

PHPSOAPのリクエストどう書けばいいのかわからんので、ヒアドキュメントに全部突っ込む形の使い捨てスクリプトを書いた。*1

上記スクリプト内では、スケジュールのタイトルしか取得していないが、 当然スケジュールの時間、詳細情報、コメントなども取得できる。

サイボウズ導入してる会社は多いと思うので何かの役に立てば。

*1:アノテーションとかコード全体的に汚いのは自分しか使わない前提で書いたスクリプトなので勘弁

#20/20 Bit Manipulation: Lonely Integer [Cracking the Coding Interview Challenges]

#20 Bit Manipulation: Lonely Integer

www.hackerrank.com

入力される数は、1つの数を除いて、ペアになっている。

入力される数の中で、ペアになっていない数字を見つけて出力する問題。

solution

XOR(排他的論理和)を使うことで、ペアになっていない数字を簡単に見つけられる。

以下rubyでの解。

input = $stdin.read.split("\n")
n = input.shift
nums = input.shift.split(" ").map(&:to_i)
puts nums.inject {|result, num| result ^ num }

github.com

排他的論理和

排他的論理和 - Wikipedia

感想

Cracking the Coding Interview Challenges 20題全てコンプリートした。

平日毎朝出社前に1題解くのを目標にゆっくり取り組んでいたので、20日程度。

やってみると、2,30分悩んでしまう問題もあったり、案外すんなり解けなかったりもした。

英語の出題分を理解するのもなかなか気合が要るので、これはよい復習になった。

なんでも良いんだけど、最近はこういうコツコツと日々積み上げていくことの大事さをひしひしと感じている。

#19/20 Recursion: Davis' Staircase [Cracking the Coding Interview Challenges]

#19 Recursion: Davis' Staircase

www.hackerrank.com

1 o 2 or 3段を一度に登れる人間が、n段の階段を登る際に、何通りの登り方が存在するか計算する問題。

以下のような入力が与えられる。

3
1
3
7

最初の行が階段の数。2行目以下は、階段のステップ数。 それぞれの階段について、何通りの登り方があるかを出力する。

上記入力の場合、出力は以下となる。

1
4
44

solution

再帰関数を使って解く。これもメモ化をすることでテストケースをパスすることができた。 以下rubyでの解。

#!/bin/ruby

class Solution
    
    def initialize(n, stepsATime)
        @steps, @stepsATime, @memo = n, stepsATime, []
    end
    
    def climbingWays(current = 0)
        return @memo[current] if @memo[current]
        return 0 if current > @steps
        return 1 if current == @steps
        
        ways = 0
        @stepsATime.each do |v|
            ways += climbingWays(current + v)
        end
        return @memo[current] = ways
    end
end

input = $stdin.read.split("\n").map(&:to_i)
input.shift.times do
    puts Solution.new(input.shift, [1,2,3]).climbingWays
end

github.com

再帰とは

再帰 - Wikipedia

#18/20 Recursion: Fibonacci Numbers [Cracking the Coding Interview Challenges]

#18 Recursion: Fibonacci Numbers

www.hackerrank.com

入力される数値nに対して、n項のフィボナッチ数を求めて出力する問題。

solution

問題文の通りに実装すると以下のようになる。

def fib(n)
    return n if n <= 1
    return fib(n-1)+fib(n-2)
end
puts fib(gets.to_i)

これは正しいが、計算量が大きくなり、テストケースをパスできない。

今回はメモ化することでフィボナッチ計算を高速化する。 以下rubyでの解答。

class Solution
    def initialize
        @memo = [0, 1]
    end
    
    def fib(n)
        return if n < 0
        return @memo[n] if @memo[n]
        return @memo[n] = self.fib(n - 1) + self.fib(n - 2)
    end
end
puts (Solution.new).fib(gets.to_i)

github.com

フィボナッチ数とは

フィボナッチ数 - Wikipedia

#17/20 Time Complexity: Primalityh [Cracking the Coding Interview Challenges]

#17 Time Complexity: Primality

www.hackerrank.com

素数判定を行う問題。 以下のような入力が与えられる。

3
12
5
7

一行目はデータの数。それ以降の数に対して、素数かどうか判定を行い、素数であれば'Prime'、そうでなければ'Not prime'を出力する。 上記の場合の出力は以下となる。

Not prime
Prime
Prime

solution

単純な試し割りではタイムアウトでテストケースをパスできない。

試し割りよりも高速なエラトステネスの篩アルゴリズムで解く。 以下はrubyでの解。

def isPrime(num)
    return true if num == 2
    return false if num <= 1 || num % 2 == 0
    ary = (3..Math.sqrt(num).floor).to_a

    while val = ary.shift
        return false if num % val == 0
        ary.delete_if{ |v| v % val == 0 }
    end
    return true
end

input = $stdin.read.split("\n").map(&:to_i)
n = input.shift
n.times { puts isPrime(input.shift) ? 'Prime' : 'Not prime' }

github.com

素数判定について

素数判定 - Wikipedia

エラトステネスの篩とは

エラトステネスの篩 - Wikipedia

計算量(ランダウ記号)について

ランダウの記号 - Wikipedia

計算量算出については、この記事が非常に優しかった。 [初心者向け] プログラムの計算量を求める方法 by @cotrpepe on @Qiita http://qiita.com/cotrpepe/items/1f4c38cc9d3e3a5f5e9c