Rails覚え書き

  • 7
    Like
  • 0
    Comment

概要

Ruby on Rails ガイド (5.0 対応)を元に覚え書きを書く。

Railsとは

  • Rubyプログラミング言語で書かれたWebアプリケーションフレームワーク
    • デンマークのプログラマ David Heinemeier Hansson(DHH)が作った
    • 他の多くの言語によるWebアプリケーションフレームワークと比較して、アプリケーションを作成する際のコード量がより少なくて済む
      • それにもかかわらず、多くの機能が実装できる
  • Railsの哲学
    • 同じことを繰り返すな (Don't Repeat Yourself: DRY)
      • システムを構成する知識のあらゆる部品は、常に単一であり、明確であり、信頼できる形で表現されていなければならない
      • DRYを守ることによってコードに変更が生じた際に、何か所も修正する必要がなくなる
    • 設定より規約(Convention over Configuration: CoC)
      • データベースのテーブル名はモデル名の複数形
        • 例) モデル:Employee, テーブル名:employees
        • /employeesというURLは社員の一覧を表す
        • 社員ID:1の社員を表すURLは/employees/1
      • 規約に従うことにより設定項目を省略できる
      • 他のエンジニアと共通のルールでコミュニケーションがとれる
    • (Representational State Transfer: REST)
      • すべてのリソースに一意となる識別子(URI)がある
      • URIを通してリソースを操作する手段を提供する
        • /employees/1 : リソース
        • /employees/1にHTTPのGETメソッドでアクセス : 情報の取得処理
        • /employees/1にDELETEメソッドでアクセス : 社員情報の削除

階層構造

