概要
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メソッドでアクセス : 社員情報の削除
- 同じことを繰り返すな (Don't Repeat Yourself: DRY)
階層構造
.
|-- 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」
- 例では:articleというシンボルを渡す
$ 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
- この状態ではフォームの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アクションに関連付けられる
- /articles/newというルーティングは、このフォームを最初に表示するときに使用されるもの
$ 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モデルと対応付けられる
- 例で作成されるもの
- Articleモデルの作成
- マイグレーション
- マイグレーションはRubyのクラス
- データベーステーブルの作成や変更を簡単に行うためのしくみ
- マイグレーションを実行するにはrakeコマンドを実行する
- マイグレーションを使用して行ったデータベース構成の変更は、後から取り消すことができる
- マイグレーションファイルの名前にはタイムスタンプが含まれており、これに基いて、マイグレーションは作成された順に実行される
- デフォルトはdevelopment環境で実行される
- production環境で実行したい場合
- RAILS_ENV=production rake db:migrate
- production環境で実行したい場合
- 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になっている
- Article.find(params[:id])
$ 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
記事の一覧を表示する
### コントローラに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アクションに移動
- 同じコントローラ内であればコントローラの指定は省略できる
- <%= link_to 'My Blog', controller: 'articles' %>
- その他
- developmentモード(Railsデフォルト)はRailsはリクエストのたびにアプリケーションを再読み込みする
- 開発をやりやすくするためであり、変更を行なうたびにRailsのWebサーバーを再起動する必要は無い
- developmentモード(Railsデフォルト)はRailsはリクエストのたびにアプリケーションを再読み込みする
### トップページから記事の一覧、記事の作成へのリンクを追加
$ 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) 操作
- データの検証 (バリデーション)
- 洗練された検索機能
- 複数のモデルを関連付ける(リレーションシップ)
- Active Recordの機能
- バリデーション
- 例は検証が通らない内容を持つ@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) に含まれる
- :methodオプション
### 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)
- 1つのコメントは1つの記事に属する (Each comment belongs to one article)。
- この宣言により実現される事の一例
- @articleというインスタンス変数に記事が1つ含まれている場合
- @article.commentsと書くだけでその記事に関連付けられているコメントをすべて取得できる
- @articleというインスタンス変数に記事が1つ含まれている場合
- 記事とコメントの関係を宣言
- Active Recordの関連付け機能により、2つのモデルの間にリレーションシップを簡単に宣言することができる
### コメントモデルの作成
$ 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というテキスト型のカラムが生成される
- コマンド : ./bin/rails generate scaffold task content:text
-
マイグレーションファイルを基にテーブルを作成
- rakeタスクとして定義されている
- db:create : データベースの作成
- db:migrate : テーブルへの変更を行う
- マイグレーションファイルを新たに作成するごとに実施する
- rakeタスクとして定義されている
### 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)
- ブログの記事の例
- 参照
- 作成
- 更新
- 削除
- ブログの記事の例
- リソースの親子関係
- ブログの記事の例
- 記事にはコメントが複数付く
- 記事はカテゴリに属する
- ブログの記事の例
- APIのユーザが必要としている情報資源(リソース)
- 注意点
- リソース=データベーステーブルとは限らない
- リソースの洗い出しはDB設計にそれとなく似ているが、APIはあくまでインタフェースなのでひとつ
- ⇒リソースの中にメタデータとして関連する情報を持っていても問題ない
- リソース=データベーステーブルとは限らない
URL設計
非常に奥深く、様々な要素に関わってくるので慎重に設計する必要がある。
- APIのURL設計で意識すべき点
- ひと目でAPIと分かるようなURLにする
- ディレクトリに分ける
- サブドメインに切る
- http://api.example.com
- サブドメインに切った方がディレクトリ構成はフリーダムに作ることができる
- URLにAPIのバージョンを含める
- REST APIを使用する開発者がAPIのバージョンを選択しやすくなり、バージョンの切り分けも容易となるメリットが有る
- URLに動詞を含めず、複数形の名詞のみで構成する
- 良くない例
-
http://api.example.com/v1/article/create
- ⇒ 一つのリソースに対してCRUDの操作が必要になった場合にその操作の分だけURLが増えてしまう
-
http://api.example.com/v1/article/create
- 良い例
-
http://api.example.com/v1/articles
- article全てを指す
-
http://api.example.com/v1/articles/126
- articlesの中のID:126を指す
-
http://api.example.com/v1/articles/126/comments
- articlesの中のID:126についたcomment全てを指す
-
http://api.example.com/v1/articles/126/comments/10
- articlesの中のID:126についたcommentsの中のID:10を指す
- IDをURLに含めることでリソースごとに一意のURLを割り当てることができている
- 名詞を複数形にすることで全てのリソース(複数)を指す場合と、全てのリソース(複数)の中からIDを指定している場合を表現することができ、表現に一貫性を持たせることができている
-
http://api.example.com/v1/articles
- 良くない例
- リソースの関係性がひと目で分かるようにする
- 例
- http://api.example.com/v1/articles/126/comments
- ⇒記事ID126に関するすべてのコメントということが分かる
- 例
- アプリケーションや言語に依存する拡張子は含めない
- URLの末尾に.phpや.plなど言語に関係した拡張子を付けない
- 長くしすぎない
- 単純にURLが長すぎると実装が面倒になる
- ひと目でAPIと分かるような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 の仕様に則って実装されていた処理系
- 今は独自の進化を遂げて準拠していない
- もはやブラウザという檻から外れた汎用言語
- CommonJS の仕様に則って実装されていた処理系
- npm
- CommonJS界のパッケージ管理ツール
- node.jsで使っているパッケージ管理ツールの名前
- 基本的にサーバサイド用
- bower
- ブラウザで動く昔ながらの Javascript のためのパッケージ管理ツール
- 基本的にクライアントサイド用
- npmでインストールする
- node.jsで実装されている
- bowerを使うためにnode.jsのという処理系が必要
- パッケージ例
- JQuery
- AngularJS
- AngularJSなど一部のパッケージはbowerだけでなくnpmでも配布されている
- npmが統一リポジトリになるという話が出てはいるが片付いてはいなさそう
- ブラウザで動く昔ながらの Javascript のためのパッケージ管理ツール
- 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
================================================
動作テスト
### 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メソッドでチェックボックスをチェックした後に確認しているので成功
- returns http success
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を使う際に知っておきたいこと
- キャプチャの取り方とか基本的なこと
-
Rails でつくる API のテストの書き方(RSpec + FactoryGirl)
- APIのテストはCapybaraを使わない。Webページのテストは使う
-
Capybaraでクリックできない場合の対処法
- 上手くクリックできないとき上から試すとどれかで成功するケースが多い
- xpathまとめ
-
Capybaraでfill_inを使わずにテキストボックスに入力する
- fill_inが使えないとき
エラー対応
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のメソッドが認識されない
- エラー例
TypeError: undefined is not an object (evaluating 'i.left')
-
apybara + Rspecでvisitがundefined methodと言われたときの対策
- rails_helper.rb の
RSpec.configure
の中にconfig.include Capybara::DSL
を追加したら動いた
- rails_helper.rb の
couldn't find file 'jquery' with type 'application/javascript'
$ 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はこの記法が一般的
- 前に付くコロン(:hoge)
### 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 ||= b
は a = 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?
を実行するメソッド`
値の取り出し、受け渡し
- 文字列から特定の文字列を取り出す
### ここの「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メソッドは、フォームから送信されてきたパラメータ (つまりフォームのフィールド) を表すオブジェクト
- フォームの事を「入力フィールド」と表現することもある
- Web上に表示されるフォーム(テキストを入力するところなど)に記載されるパラメータ
- 意味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をもたないアクション
- メンバー
- idをもつアクション
- show, update, edit, destroy
- idをもつアクション
正規表現
-
Railsの正規表現でよく使われる \A \z って何??
-
\z
は末尾が改行の場合はマッチしない。\Z
だとマッチする
-
nmp
phantomjsをインストールする例。(たぶんもっと筋の良いやり方があるはず)
- 1.9.8でないと動かない機能もあるらしい
- 1.9.8を指定しているがなぜか1.9.7が入る
$ 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