Railsチュートリアル 第8章
8.1 セッション
8.1.1
演習1
省略.
演習2
routes.rb
に記述したルーティングのみが表示されました.
8.1.2
演習1
routes.rb
における設定
post '/login', to: 'sessions#create'
により実現されていますね.
8.1.3
演習1
省略.
8.1.4
Flashメッセージについて今ひとつよくわからなかったので, Railsガイドを参照しました.
flashはセッションの中の特殊な部分であり、リクエストごとにクリアされます。この特徴から、flashは「直後のリクエスト」でのみ参照可能になります。
これを踏まえて, Flashメッセージが残ってしまう理由を説明しましょう.
ログインボタンを押すことにより, /login
に対しPOSTリクエストが送られます. このとき変数flash
にエラーメッセージが格納されます.
これと同時にnew
ページがレンダリングされますが, これは同一のPOSTリクエストの中で行われます.
上の引用文からもわかるように, 次のリクエストでもまだ変数flash
にエラーメッセージは格納されたままです. だからHomeページに移動してもフラッシュメッセージが表示されたままなのです.
次にHelpページなどにアクセスし, もう一度HTTPリクエストを送ることにより, やっとflash
の中身がクリアされます.
8.1.5
演習1
flash.now
がうまく機能していることがブラウザ上でも確認できます.
8.2 ログイン
Applicationコントローラの定義内に, 追加した覚えのないメソッドがある...どこかで見落としたのでしょうか.
# app/controllers/application_controller.rb class ApplicationController < ActionController::Base protect_from_forgery with: :exception # 見知らぬ行 include SessionHelper end
8.2.1
演習1
Sxi%2FKPGHw...
のような暗号化された値で保存されていますね.
演習2
有効期限の欄にはセッション
と書かれています.
8.2.2
演習1
たしかにnil
が返ってきます.
irb(main):003:0> User.find_by(id: 2) User Load (0.9ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1 => nil
演習2
以下のようになりました. 例外を発生させないことと, 2回目にデータベースへの問い合わせが無いことが確認できますね.
>> session = {} => {} >> session[:user_id] = nil => nil >> @current_user ||= User.find_by(id: session[:user_id]) (0.6ms) SET NAMES utf8, @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483 User Load (0.3ms) SELECT `users`.* FROM `users` WHERE `users`.`id` IS NULL LIMIT 1 => nil >> session[:user_id] = User.first.id User Load (0.5ms) SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1 => 1 >> @current_user ||= User.find_by(id: session[:user_id]) User Load (1.2ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 => #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2018-06-28 05:35:56", updated_at: "2018-06-28 05:35:56", password_digest: "$2a$10$i8HBze1p.5miIytb9ahZjefM27b8EXVMSMjV/B0za7t..."> >> @current_user ||= User.find_by(id: session[:user_id]) => #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2018-06-28 05:35:56", updated_at: "2018-06-28 05:35:56", password_digest: "$2a$10$i8HBze1p.5miIytb9ahZjefM27b8EXVMSMjV/B0za7t...">
8.2.3
Bootstrapのドロップダウンがうまく機能しません. 何行かコードを追加すると動くという記事もありましたが, 浅い理解でコードを書き換えるのも嫌なので後回しにします.
と思ったらただのタイポでした. ではBootstrapが動かないと報告している記事はなんだったんでしょう.
演習1, 2
省略.
8.2.4
演習1, 2
省略.
8.2.5
演習1, 2
省略.
8.3 ログアウト
演習1
省略.
演習2
ログアウトと同時にcookieの値が変化していることがわかります.
Railsチュートリアル 第7章
7.1 ユーザーを表示する
7.1.1
演習1
以下の通りでした.
--- !ruby/object:ActionController::Parameters parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess controller: static_pages action: about permitted: false
演習2
全く同じものが表示されました.
irb(main):002:0> puts user.attributes.to_yaml --- id: 4 name: Michael Jackson email: mhartl@example.com created_at: !ruby/object:ActiveSupport::TimeWithZone utc: &1 2018-06-25 09:33:51.000000000 Z zone: &2 !ruby/object:ActiveSupport::TimeZone name: Etc/UTC time: *1 updated_at: !ruby/object:ActiveSupport::TimeWithZone utc: &3 2018-06-25 09:41:15.000000000 Z zone: *2 time: *3 password_digest: "$2a$10$tM4ebAaSFIzfhqzi6.Lo/Ofs6/TfKpnP7LrgkCRV/z02U.CjwKubu" => nil
7.1.2
演習1, 2
こんな感じでしょうかね.
# app/views/users/show.html.erb <%= @user.name %>, <%= @user.email %>, <%= @user.created_at %>, <%= @user.updated_at %><br> <%= Time.now %>
7.1.3
Dockerで環境を構築している場合は, Byebugを利用するためにひと工夫必要なようです. 詳しくはこちらに記載しました.
users_controller.rb
にdebugger
を追記したら, 以下のコマンドでコンテナの標準入出力とローカルマシンのターミナルをアタッチします.
docker attach <Rails' container name>
この状態で/users/1
にアクセスすると, Byebugのプロンプトがターミナルに表示されます.
[1, 9] in /myapp/app/controllers/users_controller.rb 1: class UsersController < ApplicationController 2: def show 3: @user = User.find(params[:id]) 4: byebug => 5: end 6: 7: def new 8: end 9: end (byebug)
params
の内容をYAML形式で表示してみましょう.
(byebug)puts params.to_yaml --- !ruby/object:ActionController::Parameters parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess controller: users action: show id: '1' permitted: false nil
y
メソッドも試してみます.
(byebug) y params *** NoMethodError Exception: undefined method `y' for #<UsersController:0x00007fe159010210>
Byebugではy
メソッドは使えないみたいですね.
演習2
new
アクションではインスタンスメソッドの@user
は定義されていませんね.
7.1.4
演習1, 2
省略.
演習3
リスト7.12と7.13を見比べると, コードの量が異なりますね. ハッシュを引数にとる場合は, ハッシュの中から値を取り出すコードを書く必要があります. しかしキーワード引数であれば, それは不要です.
他にも, メソッドが予期していない引数を受け取った場合にも挙動が異なります. ハッシュ引数の場合, 以下のような誤った引数を指定してもエラーが発生しません.
grabatar_for @user, size: 100, shape: :square
ハッシュを受け取っている以上, 間違った引数と認識されないわけですね. しかしキーワード引数の場合, 上記のような引数を指定するとエラーが発生します. 定義していないキーワードが存在するからですね.
7.2 ユーザー登録フォーム
7.2.1
演習1
:name
を:nome
と書き換えユーザー登録ページにアクセスすると, 以下のようなエラーが表示されました.
undefined method `nome' for #<User:0x00007fe154bddef8>
演習2
Vimの置換コマンドを使ってf
をfoobar
に書き換えましょう.
:%s/f\./foobar\.
ファイル全体のf.
をfoobar.
に置換しています. これだけではブロック引数の|f|
の部分は置換できないので, そこは手動で書き換えましょう.
以下のようにしてしまうと無駄なところまで置換されるので注意ですね.
:%s/f/foobar
この状態でユーザー登録ページにアクセスしても, 問題なく表示されます. では何が問題なのでしょうか...? わかりませんでした. でもあまりよくないとのことですから, 戻しておきました. 戻すときは以下のコマンドで良いですね.
:%s/foobar/f
7.2.2
演習1
いちいちHTMLタグを書くのなんて面倒ですもんね.
7.3 ユーザー登録失敗
7.3.1
Rails側ではシンボル, HTML側では文字列というのが少しややこしいですね.
7.3.2
演習1
省略.
7.3.3
演習1
省略.
演習2
入力データの検証に失敗した場合, create
コントローラによって, new
ページがレンダリングされます. あくまでアクセスしているURLはcreate
に対応する/users
ということでしょうかね.
7.3.4
演習1
こちらを参考にしました.
assert_select 'div#error_explanation' assert_select 'div.field_with_errors' assert_select 'li', "Name can't be blank" assert_select 'li', "Email is invalid" assert_select 'li', "Password is too short (minimum is 6 characters)" assert_select 'li', "Password confirmation doesn't match Password"
演習2
何が問題になっているかを整理しましょう.
config/routes.rb
に以下のルーティングを設定しました.
post '/signup', to: 'users#create'
これによって, /signup
へのPOST
リクエストに, create
アクションが対応するようになりました.
しかしテストコードでPOST
リクエストをしているURLは/users
のままです.
assert_no_difference 'User.count' do post users_path, params: #略 end
それなのにテストをパスしまっているのはどうしてだろうというのが, この演習の問いです.
答えとしては, routes.rb
に上記の設定をしたとしても, /users
へのPOST
リクエストとcreate
アクションの対応は消えたわけではないとうことでしょうね.
演習3
以下のように変更し, テストにパスすることを確認しました.
assert_no_difference 'User.count' do post signup_path, params: #略 end
演習4
正しいアクションが指定されているかを確認するテストを書き加え, テストにパスすることを確認しました.
assert_select 'form[action="/signup"]'
7.4 ユーザー登録成功
7.4.1
演習1, 2
省略.
7.4.2
演習1, 2
省略.
7.4.3
演習1, 2
省略.
7.4.4
演習1
これまでのように, HTMLタグの有無を確認するテストをはじめは思いつきました.
assert_select 'div.alert-success'
flash
が空でないかを確認するのであれば, 以下のように書けます.
assert_not flash.empty?
この書き方でテストがパスすることが確認できました. しかしこれだと演習2の変更がうまくいっているかどうかのテストはできませんね.
演習2, 3
省略.
演習4
データの保存に失敗した場合はリダイレクトが行われません. したがってテンプレートファイルのassertionでこのバグを検知することができます.
7.5 プロのデプロイ
省略.
Docker環境でByebugを使う方法
概要
Railsチュートリアルの第7章でByebugを使っています. Docker環境でByebugを利用するためには, 追加手順が必要なので書き残しておきます.
本当ははじめから全てが整った環境を構築できれば申し分ないですが, 途中からでも以下の手順を実行すれば問題ありません.
手順
以前書いたこちらの記事の通りに環境が出来上がっている状態からスタートします.
docker-compose.yml
に, 以下の設定を加えます.
# docker-compose.yml services: web: stdin_open: true tty: true
これら2つの設定により, コンテナ内の標準入出力をローカルマシンのターミナルにアタッチする準備が整います.
さてComposeファイルを修正したので, それを反映させなくてはなりません. 先ほども述べたように, すでに環境が構築し終わった後にこの変更を加えています. この場合は以下のコマンドを実行すればOKです.
$ docker-compose up -d
公式ドキュメントを見て, コマンドの説明を見てみましょう.
If there are existing containers for a service, and the service’s configuration or image was changed after the container’s creation,
docker-compose up
picks up the changes by stopping and recreating the containers (preserving mounted volumes).
service's configuration
への変更は, コンテナを再構築することにより適用されるということですね.
コンテナが起動した状態でもいいの?
上で引用した説明分に書かれているように, すでにコンテナを起動している状態でも上のコマンドを実行することができます.
docker-compose restart
ではないの?
これについては公式ドキュメントに書いてあります.
If you make changes to your
docker-compose.yml
configuration these changes are not reflected after running this command.
このコマンドではComposeファイルの変更は反映されません.
docker-compose build
からやり直す必要はないの?
これも公式ドキュメントに書いてあります.
If you change a service’s Dockerfile or the contents of its build directory, run
docker-compose build
to rebuild it.
このコマンドを使うのは, Dockerfile
やGemfile
に変更があった場合です.
Byebugの使い方
上の手順を実行することにより, Byebugを使う準備が整いました. ここからはByebugの使い方です. 以下のコマンドを実行し, コンテナが起動した状態にあるとします.
$ docker-compose up -d
次のコマンドで, Railsが動いているコンテナのIDもしくはコンテナ名を調べます.
$ docker ps
あとは今調べたコンテナの標準入出力を, ローカルマシンのターミナルにアタッチするコマンドを実行します.
$ docker attach <container name or ID>
この状態でWebアプリケーションにアクセスすると, Byebugの出力がターミナルに表示されます.
デタッチする
コンテナの標準入出力を, ローカルマシンのターミナルにアタッチする方法がわかりました. 大事なのはここからです. デタッチを正しく行わないと面倒なことになります.
デバッグが終了し, コンテナの標準入出力をデタッチしたい場合は以下を実行してください.
Ctrl+P, Ctrl+Q
つまりCtrl
キーを押しっぱにしたままpq
と押せばいいわけですね. もしCtrl+C
を押してしまった場合は面倒なことになる可能性があります. そのときは こちらの記事を参照してください.
【Rails, Docker】コンテナが起動後すぐに落ちてしまうトラブルの原因とその対処法
概要
DockerでRails環境を構築すると, コンテナが起動しないトラブルに見舞われることがあります. その原因と対処法を説明します.
トラブルケース
以下のようなコマンドでRails Serverが稼働しているコンテナに入ったとします.
docker-compose exec <Rails container> bash
一通り作業を終え, コンテナから出る際に
Ctrl+D
で抜けるとコンテナが停止してしまいます. そこであわてて
docker-compose up -d
としても, ほんの一瞬起動するだけで, すぐコンテナが停止してしまいます.
対処法
原因よりも大事であろう対処法を先に書きます. 以下のファイルを削除してください.
application_root/tmp/pids/server.pid
普通applicaion_root
はローカルマシンにマウントしているでしょうから, コンテナが起動できなくても削除できるはずです. 削除し終わったら, いつも通り起動させれば問題なく動きます.
docker-compose up -d
原因のヒント
ここから原因について述べていきましょう. まずは原因のヒントとなるログメッセージを見ます.
この症状が見られた際, -d
をつけずに起動を試みると, 起動中のログメッセージが表示されます.
docker-compose up
ログメッセージを見ると, このように書かれた行が見つかります.
web_1 | => Rails 5.2.0 application starting in development web_1 | => Run `rails server -h` for more startup options web_1 | A server is already running. Check /myapp/tmp/pids/server.pid. web_1 | Exiting micropost_web_1 exited with code 1
起動しようと頑張っていますが, 最終的にはExit
していますね. その理由がまさにこれです.
A server is already running.
つまり原因は, 「すでにサーバーが起動しているんだし, 俺の出番は無ぇな」とRailsが判断してしまっていることにあります. Rails Serverが起動しているものと, Railsが勘違いしてしまったわけですね.
その勘違いの原因となっているのが, tmp/pids/server.pid
というファイルです. 中身を見てみましょう.
$ cat tmp/pids/server.pid 1
ひとつだけ書かれた数字はRails ServerのPIDです. 大学で数学を専攻していた方は単項イデアル整域だと勘違いするかもしれませんが, これはProcess ID
の略です.
Linuxでは各プロセスにIDをつけて管理しています. またDockerでは, プロセスIDが1であるプロセスが終了した場合, コンテナも終了するようになっています.
これでトラブルが発生した流れが見えてきましたね.
トラブル発生の流れ
Ctrl+D
で強制終了してしまったことにより, tmp/pids/server.pid
が残ったままになってしまいました. docker-compose down
で終了したならば, これらの一時ファイルやネットワーク, コンテナを自動で消去してくれるはずでした.
この状態で起動を試みると, Railsはtmp/pids/
内の消去されなかったファイルを見て, Rails Serverが起動していると勘違いを起こします.
するとRails Serverは起動を中止します.
プロセスIDが1であるプロセスが終了したため, コンテナも停止します.
まとめ
- 正しいコマンドで終了しましょう.
- トラブルが起きたら落ち着いて対処しましょう.
Railsチュートリアル 第6章
6.1 Userモデル
6.1.1
演習1
schema.rb
の内容は以下の通りです.
ActiveRecord::Schema.define(version: 2018_06_25_044152) do create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| t.string "name" t.string "email" t.datetime "created_at", null: false t.datetime "updated_at", null: false end end
演習2, 3
省略.
6.1.2
演習1, 2
以下のコマンドを実行し, コンテナの中に入ります.
$ docker-compose exec web bash
Railsの対話モードに入ります.
$ rails console
クラスについて調べます.
>> user = User.new (0.5ms) SET NAMES utf8, @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483 => #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil> >> user.class.superclass => ApplicationRecord(abstract) >> user.class.superclass.superclass => ActiveRecord::Base
6.1.3
演習1
たしかにString
クラスです.
>> user = User.new(name: "Michael Hartl", email: "mhartl@example.com") => #<User id: nil, name: "Michael Hartl", email: "mhartl@example.com", created_at: nil, updated_at: nil> >> user.name.class => String >> user.email.class => String
演習2
こんな感じです.
>> user.created_at.class => ActiveSupport::TimeWithZone >> user.updated_at.class => ActiveSupport::TimeWithZone
6.1.4
演習1
find_by_name
メソッドも使えました.
>> User.find_by(name: "Michael Hartl") User Load (0.8ms) SELECT `users`.* FROM `users` WHERE `users`.`name` = 'Michael Hartl' LIMIT 1 => #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2018-06-25 05:01:54", updated_at: "2018-06-25 05:01:54"> >> User.find_by_name("Michael Hartl") User Load (0.9ms) SELECT `users`.* FROM `users` WHERE `users`.`name` = 'Michael Hartl' LIMIT 1 => #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2018-06-25 05:01:54", updated_at: "2018-06-25 05:01:54">
演習2, 3
>> User.all.class => User::ActiveRecord_Relation >> User.all.length User Load (1.0ms) SELECT `users`.* FROM `users` => 2
6.1.5
演習1
>> user.name = "Michael Jackson" => "Michael Jackson" >> user.save (0.6ms) SAVEPOINT active_record_1 User Update (1.0ms) UPDATE `users` SET `name` = 'Michael Jackson', `updated_at` = '2018-06-25 05:23:28' WHERE `users`.`id` = 1 (0.6ms) RELEASE SAVEPOINT active_record_1 => true
演習2
>> user.update_attributes(email: "mjackson@example.com") (0.6ms) SAVEPOINT active_record_1 User Update (0.6ms) UPDATE `users` SET `email` = 'mjackson@example.com', `updated_at` = '2018-06-25 05:24:00' WHERE `users`.`id` = 1 (0.9ms) RELEASE SAVEPOINT active_record_1 => true
演習3
>> user.created_at = 1.year.ago => Sun, 25 Jun 2017 05:24:28 UTC +00:00
6.2 ユーザーを検証する
6.2.1
省略.
6.2.2
演習1
name
とemail
は空白であってはならないからですね.
>> u = User.new => #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil> >> u.valid? => false >> u.errors.full_messages => ["Name can't be blank", "Email can't be blank"]
演習2
ハッシュに使われているキーがシンボルであることに注意すると, 以下のようにエラーメッセージを取得することができます.
>> u.errors.messages => {:name=>["can't be blank"], :email=>["can't be blank"]} >> u.errors.messages[:email] => ["can't be blank"]
6.2.3
演習1, 2
以下のようなエラーメッセージが得られます.
>> u = User.new => #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil> >> u.name = "a"*51 => "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" >> u.valid? => false >> u.errors.full_messages => ["Name is too long (maximum is 50 characters)", "Email can't be blank"]
6.2.4
省略.
6.2.5
- アプリケーションレベルの一意性
- データベースレベルの一意性
- インデックス
- コールバックメソッド
などかなり盛りだくさんなセクションでしたが, チュートリアル通りに進めればよいだけなので, 省略します.
6.3 セキュアなパスワードを追加する
6.3.1
パスワード保存用のカラムをマイグレーションファイルに定義してdb:migrate
. ここまでしっかりやってくれば予想ができる手順ですね. それにしてもhas_secure_password
の万能感がハンパないです.6.3.2
rails console
にて確かめました.
>> user = User.new(name: "Michael Hartl", email: "mhartl@example.com") => #<User id: nil, name: "Michael Hartl", email: "mhartl@example.com", created_at: nil, updated_at: nil, password_digest: nil> >> user.valid? => false >> user.errors.full_messages => ["Password can't be blank"]
6.3.3
rails console
にて確かめました.
>> user = User.new >> user.name = "Foo" >> user.email = "foo@bar.com" >> user.password = "pass" >> user.valid? => false >> user.errors.full_messages => ["Password is too short (minimum is 6 characters)"]
6.3.4
演習1
まずはUser
を作り, 保存します.
>> user = User.new(name: "Michael Hartl", email: "mhartl@example.com", password: "foobar", password_confirmation: "foobar") >> user.save
一旦exit
してから再びconsole
を起動します. 検索してみると確かにさっき保存したレコードが存在しますね.
>> user = User.find_by_name("Michael Hartl") => #<User id: 4, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2018-06-25 09:33:51", updated_at: "2018-06-25 09:33:51", password_digest: "$2a$10$tM4ebAaSFIzfhqzi6.Lo/Ofs6/TfKpnP7LrgkCRV/z0...">
演習2
名前を書き換えて保存を試みます.
>> user.name = "Michael Jackson" => "Michael Jackson" >> user.save => false >> user.errors.full_messages => ["Password can't be blank", "Password is too short (minimum is 6 characters)"]
パスワードを要求されてしまいました. データを更新するには認証が必要ということでしょうかね.
演習3
検証を回避しつつデータを更新する方法が, 6.1.5に書かれています.
>> user.update_attribute(:name, "Michael Jackson") => true
Railsチュートリアル第5章
5.1 構造を追加する
5.1.1
レイアウトファイルを更新し, home.html.erb
も編集をします. curl
コマンドでRailsのロゴをダウンロードしました. 確認のためにFinderからロゴ画像を開いたところ, 邪魔な.DS_Store
ファイルが生成されてしまいました.
$ git status # 中略 Untracked files: (use "git add <file>..." to include in what will be committed) ../../../.DS_Store ../../.DS_Store ../.DS_Store rails.png
調べてみると, いらないファイルのようですね. 一気に削除してしまいましょう. しかしこの削除作業は今後も行う可能性があるので, 削除コマンドのエイリアスを作成することにします. ~/.bashrc
にエイリアスを定義します.
# ~/.bashrc alias delds='find ~/path/to/app/root/ -name .DS_Store | xargs rm -v'
変更を適用しましょう.
$ source ~/.bashrc
あとは定義したコマンドを実行すればファイルを削除できます. これらのファイルは今後発生する可能性があるわけですが, Gitにも追跡して欲しくありません. ということで.gitignore
にも書いておきましょう.
# .gitignore .DS_Store
演習1, 2, 3
本文と同様なので省略しますが, 猫画像は消しておきましょう.
5.1.2
省略します.
5.1.3
こちらも書かれている通りにやるだけなので省略します.
5.2 Sassとアセットパイプライン
省略します.
5.3 レイアウトのリンク
5.3.1
このセクションの内容はすでに3章で終えていますね.
5.3.2
演習1, 2, 3
エディタのUNDO機能を使って変更を元に戻すと書かれていますが, Gitの機能を使うほうが楽な気がします. まずは演習以前の内容をコミットしておきます.
$ git commit -a
演習を終えたら, ワークツリーの変更をなかったことにします.
# アプリケーションルートで実行 $ git checkout .
5.3.3
省略します.
5.3.4
演習1
省略します.
演習2
application_helper_test.rb
は以下のようになりました.
require 'test_helper' class ApplicationHelperTest < ActionView::TestCase def setup @base_title = 'Ruby on Rails Tutorial Sample App' end test "full title helper" do assert_equal full_title ,@base_title assert_equal full_title("Help"), "Help | #{@base_title}" end end
5.4 ユーザー登録: 最初のステップ
5.4.1
はじめ間違って以下のように実行してしまいました.
docker-compose exec -T web rails generate controller User new
どこが違うのかというと, User
が単数形になっています. これのせいでエラーが出て, 少し時間を無駄にしてしまいました.
5.4.2
演習1, 2
省略します.
演習3
テストコードは以下のようになりました.
# test/integration/site_layout_test.rb get signup_path assert_select "title", full_title("Sign up")
Railsチュートリアル 第4章
Railsチュートリアルの第4章です. この章はわりと演習が楽しかったですね.
4.1 動機
トピックブランチを作成し, チェックアウトします.
4.1.1
省略.
4.1.2
カスタムヘルパーの作成は, 4.2を終えてからにします.
4.2 文字列とメソッド
4.2.1
省略.
4.2.2
省略.
4.2.3
ぱっと見, 文字列展開ちゃんとされるの?と思いましたが大丈夫でしたね.
>> string = "foobar" >> puts "The string '#{string}' is nonempty." unless string.empty?
演習1
省略.
演習2
リバースしたかどうかが分かる単語でも試してみるとよいですね.
演習3
true
が返ってきました.
演習4
nil
が返ってきました.
>> s = "onomatopoeia" >> puts "It's a palinrome!" if s == s.reverse => nil
4.2.4
演習1, 2
真似して書くだけですね.
演習3
次のようになりました.
>> palindrome_tester("racecar").nil? It's a palindrome! => true
4.2.5
ここでやり残していたカスタムヘルパーの作成を行いました.
テストコードを書き直します.
# test/controllers/static_pages_controller_test.rb # の一部を抜粋しています def setup @base_title = "Ruby on Rails Tutorial Sample App" end test "should get root" do get root_url assert_response :success end test "should get home" do get static_pages_home_url assert_response :success assert_select "title", @base_title #書き直し end
レイアウトを変更します.
# app/views/layouts/application.html.erb # の一部を抜粋しています <title><%= full_title(yield(:title)) %></title>
Home
ではbase_title
しか表示させないので, タイトル設定を消します.
# app/views/static_pages/home.html.erb <% provide(:title, "Home") %> # この行を削除 <h1>Sample App</h1> <p> This is the home page for the <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a> sample application. </p>
4.3 他のデータ構造
4.3.1
演習1, 2, 3
# 指定された文字列を変数sに格納 >> s = "A man, a plan, a canal, Panama" => "A man, a plan, a canal, Panama" # 文字列を分割して配列にする >> a = s.split(", ") => ["A man", "a plan", "a canal", "Panama"] # 再びひとつの文字列に >> s = a.join => "A mana plana canalPanama" # スペースを無くします >> s = s.split.join => "AmanaplanacanalPanama" # このままでは回文ではありませんが, >> s == s.reverse => false # 小文字に揃えれば回文です # メソッドチェーンの順番を変えたものもチェックしておきました. >> s.downcase == s.downcase.reverse => true >> s.downcase == s.reverse.downcase => true
演習4
# 範囲オブジェクトを配列に変換し, 変数tに格納 >> t = ("a".."z").to_a => ["a", "b", (途中省略), "z"] # 指定された2つとも取り出せるかと思いきやダメ >> t[7,-7] => nil >> t[7] => "h" >> t[-7] => "t" # インデックス2以降から3つ取り出す, という意味なんですね >> t[2,3] => ["c", "d", "e"]
4.3.2
演習1
範囲オブジェクトをかっこで囲うのを忘れてはいけませんね. またputs
を書かないと出力が目に見えません.
>> (0..16).each {|n| puts n**2} 0 1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 => 0..16
演習2
ブロックを使わずとも定義できてしまいますね.
>> def yeller (array) >> array.join.upcase >> end => :yeller >> yeller("abc".split) => "ABC"
ブロックを使うなら以下のようになりますかね.
>> def yeller (array) >> array.map {|s| s.upcase}.join >> end => :yeller >> yeller("abc".split) => "ABC"
演習3
本文中にある方法だと, 異なる8文字の順列を作ることになります. ここはもう一歩踏み込んで, 重複ありの文字列を生成させてみます. 以下のような実装を思いつきました.
>> def random_dubdomain >> (0..7).map do >> ("a".."z").to_a.shuffle[0] >> end >> .join >> end => :random_dubdomain >> random_dubdomain => "uknjxsoa" >> random_dubdomain => "htoenews"
はじめ8.times
を使うことを思いつきましたが, 無駄にローカル変数を定義することになりそうです.
>> def random_subdomain >> tmp = [] >> 8.times do >> tmp << ("a".."z").to_a.shuffle[0] >> end >> tmp.join >> end => :random_subdomain >> random_subdomain => "xruoispc" >> random_subdomain => "wqdizcoc"
演習4
ヒント通りにやりました. split('')
がポイントですね.
>> def string_shuffle (s) >> s.split('').shuffle.join >> end => :string_shuffle >> string_shuffle("foobar") => "ofbaro"
4.3.3
演習1
rails console
では日本語が打てないので英語で対処しました.
>> hash = { "one" => "uno", "two" => "dos", "three" => "tres" } => {"one"=>"uno", "two"=>"dos", "three"=>"tres"} >> hash.each do |key, value| >> puts "'#{key}' is '#{value}' in Spanish" >> end 'one' is 'uno' in Spanish 'two' is 'dos' in Spanish 'three' is 'tres' in Spanish => {"one"=>"uno", "two"=>"dos", "three"=>"tres"}
演習2
サザエさんにご登場いただきました. 本文にあるように, 中括弧{ }
の内側には空白を入れています.
>> person1 = { first: "Masuo", last: "Fuguta" } => {:first=>"Masuo", :last=>"Fuguta"} >> person2 = { first: "Sazae", last: "Fuguta" } => {:first=>"Sazae", :last=>"Fuguta"} >> person3 = { first: "Tarao", last: "Fuguta" } => {:first=>"Tarao", :last=>"Fuguta"} >> params = { father: person1, mather: person2, child: person3 } => {:father=>{:first=>"Masuo", :last=>"Fuguta"}, :mather=>{:first=>"Sazae", :last=>"Fuguta"}, :child=>{:first=>"Tarao", :last=>"Fuguta"}} >> params[:father][:first] == person1[:first] => true
演習3
ランダムな文字列を作るメソッドを定義します.
>> def random_string >> (0..15).map do >> ("a".."z").to_a.shuffle[0] >> end >> .join >> end => :random_string
user
を作ります.
>> user = { name: "logicoffee", email: "logicoffee@example.com", password_digest: random_string } => {:name=>"logicoffee", :email=>"logicoffee@example.com", :password_digest=>"wjvqvczpmxmmvdwe"}
演習4
ここを見てみると, 2つのハッシュを組み合わせて新たなハッシュを返すメソッドのようですね. キーが重複している場合は上書きされるようです. やってみましょう.
>> { "a" => 100, "b" => 200 }.merge({ "b" => 300 }) => {"a"=>100, "b"=>300}
やはりそのようですね.
4.4 Rubyにおけるクラス
4.4.1
演習1, 2, 3
範囲オブジェクトの比較には注意が必要ですね.
>> r = Range.new(0,10) => 0..10 >> r == 0..10 Traceback (most recent call last): 1: from (irb):4 ArgumentError (bad value for range) >> r == (0..10) => true
4.4.2
演習1
>> r = 0..10 => 0..10 >> r.class => Range >> r.class.superclass => Object >> r.class.superclass.superclass => BasicObject >> h = { name: "Taro" } => {:name=>"Taro"} >> h.class => Hash >> h.class.superclass => Object >> h.class.superclass.superclass => BasicObject >> s = :name => :name >> s.class => Symbol >> s.class.superclass => Object >> s.class.superclass.superclass => BasicObject
演習2
たしかにself
キーワードを省略できますね.
>> class Word < String >> def palindrome? >> self == reverse >> end >> end => :palindrome? >> s = Word.new("level") => "level" >> s.palindrome? => true
4.4.3
演習1
例のように=> :String
とはなりませんでした.
>> class String >> def palindrome? >> self == self.reverse >> end >> end => :palindrome? >> "racecar".palindrome? => true >> "onomatopoeia".palindrome? => false >> "Malayalam".downcase.palindrome? => true
演習2, 3
>> class String >> def shuffle >> self.split("").shuffle.join >> end >> end => :shuffle >> "abcdefg".shuffle => "dgcbeaf" >> class String >> def shuffle >> split("").shuffle.join >> end >> end => :shuffle >> "onomatopoeia".shuffle => "eopiooataonm"
4.4.4
省略します.
4.4.5
演習1, 2, 3
example_user.rb
を用意します. full_name
メソッドと, alphabetical_name
メソッドで, インスタンス変数へのアクセス方法を変えてみました.
class User attr_accessor :first_name, :last_name, :email def initialize (attributes = {}) @first_name = attributes[:first_name] @last_name = attributes[:last_name] @email = attributes[:email] end def full_name "#{first_name} #{last_name}" end def alphabetical_name "#{@last_name}, #{@first_name}" end def formatted_email "#{full_name} <#{email}>" end end
特に問題なくできました.
>> require './example_user.rb' => true >> user = User.new(first_name: "Michael", last_name: "Hartle", email: "mhartl@example.com") => #<User:0x000055993280b6c0 @first_name="Michael", @last_name="Hartle", @email="mhartl@example.com"> >> user.full_name => "Michael Hartle" >> user.alphabetical_name => "Hartle, Michael" >> user.formatted_email => "Michael Hartle <mhartl@example.com>" >> user.full_name.split == user.alphabetical_name.split(', ').reverse => true