.
|-- Gemfile  ### Railsアプリケーションで必要となるgemの依存関係を記述
|-- Gemfile.lock
|-- README.rdoc
|-- Rakefile   ### コマンドラインから実行できるタスクを記述。なるべくlib/tasksフォルダの下にRake用のファイルを追加する。
|-- 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   ### 従来bundle exec railsとしていたものをここから直接実行できる
|   |-- rake
|   `-- spring
|-- config   ### 設定ファイルを配置
|   |-- application.rb
|   |-- boot.rb
|   |-- database.yml
|   |-- environment.rb
|   |-- environments   ### 環境(RAILS_ENV)毎のRailsの設定を記述
|   |   |-- development.rb
|   |   |-- production.rb
|   |   `-- test.rb
|   |-- initializers   ### Rails起動時に読み込まれる
|   |   |-- 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   ### アプリケーションの起動に必要となる、Rackベースのサーバー用のRack設定ファイルです。
|-- db           ### データベース関連ファイルの配置
|   `-- seeds.rb
|-- lib     ### gem以外のライブラリ、アプリケーションで使用する拡張モジュールを配置。
|   |-- assets
|   `-- tasks
|-- log     ### ログが出力される
|-- public   ### assetとして管理しない静的ファイルやコンパイル済みアセットを配置。外部からそのまま参照できる。
|   |-- 404.html
|   |-- 422.html
|   |-- 500.html
|   |-- favicon.ico
|   `-- robots.txt
|-- test    ### テストケースを配置。Unitテスト、フィクスチャなど。
|   |-- controllers
|   |-- fixtures
|   |-- helpers
|   |-- integration
|   |-- mailers
|   |-- models
|   `-- test_helper.rb
|-- tmp   ### アプリケーションが使うtmpディレクトリ。キャッシュ、pid、セッションファイルなどの一時ファイル。
|   `-- cache
|       `-- assets
`-- vendor   ### 外部ライブラリが使うディレクトリ
    `-- assets
        |-- javascripts
        `-- stylesheets

Railsを使ってみる

ひな形の作成

### テスト用ディレクトリの作成
$ mkdir blog

### ディレクトリの移動
$ cd blog

### Gemfile作成
$ bundle init

### 以下のようにバージョンを追加
$ vi Gemfile
============================================================
gem 'rails', '4.1.9'
============================================================

$ bundle install --path vendor/bundle
$ bundle exec rails new . --skip-bundle
============================================================
       exist
      create  README.rdoc
      create  Rakefile
      create  config.ru
      create  .gitignore
    conflict  Gemfile
Overwrite /home/user/blog/Gemfile? (enter "h" for help) [Ynaqdh] y
       force  Gemfile
      create  app
      create  app/assets/javascripts/application.js
      create  app/assets/stylesheets/application.css
      create  app/controllers/application_controller.rb
      create  app/helpers/application_helper.rb
      create  app/views/layouts/application.html.erb
      create  app/assets/images/.keep
      create  app/mailers/.keep
      create  app/models/.keep
      create  app/controllers/concerns/.keep
      create  app/models/concerns/.keep
      create  bin
      create  bin/bundle
      create  bin/rails
      create  bin/rake
      create  config
      create  config/routes.rb
      create  config/application.rb
      create  config/environment.rb
      create  config/secrets.yml
      create  config/environments
      create  config/environments/development.rb
      create  config/environments/production.rb
      create  config/environments/test.rb
      create  config/initializers
      create  config/initializers/assets.rb
      create  config/initializers/backtrace_silencers.rb
      create  config/initializers/cookies_serializer.rb
      create  config/initializers/filter_parameter_logging.rb
      create  config/initializers/inflections.rb
      create  config/initializers/mime_types.rb
      create  config/initializers/session_store.rb
      create  config/initializers/wrap_parameters.rb
      create  config/locales
      create  config/locales/en.yml
      create  config/boot.rb
      create  config/database.yml
      create  db
      create  db/seeds.rb
      create  lib
      create  lib/tasks
      create  lib/tasks/.keep
      create  lib/assets
      create  lib/assets/.keep
      create  log
      create  log/.keep
      create  public
      create  public/404.html
      create  public/422.html
      create  public/500.html
      create  public/favicon.ico
      create  public/robots.txt
      create  test/fixtures
      create  test/fixtures/.keep
      create  test/controllers
      create  test/controllers/.keep
      create  test/mailers
      create  test/mailers/.keep
      create  test/models
      create  test/models/.keep
      create  test/helpers
      create  test/helpers/.keep
      create  test/integration
      create  test/integration/.keep
      create  test/test_helper.rb
      create  tmp/cache
      create  tmp/cache/assets
      create  vendor/assets/javascripts
      create  vendor/assets/javascripts/.keep
      create  vendor/assets/stylesheets
      create  vendor/assets/stylesheets/.keep
============================================================
$ rm Gemfile.lock

### コメントアウトを外す
$ vi Gemfile
============================================================
gem 'therubyracer',  platforms: :ruby
============================================================
$ bundle install --path vendor/bundle

コントローラとビューの作成

  • コントローラ
    • アプリケーションに対する特定のリクエストを受け取って処理する
      • ルーティング は、リクエストをどのコントローラに割り振るかを決定するためのもの
      • 1つのコントローラに対して複数のルーティングがあるのはよくある
    • コントローラにはいくつかのアクションがある
      • アクション
        • 情報を集めてビューに送り出すのが役割
          • いくつかの異なるルーティングに対して、それぞれ異なるアクションを割り当てることができる
  • ビュー
    • 情報をユーザーが読める形式で表示することが役割
      • 表示する情報を集めるのは コントローラ であって、ビューではない
      • ビューは、コントローラが作成した情報に対して余計なことをせずに表示する必要がある
      • ビューテンプレートで使用できる言語は、デフォルトではeRuby (ERBとも、Embedded Rubyとも呼ばれます)
        • ERBで書かれたコードは、ユーザーに表示される前のリクエストサイクルでRailsによって処理される

コントローラの作成

### welcomeという名前のコントローラの中にindexというアクションを作成
$ bundle exec rails generate controller welcome index
================================================
      create  app/controllers/welcome_controller.rb ### welcomeコントローラ
       route  get 'welcome/index'
      invoke  erb
      create    app/views/welcome
      create    app/views/welcome/index.html.erb  ### welcomeコントローラに対応するindexビュー
      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
================================================

### ルーティングが設定されている
$ head config/routes.rb
================================================
Rails.application.routes.draw do
  get 'welcome/index'
...
================================================
$ bundle exec rake routes
================================================
       Prefix Verb URI Pattern              Controller#Action
welcome_index GET  /welcome/index(.:format) welcome#index
================================================

### コントローラとビューの確認
$ cat app/controllers/welcome_controller.rb
================================================
class WelcomeController < ApplicationController
  def index
  end
end
================================================

$ cat app/views/welcome/index.html.erb
================================================
<h1>Welcome#index</h1>
<p>Find me in app/views/welcome/index.html.erb</p>
================================================

### サーバを起動
$ bundle exec rails s
================================================
=> Booting WEBrick
=> Rails 4.1.9 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
=> Notice: server is listening on all interfaces (0.0.0.0). Consider using 127.0.0.1 (--binding option)
=> Ctrl-C to shutdown server
[2016-08-02 09:57:04] INFO  WEBrick 1.3.1
[2016-08-02 09:57:04] INFO  ruby 2.1.5 (2014-11-13) [x86_64-linux]
[2016-08-02 09:57:04] INFO  WEBrick::HTTPServer#start: pid=1706 port=3000
================================================

### アクセス
http://(IPアドレス):3000/welcome/index

トップにアクセスしてWelcomeページを表示させるようにする

  • config/routes.rb
    • ルーティングファイル
      • 外部からのリクエストをどのようにコントローラとアクションに振り分けるかを記述
      • DSL (ドメイン特化言語: domain-specific language) という特殊な言語を使用
### アプリケーションのルートURLへのアクセスをwelcomeコントローラのindexアクションに割り当て
$ vi config/routes.rb
================================================
root 'welcome#index'
================================================

$ bundle exec rake routes
================================================
       Prefix Verb URI Pattern              Controller#Action
welcome_index GET  /welcome/index(.:format) welcome#index
         root GET  /                        welcome#index  ### これが追加された
================================================

### アクセスしてWelcomeページが表示されていることを確認
$ bundle exec rails s
http://(IPアドレス):3000

リソースの追加

  • リソース
    • よく似たオブジェクト同士が集まったもの
      • 例)記事、人、動物など
    • リソースに対する操作(CRUD)
      • 作成 (create)
      • 読み出し (read)
      • 更新 (update)
      • 削除 (destroy)
    • ルーティングのresourcesメソッド
      • RESTリソースへの標準的なルーティングを宣言できる
      • ルーティングを1つずつ手作りするよりもresourcesオブジェクトを使用してルーティングを設定することが推奨されている
### resourceメソッドの追加
$ vi config/routes.rb
================================================
resources :articles
================================================

$ bundle exec rake routes
================================================
       Prefix Verb   URI Pattern                  Controller#Action
     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
welcome_index GET    /welcome/index(.:format)     welcome#index
         root GET    /                            welcome#index
================================================

### アクセスして「Routing Error」が表示されていることを確認
### ルーティングで指定された先に、リクエストを処理するように定義されたコントローラが見つからない時に発生するエラー
$ bundle exec rails s
http://(IPアドレス):3000/articles/new

### ArticlesControllerの作成
$ bundle exec rails generate controller articles
================================================
      create  app/controllers/articles_controller.rb
      invoke  erb
      create    app/views/articles
      invoke  test_unit
      create    test/controllers/articles_controller_test.rb
      invoke  helper
      create    app/helpers/articles_helper.rb
      invoke    test_unit
      create      test/helpers/articles_helper_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/articles.js.coffee
      invoke    scss
      create      app/assets/stylesheets/articles.css.scss
================================================

### 空のコントローラが作成されている
### ApplicationControllerを継承したシンプルなクラス
$ cat app/controllers/articles_controller.rb
================================================
class ArticlesController < ApplicationController
end
================================================

### アクセスして「Unknown action」が表示されていることを確認
### ルーティングの指定先にコントローラはあるがアクションが無いために表示されるエラー
$ bundle exec rails s
http://(IPアドレス):3000/articles/new

### newアクションを作成 (メソッドを定義)
### コントローラの内側で定義されたメソッドは、コントローラのアクションになる
### アクションがarticleに対するCRUD操作を担当
### 備考 : Rubyのメソッドはpublic、private、protectedに分けられますが、コントローラのアクションになれるのはpublicメソッドだけ
$ vi app/controllers/articles_controller.rb
================================================
class ArticlesController < ApplicationController

  def new
  end

end
================================================

### アクセスして「Template is missing」が表示されていることを確認
### アクションはあるがビューが無いためのエラー
$ bundle exec rails s
http://(IPアドレス):3000/articles/new
================================================
Template is missing

Missing template articles/new,  ### articles/newというテンプレートがあるべき
application/new with {  ### articles/newが無い場合はapplication/newを探す⇒ApplicationControllerコントローラを継承しているため
:locale=>[:en],   ### 何語向けか。デフォルトは英語(en)
:formats=>[:html],    ### 応答時に返されるテンプレートのフォーマット。デフォルトはhtml
:variants=>[], 
:handlers=>[   ### テンプレートを描画するときに使用されるテンプレートハンドラ
 :erb,    ### HTMLテンプレートで最もよく使用されるテンプレートハンドラ
 :builder,  ### XMLテンプレートで使用される
 :raw, 
 :ruby, 
 :coffee,    ### CoffeeScript
 :jbuilder
 ]
}. 
Searched in: * "/var/share/blog/app/views"  ### Railsがテンプレートを探した場所
================================================

### テンプレートを作成
### テンプレートのファイル名 articles/new.html.erb
### htmlはテンプレートのフォーマット、erbは使用されるハンドラー
### Railsは、articles/newというテンプレートをapp/views配下で探そうとする。
$ vi /var/share/blog/app/views/articles/new.html.erb
================================================
<h1>New Article</h1>
================================================

### アクセスして「New Article」が表示されていることを確認
$ bundle exec rails s
http://(IPアドレス):3000/articles/new

フォームの作成

  • テンプレート内にフォームを作成するために、form builderを利用する
    • form_forというヘルパーメソッドがあり、主にこれを使用してフォームを作成する
    • form_forメソッドを呼び出すときには、このフォームを識別するためのオブジェクトを渡す
      • 例では:articleというシンボルを渡す
        • form_forヘルパーは、これを見て何のフォームであるかを知る
      • メソッドのブロックの内側はFormBuilderオブジェクトを置く
        • 通例では「f」
$ vi /var/share/blog/app/views/articles/new.html.erb
================================================
<h1>New Article</h1>

<%= form_for :article do |f| %> ### form_forヘルパーメソッドでフォームを作成
  <p>
    <%= f.label :title %><br>   ### タイトルのラベル
    <%= f.text_field :title %>  ### タイトルのテキスト入力フィールド
  </p>

  <p>
    <%= f.label :text %><br>    ### 記事本文のラベル
    <%= f.text_area :text %>    ### 記事本文のテキスト入力
  </p>

  <p>
    <%= f.submit %>  ### fオブジェクトに対してsubmitを実行⇒フォームの送信ボタンが作成される
  </p>
<% end %>
================================================

### アクセスして「New Article」とフォーム表示されていることを確認
$ bundle exec rails s
http://(IPアドレス):3000/articles/new

表示例
01.png

ソースの表示
02.png

  • この状態ではフォームのaction属性の送信先が/articles/newになってしまっている
    • /articles/newというルーティングは、このフォームを最初に表示するときに使用されるもの
      • 記入されたフォームの送信先まで同じルーティングにしてしまうのはおかしい
      • /articles/newはフォームの表示専用にすべき
    • form_forの:urlオプションで指定する
      • Railsでは、新しいフォームの送信先となるアクションは"create"にするのが普通
      • articles_pathヘルパー
        • articlesという接頭語に関連付けられているURIパターンをフォームの送信先とするようRailsに指示する
        • デフォルトに従ってPOSTリクエストとしてルーティングに送信される
          • Prefix「articles」のVerbが「POST」だからcreateアクション
          • -> 「GET」の場合はindexアクション
        • 現在のコントローラであるArticlesControllerのcreateアクションに関連付けられる
$ vi /var/share/blog/app/views/articles/new.html.erb
================================================
<h1>New Article</h1>

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

$ bundle exec rake routes
================================================
       Prefix Verb   URI Pattern                  Controller#Action
     articles GET    /articles(.:format)          articles#index
              POST   /articles(.:format)          articles#create ### articles_pathによりここに関連付けられる
  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
welcome_index GET    /welcome/index(.:format)     welcome#index
         root GET    /                            welcome#index
================================================

### アクセスして記事に入力し、submitを押すと「Unknown action」が表示される
$ bundle exec rails s
http://(IPアドレス):3000/articles/new

記事を作成する

  • フォームを送信
    • フォームに含まれるフィールドはパラメータとしてRailsに送信
    • 受け取ったコントローラ内のアクションで参照可能
      • これを使用して特定のタスクを実行
  • renderメソッド
    • 単純なハッシュを引数に取る
    • 例) render plain: params[:article].inspect
      • ハッシュのキー : plain
      • ハッシュの値 : params[:article].inspect
    • paramsメソッド
      • フォームから送信されてきたパラメータ (つまりフォームのフィールド) を表すオブジェクト
      • ActiveSupport::HashWithIndifferentAccessオブジェクトを返す
      • 文字列またはシンボルを使用して、このオブジェクトのハッシュのキーを指定できる
      • inspectメソッドは取得した配列を整形してくれる
### createアクションの追加
$ vi app/controllers/articles_controller.rb
================================================
class ArticlesController < ApplicationController
  def new
  end

  def create
  end
end
================================================

### アクセスして記事に入力し、submitを押すと「Template is missing」が表示される
### createアクションは記事をDBに保存することが役割なので無視でよい
$ bundle exec rails s
http://(IPアドレス):3000/articles/new

### createアクションの修正
$ vi app/controllers/articles_controller.rb
================================================
class ArticlesController < ApplicationController
  def new
  end

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

### アクセスして記事に入力し、submitを押す
### フォームから送信されたパラメータをそのまま表示
$ bundle exec rails s
http://(IPアドレス):3000/articles/new
================================================
{"title"=>"Titleに入力", "text"=>"Textに入力"}

### Rails5計の場合
<ActionController::Parameters {"title"=>"Titleに入力", "text"=>"Textに入力"} permitted: false>
================================================

モデルを作成する

  • Railsのモデル
    • 単数形の名前
    • 対応するデータベーステーブル名は複数形
  • generate model Article
    • Articleモデルの作成
      • 例で作成されるもの
        • string型の title 属性
        • text型の text 属性
        • -> stringは短い文字列(255文字以下)のとき
        • -> それ以上の文字数になるとrailsのバージョンによっては落ちる場合があるらしい
      • これらの属性は、マイグレーションを実行するとデータベースのarticlesテーブルに自動的に追加され、Articleモデルと対応付けられる
  • マイグレーション
    • マイグレーションはRubyのクラス
    • データベーステーブルの作成や変更を簡単に行うためのしくみ
    • マイグレーションを実行するにはrakeコマンドを実行する
    • マイグレーションを使用して行ったデータベース構成の変更は、後から取り消すことができる
    • マイグレーションファイルの名前にはタイムスタンプが含まれており、これに基いて、マイグレーションは作成された順に実行される
    • デフォルトはdevelopment環境で実行される
      • production環境で実行したい場合
        • RAILS_ENV=production rake db:migrate
    • changeメソッド
      • マイグレーションの実行時に呼び出される
      • このメソッドで定義されてる操作は取り消しが可能
### Articleモデルの作成
$ bundle exec rails generate model Article title:string text:text
================================================
      invoke  active_record
      create    db/migrate/20160802024522_create_articles.rb ### マイグレーションファイル
      create    app/models/article.rb
      invoke    test_unit
      create      test/models/article_test.rb
      create      test/fixtures/articles.yml
================================================

$ cat db/migrate/20160802024522_create_articles.rb
================================================
class CreateArticles < ActiveRecord::Migration
  def change  ### changeメソッド
    create_table :articles do |t|  ### articlesというテーブルが作成される
      t.string :title ### 文字列カラムが作成される
      t.text :text ### テキストカラムが作成される

      t.timestamps  ### 作成日と更新日を追跡するためのタイムスタンプフィールドを作成
    end
  end
end
================================================

### Articlesテーブルがデータベース上に作成する
$ bundle exec rake db:migrate
================================================
== 20160802024522 CreateArticles: migrating ===================================
-- create_table(:articles)
   -> 0.0009s
== 20160802024522 CreateArticles: migrated (0.0009s) ==========================
================================================

データを保存する

app/controllers/articles_controller.rbのcreateアクションを変更する

  • Railsのすべてのモデルは初期化時に属性(フィールド)を与えられる
    • それらはデータベースカラムに自動的に対応付けられる
    • @article.saveで、このモデルをデータベースに保存する
    • ユーザーをshowアクションにリダイレクトする
      • モデルを保持している@articleをredirect_toで指定するだけで、そのモデルを表示するためのshowアクションにリダイレクトされる
  • strong_parameters
    • セキュリティの高いアプリケーションを開発するための機能のひとつ
    • コントローラが受け取ったパラメータをノーチェックでまるごと自動的にモデルに渡すのは危険
      • パラメータがチェックされていない点を攻撃者に悪用される可能性がある
      • もともとフォームになかったフィールドが攻撃者によって密かに追加され、それがアプリケーションの整合性を脅かすなど
      • マスアサインメント
        • チェックされていないパラメータをまるごとモデルに保存する行為
        • これが発生すると、正常なデータの中に悪意のあるデータが含まれてしまう
$ vi app/controllers/articles_controller.rb
================================================
class ArticlesController < ApplicationController
  def new
  end

  def create
    @article = Article.new(params[:article]) ### params[:article]に属性が入っている

    @article.save  ### モデルをデータベースに保存
    redirect_to @article  ### @articleをしているするだけでユーザーをshowアクションにリダイレクトできる
    #render plain: params[:article].inspect
  end
end
================================================

### アクセスして記事に入力し、submitを押す
### 「ActiveModel::ForbiddenAttributesError in ArticlesController#create」が表示される
### セキュリティの高いアプリケーションを開発するための機能制限にひっかかった
$ bundle exec rails s
http://(IPアドレス):3000/articles/new


$ vi app/controllers/articles_controller.rb
================================================
class ArticlesController < ApplicationController
  def new
  end

  def create
    #@article = Article.new(params[:article])
    @article = Article.new(params.require(:article).permit(:title, :text)) ### titleとtextのみ利用可能かつ必須

    @article.save
    redirect_to @article
    #render plain: params[:article].inspect
  end
end
================================================

### アクセスして記事に入力し、submitを押す
### 「Unknown action」が表示される
### showアクションが無いため
$ bundle exec rails s
http://(IPアドレス):3000/articles/new

### 共用できるようにこのメソッドをくくりだしておくのが普通
$ vi app/controllers/articles_controller.rb
================================================
class ArticlesController < ApplicationController
  def new
  end

  def create
    #@article = Article.new(params[:article])
    #@article = Article.new(params.require(:article).permit(:title, :text))
    @article = Article.new(article_params)

    @article.save
    redirect_to @article
    #render plain: params[:article].inspect
  end

  private  ### private宣言。マスアサインメントを避けるだけでなく、外部から不正に呼び出されないようにするため
    def article_params
      params.require(:article).permit(:title, :text)
    end
end
================================================

記事を表示する

  • showアクションの追加
    • Article.find(params[:id])
      • 取り出したい記事をデータベースから探す
        • リクエストの:idパラメータを取り出すためにparams[:id]を引数としてfindに渡している
        • 取り出した記事オブジェクトへの参照を保持するために、通常の変数ではなく、インスタンス変数が使用されている
          • Railsではコントローラのインスタンス変数はすべてビューに渡されるようになっている
          • Railsはそのために背後でインスタンス変数をコントローラからビューに絶え間なくコピーし続けている
    • createアクションの@articleとshowアクションの@articleは別物
      • createアクションでfindする前の@articleはnilになっている
$ vi app/controllers/articles_controller.rb
================================================
class ArticlesController < ApplicationController
  def new
  end

  def create
    #@article = Article.new(params[:article])
    #@article = Article.new(params.require(:article).permit(:title, :text))
    @article = Article.new(article_params)

    @article.save
    redirect_to @article
    #render plain: params[:article].inspect
  end

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

  private
    def article_params
      params.require(:article).permit(:title, :text)
    end
end
================================================

### ビューの作成
$ vi app/views/articles/show.html.erb
================================================
<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

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

### アクセスして記事に入力し、submitを押す
$ bundle exec rails s
http://(IPアドレス):3000/articles/new

03.png

記事の一覧を表示する

### コントローラにindexを追加
$ vi app/controllers/articles_controller.rb
================================================
class ArticlesController < ApplicationController
  def new
  end

  def create
    #@article = Article.new(params[:article])
    #@article = Article.new(params.require(:article).permit(:title, :text))
    @article = Article.new(article_params)

    @article.save
    redirect_to @article
    #render plain: params[:article].inspect
  end

  def show
    @article = Article.find(params[:id])  ### 一つの記事を検索
  end

  def index
    @articles = Article.all  ### 記事一覧を検索
  end

  private
    def article_params
      params.require(:article).permit(:title, :text)
    end
end
================================================

### ビューを追加
$ vi 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>
    </tr>
  <% end %>
</table>
================================================

### アクセスして記事一覧が表示されることを確認する
$ bundle exec rails s
http://(IPアドレス):3000/articles/

リンクの追加

  • link_toメソッド
    • Railsのビルトインヘルパーの1つ
    • 指定されたテキストに基いたリンクを作成し、ジャンプ先を表示
      • <%= link_to 'My Blog', controller: 'articles' %>
        • articlesコントローラを指定。indexアクションに移動(?)
      • <%= link_to 'New article', new_article_path %>
        • rake routesのPrefixでリンク先を指定。newアクションに移動
        • 同じコントローラ内であればコントローラの指定は省略できる
  • その他
    • developmentモード(Railsデフォルト)はRailsはリクエストのたびにアプリケーションを再読み込みする
      • 開発をやりやすくするためであり、変更を行なうたびにRailsのWebサーバーを再起動する必要は無い
### トップページから記事の一覧、記事の作成へのリンクを追加
$ vi app/views/welcome/index.html.erb
================================================
<h1>Welcome#index</h1>
<p>Find me in app/views/welcome/index.html.erb</p>
<%= link_to 'My Blog', controller: 'articles' %>  ### 記事の一覧
<%= link_to 'New article', new_article_path %>  ### 記事の作成
================================================

### 記事の作成画面からindexアクションに戻るリンクを追加
$ vi app/views/articles/new.html.erb
================================================
<h1>New Article</h1>

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

<%= link_to 'Back', articles_path %>  ### 追加
================================================

### 個別記事からindexアクションに戻るリンクを追加
$ vi 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 %>
================================================

### リンクをたどれることを確認
$ bundle exec rails s
http://(IPアドレス):3000/

検証(バリデーション)の追加

  • ActiveRecord::Baseクラス
    • Active Recordの機能
      • 基本的なデータベースCRUD (Create、Read、Update、Destroy) 操作
      • データの検証 (バリデーション)
      • 洗練された検索機能
      • 複数のモデルを関連付ける(リレーションシップ)
  • バリデーション
    • 例は検証が通らない内容を持つ@articleに対して@article.saveを実行するとfalseが返されるようになる
### バリデーションを追加
$ vi app/models/article.rb
================================================
class Article < ActiveRecord::Base
  validates :title, presence: true,    ### 全ての記事でタイトルが必須
                    length: { minimum: 5 }   ### 文字数は5文字以上
end
================================================

* createアクションのrender 'new'を使う理由
    * ビューのnewテンプレートが描画されたときに、@articleオブジェクトがビューのnewテンプレートに返されるようにするため
    * renderによる描画は、フォームの送信時と同じリクエスト内で行われ
    * redirect_toはサーバーに別途リクエストを発行するようブラウザに対して指示する
        * renderに比べて不要なやり取りが発生

### createアクションがバリデーションの結果を利用するように修正
$ vi app/controllers/articles_controller.rb
================================================
class ArticlesController < ApplicationController
  def new
    @article = Article.new   ### @articleというインスタンス変数が新たに作成されるように修正
     ### ビューで受け取る@articleがnilになってしまい、@article.errors.any?を呼び出すところでエラーになってしまうため
  end

  def create
    #@article = Article.new(params[:article])
    #@article = Article.new(params.require(:article).permit(:title, :text))
    @article = Article.new(article_params)

    if @article.save  ### saveを実行する
      redirect_to @article  ### 成功した場合はshowアクションにリダイレクト
    else
      render 'new'  ### 失敗した場合は記事作成のページを再度表示させる
    end

    #@article.save
    #redirect_to @article
    #render plain: params[:article].inspect
  end

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

  def index
    @articles = Article.all
  end

  private
    def article_params
      params.require(:article).permit(:title, :text)
    end
end
================================================

### 短いタイトルを入力すると反映されないことを確認
$ bundle exec rails s
http://(IPアドレス):3000/articles/

※ これ以降bundle exec rails sの記述は省略。適宜確認してください。

* pluralize
    * 数値を受け取ってそれに応じて英語の「単数形/複数形」活用を行ってくれるRailsのヘルパーメソッド
    * 数値が1より大きい場合は、引数の文字列を自動的に複数形に変更

### 入力の問題点を表示するように修正
$ vi app/views/articles/new.html.erb
================================================
<h1>New Article</h1>

<%= 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 %>
================================================

### updateアクションの追加
$ vi app/controllers/articles_controller.rb
================================================
class ArticlesController < ApplicationController
  def new
    @article = Article.new
  end

  def create
    #@article = Article.new(params[:article])
    #@article = Article.new(params.require(:article).permit(:title, :text))
    @article = Article.new(article_params)

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

    #@article.save
    #redirect_to @article
    #render plain: params[:article].inspect
  end

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

  def index
    @articles = Article.all
  end

  def update  ### 追加
    @article = Article.find(params[:id])

    if @article.update(article_params)
      redirect_to @article
    else
      render 'edit'
    end
  end

  private
    def article_params
      params.require(:article).permit(:title, :text)
    end
end
================================================

### editアクションへのリンクを全記事の一覧に追加
$ vi app/views/articles/index.html.erb
================================================
<h1>Listing articles</h1>

<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>
================================================

### showにもEditリンクを追加
$ vi 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 %>
| <%= link_to 'Edit', edit_article_path(@article) %>
================================================

記事を更新する

  • 更新の場合のフォームの送信先はupdateアクション
  • method: :patchというオプション
    • PATCHというHTTPメソッドを使用してこのフォームを送信しようとしている
    • PATCHメソッドは、RESTプロトコルに基いてリソースを 更新 するために使用される
  • from_forヘルパーメソッド
    • 最初のパラメータ
    • @articleのようなオブジェクトを使用できる
      • ヘルパーはそのパラメータに含まれているフィールドを使用してフォームの項目を埋め
    • @articleのようなインスタンス変数の代わりに:articleのようなシンボルを渡しても動作は全く同じになる
### editアクションを追加
$ vi app/controllers/articles_controller.rb
================================================
class ArticlesController < ApplicationController
  def new
    @article = Article.new
  end

  def create
    #@article = Article.new(params[:article])
    #@article = Article.new(params.require(:article).permit(:title, :text))
    @article = Article.new(article_params)

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

    #@article.save
    #redirect_to @article
    #render plain: params[:article].inspect
  end

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

  def index
    @articles = Article.all
  end

  def edit
    @article = Article.find(params[:id])  ### ここを追加
  end

  private
    def article_params
      params.require(:article).permit(:title, :text)
    end
end
================================================

### ビューの追加
$ vi app/views/articles/edit.html.erb
================================================
<h1>Editing article</h1>

<%= form_for :article, url: article_path(@article), method: :patch 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 %>
================================================

部分テンプレート(パーシャル)を使用してビューの重複コードをきれいにする

  • 無駄な重複を取り除く
    • newとeditのビューはほとんど同じ
  • パーシャルのファイル名の先頭にはアンダースコアを追加
    • Railsの慣例
### パーシャルの作成
$ vi app/views/articles/_form.html.erb
================================================
<%= form_for @article do |f| %>   ### ここが「:article」だとurlを指定しなければフォームの送信先がnewになってしまう
  <% 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 %>
================================================

### パーシャルの利用
$ vi app/views/articles/new.html.erb
================================================
<h1>New article</h1>

<%= render 'form' %>

<%= link_to 'Back', articles_path %>
================================================

$ vi app/views/articles/edit.html.erb
================================================
<h1>Edit article</h1>

<%= render 'form' %>

<%= link_to 'Back', articles_path %>
================================================

記事を削除する

  • ビューは作成せずにindexアクションにリダイレクトする
  • HTML5の属性
    • :methodオプション
      • deleteメソッドとリンクを送信
    • :'data-confirm'オプション
      • 本当に削除してよいかどうかを確認するメッセージを表示する
      • jquery_ujsというJavaScriptファイルによって自動的に行われる
        • このファイルはアプリケーションの生成時に自動的にアプリケーションレイアウト(app/views/layouts/application.html.erb) に含まれる
### destroyアクションを追加
$ vi app/controllers/articles_controller.rb
================================================
...
  def destroy
    @article = Article.find(params[:id])
    @article.destroy

    redirect_to articles_path  ### ビューは作成せずにindexアクションにリダイレクトする
  end
...
================================================

### destroyリンク追加
$ vi app/views/articles/index.html.erb
================================================
<h1>Listing articles</h1>

<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>
================================================

2つ目のモデルを追加する

  • アソシエーション (関連付け)
    • Active Recordの関連付け機能により、2つのモデルの間にリレーションシップを簡単に宣言することができる
      • 記事とコメントの関係を宣言
        • 1つのコメントは1つの記事に属する (Each comment belongs to one article)。
          • belongs_to :article (app/models/comment.rb)
        • 1つの記事は複数のコメントを持てる (One article can have many comments)。
          • has_many :comments (app/models/article.rb)
      • この宣言により実現される事の一例
        • @articleというインスタンス変数に記事が1つ含まれている場合
          • @article.commentsと書くだけでその記事に関連付けられているコメントをすべて取得できる
### コメントモデルの作成
$ bundle exec rails generate model Comment commenter:string body:text article:references
================================================
      invoke  active_record
      create    db/migrate/20160804074617_create_comments.rb
      create    app/models/comment.rb   ### コメントモデル
      invoke    test_unit
      create      test/models/comment_test.rb   ###     Commentモデルをテストするためのハーネス
      create      test/fixtures/comments.yml   ### テストで使用するサンプルコメント
================================================

### コメントモデルの確認
$ cat app/models/comment.rb
================================================
class Comment < ActiveRecord::Base
  belongs_to :article   ### 1つのコメントは1つの記事に属する
end
================================================

### マイグレーションファイルの確認
$ db/migrate/20160804074617_create_comments.rb
================================================
class CreateComments < ActiveRecord::Migration
  def change
    create_table :comments do |t|
      t.string :commenter
      t.text :body
      t.references :article, index: true   ### `article_id`という整数カラムが追加される。2つのモデルの関連付けを指定するための外部キーを設定し、関連付け用のインデックスもカラム上に作成される

      t.timestamps
    end
  end
end
================================================

### マイグレーションの実行
$ bundle exec rake db:migrate
================================================
== 20160804074617 CreateComments: migrating ===================================
-- create_table(:comments)
   -> 0.0030s
== 20160804074617 CreateComments: migrated (0.0031s) ==========================
================================================

### articleモデルの編集
$ vi app/models/article.rb
================================================
class Article < ActiveRecord::Base
  has_many :comments   ### 1つの記事は複数のコメントを持てる
  validates :title, presence: true,
                    length: { minimum: 5 }
end
================================================

### コメントへのルーティングを追加
$ vi config/routes.rb
================================================
...
resources :articles do
  resources :comments
end
...
================================================

### ルーティングの確認
### comments関連のルーティングが追加されている
$ bundle exec rake routes
================================================
              Prefix Verb   URI Pattern                                       Controller#Action
       welcome_index GET    /welcome/index(.:format)                          welcome#index
    article_comments GET    /articles/:article_id/comments(.:format)          comments#index
                     POST   /articles/:article_id/comments(.:format)          comments#create
 new_article_comment GET    /articles/:article_id/comments/new(.:format)      comments#new
edit_article_comment GET    /articles/:article_id/comments/:id/edit(.:format) comments#edit
     article_comment GET    /articles/:article_id/comments/:id(.:format)      comments#show
                     PATCH  /articles/:article_id/comments/:id(.:format)      comments#update
                     PUT    /articles/:article_id/comments/:id(.:format)      comments#update
                     DELETE /articles/:article_id/comments/:id(.:format)      comments#destroy
            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
================================================

### コントローラを生成
### コメントコントローラでコメントを追加したり削除したりできるようにする
$ bundle exec rails generate 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
      create      test/helpers/comments_helper_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/comments.js.coffee
      invoke    scss
      create      app/assets/stylesheets/comments.css.scss
================================================

### 新規コメントを追記できるようにビューを編集
$ vi 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| %>   ### fに配列を渡している。「/articles/1/comments」のようなネストしたルーティングを生成する。
  <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 'Back', articles_path %>
| <%= link_to 'Edit', edit_article_path(@article) %>
================================================

### commentsコントローラの編集
$ app/controllers/comments_controller.rb
================================================
class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])  ### まず記事のオブジェクトを保存して@articleに保存
    @comment = @article.comments.create(comment_params)  ### その後@article.commentsに対してcreateメソッドを実行してコメントの作成と保存を同時におこなっている(buildメソッドを使うと作成のみ)。コメントと記事が自動的にリンクされ、指定された記事に対してコメントが従属するようになる。
    redirect_to article_path(@article)   ### article_path(@article)ヘルパーを呼び出すことでshowアクションが呼び出される
  end

  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end
end
================================================

### ビューの編集
$ vi 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 Article', edit_article_path(@article) %> |
<%= link_to 'Back to Articles', articles_path %>
================================================

パーシャルを利用してコメントのコードをきれいにする

### パーシャルの作成
$ vi app/views/comments/_comment.html.erb
================================================
<p>
  <strong>Commenter:</strong>
  <%= comment.commenter %>
</p>

<p>
  <strong>Comment:</strong>
  <%= comment.body %>
</p>
================================================
$ vi 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 %>
================================================

### ビューの編集
$ vi 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 %>   ### app/views/comments/_comment.html.erbパーシャルが@article.commentsコレクションに含まれているコメントをすべて出力する。

<h2>Add a comment:</h2>
<%= render "comments/form" %>  ### これでapp/views/comments/_form.html.erbパーシャルを描画すれば良いとRailsがよしなに解釈してくれる

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

コメントを削除する

### app/views/comments/_comment.html.erbパーシャルにリンクを追加
$ vi 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],   ### /articles/:article_id/comments/:idというリクエストがCommentsControllerに送信される
               method: :delete,
               data: { confirm: 'Are you sure?' } %>
</p>
================================================

### コントローラにDestoryアクションを追加
$ vi 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

  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
================================================

### 関連づけられたオブジェクトも削除するようにする
$ vi app/models/article.rb
================================================
class Article < ActiveRecord::Base
  has_many :comments, dependent: :destroy   ### これで記事を削除するとコメントも一緒にデータベース上から削除してくれる
  validates :title, presence: true,
                    length: { minimum: 5 }
end
================================================

Basic認証

### コントローラの修正
$ vi app/controllers/articles_controller.rb
================================================
class ArticlesController < ApplicationController

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

...
================================================

### コメントの削除を認証ユーザにのみ許可
$ vi app/controllers/comments_controller.rb
================================================
class CommentsController < ApplicationController

  http_basic_authenticate_with name: "ftakao2007", password: "pass", only: :destroy

...
================================================

scaffoldでコードを生成

  • scaffoldコードを生成するだけで、ごくシンプルなWebアプリケーションができる
  • scaffoldで行われる事

    • 指定したテーブルに対してのCRUD操作を行うMVCそれぞれのコードの生成
    • 指定したテーブルに対してのデータベースのテーブル定義を行うmigrationファイルの生成
  • 「Task」モデルを作成

    • コマンド : ./bin/rails generate scaffold task content:text
      • tasksテーブルが生成される
      • contentというテキスト型のカラムが生成される
  • マイグレーションファイルを基にテーブルを作成

    • rakeタスクとして定義されている
      • db:create : データベースの作成
      • db:migrate : テーブルへの変更を行う
        • マイグレーションファイルを新たに作成するごとに実施する
### scaffoldでコードを生成
$ ./bin/rails generate scaffold task content:text
============================================================
      invoke  active_record
      create    db/migrate/20151126035942_create_tasks.rb  ### マイグレーションファイル
      create    app/models/task.rb
      invoke    test_unit
      create      test/models/task_test.rb
      create      test/fixtures/tasks.yml
      invoke  resource_route
       route    resources :tasks
      invoke  scaffold_controller
      create    app/controllers/tasks_controller.rb 
      invoke    erb
      create      app/views/tasks
      create      app/views/tasks/index.html.erb
      create      app/views/tasks/edit.html.erb
      create      app/views/tasks/show.html.erb
      create      app/views/tasks/new.html.erb
      create      app/views/tasks/_form.html.erb
      invoke    test_unit
      create      test/controllers/tasks_controller_test.rb
      invoke    helper
      create      app/helpers/tasks_helper.rb
      invoke      test_unit
      create        test/helpers/tasks_helper_test.rb
      invoke    jbuilder
      create      app/views/tasks/index.json.jbuilder
      create      app/views/tasks/show.json.jbuilder
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/tasks.js.coffee
      invoke    scss
      create      app/assets/stylesheets/tasks.css.scss
      invoke  scss
      create    app/assets/stylesheets/scaffolds.css.scss
============================================================

### db作成
$ ./bin/rake db:create

### テーブルの変更
$ ./bin/rake db:migrate
============================================================
== 20151126035942 CreateTasks: migrating ======================================
-- create_table(:tasks)
   -> 0.0010s
== 20151126035942 CreateTasks: migrated (0.0011s) =============================
============================================================

REST API

Railsに限った話ではないが重要なことなのでまとめる。

REST APIとは

  • RESTの原則に沿った形で設計されたAPI
    • HTTPの技術を最大限活用する、シンプルな設計方法
    • 「何のリソースを」「どのように」操作するかをURIやHTTPメソッドで表現する
    • リソース指向の設計
  • RESTの原則
    • セッションなどの状態管理を行わない
      • やり取りされる情報はそれ自体で完結して解釈することができる
      • Webシステムでは、HTTP自体にはセッション管理の機構はない
    • 情報を操作する命令の体系が予め定義・共有されている
      • WebシステムではHTTPのGETやPOSTなどに相当
    • すべての情報は汎用的な構文で一意に識別される
      • URLやURIに相当
    • 情報の内部に、別の情報や(その情報の別の)状態へのリンクを含めることができる
      • ハイパーメディア的な書式で情報を表現する
      • HTMLやXMLに相当

設計時に考える事

  • 鉄則
    • シンプルであること
  • リソースの決定
    • APIのユーザが必要としている情報資源(リソース)
      • ブログの例
        • 記事
        • コメント
        • タグ
        • カテゴリ
    • リソースに対して必要な操作(CRUD)
      • ブログの記事の例
        • 参照
        • 作成
        • 更新
        • 削除
    • リソースの親子関係
      • ブログの記事の例
        • 記事にはコメントが複数付く
        • 記事はカテゴリに属する
  • 注意点
    • リソース=データベーステーブルとは限らない
      • リソースの洗い出しはDB設計にそれとなく似ているが、APIはあくまでインタフェースなのでひとつ
      • ⇒リソースの中にメタデータとして関連する情報を持っていても問題ない

URL設計

非常に奥深く、様々な要素に関わってくるので慎重に設計する必要がある。

  • APIのURL設計で意識すべき点
    • ひと目でAPIと分かるようなURLにする
    • URLにAPIのバージョンを含める
    • URLに動詞を含めず、複数形の名詞のみで構成する
    • リソースの関係性がひと目で分かるようにする
    • アプリケーションや言語に依存する拡張子は含めない
      • URLの末尾に.phpや.plなど言語に関係した拡張子を付けない
    • 長くしすぎない
      • 単純にURLが長すぎると実装が面倒になる
  • 良いURLはCool URIと言われ、半永久的に変わらないと言われている

HTTPメソッドを決める

REST APIに使用するのはPOST,GET,PUT,DELETE

  • POST
    • Createを表す
    • リソースを新規作成する場合に使用
    • IDが自明でない場合に使用される
      • IDが自明のときはPUTを使う
    • 実質はリソースを指定せずに上位リソースへ更新を行っている
      • リソースの指定が(/article/10)のときは(/article)を更新している
    • リソースの更新が冪等とはならない
  • GET
    • Readを表す
    • リソースを取得する場合に使用
    • 取得の結果は冪等になる
  • PUT
    • Create/Replaceを表す
    • POSTと違い、IDが自明な場合に使用
    • URLで指定したリソース(/article/10)が存在する場合は更新(置き換え)を行う
    • リソースが存在しない場合は登録を行う
    • リソース更新が冪等となる際に使用
  • DELETE
    • Deleteを表す
    • リソースの削除をする場合に使用
    • 削除の結果は冪等になる

参考

テスト

React

Reactに直接関係のない周辺ツールについてもまとめる

関連パッケージ

  • node.js
    • CommonJS の仕様に則って実装されていた処理系
      • 今は独自の進化を遂げて準拠していない
      • もはやブラウザという檻から外れた汎用言語
  • npm
    • CommonJS界のパッケージ管理ツール
    • node.jsで使っているパッケージ管理ツールの名前
    • 基本的にサーバサイド用
  • bower
    • ブラウザで動く昔ながらの Javascript のためのパッケージ管理ツール
      • 基本的にクライアントサイド用
    • npmでインストールする
    • node.jsで実装されている
      • bowerを使うためにnode.jsのという処理系が必要
    • パッケージ例
      • JQuery
      • AngularJS
        • AngularJSなど一部のパッケージはbowerだけでなくnpmでも配布されている
        • npmが統一リポジトリになるという話が出てはいるが片付いてはいなさそう
  • babel
    • 次世代のJavaScriptの標準機能を、ブラウザのサポートを待たずに使えるようにするNode.js製のツール
    • 機能をサポートしていないブラウザでも動くコードに変換(トランスパイル)する
    • JavaScriptのコードを生成するという意味ではCoffeeScriptやTypeScriptの仲間
  • ビルドツール
    • npmからインストールできる
    • webpack
      • WebApp に必要なリソースの依存関係を解決し、アセット(配布物)を生成するビルドツール
      • 書いたJSファイル(Reactのコード)をひとつのファイルにいい感じにまとめて出力してくれる
      • JavaScript だけでなく、CoffeeScript や TypeScript、CSS 系、画像ファイルなどを扱うことができる
      • 最適化したアセットの生成を目的
    • Grunt
      • 遅い! 設定ファイルが分かりにくい! とか言われつつもデファクト感がある
      • プラグインが最多
    • glup.js
      • シンプルな設定ファイルと、実行速度に定評あり
      • プラグインをpipeして連続実行できるため、中間ファイルの生成が不要
      • Node.jsの特性を活かした、非同期処理でパラレルに複数のタスクを走らせる事が可能

Reactの基礎

Rspec

  • Rubyのテストフレームワークの一種

インストール

### Gemfileにrspec-railsを追加
$ vi Gemfile
================================================
group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platform: :mri
  gem 'rspec-rails'      # 追加
  gem 'capybara'         # 追加 (findやcheckメソッドが使えるようになる)
end
================================================

### インストールと初期ファイル作成
$ bundle install --path vendor/bundle
$ bundle exec rails generate rspec:install
================================================
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb
================================================

### rails_helper.rb にCapybaraの設定を追記
$ vi spec/rails_helper.rb
================================================
require 'rspec/rails'
require 'capybara/rails'   # 追加
require 'capybara/rspec'   # 追加
...
RSpec.configure do |config|
  config.include Capybara::DSL     # 追加
================================================

### モデルやコントローラを作成したときに自動でRspecファイルも作成されるようにする
### config.generatorsのところを追加
$ vi config/application.rb
================================================
...
module Blog
  class Application < Rails::Application
    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration should go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded.
    config.generators do |g|
      g.test_framework = "rspec"
      #g.controller_specs = false     # controllerのRspecファイルを作成しない場合はコメントアウトを外す
      #g.helper_specs = false     # helperのRspecファイルを作成しない場合はコメントアウトを外す
      #g.view_specs = false     # viewのRspecファイルを作成しない場合はコメントアウトを外す
    end
  end
end
================================================

動作テスト

以下のようなページを想定
rspec1.png

### rake routes
$ bundle exec rake routes
================================================
       Prefix Verb   URI Pattern                  Controller#Action
welcome_index GET    /welcome/index(.:format)     welcome#index
         root GET    /                            welcome#index
================================================

### app/controllers/welcome_controller.rb
================================================
class WelcomeController < ApplicationController
  def index
  end
end
================================================

### app/views/welcome/index.html.erb
================================================
<h1>Hello, Rails!</h1>
<p>Find me in app/views/welcome/index.html.erb</p>

<p>
<input type="checkbox" id="checkbox1" name="check1" value="1" checked="checked">a
<input type="checkbox" id="checkbox2" name="check2" value="2">b
<input type="checkbox" id="checkbox3" name="check3" value="3">c
</p>
================================================

indexアクションのテストと、「Hello, Rails!」が表示されていること、チェックボックの状態を確認する

$ mkdir -p spec/controllers
$ vi spec/controllers/welcome_controller_spec.rb
================================================
require 'rails_helper'

RSpec.describe WelcomeController, type: :controller do

  describe "GET #index" do
    it "returns http success" do
      get :index
      expect(response).to have_http_status(:success)
    end
  end

end
================================================

$ mkdir -p spec/features
$ vi spec/features/welcome_page_spec.rb
================================================
require 'rails_helper'

describe 'トップページ' do
  specify '挨拶文を表示' do
    visit root_path
    expect(page).to have_css('h1', text: 'Hello, Rails!')
  end

  specify 'チェックボックス「a」がチェックされている' do
    visit root_path
    checkbox = find("#checkbox1")
    expect(checkbox).to be_checked
  end

  specify 'チェックボックス「b」がチェックされている' do
    visit root_path
    checkbox = find("#checkbox2")
    expect(checkbox).to be_checked
  end

  specify 'チェックボックス「c」がチェックされている' do
    visit root_path
    check 'checkbox3'
    checkbox = find("#checkbox3")
    expect(checkbox).to be_checked
  end

end
================================================

### コントローラのテスト
$ bundle exec rspec -fd spec/controllers/welcome_controller_spec.rb
================================================
WelcomeController
  GET #index
    returns http success

Finished in 0.01841 seconds (files took 4.56 seconds to load)
1 example, 0 failures
================================================

### Capybaraの動作テスト
$ bundle exec rspec -fd spec/features/welcome_page_spec.rb
================================================
トップページ
  挨拶文を表示
  チェックボックス「a」がチェックされている
  チェックボックス「b」がチェックされている (FAILED - 1)
  チェックボックス「c」がチェックされている

Failures:

  1) トップページ チェックボックス「b」がチェックされている
     Failure/Error: expect(checkbox).to be_checked
       expected `#<Capybara::Node::Element tag="input" path="/html/body/p[2]/input[2]">.checked?` to return true, got false
     # ./spec/features/welcome_page_spec.rb:18:in `block (2 levels) in <top (required)>'

