🍎Railsのformに絵文字を表示する🎉
"文字列".html_safeで表示できる
HTMLで絵文字を表示したい場合、「HTML Entity」や「実体参照」と言われる形式で記述すれば普通は表示される。
😎 ...... 😎
しかしRailsのformタグ内では素直に表示してくれず、実体参照はただの文字列として認識されてしまう。
例として次のformを見てみよう。
<%= form_with url: search_path, method: :get do |f| %> <%= f.text_field :keywords, placeholder: "Search", class: "form-control" %> <%= f.submit value="🔍", class: "btn btn-default" %> <% end %>
このフォームは検索欄を表している。以下の部分で検索ボタンに虫めがねの絵文字「🔍」を利用しているが、実はこの書き方では「🔍」と表示されてしまう。
<%= f.submit value="🔍", class: "btn btn-default" %>
そこでhtml_safeメソッドを利用する。
<%= f.submit value="🔍".html_safe, class: "btn btn-default" %>
これでRailsのHTMLの特殊文字に対するエスケープ処理がスキップされ、絵文字として出力されるようになる。
参考
NoMethodError | undefined method `offset' for #<Array:***> | Pagy
NoMethodError | undefined method `offset' for #<Array:***> | Pagy
状況
Pagyというgemを利用してページネーションを行うコードにおいてpagyメソッドに関する標題のエラーが発生しました。該当のコードは次のような構成です。
items = Post.where(['content LIKE ?', "%#{keyword}%"]) results = [] results += items @pagy, @items = pagy(items)
原因
pagyの引数はActiveRecord::Relationクラスに属している必要があります。たとえばall
やwhere
の返り値のクラスがそれです。
今回のエラーは引数がStringの配列になっていたため発生していました。
解決策
引数をActiveRecord::Relationに変換します。
results.first.class.where(id: results.map(&:id))
一件落着!
参考
Railsアプリにfavicon(タブ表示アイコン)を設定する
application.html.erbでfavicon_link_tagメソッドの引数としてapp/assets/images/に用意した画像のicoファイルを指定するだけでOKです。
app/views/layouts/application.html.erb
<%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> <%= favicon_link_tag 'favicon.ico' %>
favicon_link_tagは引数なしの場合、/app/assets/images/favicon.icoを自動で読みに行ってくれます。
OmniAuthによるTwitter認証機能の実装 | Rails | Devise | email取得
- 前提
- OmniAuthについて
- Twitter APIの準備
- Gemfile
- devise.rb
- user.rb
- routes.rb
- omniauth_callbacks_controller
- Viewの実装
- Test
- 認証機能の確認
- Herokuデプロイ
前提
- Ruby 2.5.3 / Rails 5.2.3
- DeviseでUserモデルを作成済み
- 個々のユーザーをemailで識別する
- Terms of ServiceとPrivacy Policyを用意できる
OmniAuthについて
TwitterやFacebookなどのSNSアカウントを通じてWebアプリケーションのログイン認証を行うことができるgemです。
今回はTwitter認証のためにomniauthとomniauth-twitter、omniauth-rails_csrf_protectionという3つのgemを使用します。omniauth-rails_csrf_protectionはクロスサイトリクエストフォージェリという脆弱性への対策として導入します。
GitHub - omniauth/omniauth: OmniAuth is a flexible authentication system utilizing Rack middleware.
GitHub - arunagw/omniauth-twitter: OmniAuth strategy for Twitter
Twitter APIの準備
Developerアカウント登録
Developer — Twitter Developers
アプリの作成
Callback URL
Callback URLs — Twitter Developers 公式によるとlocalhostはcallback URLとして使っちゃダメとのこと(何故?)。開発中はlocalhost:3000じゃなくて127.0.0.1:3000を使うと良いのか。
Don’t use localhost as a callback URL Instead of using localhost, please use a custom host locally or
http(s)://127.0.0.1.
Terms of service URL / Privacy Policy URL
Twitter APIでemailを取得するにはTerms of service(利用規約) URLとPrivacy Policy URLの登録が必要。emailを扱う場合はこれらのページを準備しておきましょう。
参考
Webサービス個人開発するなら知りたい利用規約とプライバシーポリシーの作り方 - Qiita
掲示版・SNS系向きの利用規約の雛形(ひな型) | Webサイトの利用規約(無料テンプレート・商用利用可)
プライバシーポリシーの雛形(ひな型) | Webサイトの利用規約(無料テンプレート・商用利用可)
ここからRails側での作業に入ります。
Gemfile
gem 'omniauth' gem "omniauth-rails_csrf_protection" gem 'omniauth-twitter'
編集後、bundle
を実行します。
devise.rb
Developerサイトで用意したアプリのAPIキーはアプリ詳細ページのKeys and tokens > Consumer API keysに記載されています。これらのキーをdevise.rbに設定します。
config/initializers/devise.rb
config.omniauth :twitter, ENV['TWITTER_API_KEY'], ENV['TWITTER_API_SECRET_KEY']
環境変数ENVはdotenvを利用して定義します。
.env
TWITTER_API_KEY='' TWITTER_API_SECRET_KEY=''
忘れぬうちに本番環境にも環境変数をセットしておきましょう。
$ heroku config:set TWITTER_API_KEY='' $ heroku config:set TWITTER_API_SECRET_KEY=''
user.rb
今回は次の要件を踏まえて実装します。 - Userモデルの作成にemailとpasswordが必須(deviseのデフォルト設定) - Twitterアカウントの表示名、アイコン画像、プロフィールをアプリのユーザーアカウントに反映させる
Usersテーブルの主なカラムは次の通り。 - email - password - name - icon - profile - provider - uid
既存のUserモデルに必要なカラムをマイグレーションで加えておきます。
$ rails g migration add_columns_to_users provider uid
deviseメソッドに:omniauthableを追加
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable, :confirmable, :omniauthable
User.rbにfind_or_create_from_authメソッドを定義
認証時、TwitterアカウントのemailがDBのUsersテーブルに存在すればそのユーザーのオブジェクトを返し、なければTwitterアカウントの情報を用いてユーザーを新規作成、DBに保存するメソッドを定義します。この例ではオプションとしてアイコン画像とプロフィール文、SNSプロバイダー名、固有IDを引き継いでいます。
def self.find_or_create_from_auth(auth) find_or_create_by(email: auth.info.email) do |user| user.password = Devise.friendly_token[0, 20] user.name = auth.info.name user.remote_icon_url = auth.info.image user.profile = auth.info.description user.provider = auth.provider user.uid = auth.uid user.skip_confirmation! end end
user.remote_icon_url
について:carrierwaveの画像アップローダーを用意している場合、remote_画像のカラム名_url
という形式でWeb上の画像URLを格納した上でモデルのインスタンスを新規作成すると、アップローダーがリンク先から画像をダウンロードしてストレージにアップロードしてくれます。user.skip_confirmation!
はSNS認証を通じたユーザー登録の際、メールアドレスの確認をスキップするために記述しています。ちなみにこのメソッドが行っているのは、ユーザーデータのconfirmed_atへの現在時刻の入力、保存です。
routes.rb
devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
omniauth_callbacks_controller
認証を行うためのコントローラーomniauth_callbacks_controllerを用意します。
$ rails g controller users/omniauth_callbacks
以下のようにコントローラーを編集します。 app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < ApplicationController def twitter callback_from :twitter end def failure redirect_to new_user_registration_url end private def callback_from(provider) provider = provider.to_s @user = User.find_or_create_from_auth(request.env['omniauth.auth'].except('extra')) if @user.persisted? # DBに保存済みかどうかを判定 flash[:notice] = I18n.t('devise.omniauth_callbacks.success', kind: provider.capitalize) sign_in_and_redirect @user, event: :authentication else session["devise.#{provider}_data"] = request.env['omniauth.auth'].except('extra') redirect_to new_user_session_url end end end
Viewの実装
例えば以下のようにログインページやユーザー登録ページにSNS認証ボタンを設置します。 app/views/devise/registrations/new.html.erb
<h3>SNS Authentication</h3> <%= button_to 'Twitter', user_twitter_omniauth_authorize_path %>
このとき注意しなければならないのは認証にGETメソッドを使うべきでないという点です。
クロスサイトリクエストフォージェリの対策としてプロバイダへのリンクには、link_toならmethod: :post
を付ける、あるいはbutton_toを使用するなどしてPOSTリクエストを必ず使用するようにしましょう。
Test
Railsでomniauthを使ったログイン機構の統合テスト - 気持ち
とりあえず用意した簡単なテストですが一例としてはこんな感じ。
test/fixtures/users.yml
john: email: "john@example.com" encrypted_password: <%= Devise::Encryptor.digest(User, 'password') %> confirmed_at: <%= Time.now - 100 %> provider: 'twitter' uid: '123456'
test/integration/users_login_test.rb
class UsersLoginTest < ActionDispatch::IntegrationTest include Devise::Test::IntegrationHelpers def setup Warden.test_mode! @user = users(:john) # OmniAuth configuration OmniAuth.config.test_mode = true OmniAuth.config.mock_auth[:twitter] = OmniAuth::AuthHash.new( provider: 'twitter', uid: '123456', info: { email: 'john@example.com', name: 'John English' } ) end test 'login and logout with twitter account' do get new_user_session_path assert_select 'form[action=?]', user_twitter_omniauth_authorize_path post '/users/auth/twitter', params: OmniAuth.config.mock_auth[:twitter] assert_redirected_to '/users/auth/twitter/callback' follow_redirect! assert_redirected_to root_path follow_redirect! assert_select 'a[href=?]', destroy_user_session_path delete destroy_user_session_path assert_nil session[:user_id] end end
認証機能の確認
ブラウザから127.0.0.1:3000にアクセスします。
問題がなければlocalhost:3000のページと同じViewが表示されるはずです。
そこからログイン画面に移動してTwitter認証リンクボタンを押してみましょう。
正常にリダイレクトされ、ログインに成功すれば勝利は目前です。
Herokuデプロイ
$ heroku pg:reset DATABASE $ bundle exec rails assets:precompile $ git push heroku $ heroku run rails db:migrate
herokuへのデプロイが完了したらアプリのWeb URLにアクセスし、Twitter認証機能をチェックしてみてください。
無事、Twitter認証に成功したでしょうか?
もしそうであればあなたの勝利です。おつかれさまでした。
それともエラーに出くわしましたか?
ひとまずリフレッシュしてください。それから関連するファイルを見直したり、自分の理解した内容を整理してみましょう。諦めなければきっと解決の糸口が見つかるはずです。
Rails Tutorial 第4版 第7章 演習 解答例
この文書はRails Tutorial 第4版 第7章 ユーザー登録の演習に対する個人の解答例です。解答には誤りや不適切な表現が含まれていることがありますが、もし誤謬を見つけたらコメント頂けると嬉しいです。
それでは、やっていきましょう!
7.1 ユーザーを表示する
7.1.1 デバッグとRails環境
1. ブラウザから /about にアクセスし、デバッグ情報が表示されていることを確認してください。このページを表示するとき、どのコントローラとアクションが使われていたでしょうか? paramsの内容から確認してみましょう。
Aboutページ下部に表示されたparamsの内容を確認します。
--- !ruby/object:ActionController::Parameters parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess controller: static_pages action: about permitted: false
この内容から、Aboutページを表示するとき、static_pagesコントローラのaboutアクションが使われていたことがわかります。
2. Railsコンソールを開き、データベースから最初のユーザー情報を取得し、変数userに格納してください。その後、puts user.attributes.to_yamlを実行すると何が表示されますか? ここで表示された結果と、yメソッドを使ったy user.attributesの実行結果を比較してみましょう。
>> user = User.first User Load (0.5ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]] => #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2019-07-26 10:03:57", updated_at: "2019-07-26 10:03:57", password_digest: "$2a$12$pbYTT6aNzZj7Be26vSbtRuqAA59NgPEY5QAA34BMYpk..."> >> puts user.attributes.to_yaml --- id: 1 name: Michael Hartl email: mhartl@example.com created_at: !ruby/object:ActiveSupport::TimeWithZone utc: &1 2019-07-26 10:03:57.479399000 Z zone: &2 !ruby/object:ActiveSupport::TimeZone name: Etc/UTC time: *1 updated_at: !ruby/object:ActiveSupport::TimeWithZone utc: &3 2019-07-26 10:03:57.479399000 Z zone: *2 time: *3 password_digest: "$2a$12$pbYTT6aNzZj7Be26vSbtRuqAA59NgPEY5QAA34BMYpk7CKubgZTXS" => nil
>> y user.attributes --- id: 1 name: Michael Hartl email: mhartl@example.com created_at: !ruby/object:ActiveSupport::TimeWithZone utc: &1 2019-07-26 10:03:57.479399000 Z zone: &2 !ruby/object:ActiveSupport::TimeZone name: Etc/UTC time: *1 updated_at: !ruby/object:ActiveSupport::TimeWithZone utc: &3 2019-07-26 10:03:57.479399000 Z zone: *2 time: *3 password_digest: "$2a$12$pbYTT6aNzZj7Be26vSbtRuqAA59NgPEY5QAA34BMYpk7CKubgZTXS" => nil
puts user.attributes.to_yaml
とy user.attributes
のコマンド実行結果は同じでした。
※そもそもyメソッドは何なのかというと、YAML形式でオブジェクトの中身を確認するメソッドです。YAMLとは、YAML Ain't Markup Languageの略で、構造化されたデータを表現するためのフォーマットの一つです。
7.1.2 Usersリソース
1. 埋め込みRubyを使って、マジックカラム (created_atとupdated_at) の値をshowページに表示してみましょう (リスト 7.4)。
app/views/users/show.html.erb
<%= @user.name %>, <%= @user.email %> <br> created at <%= @user.created_at %> <br> updated at <%= @user.updated_at %>
2. 埋め込みRubyを使って、Time.nowの結果をshowページに表示してみましょう。ページを更新すると、その結果はどう変わっていますか? 確認してみてください。
app/views/users/show.html.erb
<%= @user.name %>, <%= @user.email %> <br> created at <%= @user.created_at %> <br> updated at <%= @user.updated_at %> <br> Time.now ... <%= Time.now %>
Time.nowの表示は現在時刻に合わせて変化します。
7.1.3 debuggerメソッド
1. showアクションの中にdebuggerを差し込み (リスト 7.6)、ブラウザから /users/1 にアクセスしてみましょう。その後コンソールに移り、putsメソッドを使ってparamsハッシュの中身をYAML形式で表示してみましょう。ヒント: 7.1.1.1の演習を参考にしてください。その演習ではdebugメソッドで表示したデバッグ情報を、どのようにしてYAML形式で表示していたでしょうか?
(byebug) puts params.to_yaml --- !ruby/object:ActionController::Parameters parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess controller: users action: show id: '1' permitted: false nil
2. newアクションの中にdebuggerを差し込み、/users/new にアクセスしてみましょう。@userの内容はどのようになっているでしょうか? 確認してみてください。
(byebug) @user nil
7.1.4 Gravatar画像とサイドバー
1. (任意) Gravatar上にアカウントを作成し、あなたのメールアドレスと適当な画像を紐付けてみてください。メールアドレスをMD5ハッシュ化して、紐付けた画像がちゃんと表示されるかどうか試してみましょう。
>> User.create(name: "varcyrano", email: "var.cyrano@gmail.com", password: "abc123", password_confirmation: "abc123") (0.7ms) begin transaction User Exists (0.8ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ? [["email", "var.cyrano@gmail.com"], ["LIMIT", 1]] User Create (1.9ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest") VALUES (?, ?, ?, ?, ?) [["name", "varcyrano"], ["email", "var.cyrano@gmail.com"], ["created_at", "2019-07-30 06:07:44.361200"], ["updated_at", "2019-07-30 06:07:44.361200"], ["password_digest", "$2a$12$NLpbERq4USDB2WPT5worxeJtT3JqE6RywPsCW5ihp/hcG0Of0zXxu"]] (1.4ms) commit transaction => #<User id: 3, name: "varcyrano", email: "var.cyrano@gmail.com", created_at: "2019-07-30 06:07:44", updated_at: "2019-07-30 06:07:44", password_digest: "$2a$12$NLpbERq4USDB2WPT5worxeJtT3JqE6RywPsCW5ihp/h...">
ユーザー詳細ページにアクセスすると下記のように画像がきちんと表示されることが確認できます。
2. 7.1.4で定義したgravatar_forヘルパーをリスト 7.12のように変更して、sizeをオプション引数として受け取れるようにしてみましょう。うまく変更できると、gravatar_for user, size: 50といった呼び出し方ができるようになります。重要: この改善したヘルパーは10.3.1で実際に使います。忘れずに実装しておきましょう。
リスト 7.12: gravatar_forヘルパーにオプション引数を追加する
app/helpers/users_helper.rb
module UsersHelper # 引数で与えられたユーザーのGravatar画像を返す def gravatar_for(user, options = { size: 80 }) gravatar_id = Digest::MD5::hexdigest(user.email.downcase) size = options[:size] gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}" image_tag(gravatar_url, alt: user.name, class: "gravatar") end end
3. オプション引数は今でもRubyコミュニティで一般的に使われていますが、Ruby 2.0から導入された新機能「キーワード引数 (Keyword Arguments)」でも実現することができます。先ほど変更したリスト 7.12を、リスト 7.13のように置き換えてもうまく動くことを確認してみましょう。この2つの実装方法はどういった違いがあるのでしょうか? 考えてみてください。
リスト 7.13: gravatar_forヘルパーにキーワード引数を追加する
app/helpers/users_helper.rb
module UsersHelper # 引数で与えられたユーザーのGravatar画像を返す def gravatar_for(user, size: 80) gravatar_id = Digest::MD5::hexdigest(user.email.downcase) gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}" image_tag(gravatar_url, alt: user.name, class: "gravatar") end end
キーワード引数を使うと、新しく変数を用意しなくても引数名を直接利用できます。
7.2 ユーザー登録フォーム
7.2.1 form_forを使用する
1. 試しに、リスト 7.15にある:nameを:nomeに置き換えてみましょう。どんなエラーメッセージが表示されるようになりますか?
NoMethodError in Users#new Showing /Users/***/Rails-Tutorial/sample_app/app/views/users/new.html.erb where line #8 raised:
undefined method `nome' for #<User:0x00007fc2617b6d78>
2. 試しに、ブロックの変数fをすべてfoobarに置き換えてみて、結果が変わらないことを確認してみてください。確かに結果は変わりませんが、変数名をfoobarとするのはあまり良い変更ではなさそうですね。その理由について考えてみてください。
フォーム画面に使われる変数fは"form"の頭文字に由来しています。"foobar"は意味を持たない名前として使われるため、フォームオブジェクトという明確な意味を持つ今回のような場合に使用するのは不適です。
7.2.2 フォームHTML
1. Learn Enough HTML to Be DangerousではHTMLをすべて手動で書き起こしていますが、なぜformタグを使わなかったのでしょうか? 理由を考えてみてください。
入力フォームがないから。
7.3 ユーザー登録失敗
7.3.1 正しいフォーム
演習なし。
7.3.2 Strong Parameters
1. /signup?admin=1 にアクセスし、paramsの中にadmin属性が含まれていることをデバッグ情報から確認してみましょう。
--- !ruby/object:ActionController::Parameters parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess admin: '1' controller: users action: new permitted: false
7.3.3 エラーメッセージ
1. 最小文字数を5に変更すると、エラーメッセージも自動的に更新されることを確かめてみましょう。
app/models/user.rb
... validates :password, presence: true, length: { minimum: 5 } ...
パスワードの文字数を5文字未満にしてフォームを送信すると次のエラーメッセージが表示されます。
これでエラーメッセージが自動的に更新されることが確認できました。
2. 未送信のユーザー登録フォーム (図 7.12) のURLと、送信済みのユーザー登録フォーム (図 7.18) のURLを比べてみましょう。なぜURLは違っているのでしょうか? 考えてみてください。
config/routes.rbにおいて
未送信のユーザー登録フォームのURL(http://localhost:3000/signup)はget '/signup', to:'users#new'
に、
送信済みのユーザー登録フォームのURL(http://localhost:3000/users)はresources :users
に関連付けられている。
したがってフォームを送信するとき、signupパスからUsersControllerのcreateアクションが呼ばれ、usersパスに遷移するためフォームの送信前後でURLが異なる。
7.3.4 失敗時のテスト
1. リスト 7.20で実装したエラーメッセージに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。リスト 7.25にテンプレートを用意しておいたので、参考にしてください。
require 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest test "invalid signup information" do get signup_path assert_no_difference 'User.count' do post users_path, params: { user: {name: "", email: "user@invalid", password: "foo", password_confirmation: "bar" }} end assert_template 'users/new' assert_select ' div#error_explanation' assert_select 'div.alert' end end
2. ユーザー登録フォームのURLは /signup ですが、無効なユーザー登録データを送付するとURLが /users に変わってしまいます。これはリスト 5.43で追加した名前付きルート (/signup) と、RESTfulなルーティング (リスト 7.3) のデフォルト設定との差異によって生じた結果です。リスト 7.26とリスト 7.27の内容を参考に、この問題を解決してみてください。うまくいけばどちらのURLも /signup になるはずです。あれ、でもテストは greenのままになっていますね...、なぜでしょうか? (考えてみてください)
リスト7.26とリスト7.27の内容を反映させた後、サーバーを再起動するとフォーム送信前後でURLは不変となります。
テストがgreenのままなのは、テストで使用しているパスがusers_pathのままになっているからです。4番目の演習で実際にそれを確かめることになります。
3. リスト 7.25のpost部分を変更して、先ほどの演習課題で作った新しいURL (/signup) に合わせてみましょう。また、テストが greenのままになっている点も確認してください。
test/integration/users_signup_test.rbのget users_path
をget signup_path
に変更します。
require 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest test "invalid signup information" do get signup_path assert_no_difference 'User.count' do post signup_path, params: { user: {name: "", email: "user@invalid", password: "foo", password_confirmation: "bar" }} end assert_template 'users/new' assert_select ' div#error_explanation' assert_select 'div.alert' end end
testを実行するとgreenとなります。
4. リスト 7.27のフォームを以前の状態 (リスト 7.20) に戻してみて、テストがやはり greenになっていることを確認してください。これは問題です! なぜなら、現在postが送信されているURLは正しくないのですから。assert_selectを使ったテストをリスト 7.25に追加し、このバグを検知できるようにしてみましょう (テストを追加して redになれば成功です)。その後、変更後のフォーム (リスト 7.27) に戻してみて、テストが green になることを確認してみましょう。ヒント: フォームから送信してテストするのではなく、’form[action="/signup"]’という部分が存在するかどうかに着目してテストしてみましょう。
form[action="/signup"]
という記述がHTMLに含まれているかを確認します。
test/integration/users_signup_test.rb
require 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest test "invalid signup information" do get signup_path assert_no_difference 'User.count' do post signup_path, params: { user: {name: "", email: "user@invalid", password: "foo", password_confirmation: "bar" }} end assert_template 'users/new' assert_select ' div#error_explanation' assert_select 'div.alert' assert_select 'form[action="/signup"]' end end
テストを実行するとredになります。
$ rails t Running via Spring preloader in process 19489 Started with run options --seed 27929 FAIL["test_invalid_signup_information", #<Minitest::Reporters::Suite:0x00007fe0576d4ba8 @name="UsersSignupTest">, 0.877714999995078] test_invalid_signup_information#UsersSignupTest (0.88s) Expected at least 1 element matching "form[action="/signup"]", found 0.. Expected 0 to be >= 1. test/integration/users_signup_test.rb:15:in `block in <class:UsersSignupTest>' 18/18: [===========] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.92986s 18 tests, 38 assertions, 1 failures, 0 errors, 0 skips
app/views/users/new.html.erbのform_forに関する記述を<%= form_for(@user, url: signup_path) do |f| %>
に戻して再度テストを実行するとgreenになります。
7.4 ユーザー登録成功
7.4.1 登録フォームの完成
1. 有効な情報を送信し、ユーザーが実際に作成されたことを、Railsコンソールを使って確認してみましょう。
http://localhost:3000/signup でユーザー登録を完了した後にRailsコンソールで作成したユーザーを確認する。
>> user = User.last User Load (0.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT ? [["LIMIT", 1]] => #<User id: 4, name: "name", email: "email@email.email", created_at: "2019-07-30 12:35:52", updated_at: "2019-07-30 12:35:52", password_digest: "$2a$12$xXqbugf6THLtPkVkY8O9LuOJITEAQnPYFnat2W5Ueh4...">
2. リスト 7.28を更新し、redirect_to user_url(@user)とredirect_to @userが同じ結果になることを確認してみましょう。
確認してみましょう。
7.4.2 flash
演習なし。
7.4.3 実際のユーザー登録
演習なし。
7.4.4 成功時のテスト
演習なし。
ここでリスト7.26 test/integration/users_signup_test.rb について修正事項があります。
post_via_redirectメソッドはRails 5.1以降では使用できなくなったため、下記のようにfollow_redirect!メソッドを使用してリダイレクトするように変更します。
test "valid signup information" do get signup_path assert_difference 'User.count', 1 do post users_path, params: { user: { name: "Example User", email: "user@example.com", password: "password", password_confirmation: "password" }} follow_redirect! end assert_template 'users/show' end
7.5 プロのデプロイ
7.5.1 本番環境でのSSL
演習なし。
7.5.2 本番環境用Webサーバー
演習なし。
リスト7.30の後のbundle exec rake test
コマンドでエラーが出てきたら7.4.4の修正事項に対応していない可能性があるので、確認してください。
7.5.3 Rubyのバージョン番号
演習なし。
7.6 最後に
7.6.1 本章のまとめ
演習なし。
7.7 演習
1. リスト7.31のコードを使用して、7.1.4で定義されたgravatar_forヘルパーにオプションのsizeパラメーターを取ることができる (gravatar_for user, size: 40のようなコードをビューで使用できる) ことを確認してください。(9.3.1でこれを改善したヘルパーを使います)
確認してください。
2. リスト7.18で実装したエラーメッセージに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。リスト7.32にテンプレートを用意しておいたので、参考にしてください。
test/integration/users_signup_test.rb
test "invalid signup information" do get signup_path assert_no_difference 'User.count' do post users_path, params: { user: {name: "", email: "user@invalid", password: "foo", password_confirmation: "bar" }} end assert_template 'users/new' assert_select 'div#error_explanation' assert_select 'div.field_with_errors' assert_select 'ul' do assert_select 'li', 'Name can\'t be blank' assert_select 'li', 'Email is invalid' assert_select 'li', 'Password confirmation doesn\'t match Password' assert_select 'li', 'Password is too short (minimum is 6 characters)' end end
3. 7.4.2で実装したflashに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。 リスト7.33に最小限のテンプレートを用意しておいたので、参考にしてください (ヒント: FILL_INメソッドを適切なコードに置き換えると完成します)。(テキストに対するテストは壊れやすいです。文量の少ないflashのキーであっても、それは同じです。個人的には、flashが空でないかをテストするだけの場合が多いです)
test "valid signup information" do get signup_path assert_difference 'User.count', 1 do post users_path, params: { user: { name: "Example User", email: "user@example.com", password: "password", password_confirmation: "password" }} follow_redirect! end assert_template 'users/show' assert_not flash.empty? end
4. 7.4.2で触れたように、flash用のHTML (リスト7.25) は読みにくいです。より読みやすくしたリスト7.34のコードに対してテストスイートを実行し、こちらも正常に動作することを確認してください。このコードでは、Railsのcontent_tagヘルパーを使用しています。
確認してください。
完
Rails Tutorial 第4版 第6章 演習 解答例
この文書はRails Tutorial 第4版 第6章 ユーザーのモデルを作成するの演習に対する個人の解答例です。解答には誤りや不適切な表現が含まれていることがありますが、もし誤謬を見つけたらコメント頂けると嬉しいです。
それでは、やっていきましょう!
6.1 Userモデル
6.1.1 データベースの移行
1. Railsはdb/ディレクトリの中にあるschema.rbというファイルを使っています。これはデータベースの構造 (スキーマ (schema) と呼びます) を追跡するために使われます。さて、あなたの環境にあるdb/schema.rbの内容を調べ、その内容とマイグレーションファイル (リスト 6.2) の内容を比べてみてください。
db/schema.rb
# This file is auto-generated from the current state of the database. Instead # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. # # Note that this schema.rb definition is the authoritative source for your # database schema. If you need to create the application database on another # system, you should be using db:schema:load, not running all the migrations # from scratch. The latter is a flawed and unsustainable approach (the more migrations # you'll amass, the slower it'll run and the greater likelihood for issues). # # It's strongly recommended that you check this file into your version control system. ActiveRecord::Schema.define(version: 2019_07_13_160043) do create_table "users", force: :cascade do |t| t.string "name" t.string "email" t.datetime "created_at", null: false t.datetime "updated_at", null: false end end
db/migrate/20190713160043_create_users.rb
class CreateUsers < ActiveRecord::Migration[5.2] def change create_table :users do |t| t.string :name t.string :email t.timestamps end end end
schemaファイルとマイグレーションファイルとの間では、create_table "users"
以下のブロックの内容がほぼ一致しています。一部異なるのはマイグレーションファイルにおけるt.timestamps
がschemaファイルではt.datetime "created_at", null: false
とt.datetime "updated_at", null: false
に変化しているように見えることです。
2. ほぼすべてのマイグレーションは、元に戻すことが可能です (少なくとも本チュートリアルにおいてはすべてのマイグレーションを元に戻すことができます)。元に戻すことを「ロールバック (rollback)と呼び、Railsではdb:rollbackというコマンドで実現できます。$ rails db:rollback
上のコマンドを実行後、db/schema.rbの内容を調べてみて、ロールバックが成功したかどうか確認してみてください (コラム 3.1ではマイグレーションに関する他のテクニックもまとめているので、参考にしてみてください)。上のコマンドでは、データベースからusersテーブルを削除するためにdrop_tableコマンドを内部で呼び出しています。これがうまくいくのは、drop_tableとcreate_tableがそれぞれ対応していることをchangeメソッドが知っているからです。この対応関係を知っているため、ロールバック用の逆方向のマイグレーションを簡単に実現することができるのです。なお、あるカラムを削除するような不可逆なマイグレーションの場合は、changeメソッドの代わりに、upとdownのメソッドを別々に定義する必要があります。詳細については、Railsガイドの「Active Record マイグレーション」を参照してください。
ロールバック後のschemaファイルを以下に示します。
db/schema.rb
# This file is auto-generated from the current state of the database. Instead # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. # # Note that this schema.rb definition is the authoritative source for your # database schema. If you need to create the application database on another # system, you should be using db:schema:load, not running all the migrations # from scratch. The latter is a flawed and unsustainable approach (the more migrations # you'll amass, the slower it'll run and the greater likelihood for issues). # # It's strongly recommended that you check this file into your version control system. ActiveRecord::Schema.define(version: 0) do end
usersテーブルのカラムに関する記述が消えているので、マイグレートする前の状態に戻ったようです。
3. もう一度rails db:migrateコマンドを実行し、db/schema.rbの内容が元に戻ったことを確認してください。
コードは省略しますが、db/schema.rbの内容が元に戻ることを確認しました。
6.1.2 modelファイル
1. Railsコンソールを開き、User.newでUserクラスのオブジェクトが生成されること、そしてそのオブジェクトがApplicationRecordを継承していることを確認してみてください (ヒント: 4.4.4で紹介したテクニックを使ってみてください)。
>> user = User.new => #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil> >> user.class.superclass => ApplicationRecord(abstract)
2. 同様にして、ApplicationRecordがActiveRecord::Baseを継承していることについて確認してみてください。
>> ApplicationRecord.superclass => ActiveRecord::Base
6.1.3 ユーザーオブジェクトを作成する
1. user.nameとuser.emailが、どちらもStringクラスのインスタンスであることを確認してみてください。
>> user.name.class=> String >> user.email.class => String
user.nameとuser.emailがStringクラスのインスタンスであることを確認できました。
2. created_atとupdated_atは、どのクラスのインスタンスでしょうか?
>> user.created_at.class => ActiveSupport::TimeWithZone >> user.updated_at.class => ActiveSupport::TimeWithZone
両者はActiveSupport::TimeWithZoneクラスのインスタンスです。
6.1.4 ユーザーオブジェクトを検索する
1. nameを使ってユーザーオブジェクトを検索してみてください。また、 find_by_nameメソッドが使えることも確認してみてください (古いRailsアプリケーションでは、古いタイプのfind_byをよく見かけることでしょう)。
>> User.find_by(name: "Michael Hartl") User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."name" = ? LIMIT ? [["name", "Michael Hartl"], ["LIMIT", 1]] => #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2019-07-13 17:54:18", updated_at: "2019-07-13 17:54:18"> >> User.find_by_name("Michael Hartl") User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."name" = ? LIMIT ? [["name", "Michael Hartl"], ["LIMIT", 1]] => #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2019-07-13 17:54:18", updated_at: "2019-07-13 17:54:18">
2. 実用的な目的のため、User.allはまるで配列のように扱うことができますが、実際には配列ではありません。User.allで生成されるオブジェクトを調べ、ArrayクラスではなくUser::ActiveRecord_Relationクラスであることを確認してみてください。
>> User.all.class => User::ActiveRecord_Relation
3. User.allに対してlengthメソッドを呼び出すと、その長さを求められることを確認してみてください (4.2.3)。Rubyの性質として、そのクラスを詳しく知らなくてもなんとなくオブジェクトをどう扱えば良いかわかる、という性質があります。これをダックタイピング (duck typing) と呼び、よく次のような格言で言い表されています「もしアヒルのような容姿で、アヒルのように鳴くのであれば、それはもうアヒルだろう」。(訳注: そういえばRubyKaigi 2016の基調講演で、Ruby作者のMatzがダックタイピングについて説明していました。2〜3分の短くて分かりやすい説明なので、ぜひ視聴してみてください!)
>> User.all.length User Load (0.1ms) SELECT "users".* FROM "users" => 1
6.1.5 ユーザーオブジェクトを更新する
1. userオブジェクトへの代入を使ってname属性を使って更新し、saveで保存してみてください。
=> "yamada" >> user.save (0.2ms) SAVEPOINT active_record_1 User Update (0.5ms) UPDATE "users" SET "name" = ?, "email" = ?, "updated_at" = ? WHERE "users"."id" = ? [["name", "yamada"], ["email", "m@example.com"], ["updated_at", "2019-07-13 18:42:56.552791"], ["id", 1]] (0.1ms) RELEASE SAVEPOINT active_record_1 => true
2. 今度はupdate_attributesを使って、email属性を更新および保存してみてください。
>> user.update_attributes(name: "Yanagida", email: "y@gmail.com") (0.4ms) SAVEPOINT active_record_1 User Update (0.7ms) UPDATE "users" SET "name" = ?, "email" = ?, "updated_at" = ? WHERE "users"."id" = ? [["name", "Yanagida"], ["email", "y@gmail.com"], ["updated_at", "2019-07-13 18:44:59.680964"], ["id", 1]] (0.2ms) RELEASE SAVEPOINT active_record_1 => true
3. 同様にして、マジックカラムであるcreated_atも直接更新できることを確認してみてください。ヒント: 更新するときは「1.year.ago」を使うと便利です。これはRails流の時間指定の1つで、現在の時刻から1年前の時間を算出してくれます。
>> user.update_attributes(created_at: 1.year.ago) (0.1ms) SAVEPOINT active_record_1 User Update (0.3ms) UPDATE "users" SET "created_at" = ?, "updated_at" = ? WHERE "users"."id" = ? [["created_at", "2018-07-13 18:46:23.868735"], ["updated_at", "2019-07-13 18:46:23.882587"], ["id", 1]] (0.1ms) RELEASE SAVEPOINT active_record_1 => true
6.2 ユーザーを検証する
6.2.1 有効性を検証する
1. コンソールから、新しく生成したuserオブジェクトが有効 (valid) であることを確認してみましょう。
>> user = User.new(name: "Ruby Rails") => #<User id: nil, name: "Ruby Rails", email: nil, created_at: nil, updated_at: nil> >> user.valid? => true
2. 6.1.3で生成したuserオブジェクトも有効であるかどうか、確認してみましょう。
>> user = User.new => #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil> >> user.valid? => true
6.2.2 存在性を検証する
1. 新しいユーザーuを作成し、作成した時点では有効ではない (invalid) ことを確認してください。なぜ有効ではないのでしょうか? エラーメッセージを確認してみましょう。
>> 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を実行すると、ハッシュ形式でエラーが取得できることを確認してください。emailに関するエラー情報だけを取得したい場合、どうやって取得すれば良いでしょうか?
>> 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. 長すぎるnameとemail属性を持ったuserオブジェクトを生成し、有効でないことを確認してみましょう。
>> user = User.new(name: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", email: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@mail.net") => #<User id: nil, name: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...", email: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...", created_at: nil, updated_at: nil> >> user.valid? => false
2. 長さに関するバリデーションが失敗した時、どんなエラーメッセージが生成されるでしょうか? 確認してみてください。
>> user.errors.full_messages => ["Name is too long (maximum is 50 characters)", "Email is too long (maximum is 255 characters)"]
6.2.4 フォーマットを検証する
1. リスト 6.18にある有効なメールアドレスのリストと、リスト 6.19にある無効なメールアドレスのリストをRubularのYour test string:に転記してみてください。その後、リスト 6.21の正規表現をYour regular expression:に転記して、有効なメールアドレスのみがすべてマッチし、無効なメールアドレスはすべてマッチしないことを確認してみましょう。
Your regular expression:
[\w+\-.]+@[a-z\d\-.]+\.[a-z]+
Your test string:
user@example.com USER@foo.COM A_US-ER@foo.bar.org first.last@foo.jp alice+bob@baz.cn user@example,com user_at_foo.org user.name@example. foo@bar_baz.com foo@bar+baz.com
Match resultに上から5番目までのメールアドレスの文字背景に色がついていればOKです。
https://rubular.com/r/sAo4p5M1E6bg3R
2. 先ほど触れたように、リスト 6.21のメールアドレスチェックする正規表現は、foo@bar..comのようにドットが連続した無効なメールアドレスを許容してしまいます。まずは、このメールアドレスをリスト 6.19の無効なメールアドレスリストに追加し、これによってテストが失敗することを確認してください。次に、リスト 6.23で示した、少し複雑な正規表現を使ってこのテストがパスすることを確認してください。
テストにfoo@bar..comを追加します。
test/models/user_test.rb
test "email validation should reject invalid addresses" do invalid_addresses = %w[user@example,com user_at_foo.org user.name@example. foo@bar_baz.com foo@bar+baz.com foo@bar..com] invalid_addresses.each do |invalid_address| @user.email = invalid_address assert_not @user.valid?, "#{invalid_address.inspect} should be invalid" end end
テストを実行します。
$ rails test:models Started with run options --seed 32530 FAIL["test_email_validation_should_reject_invalid_addresses", #<Minitest::Reporters::Suite:0x00007fa06b19bc08 @name="UserTest">, 0.03620800003409386] test_email_validation_should_reject_invalid_addresses#UserTest (0.04s) "foo@bar..com" should be invalid test/models/user_test.rb:46:in `block (2 levels) in <class:UserTest>' test/models/user_test.rb:44:in `each' test/models/user_test.rb:44:in `block in <class:UserTest>' 7/7: [==============] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.03694s 7 tests, 16 assertions, 1 failures, 0 errors, 0 skips
結果はredです。リスト6.23よりuser.rbで使用する正規表現をVALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
として再度テストします。
$ rails test:models Started with run options --seed 30297 7/7: [==============] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.02860s 7 tests, 16 assertions, 0 failures, 0 errors, 0 skips
greenになることが確認できました。
3. foo@bar..comをRubularのメールアドレスのリストに追加し、リスト 6.23の正規表現をRubularで使ってみてください。有効なメールアドレスのみがすべてマッチし、無効なメールアドレスはすべてマッチしないことを確認してみましょう。
確認できました。
https://rubular.com/r/6UlnUeFkdbBL3X
6.2.5 一意性を検証する
1. リスト 6.33を参考に、メールアドレスを小文字にするテストをリスト 6.32に追加してみましょう。ちなみに追加するテストコードでは、データベースの値に合わせて更新するreloadメソッドと、値が一致しているかどうか確認するassert_equalメソッドを使っています。リスト 6.33のテストがうまく動いているか確認するためにも、before_saveの行をコメントアウトして redになることを、また、コメントアウトを解除すると greenになることを確認してみましょう。
メールアドレスを小文字にするテストをリスト 6.32に追加し、user.rbのbefore_saveの行をコメントアウトしてテストを実行します。
$ rails test:models Started with run options --seed 55447 FAIL["test_email_addresses_should_be_saved_as_lower-case", #<Minitest::Reporters::Suite:0x00007f935f3febd0 @name="UserTest">, 0.04487500002142042] test_email_addresses_should_be_saved_as_lower-case#UserTest (0.04s) Expected: "foo@example.com" Actual: "Foo@ExAMPle.CoM" test/models/user_test.rb:61:in `block in <class:UserTest>' 9/9: [==============] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.05882s 9 tests, 17 assertions, 1 failures, 0 errors, 0 skips
redになることが確認できました。コメントアウトを解除して再度テストします。
$ rails test:models Started with run options --seed 55480 9/9: [==============] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.06251s 9 tests, 17 assertions, 0 failures, 0 errors, 0 skips
greenになることが確認できました。
2. テストスイートの実行結果を確認しながら、before_saveコールバックをemail.downcase!に書き換えてみましょう。ヒント: メソッドの末尾に!を付け足すことにより、email属性を直接変更できるようになります (リスト 6.34)。
app/models/user.rb
class User < ApplicationRecord before_save { email.downcase! } validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } end
テストを実行します。
$ rails test:models Started with run options --seed 3506 9/9: [==============] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.07236s 9 tests, 17 assertions, 0 failures, 0 errors, 0 skips
greenになることが確認できました。
6.3 セキュアなパスワードを追加する
6.3.1 ハッシュ化されたパスワード
演習なし。
6.3.2 ユーザーがセキュアなパスワードを持っている
1. この時点では、userオブジェクトに有効な名前とメールアドレスを与えても、valid?で失敗してしまうことを確認してみてください。
>> user = User.new(name: "foo", email: "foo@example.com")=> #<User id: nil, name: "foo", email: "foo@example.com", created_at: nil, updated_at: nil, password_digest: nil> >> user.valid? User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ? [["email", "foo@example.com"], ["LIMIT", 1]] => false
2. なぜ失敗してしまうのでしょうか? エラーメッセージを確認してみてください。
>> user.errors.full_messages => ["Password can't be blank"]
パスワードが設定されていないから。
6.3.3 パスワードの最小文字数
1. 有効な名前とメールアドレスでも、パスワードが短すぎるとuserオブジェクトが有効にならないことを確認してみましょう。
>> user = User.new(name: "foo", email: "foo@example.com", password: "abc", password_confirmation: "abc") => #<User id: nil, name: "foo", email: "foo@example.com", created_at: nil, updated_at: nil, password_digest: "$2a$12$xBDNuHJPMcihRHSEP2vsCeMTU8gV8Nu2q5ofQ0xsMVO..."> >> user.valid? User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ? [["email", "foo@example.com"], ["LIMIT", 1]] => false
2. 上で失敗した時、どんなエラーメッセージになるでしょうか? 確認してみましょう。
>> user.errors.full_messages => ["Password is too short (minimum is 6 characters)"]
6.3.4 ユーザーの作成と認証
1. コンソールを一度再起動して (userオブジェクトを消去して)、このセクションで作ったuserオブジェクトを検索してみてください。
>> user = User.find_by(name: "Michael Hartl") User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."name" = ? LIMIT ? [["name", "Michael Hartl"], ["LIMIT", 1]] => #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2019-07-26 10:03:57", updated_at: "2019-07-26 10:03:57", password_digest: "$2a$12$pbYTT6aNzZj7Be26vSbtRuqAA59NgPEY5QAA34BMYpk...">
2. オブジェクトが検索できたら、名前を新しい文字列に置き換え、saveメソッドで更新してみてください。うまくいきませんね...、なぜうまくいかなかったのでしょうか?
>> user.name = "Mike Hardin" => "Mike Hardin" >> user.save (0.1ms) begin transaction User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) AND "users"."id" != ? LIMIT ? [["email", "mhartl@example.com"], ["id", 1], ["LIMIT", 1]] (0.1ms) rollback transaction => false
保存に失敗してしまいます。 userオブジェクトのエラー内容を確認してみます。
>> user.errors.full_messages => ["Password can't be blank", "Password is too short (minimum is 6 characters)"]
userにパスワードが設定されていないため、バリデーションで弾かれています。 ※「User.createメソッドを使ってユーザーを作成したときにパスワードを設定したじゃないか」と思う人もいるかもしれませんが、パスワードはハッシュ化されpassword_digest属性に保存されるため、データベースからユーザーデータを取得しても直接パスワードが得られるわけではありません。今回の場合、userオブジェクトのpassword属性とpassword_confirmation属性は空っぽの状態です。
3. 今度は6.1.5で紹介したテクニックを使って、userの名前を更新してみてください。
update_attributeメソッドを用いて指定の属性を更新します。
>> user.update_attribute(:name, "Mike Hardin") (0.1ms) SAVEPOINT active_record_1 User Update (0.7ms) UPDATE "users" SET "name" = ?, "updated_at" = ? WHERE "users"."id" = ? [["name", "Mike Hardin"], ["updated_at", "2019-07-28 15:53:42.066320"], ["id", 1]] (0.1ms) RELEASE SAVEPOINT active_record_1 => true
無事更新することができました。
6.4 最後に
6.4.1 本章のまとめ
演習なし。
完
Rails Tutorial 第4版 第5章 演習 解答例
この文書はRails Tutorial 第4版 第5章 レイアウトを作成するの演習に対する個人の解答例です。解答には誤りや不適切な表現が含まれていることがありますが、もし誤謬を見つけたらコメント頂けると嬉しいです。
それでは、やっていきましょう!
- 5.1 構造を追加する
- 5.2 Sassとアセットパイプライン
- 5.3 レイアウトのリンク
- 5.3.1 Contactページ
- 5.3.2 RailsのルートURL
- 1. 実は名前付きルートは、as:オプションを使って変更することができます。有名なFar Sideの漫画に倣って、Helpページの名前付きルートをhelfに変更してみてください (リスト 5.29)。
- 2. 先ほどの変更により、テストが redになっていることを確認してください。リスト 5.28を参考にルーティングを更新して、テストを greenにして見てください。
- 3. エディタのUndo機能を使って、今回の演習で行った変更を元に戻して見てください。
- 5.3.3 名前付きルート
- 1. リスト 5.29のようにhelfルーティングを作成し、レイアウトのリンクを更新してみてください。
- 2. 前回の演習と同様に、エディタのUndo機能を使ってこの演習で行った変更を元に戻してみてください。
- 5.3.4 リンクのテスト
- 1. footerパーシャルのabout_pathをcontact_pathに変更してみて、テストが正しくエラーを捕まえてくれるかどうか確認してみてください。
- 2. リスト 5.35で示すように、Applicationヘルパーで使っているfull_titleヘルパーを、test環境でも使えるようにすると便利です。こうしておくと、リスト 5.36のようなコードを使って、正しいタイトルをテストすることができます。ただし、これは完璧なテストではありません。例えばベースタイトルに「Ruby on Rails Tutoial」といった誤字があったとしても、このテストでは発見することができないでしょう。この問題を解決するためには、full_titleヘルパーに対するテストを書く必要があります。そこで、Applicationヘルパーをテストするファイルを作成し、リスト 5.37のFILL_INの部分を適切なコードに置き換えてみてください。ヒント: リスト 5.37ではassert_equal <期待される値>, <実際の値>といった形で使っていましたが、内部では==演算子で期待される値と実際の値を比較し、正しいかどうかのテストをしています。
- 5.4 ユーザー登録: 最初のステップ
- 5.4.1 Usersコントローラ
- 1. 表 5.1を参考にしながらリスト 5.41を変更し、users_new_urlではなくsignup_pathを使えるようにしてみてください
- 2. 先ほどの変更を加えたことにより、テストが redになったことを確認してください。なお、この演習はテスト駆動開発 (コラム 3.3) で説明した red/green のリズムを作ることを目的としています。このテストは次の5.4.2で greenになるよう修正します。
- 5.4.2
- 1. もしまだ5.4.1.1の演習に取り掛かっていなければ、まずはリスト 5.41のように変更し、名前付きルートsignup_pathを使えるようにしてください。また、リスト 5.43で名前付きルートが使えるようになったので、現時点でテストが greenになっていることを確認してください。
- 2. 先ほどのテストが正しく動いていることを確認するため、signupルートの部分をコメントアウトし、テストredになることを確認してください。確認できたら、コメントアウトを解除してgreenの状態に戻してください。
- 3. リスト 5.32の統合テストにsignupページにアクセスするコードを追加してください (getメソッドを使います)。コードを追加したら実際にテストを実行し、結果が正しいことを確認してください。ヒント: リスト 5.36で紹介したfull_titleヘルパーを使ってみてください。
- 5.4.1 Usersコントローラ
- 5.5 最後に
- 5.5.1 本章のまとめ