Finished in 0.30855 seconds (files took 4.78 seconds to load)
4 examples, 1 failure

Failed examples:

rspec ./spec/features/welcome_page_spec.rb:15 # トップページ チェックボックス「b」がチェックされている
================================================
  • 実行結果解説
    • returns http success
      • 200 OK が返ってきているので成功
    • 挨拶文を表示
      • h1タグ内に「Hello, Rails!」が表示されているので成功
    • チェックボックス「a」がチェックされている
      • チェックされているので成功
    • チェックボックス「b」がチェックされている (FAILED - 1)
      • チェックされていないので失敗
    • チェックボックス「c」がチェックされている
      • checkメソッドでチェックボックスをチェックした後に確認しているので成功

FactoryGirl

書き方覚書

Webのレスポンスを確認

================================================
require 'rails_helper'

RSpec.describe Webmail::Admin::MailsController, type: :controller do
  it 'rootにアクセスするとhttp://test.host/_admin/loginにリダイレクトして302を返す' do
    get :index
    expect(response).to redirect_to('http://test.host/_admin/login')
    #response.status.should == 302        ### 古い書き方。Deprecation Warningsが出る
    expect(response.status).to eq(302)
  end
end
================================================

特定のタグのPATH一覧を表示

### input(チェックボックス)の一覧を表示
> all('input', visible: :all)
================================================
=> [#<Capybara::Node::Element tag="input" path="//HTML[1]/BODY[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[2]/TABLE[1]/TBODY[1]/TR[1]/TD[2]/P[1]/INPUT[1]"
>, #<Capybara::Node::Element tag="input" path="//HTML[1]/BODY[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[2]/TABLE[1]/TBODY[1]/TR[1]/TD[2]/P[1]/INPUT[2]"
>, #<Capybara::Node::Element tag="input" path="//HTML[1]/BODY[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[2]/TABLE[1]/TBODY[1]/TR[1]/TD[2]/P[1]/INPUT[3]"
>, #<Capybara::Node::Element tag="input" path="//HTML[1]/BODY[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[2]/TABLE[1]/TBODY[1]/TR[1]/TD[2]/FORM[1]/INPUT[1]"
>, #<Capybara::Node::Element tag="input" path="//HTML[1]/BODY[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[2]/TABLE[1]/TBODY[1]/TR[1]/TD[2]/FORM[1]/INPUT[2]"
>, #<Capybara::Node::Element tag="input" path="//HTML[1]/BODY[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[2]/TABLE[1]/TBODY[1]/TR[1]/TD[2]/FORM[1]/TABLE[1]/TBODY[1]/TR[1]/TD[1]/INPUT[1]"
>, #<Capybara::Node::Element tag="input" path="//HTML[1]/BODY[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[2]/TABLE[1]/TBODY[1]/TR[1]/TD[2]/FORM[1]/TABLE[1]/TBODY[1]/TR[2]/TD[1]/INPUT[1]"
>, #<Capybara::Node::Element tag="input" path="//HTML[1]/BODY[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[2]/TABLE[1]/TBODY[1]/TR[1]/TD[2]/FORM[1]/TABLE[1]/TBODY[1]/TR[2]/TD[1]/INPUT[2]"
>, #<Capybara::Node::Element tag="input" path="//HTML[1]/BODY[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[2]/TABLE[1]/TBODY[1]/TR[1]/TD[2]/FORM[1]/TABLE[1]/TBODY[1]/TR[2]/TD[3]/INPUT[1]"
>, #<Capybara::Node::Element tag="input" path="//HTML[1]/BODY[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[2]/TABLE[1]/TBODY[1]/TR[1]/TD[2]/FORM[1]/TABLE[1]/TBODY[1]/TR[2]/TD[3]/INPUT[2]"
>, #<Capybara::Node::Element tag="input" path="//HTML[1]/BODY[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[2]/TABLE[1]/TBODY[1]/TR[1]/TD[2]/FORM[2]/INPUT[1]"
>, #<Capybara::Node::Element tag="input" path="//HTML[1]/BODY[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[2]/TABLE[1]/TBODY[1]/TR[1]/TD[2]/FORM[2]/INPUT[2]"
>, #<Capybara::Node::Element tag="input" path="//HTML[1]/BODY[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[2]/TABLE[1]/TBODY[1]/TR[1]/TD[2]/FORM[2]/TABLE[1]/TBODY[1]/TR[1]/TH[2]/INPUT[1]"
>, #<Capybara::Node::Element tag="input" path="//HTML[1]/BODY[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[2]/TABLE[1]/TBODY[1]/TR[1]/TD[2]/FORM[2]/TABLE[1]/TBODY[1]/TR[2]/TD[2]/INPUT[1]">]  ※ これを使う
================================================

### xpathを利用してチェックボックスのチェックを確認する
> check('checkbox3')
> checkbox_xpath = find(:xpath, '//HTML[1]/BODY[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[2]/TABLE[1]/TBODY[1]/TR[1]/TD[2]/FORM[2]/TABLE[1]/TBODY[1]/TR[2]/TD[2]/INPUT[1]')
> expect(checkbox_xpath).to be_checked

画面に表示されていない要素を検索する

### 画面に表示されている要素を検出(デフォルト)
> all('input', visible: :visible)

### 画面に表示されていない要素を検出
> all('input', visible: :hidden)

### 全ての要素を検出
> all('input', visible: :all)

### 画面に表示されているPATHをfind(デフォルト)
> find(:xpath, '//HTML[1]/BODY[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[2]/TABLE[1]/TBODY[1]/TR[1]/TD[2]/FORM[2]/TABLE[1]/TBODY[1]/TR[1]/TH[2]/INPUT[1]')

### 画面に表示されていないPATHをfind
> find(:xpath, '//HTML[1]/BODY[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/DIV[2]/TABLE[1]/TBODY[1]/TR[1]/TD[2]/FORM[1]/TABLE[1]/TBODY[1]/TR[2]/TD[1]/INPUT[1]', visible: false)

見えてないところの

単語

参考

エラー対応

Capybara::Poltergeist::JavascriptError:

rails_helper.rbに:js_errors => falseと書いておく。JSのエラーはよくあることらしい。。

Capybara.javascript_driver = :poltergeist
Capybara.register_driver :poltergeist do |app|  # 追加
  Capybara::Poltergeist::Driver.new(app, :js_errors => false, :timeout => 60)  # 追加
end  # 追加

RspecでCapybaraのメソッドが認識されない

couldn't find file 'jquery' with type 'application/javascript'

01.png

$ bundle exec rake assets:precompile

db:migrateでエラー

$ bundle exec rails db:migrate RAILS_ENV=production
================================================
rails aborted!
NoMethodError: private method `gem' called for #<Rails::Application::Configuration:0x007f2f12cc6c10>
/data/rails/dfs/vendor/bundle/ruby/2.3.0/gems/railties-5.0.0.1/lib/rails/railtie/configuration.rb:95:in `method_missing'
/data/rails/dfs/config/application.rb:87:in `<class:Application>'
================================================

### config/application.rb のgem.configの記述を修正したらいけた
vi config/application.rb
================================================
-    config.gem 'delayed_job'
+    config.gem = 'delayed_job'
================================================

RailsのRootから実行しないとエラーになる

$ bundle exec rspec -fd sample01_spec.rb
================================================
bundler: failed to load command: rspec (/home/user/blog/vendor/bundle/ruby/2.3.0/bin/rspec)
LoadError: cannot load such file -- rails_helper
  /home/user/blog/spec/sample/sample01_spec.rb:1:in `require'
  /home/user/blog/spec/sample/sample01_spec.rb:1:in `<top (required)>'
  /home/user/blog/vendor/bundle/ruby/2.3.0/gems/rspec-core-3.5.3/lib/rspec/core/configuration.rb:1435:in `load'
  /home/user/blog/vendor/bundle/ruby/2.3.0/gems/rspec-core-3.5.3/lib/rspec/core/configuration.rb:1435:in `block in load_spec_files'
  /home/user/blog/vendor/bundle/ruby/2.3.0/gems/rspec-core-3.5.3/lib/rspec/core/configuration.rb:1433:in `each'
  /home/user/blog/vendor/bundle/ruby/2.3.0/gems/rspec-core-3.5.3/lib/rspec/core/configuration.rb:1433:in `load_spec_files'
  /home/user/blog/vendor/bundle/ruby/2.3.0/gems/rspec-core-3.5.3/lib/rspec/core/runner.rb:100:in `setup'
  /home/user/blog/vendor/bundle/ruby/2.3.0/gems/rspec-core-3.5.3/lib/rspec/core/runner.rb:86:in `run'
  /home/user/blog/vendor/bundle/ruby/2.3.0/gems/rspec-core-3.5.3/lib/rspec/core/runner.rb:71:in `run'
  /home/user/blog/vendor/bundle/ruby/2.3.0/gems/rspec-core-3.5.3/lib/rspec/core/runner.rb:45:in `invoke'
  /home/user/blog/vendor/bundle/ruby/2.3.0/gems/rspec-core-3.5.3/exe/rspec:4:in `<top (required)>'
  /home/user/blog/vendor/bundle/ruby/2.3.0/bin/rspec:23:in `load'
  /home/user/blog/vendor/bundle/ruby/2.3.0/bin/rspec:23:in `<top (required)>'
================================================

$ cd ../..
$ bundle exec rspec -fd spec/sample/sample01_spec.rb
================================================

webmail/admin/mails/show
  1 + 1 は 2 になること

Finished in 0.0104 seconds (files took 2.04 seconds to load)
1 example, 0 failures

Coverage report generated for RSpec to /home/user/coverage. 56 / 9698 LOC (0.58%) covered.
================================================

translation missing: ja.activerecord.errors.messages.record_invalid

rails-i18nをインストールする

$ bundle exec rspec -fd spec/controllers/webmail/admin/mails_controller_spec.rb
================================================

Webmail::Admin::MailsController
  rootにアクセスするとhttp://test.host/_admin/loginにリダイレクトして302を返す (FAILED - 1)

Failures:

  1) Webmail::Admin::MailsController rootにアクセスするとhttp://test.host/_admin/loginにリダイレクトして302を返す
     Failure/Error: @user = create(:user)

     ActiveRecord::RecordInvalid:
       translation missing: ja.activerecord.errors.messages.record_invalid    ※ これ
     # ./spec/controllers/webmail/admin/mails_controller_spec.rb:7:in `block (2 levels) in <top (required)>'

Finished in 0.04958 seconds (files took 2.22 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/controllers/webmail/admin/mails_controller_spec.rb:18 # Webmail::Admin::MailsController rootにアクセスするとhttp://test.host/_admin/loginにリダイレクトして302を返す

Coverage report generated for RSpec to /home/user/blog/coverage. 189 / 9210 LOC (2.05%) covered.
================================================

$ vi Gemfile
================================================
  gem 'rails-i18n'
================================================
$ bundle install --path vendor/bundle
$ bundle exec rspec -fd spec/controllers/webmail/admin/mails_controller_spec.rb
================================================
Webmail::Admin::MailsController
  rootにアクセスするとhttp://test.host/_admin/loginにリダイレクトして302を返す (FAILED - 1)

Failures:

  1) Webmail::Admin::MailsController rootにアクセスするとhttp://test.host/_admin/loginにリダイレクトして302を返す
     Failure/Error: @user = create(:user)

     ActiveRecord::RecordInvalid:
       バリデーションに失敗しました: ユーザーIDはすでに登録されています。   ※ DBに存在しているユーザをFactoryGirlで指定しているから。存在していないユーザであれば大丈夫。
     # ./spec/controllers/webmail/admin/mails_controller_spec.rb:7:in `block (2 levels) in <top (required)>'

Finished in 0.24015 seconds (files took 2.06 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/controllers/webmail/admin/mails_controller_spec.rb:18 # Webmail::Admin::MailsController rootにアクセスするとhttp://test.host/_admin/loginにリダイレクトして302を返す

Coverage report generated for RSpec to /home/user/blog/coverage. 189 / 9210 LOC (2.05%) covered.
================================================

add gem 'rails-controller-testing' to your Gemfile.

rails-controller-testing をインストールする

================================================
Failures:

  1) Webmail::Admin::MailsController render_template :index
     Failure/Error: expect(response).to render_template(:index)

     NoMethodError:
       assert_template has been extracted to a gem. To continue using it,
               add `gem 'rails-controller-testing'` to your Gemfile.
     # ./spec/controllers/webmail/admin/mails_controller_spec.rb:22:in `block (2 levels) in <top (required)>'
================================================
$ vi Gemfile
================================================
  gem 'rails-controller-testing'
================================================
$ bundle install --path vendor/bundle
$ bundle exec rspec -fd spec/controllers/webmail/admin/mails_controller_spec.rb
================================================

参考

メモ

特定のファイのマイグレーションを実行

コロンについて (Ruby)

  • コロンが前についたり後ろについたり意味が分からない
    • 前に付くコロン(:hoge)
      • シンボル
    • 後ろに付くコロン(hoge:)
      • ハッシュキーにシンボルを使う場合はコロンをハッシュキーの後ろにつけると矢印を省略して書けるというRuby1.9の表記方法
      • Railsはこの記法が一般的
### Ruby1.9より前のハッシュの書き方
### 「:france」はシンボルでハッシュのキー
### Parisはハッシュのバリュー
capital = { :france =>"Paris", :japan => "Tokyo", :uk => "London" }

### Ruby1.9以降で書けるハッシュの書き方
### 「france:」はシンボルでハッシュのキー。シンボルのコロンを後ろに付けることで「=>」を省略してかける
### Parisはハッシュのバリュー
capital = { france: "Paris", japan: "Tokyo" , uk: "London" }

### 例
[1] pry(main)> params = {}
=> {}
[2] pry(main)> params[:user] = { name: "vagrant", email: "vagrant@example.com" }   ### 「name」はシンボル
=> {:name=>"vagrant", :email=>"vagrant@example.com"}
[3] pry(main)> params[:user][:name]
=> "vagrant"

改行コード変更

### msgの改行コードを LFからCR+LFに変換する
================================================
0> "hoge\nfuga".gsub(/\n/m, "\r\n")
=> "hoge\r\nfuga"
================================================

文字化け対策

0> str = "テスト"
================================================
=> "\xE3\x83\x86\xE3\x82\xB9\xE3\x83\x88"
================================================

0> str.encoding
================================================
=> #<Encoding:ASCII-8BIT>
================================================

0> str.force_encoding("utf-8")
================================================
=> "テスト"
================================================

0> str2
================================================
=> "テスト"
================================================

0> str2 == "テスト"
================================================
=> false
================================================

0> str2 == "テスト".force_encoding("utf-8")
================================================
=> true
================================================

||=について (Ruby)

a ||= ba = a || b と同じ

  • aがnilのときbを代入
  • aがnilでないときはaの値はそのまま

[1] pry(main)> b = 5
=> 5

### aはnil
[2] pry(main)> a
NameError: undefined local variable or method `a' for main:Object
from (pry):2:in `__pry__'

### bの値5が入る
[3] pry(main)> a ||= b
=> 5
[4] pry(main)> a
=> 5

[5] pry(main)> c=10
=> 10

### cの値10は入らない
[6] pry(main)> a ||= c
=> 5

Rubyでの||=(ダブルパイプイコール ・Double Pipe / Or Equals)について

a = b || c || d

  • bを評価
    • bがnilでなければ値を代入
  • bがnilの場合cを評価
    • cがnilでなければ値を代入
  • cがnilの場合dを評価
    • dがnilでなければ値を代入
  • dがnilの場合aは何も変更されない

?(クエスチョン)や:(コロン)について (Ruby)

  • a ? b : c
    • 三項演算子
    • if a then b else c end と同じ
      • aが真のときb
      • aが偽のときc
[1] pry(main)> a=1
=> 1
[2] pry(main)> b=2
=> 2
[3] pry(main)> c=3
=> 3

### aが真のときb
[4] pry(main)> a ? b : c
=> 2

[5] pry(main)> a=nil
=> nil

### aが偽のときc
[6] pry(main)> a ? b : c
=> 3

ビューの<%について

  • <% ~ %>
    • 出力が不要なコードを書く場合
  • <%= "文字列" %>
    • 「文字列」を画面に出力して改行する。
  • <%== "文字列" %>
    • 結果をエスケープして「文字列」を画面に出力して改行する。
    • ここでは上の例と同じ
  • <%= "文字列" -%>
    • 「文字列」を画面に出力する。改行はしない。

参考 : ビュー(view)

present? (Rails)

  • !blank? を実行するメソッド`
    • nil, "", " "(半角スペースのみ), , {}(空のハッシュ) のときにfalseを返す
    • blank? (Rails)
      • nil, "", " "(半角スペースのみ), , {}(空のハッシュ) のときにtrueを返

値の取り出し、受け渡し

  • 文字列から特定の文字列を取り出す
### ここの「Date:」直後の日時を抜き取る
0> @item.rfc822
=> "Return-Path: (~中略~) \r\nDate: Mon, 24 Oct 2016 19:48:57 +0900\r\nFrom: (~中略~) http://google.com\r\n"

### ()でキャプチャした部分を[1]で取得
0> @item.rfc822.match(/\r\nDate: (.+) \+0900/)[1]
=> "Mon, 24 Oct 2016 19:48:57"

### パターンにマッチする部分をMatchDataオブジェクトの[0]で取得
0> @item.rfc822.match(/\r\nDate: (.+) \+0900/)[0]
=> "\r\nDate: Mon, 24 Oct 2016 19:48:57 +0900"


  • 配列の要素を結合して文字列にする
0> @item.address
=> ["test1@dockernet.co.jp", "test2@dockernet.co.jp"]

0> @item.address.join(",")
=> "test1@dockernet.co.jp,test2@dockernet.co.jp"
  • コントローラからヘルパーへ値を渡す
    • :plan_flgの値「plan」を渡す
    • ヘルパーのファイルは大体どのビューも読み込んでいそう
      • 詳細は別途確認
### あるビューの中身
================================================
<div class="timeSet">
  <%= date_picker f, 'st_at', :errors=>@item.errors, :value=>hoge, :plan_flg=>'plan' %>
</div>
================================================

### あるヘルパーのメソッドの中身
================================================
def date_picker(f, name, _options={})
...
  plan_flg = options[:plan_flg].to_s  ### ビューの:plan_flgの値「plan」が入る。この場合はto_sは無くてもよさそう
  if plan_flg == 'plan'
    ret += "<span id=\"plan\">"  + "</span>"
  end
...
end
================================================
  • 値の取り出し
    • mapを使わないといけない場合と「.」で良い場合
### show_week_one
================================================
[1] pry(#<#<Class:0x007f155e0e58b0>>)> schedule
=> #<Gw::Plan id: 14,
...
 before_starting_work_comment: "始業前コメント",
...
 week: nil,
 month: nil,
 tmp_id: "5738f8b26c82503d410473807045c384">

[2] pry(#<#<Class:0x007f155e0e58b0>>)> schedule.before_starting_work_comment
=> "始業前コメント"
================================================

### show_week
================================================
[1] pry(#<#<Class:0x007fd69290a090>>)> schedules
=> [#<Gw::Plan id: 14,
...
 before_starting_work_comment: "始業前コメント",
...
 updated_at: "2016-08-24 16:12:39",
 week: nil,
 month: nil,
 tmp_id: "5738f8b26c82503d410473807045c384">]

[2] pry(#<#<Class:0x007fd69290a090>>)> schedules.map{|s| s.before_starting_work_comment}[0]
=> "始業前コメント"

※ 配列なのでmapを使わなくても以下でも取得できる
schedule[0].before_starting_work_comment
================================================

### ユーザ名取得
================================================
[53] pry(#<#<Class:0x007f1a619dd410>>)> System::User.select(:id, :name)
=> [#<System::User id: 5194, name: "システム管理者">,
 #<System::User id: 5464, name: "徳島 太郎">,
 #<System::User id: 5465, name: "阿波 花子">,
 #<System::User id: 5466, name: "ftakao2007">]

[54] pry(#<#<Class:0x007f1a619dd410>>)> System::User.select(:id, :name).where(id: 5466)
=> [#<System::User id: 5466, name: "ftakao2007">]

[56] pry(#<#<Class:0x007f1a619dd410>>)> System::User.select(:id, :name).where(id: 5466).map{|s| [s.id, s.name]}
=> [[5466, "ftakao2007"]]

[55] pry(#<#<Class:0x007f1a619dd410>>)> System::User.select(:id, :name).where(id: 5466).map{|s| [s.id, s.name]}[0][1]
=> "ftakao2007"

[56] pry(#<#<Class:0x007f1a619dd410>>)> System::User.select(:id, :name).where(id: 5466).map{|s| s.name }
=> ["ftakao2007"]

[57] pry(#<#<Class:0x007f1a619dd410>>)> System::User.select(:id, :name).where(id: 5466).map{|s| s.name }[0]
=> "ftakao2007"
================================================

代入できないとき

``` shell
0> items[1].date
=> "2017-12-06 10:01"

### 代入できない
0> items[1].date = "2017-12-07 10:01:00 +0900"
=> undefined method `date=' for #<Webmail::Mail:0x007f0bfcb57a48>
Did you mean?  date

0> items[1].node[:message_date]
=> 2017-12-06 10:01:00 +0900

### 代入できる
0> items[1].node[:message_date] = "2017-12-06 10:01:00 +0900"
=> "2017-12-06 10:01:00 +0900"

### Hashの値取り出し
================================================
0> params[:item][:ids]
=> <ActionController::Parameters {"5"=>"1", "4"=>"1"} permitted: false>

0> params[:item][:ids].values
=> ["1", "1"]

0> params[:item][:ids].keys
=> ["5", "4"]

0> params[:item][:ids].keys.map(&:to_i)
=> [5, 4]

---

0> params[:item]
=> <ActionController::Parameters {"ids"=><ActionController::Parameters {"6"=>"1"} permitted: false>} permitted: false>

0> params[:item][:ids]
=> <ActionController::Parameters {"6"=>"1"} permitted: false>

0> params[:item][:ids].keys
=> ["6"]

0> params[:item][:ids].keys.map(&:to_i)
=> [6]

0> params[:item][:ids].keys.map(&:to_i).select(&:positive?)
=> [6]
================================================

### 色々
================================================
[5] pry(#<Webmail::Admin::MailsController>)> @item.attachments.map(&:name)
=> ["01.png"]
[6] pry(#<Webmail::Admin::MailsController>)> @item.attachments.map(&:name).join(', ')
=> "01.png"
[8] pry(#<Webmail::Admin::MailsController>)> @item.attachments.map(&:name)[0]
=> "01.png"
================================================
  • 値の展開

  • " ~ " の中で値を展開するとき

    • "#{@item}" のようにシャープをつけてかっこでくくる
  • yamlファイルから値を読み込む

### ldap.ymlのhostの値を読み込む
addomain = YAML.load_file("#{Rails.root}/config/ldap.yml")[ENV['RAILS_ENV']]['host']

JavaScriptのデバッグ方法について

参考

単語帳

フィールド

  • 意味1
    • Web上に表示されるフォーム(テキストを入力するところなど)に記載されるパラメータ
      • paramsメソッドは、フォームから送信されてきたパラメータ (つまりフォームのフィールド) を表すオブジェクト
    • フォームの事を「入力フィールド」と表現することもある
  • 意味2
    • データベースでテーブルを構成する列の事をフィールドと呼ぶことがある
      • データベースのなかった大昔から、ファイル操作、帳票や画面設計などで使われている用語
      • 国際標準規格として構造型DBが規定され、構造化DBではフィールドのことを構成要素といった呼び方をしている
    • 列は「カラム」と呼ばれることもある
      • リレーショナルDBでは、フィールドに相当するものを、カラムや列と呼ぶ
      • 標準SQLでもそう規定されている
      • カラムはDB寄りの表現
    • ちなみに行は「レコード」と呼ぶ
  • 意味3
    • モデルの属性の事
    • 以下Railsモデルの属性とDBMS側のテーブルカラムのマッピング
### sqlite3
$ less vendor/bundle/ruby/2.1.0/gems/activerecord-4.1.9/lib/active_record/connection_adapters/sqlite3_adapter.rb
================================================
...
    class SQLite3Adapter < AbstractAdapter
      include Savepoints

      NATIVE_DATABASE_TYPES = {
        primary_key:  'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL',
        string:       { name: "varchar", limit: 255 },
        text:         { name: "text" },
        integer:      { name: "integer" },
        float:        { name: "float" },
        decimal:      { name: "decimal" },
        datetime:     { name: "datetime" },
        timestamp:    { name: "datetime" },
        time:         { name: "time" },
        date:         { name: "date" },
        binary:       { name: "blob" },
        boolean:      { name: "boolean" }
      }
...
================================================

### mysql
$ less vendor/bundle/ruby/2.1.0/gems/activerecord-4.1.9/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
================================================
...
      NATIVE_DATABASE_TYPES = {
        :primary_key => "int(11) auto_increment PRIMARY KEY",
        :string      => { :name => "varchar", :limit => 255 },
        :text        => { :name => "text" },
        :integer     => { :name => "int", :limit => 4 },
        :float       => { :name => "float" },
        :decimal     => { :name => "decimal" },
        :datetime    => { :name => "datetime" },
        :timestamp   => { :name => "datetime" },
        :time        => { :name => "time" },
        :date        => { :name => "date" },
        :binary      => { :name => "blob" },
        :boolean     => { :name => "tinyint", :limit => 1 }
      }
...
================================================

### postgresql
$ less vendor/bundle/ruby/2.1.0/gems/activerecord-4.1.9/lib/active_record/connection_adapters/postgresql_adapter.rb
================================================
...
      NATIVE_DATABASE_TYPES = {
        primary_key: "serial primary key",
        string:      { name: "character varying", limit: 255 },
        text:        { name: "text" },
        integer:     { name: "integer" },
        float:       { name: "float" },
        decimal:     { name: "decimal" },
        datetime:    { name: "timestamp" },
        timestamp:   { name: "timestamp" },
        time:        { name: "time" },
        date:        { name: "date" },
        daterange:   { name: "daterange" },
        numrange:    { name: "numrange" },
        tsrange:     { name: "tsrange" },
        tstzrange:   { name: "tstzrange" },
        int4range:   { name: "int4range" },
        int8range:   { name: "int8range" },
        binary:      { name: "bytea" },
        boolean:     { name: "boolean" },
        xml:         { name: "xml" },
        tsvector:    { name: "tsvector" },
        hstore:      { name: "hstore" },
        inet:        { name: "inet" },
        cidr:        { name: "cidr" },
        macaddr:     { name: "macaddr" },
        uuid:        { name: "uuid" },
        json:        { name: "json" },
        ltree:       { name: "ltree" }
      }
...
================================================

ヘルパー

パーシャル

コレクションとメンバー

  • コレクション
    • idをもたないアクション
      • index, new, create
  • メンバー
    • idをもつアクション
      • show, update, edit, destroy

正規表現

nmp

phantomjsをインストールする例。(たぶんもっと筋の良いやり方があるはず)

$ npm install phantomjs@1.9.8
================================================
npm WARN deprecated npmconf@0.0.24: this package has been reintegrated into npm and is now out of date with respect to npm

> phantomjs@1.9.8 install /var/share/pref-hiroshima-joruri-mail_14563-rspec/node_modules/phantomjs
> node install.js

PhantomJS detected, but wrong version 2.1.1 @ /usr/local/bin/phantomjs.
Downloading https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.7-linux-x86_64.tar.bz2
Saving to /tmp/phantomjs/phantomjs-1.9.7-linux-x86_64.tar.bz2
Receiving...
  [========================================] 99% 0.0s
Received 12852K total.
Extracting tar contents (via spawned process)
Copying extracted folder /tmp/phantomjs/phantomjs-1.9.7-linux-x86_64.tar.bz2-extract-1479299548911/phantomjs-1.9.7-linux-x86_64 -> /var/share/pref-hiroshima-joruri-mail_14563-rspec/node_modules/phantomjs/lib/phantom
Writing location.js file
Done. Phantomjs binary available at /var/share/pref-hiroshima-joruri-mail_14563-rspec/node_modules/phantomjs/lib/phantom/bin/phantomjs
/var/share/pref-hiroshima-joruri-mail_14563-rspec
mqw phantomjs@1.9.8
  tqq adm-zip@0.2.1
  tqq kew@0.1.7
  tqq mkdirp@0.3.5
  tqq ncp@0.4.2
  tqw npmconf@0.0.24
  x tqw config-chain@1.1.11
  x x tqq ini@1.3.4
  x x mqq proto-list@1.2.4
  x tqq inherits@1.0.2
  x tqq ini@1.1.0
  x tqw nopt@2.2.1
  x x mqq abbrev@1.0.9
  x tqq once@1.1.1
  x tqq osenv@0.0.3
  x mqq semver@1.1.4
  tqq progress@1.1.8
  tqw request@2.78.0
  x tqq aws-sign2@0.6.0
  x tqq aws4@1.5.0
  x tqq caseless@0.11.0
  x tqw combined-stream@1.0.5
  x x mqq delayed-stream@1.0.0
  x tqq extend@3.0.0
  x tqq forever-agent@0.6.1
  x tqw form-data@2.1.2
  x x mqq asynckit@0.4.0
  x tqw har-validator@2.0.6
  x x tqw chalk@1.1.3
  x x x tqq ansi-styles@2.2.1
  x x x tqq escape-string-regexp@1.0.5
  x x x tqw has-ansi@2.0.0
  x x x x mqq ansi-regex@2.0.0
  x x x tqq strip-ansi@3.0.1
  x x x mqq supports-color@2.0.0
  x x tqw commander@2.9.0
  x x x mqq graceful-readlink@1.0.1
  x x tqw is-my-json-valid@2.15.0
  x x x tqq generate-function@2.0.0
  x x x tqw generate-object-property@1.2.0
  x x x x mqq is-property@1.0.2
  x x x tqq jsonpointer@4.0.0
  x x x mqq xtend@4.0.1
  x x mqw pinkie-promise@2.0.1
  x x   mqq pinkie@2.0.4
  x tqw hawk@3.1.3
  x x tqq boom@2.10.1
  x x tqq cryptiles@2.0.5
  x x tqq hoek@2.16.3
  x x mqq sntp@1.0.9
  x tqw http-signature@1.1.1
  x x tqq assert-plus@0.2.0
  x x tqw jsprim@1.3.1
  x x x tqq extsprintf@1.0.2
  x x x tqq json-schema@0.2.3
  x x x mqq verror@1.3.6
  x x mqw sshpk@1.10.1
  x x   tqq asn1@0.2.3
  x x   tqq assert-plus@1.0.0
  x x   tqq bcrypt-pbkdf@1.0.0
  x x   tqw dashdash@1.14.0
  x x   x mqq assert-plus@1.0.0
  x x   tqq ecc-jsbn@0.1.1
  x x   tqw getpass@0.1.6
  x x   x mqq assert-plus@1.0.0
  x x   tqq jodid25519@1.0.2
  x x   tqq jsbn@0.1.0
  x x   mqq tweetnacl@0.14.3
  x tqq is-typedarray@1.0.0
  x tqq isstream@0.1.2
  x tqq json-stringify-safe@5.0.1
  x tqw mime-types@2.1.12
  x x mqq mime-db@1.24.0
  x tqq node-uuid@1.4.7
  x tqq oauth-sign@0.8.2
  x tqq qs@6.3.0
  x tqq stringstream@0.0.5
  x tqw tough-cookie@2.3.2
  x x mqq punycode@1.4.1
  x mqq tunnel-agent@0.4.3
  tqw request-progress@0.3.1
  x mqq throttleit@0.0.2
  tqq rimraf@2.2.8
  mqq which@1.0.9

npm WARN enoent ENOENT: no such file or directory, open '/var/share/pref-hiroshima-joruri-mail_14563-rspec/package.json'
npm WARN pref-hiroshima-joruri-mail_14563-rspec No description
npm WARN pref-hiroshima-joruri-mail_14563-rspec No repository field.
npm WARN pref-hiroshima-joruri-mail_14563-rspec No README data
npm WARN pref-hiroshima-joruri-mail_14563-rspec No license field.
================================================

$ sudo cp node_modules/phantomjs/lib/phantom/bin/phantomjs /usr/local/bin/phantomjs