Railsポートフォリオ作成【設計編】ワイヤーフレームを作る

今日はカレーを作ってみました、肉体労働者です。

前回に続きポートフォリオの作成をする

yukitoku-sw.hatenablog.com


今回はワイヤーフレームを作りました

f:id:yukitoku_sw:20191201144854p:plain

こんな感じ。

難しいことは考えず、railsチュートリアルでやったような構成にしました

TOPページ
User
 一覧ページ
 詳細ページ
 新規ページ
 編集ページ
投稿
 一覧ページ
 詳細ページ
 新規ページ
 編集ページ

ワイヤーフレームを作る際は、
一覧 → 詳細 → 新規 → 編集
の順番で作成すると考えがまとまり作りやすかったです

順番なんて適当でいいかと思っていたけど意外と重要。どこに何の情報が必要か、新規で必須項目にするか、編集で追加してもらえばいいかみたいな。

あの機能もつけよう。


これもいいな。


なんて考えていると一生終わらない気がします。
なので、とにかくシンプルにしました

自分で1から考えるのは本当に難しい。けど楽しい。
と言いつ、食べログクックパッドぐるなびなどを参考にさせて頂きました



moqupsというサービスを利用し、ワイヤーフレームを作りました。
https://moqups.com/


参考にしたサイト
moqupsとは?ブラウザ上で簡単にワイヤーフレームを制作しよう|ferret


おわり!

Railsポートフォリオ作成【設計編】サイトマップ作成

指が痛い肉体労働者です。

前回に続きポートフォリオ作成する

yukitoku-sw.hatenablog.com

今回はサイトマップの作成

f:id:yukitoku_sw:20191130213658p:plain


これでいいのか?


これでいいんか??



とりあえずこれでいく。


Canvaというサイトを利用しました。

https://www.canva.com/

素材を組み合わせて20分くらいで作れました。

サイトマップ用のテンプレートもあったが、ヘンテコな複雑なものばかりだったので今回は使用しませんでした。

おわり

Railsポートフォリオ作成【準備編】

エンジニアになりたい肉体労働者です。
Railsチュートリアルなど、などなど終わったのでポートフォリオ作成に移る。
その内容を記していく。

思いつくままにコードを書き殴っていたんですが、あっち行ったりこっち行ったりで全然進まないので下記記事を参考に開発を進めていきます。

qiita.com

アプリ開発の注意点

  1. いきなり開発しない
  2. しっかり設計する
  3. 事前に効率的な開発方法をいろいろ調べる
  4. WBSを作ろう

    と、いうことで、

f:id:yukitoku_sw:20191130202914p:plain

ガントチャートを作ってみました!!

初めてのオリジナルサイト作成のため、しっかり設計をする・事前に効率的な開発方法をいろいろ調べる。というのはハードルが高い。
なのでWBSについて調べ、ガントチャートを作成することができました。

現状、このタスク内容は先ほどの記事を丸パクリ状態です。 笑

今後しっかり設計をして、効率的な開発方法を調べて、タスクを細分化していきます。

ガントチャートは、googleスプレットシートのProjectSheet planningというアドオンを利用しました。

参考にした記事

xn--t8j3bz04sl3w.xyz

boxil.jp


おわり


PS. ガントチャートのことを、ずっとガンチャートだと思ってました。

Railsチュートリアル演習問題と解答まとめのまとめ

Railsチュートリアル演習問題と解答まとめを1章から14章までやり切ったのでまとめます。

f:id:yukitoku_sw:20191019165508p:plain


第1章ゼロからデプロイまで

yukitoku-sw.hatenablog.com

この章で学べること

  • Ruby on Railsでの開発環境のセットアップ

  • Gitによるバージョン管理、Bitbucketの使い方

  • Herokuでの本番環境へのデプロイ


第2章Toyアプリケーション

yukitoku-sw.hatenablog.com

この章では簡単なアプリケーションを作りながら学ぶことができます。

  • Scaffold機能の長所・短所

  • RailsMVCとREST

自分で1からポートフォリオを作成するときは、この第2章を見直すといいと思いました。


第3章ほぼ静的なページの作成

yukitoku-sw.hatenablog.com

sample_appというtwitter風のwebアプリケーションの作成に取り掛かります。

この章で学べること


第4章Rails風味のRuby

yukitoku-sw.hatenablog.com

RailsというよりRubyについて詳しく学べます。

この章で学べること


第5章レイアウトを作成する

yukitoku-sw.hatenablog.com

この章で学べること

  • レイアウトの整え方

  • Bootstrapの使い方

  • 名前付きルートについて


第6章ユーザーのモデルを作成する

yukitoku-sw.hatenablog.com

この章で学べること

  • データモデルの修正

  • バリデーション

  • 正規表現


第7章ユーザー登録

ユーザー登録機能の実装を行う

この章で学べること

  • debug

  • 開発環境・テスト環境・本番環境について

  • form_forヘルパー

  • flash変数


第8章基本的なログイン機構

yukitoku-sw.hatenablog.com

この章で学べること

cookieを調べる際は、URL横の鍵マークではなくデベロッパーツールを使った方がわかりやすかったです。


第9章発展的なログイン機構

yukitoku-sw.hatenablog.com

この章で学べること

  • Remember me機能


第10章ユーザーの更新・表示・削除

yukitoku-sw.hatenablog.com

この章で学べること

  • Strong Parameters

  • beforeフィルター

  • ページネーション

  • サンプルデータ

この辺りから演習が難解になります。


第11章アカウントの有効化

yukitoku-sw.hatenablog.com

この章で学べること

  • メイラー機能

  • authenticatedメソッド

  • SendGrid


第12章パスワードの再設定

yukitoku-sw.hatenablog.com

この章で学べること

  • パスワードの再設定方法

演習が難しい


第13章ユーザーのマイクロポスト

yukitoku-sw.hatenablog.com

この章で学べること

  • モデルの関連付け

  • Faker

  • 画像投稿機能

  • 画像のリサイズ

  • scope

Fakerでなぜか使えないのがいくつかありました。


第14章ユーザーをフォローする

yukitoku-sw.hatenablog.com

この章で学べること

  • has_many :through

  • Ajax

  • whereメソッド


お疲れ様でした!!!

Railsチュートリアル機能拡張【マイクロポスト検索】

Railsチュートリアルで作ったsample_appに機能を拡張していく

今回は、前回に続きRansack Gemを利用してマイクロポスト検索機能を組み込みます。

yukitoku-sw.hatenablog.com


下記の記事を参考にさせていただきました。

https://github.com/activerecord-hackery/ransack

https://qiita.com/YN6127yn/items/fca9efa22536b8b2a9ef


実装

今回はマイクロポストの検索機能を追加するのでstaticpagesのHomeページとusersのShowページの2つを修正します。

コントローラーにRansackを埋め込む

[app/controllers/users_controller.rb]

  def show
    @user = User.find(params[:id])
    redirect_to root_url and return unless @user.activated?
    # @microposts = @user.microposts.paginate(page: params[:page])
    if params[:q] && params[:q].reject { |key, value| value.blank? }.present?
      @q =  @user.microposts.ransack(microposts_search_params)
      @microposts = @q.result.paginate(page: params[:page])
    else
      @q = Micropost.ransack
      @microposts = @user.microposts.paginate(page: params[:page])
    end
    @url = user_path(@user)
  end
[app/controllers/static_pages_controller.rb]
  def home
    if logged_in?
      @micropost = current_user.microposts.build
      # @feed_items = current_user.feed.paginate(page: params[:page])
      if params[:q] && params[:q].reject { |key, value| value.blank? }.present?
        @q = current_user.feed.ransack(microposts_search_params)
        @feed_items = @q.result.paginate(page: params[:page])
      else
        @q = Micropost.ransack
        @feed_items = current_user.feed.paginate(page: params[:page])
      end
      @url = root_path
    end
  end

micropost_search_paramsは上記2つのファイルで共有できるのでapplication_controllerに記述する

[app/controllers/application_controller.rb]

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  include SessionsHelper

  private

    def microposts_search_params
      params.require(:q).permit(:content_cont)
    end
 .
 .
end

VIewsにsearch_form_forを加える

前回のユーザー検索で作成したフォームをそのまま使おうとしたんですが RoutingErrorが出て上手くいきませんでした。なので、_microposts_html.erbというファイルを作成し、前回のフォームの引数を指定するバージョンを作りました。

コントローラーに@urlを記述していたのはそのためです。

[app/views/shared/_microposts_search.html.erb]

<div class="row">
  <div class="search_form">
    <%= search_form_for @q, url: @url do |f| %>
      <%= f.label :content_cont, 'Micropost Search'%>
      <div class="input-group">
        <%= f.search_field :content_cont, placeholder: "Enter keyword..", class: 'form-control' %>
        <span class="input-group-btn">
          <%= f.submit 'Search', class: "btn btn-primary" %>
        </span>
      </div>
    <% end %>
  </div>
</div>
[app/views/static_pages/_home_logged_in.html.erb]

<div class="row">
  <aside class="col-md-4">
    <section class="user_info">
      <%= render 'shared/user_info' %>
    </section>
    <section class="stats">
      <%= render 'shared/stats' %>
    </section>
    <section class="micropost_form">
      <%= render 'shared/micropost_form' %>
    </section>
  </aside>
  <div class="col-md-8">
    <h3>Micropost Feed</h3>
    <%= render 'shared/microposts_search' %>
    <%= render 'shared/feed' %>
  </div>
</div>
[app/views/users/show.html.erb]

<% provide(:title, @user.name) %>

<div class="row">
(省略)
  <div class="col-md-8">
    <%= render 'follow_form' if logged_in? %>
    <% if @user.microposts.any? %>
      <h3>Microposts (<%= @user.microposts.count %>)</h3>
      <%= render 'shared/microposts_search' %>
      <ol class="microposts">
        <%= render @microposts %>
      </ol>
      <%= will_paginate @microposts %>
    <% end %>
  </div>
</div>

完成

こんな感じです

f:id:yukitoku_sw:20191110001102p:plain

f:id:yukitoku_sw:20191110001126p:plain

Railsチュートリアル 機能拡張【ユーザー検索】

Railsチュートリアルで作ったsample_appに機能を拡張していく

今回は、Ransack Gemを利用してユーザー検索機能を組み込みます。


下記の記事を参考にさせていただきました。

https://github.com/activerecord-hackery/ransack

https://qiita.com/YN6127yn/items/fca9efa22536b8b2a9ef

https://qiita.com/LuckOfWise/items/e020e896e71d47d0c6a4

準備

Ransackのインストール

https://github.com/activerecord-hackery/ransack

Gemfileに追記し、bundle installします。

gem 'ransack'
bundle install

インストール後にサーバーを再接続する。

実装

コントローラーにRansackを埋め込む

[app/controllers/user_controller.rb]

class UsersController < ApplicationController
 .
 .
  def index
    # @users = User.where(activated: true).paginate(page: params[:page])
    if params[:q] && params[:q].reject { |key, value| value.blank? }.present?
      @q = User.ransack(search_params, activated: true)
      @title = "Serch Result"
    else
      @q = User.ransack(activated: true)
      @title = "All users"
    end
    @users = @q.result.paginate(page: params[:page])
  end
 . 
 .
 .
  private

    def search_params
      params.require(:q).permit(:name_cont)
    end
 .
 .
 end

rejectメソッドを使い空白で検索したときはAll Usersを返すようにしている

Viewにsearch_form_forを加える

[app/views/users/index.html.erb]
<% provide(:title, @title) %>
<h1><%= @title %></h1>

<!--  検索機能  -->
<%= search_form_for @q do |f| %>
  <%= f.label :name_cont %>
  <%= f.search_field :name_cont %>

  <%= f.submit %>
<% end %>

<%= will_paginate %>

<ul class="users">
  <%= render @users %>
</ul>

<%= will_paginate %>


取り敢えずできた。

全ユーザー
f:id:yukitoku_sw:20191109174143p:plain

検索結果

f:id:yukitoku_sw:20191109174313p:plain


Viewに修正を加える

<!--  検索機能  -->
<div class="row">
  <div class="search_form">
    <%= search_form_for @q do |f| %>
      <%= f.label :name_cont, 'User Search'%>
      <div class="input-group">
        <%= f.search_field :name_cont, placeholder: "Enter username..", class: 'form-control' %>
        <span class="input-group-btn">
          <%= f.submit 'Search', class: "btn btn-primary" %>
        </span>
      </div>
    <% end %>
  </div>
</div>

scssに追記する

[app/asets/stylesheets/custom.scss]
 .
 .
/* Search form  */

.search_form {
  overflow: auto;
  margin: 10px;
  .input-group {
    width: 305px;
  }
}

パーシャルにする

_search.html.erbというファイルを作りぶっこむ

[app/views/users/_search.html.erb]

<div class="row">
  <div class="search_form">
    <%= search_form_for @q do |f| %>
      <%= f.label :name_cont, 'User Search'%>
      <div class="input-group">
        <%= f.search_field :name_cont, placeholder: "Enter username..", class: 'form-control' %>
        <span class="input-group-btn">
          <%= f.submit 'Search', class: "btn btn-primary" %>
        </span>
      </div>
    <% end %>
  </div>
</div>

index.html.erbを修正

[app/views/users/index.html.erb]

<% provide(:title, @title) %>
<h1><%= @title %></h1>

<%= render 'search' %>

<%= will_paginate %>

<ul class="users">
  <%= render @users %>
</ul>

<%= will_paginate %>

すっきり〜!!

完成

こんな感じ

全ユーザー f:id:yukitoku_sw:20191109181451p:plain

検索結果 f:id:yukitoku_sw:20191109181510p:plain

第14章Railsチュートリアル演習問題と解答まとめ

Ruby on Railsチュートリアルの演習問題と解答をまとめる。


第14章 ユーザーをフォローする - Railsチュートリアル

f:id:yukitoku_sw:20191019165508p:plain


アウトプットすることで、より自分の理解を深めることを目的としています。 自分なりに調べて考えた回答のため、記載内容に誤りがある場合はコメントいただけると幸いです。



14.1.1 データモデルの問題(および解決策)

演習1

図 14.7のid=1のユーザーに対してuser.following.map(&:id)を実行すると、結果はどのようになるでしょうか? 想像してみてください。ヒント: 4.3.2で紹介したmap(&:method_name)のパターンを思い出してください。例えばuser.following.map(&:id)の場合、idの配列を返します。

解答

id=1にフォローされているユーザー(id: 2, 7, 10,)のidをそれぞれ1つずつ返す

演習2

図 14.7を参考にして、id=2のユーザーに対してuser.followingを実行すると、結果はどのようになるでしょうか? また、同じユーザーに対してuser.following.map(&:id)を実行すると、結果はどのようになるでしょうか? 想像してみてください。

解答

id=2にフォローされているユーザー(id: 1)を返す


14.1.2 User/Relationshipの関連付け

演習1

コンソールを開き、表 14.1のcreateメソッドを使ってActiveRelationshipを作ってみましょう。データベース上に2人以上のユーザーを用意し、最初のユーザーが2人目のユーザーをフォローしている状態を作ってみてください。

解答

>> user = User.first
(省略)
>> user2 = User.second
(省略)
>> user.active_relationships.create(followed_id: user2.id)
   (0.1ms)  begin transaction
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  SQL (2.5ms)  INSERT INTO "relationships" ("follower_id", "followed_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["follower_id", 1], ["followed_id", 2], ["created_at", "2019-11-04 10:27:41.371417"], ["updated_at", "2019-11-04 10:27:41.371417"]]
   (1.6ms)  commit transaction
=> #<Relationship id: 1, follower_id: 1, followed_id: 2, created_at: "2019-11-04 10:27:41", updated_at: "2019-11-04 10:27:41">


演習2

先ほどの演習を終えたら、active_relationship.followedの値とactive_relationship.followerの値を確認し、それぞれの値が正しいことを確認してみましょう。

解答

=> #<Relationship id: 1, follower_id: 1, followed_id: 2, created_at: "2019-11-04 10:27:41", updated_at: "2019-11-04 10:27:41">


14.1.3 Relationshipのバリデーション

演習1

リスト 14.5のバリデーションをコメントアウトしても、テストが成功したままになっていることを確認してみましょう。(以前のRailsのバージョンでは、このバリデーションが必須でしたが、Rails 5から必須ではなくなりました。今回はフォロー機能の実装を優先しますが、この手のバリデーションが省略されている可能性があることを頭の片隅で覚えておくと良いでしょう。)

解答

動作確認のみなので省略


14.1.4 フォローしているユーザー

演習1

コンソールを開き、リスト 14.9のコードを順々に実行してみましょう。

解答

>> michael = User.third
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? OFFSET ?  [["LIMIT
", 1], ["OFFSET", 2]]
=> #<User id: 3, name: "Ryley Abbott DVM", email: "example-2@railstutorial.org", created_at: "2019-11-03
 00:46:18", updated_at: "2019-11-03 00:46:18", password_digest: "$2a$10$LsvPDhcVk3jlVoEyO4PSmOyH/n6mXynvrwC1MpSxgYs...", remember_digest: nil, admin: false, activation_digest: "$2a$10$KC4QN336HKYIOXoQH3fJ2.T1Gy8C6GJvA9du.cmc85m...", activated: true, activated_at: "2019-11-03 00:46:18", reset_digest: nil, reset_
sent_at: nil>
>> archer = User.fourth
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? OFFSET ?  [["LIMIT", 1], ["OFFSET", 3]]
=> #<User id: 4, name: "Grace Bernier DDS", email: "example-3@railstutorial.org", created_at: "2019-11-03 00:46:18", updated_at: "2019-11-03 00:46:18", password_digest: "$2a$10$kw8OUcJpxH6.qYhDOqCiYuCR/jHjNCp8w/e2gcCn/Ad...", remember_digest: nil, admin: false, activation_digest: "$2a$10$/CmJZJhdONUJYJCkobvs8eynbxyZiDGEUCpMJkTbyze...", activated: true, activated_at: "2019-11-03 00:46:18", reset_digest: nil, reset_sent_at: nil>
>> michael.following?(archer)
  User Exists (0.3ms)  SELECT  1 AS one FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? AND "users"."id" = ? LIMIT ?  [["follower_id", 3], ["id", 4], ["LIMIT", 1]]
=> false
>> michael.follow(archer)
   (0.1ms)  begin transaction
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
  SQL (1.3ms)  INSERT INTO "relationships" ("follower_id", "followed_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["follower_id", 3], ["followed_id", 4], ["created_at", "2019-11-04 11:07:29.366144"], ["updated_at", "2019-11-04 11:07:29.366144"]]
   (2.4ms)  commit transaction
  User Load (0.3ms)  SELECT  "users".* FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? LIMIT ?  [["follower_id", 3], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<User id: 4, name: "Grace Bernier DDS", email: "example-3@railstutorial.org", created_at: "2019-11-03 00:46:18", updated_at: "2019-11-03 00:46:18", password_digest: "$2a$10$kw8OUcJpxH6.qYhDOqCiYuCR/jHjNCp8w/e2gcCn/Ad...", remember_digest: nil, admin: false, activation_digest: "$2a$10$/CmJZJhdONUJYJCkobvs8eynbxyZiDGEUCpMJkTbyze...", activated: true, activated_at: "2019-11-03 00:46:18", reset_digest: nil, reset_sent_at: nil>]>
>> michael.following?(archer)
  User Exists (0.4ms)  SELECT  1 AS one FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? AND "users"."id" = ? LIMIT ?  [["follower_id", 3], ["id", 4], ["LIMIT", 1]]
=> true
>> michael.unfollow(archer)
  Relationship Load (0.3ms)  SELECT  "relationships".* FROM "relationships" WHERE "relationships"."follower_id" = ? AND "relationships"."followed_id" = ? LIMIT ?  [["follower_id", 3], ["followed_id", 4], ["LIMIT", 1]]
   (0.1ms)  begin transaction
  SQL (0.6ms)  DELETE FROM "relationships" WHERE "relationships"."id" = ?  [["id", 2]]
   (1.3ms)  commit transaction
=> #<Relationship id: 2, follower_id: 3, followed_id: 4, created_at: "2019-11-04 11:07:29", updated_at: "2019-11-04 11:07:29">
>> michael.following?(archer)
  User Exists (0.4ms)  SELECT  1 AS one FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? AND "users"."id" = ? LIMIT ?  [["follower_id", 3], ["id", 4], ["LIMIT", 1]]
=> false
>> 


演習2

先ほどの演習の各コマンド実行時の結果を見返してみて、実際にはどんなSQLが出力されたのか確認してみましょう。

解答

演習1参照


14.1.5 フォロワー

演習1

コンソールを開き、何人かのユーザーが最初のユーザーをフォローしている状況を作ってみてください。最初のユーザーをuserとすると、user.followers.map(&:id)の値はどのようになっているでしょうか?

解答

>> user = User.first

>> user2 = User.second
 
>> user3 = User.third
 
>> user4 = User.fourth

>> user2.follow(user)

>> user3.follow(user)

>> user4.follow(user)

>> user.followers.map(&:id)
  User Load (0.3ms)  SELECT "users".* FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = ?  [["followed_id", 1]]
=> [2, 3, 4]


演習2

上の演習が終わったら、user.followers.countの実行結果が、先ほどフォローさせたユーザー数と一致していることを確認してみましょう。

解答

>> user.followers.count
   (0.3ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = ?  [["followed_id", 1]]
=> 3

user_idが2. 3. 4の3人なので一致している


演習3

user.followers.countを実行した結果、出力されるSQL文はどのような内容になっているでしょうか? また、user.followers.to_a.countの実行結果と違っている箇所はありますか? ヒント: もしuserに100万人のフォロワーがいた場合、どのような違いがあるでしょうか? 考えてみてください。

解答

>> user.followers.count
   (0.3ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = ?  [["followed_id", 1]]
=> 3

>> user.followers.to_a.count
=> 3

実行結果にsql文が出力されない。

100万人のフォロワーがいたとする
user.followers.countの場合、DB内でfollowersの合計を算出し返す。
user.followers.to_a.countの場合、DBから100万回データを取り出し配列を生成しその数の合計を返す。

下記実行結果をcountしているため100万人もいたら時間がかかる。
たった3人でこの量、DBの負担がすごそう。。

>> user.followers.to_a
=> [#<User id: 2, name: "Mr. Jade Renner", email: "example-1@railstutorial.org", created_at: "2019-11-03 00:46:17", updated_at: "2019-11-03 00:46:17", password_digest: "$2a$10$MBzfZ.ZcjOUs9GYGkTZBH.kY6U25hrORxv8DUsZ1C6o...", remember_digest: nil, admin: false, activation_digest: "$2a$10$7X19s7oIJNvcz.QJWJkvXOFDD9abC0alx/hdNtvzGCD...", activated: true, activated_at: "2019-11-03 00:46:17", reset_digest: nil, reset_sent_at: nil>, #<User id: 3, name: "Ryley Abbott DVM", email: "example-2@railstutorial.org", created_at: "2019-11-03 00:46:18", updated_at: "2019-11-03 00:46:18", password_digest: "$2a$10$LsvPDhcVk3jlVoEyO4PSmOyH/n6mXynvrwC1MpSxgYs...", remember_digest: nil, admin: false, activation_digest: "$2a$10$KC4QN336HKYIOXoQH3fJ2.T1Gy8C6GJvA9du.cmc85m...", activated: true, activated_at: "2019-11-03 00:46:18", reset_digest: nil, reset_sent_at: nil>, #<User id: 4, name: "Grace Bernier DDS", email: "example-3@railstutorial.org", created_at: "2019-11-03 00:46:18", updated_at: "2019-11-03 00:46:18", password_digest: "$2a$10$kw8OUcJpxH6.qYhDOqCiYuCR/jHjNCp8w/e2gcCn/Ad...", remember_digest: nil, admin: false, activation_digest: "$2a$10$/CmJZJhdONUJYJCkobvs8eynbxyZiDGEUCpMJkTbyze...", activated: true, activated_at: "2019-11-03 00:46:18", reset_digest: nil, reset_sent_at: nil>]


14.2.1 フォローのサンプルデータ

演習問題1

コンソールを開き、User.first.followers.countの結果がリスト 14.14で期待している結果と合致していることを確認してみましょう。

解答

>> User.first.followers.count
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
   (0.3ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = ?  [["followed_id", 1]]
=> 38


演習問題2

先ほどの演習と同様に、User.first.following.countの結果も合致していることを確認してみましょう。

解答

>> User.first.following.count
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
   (0.3ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ?  [["follower_id", 1]]
=> 49


14.2.2 統計と [Follow] フォーム

演習問題1

ブラウザから /users/2 にアクセスし、フォローボタンが表示されていることを確認してみましょう。同様に、/users/5 では [Unfollow] ボタンが表示されているはずです。さて、/users/1 にアクセスすると、どのような結果が表示されるでしょうか?

解答

f:id:yukitoku_sw:20191105200417p:plain

f:id:yukitoku_sw:20191105200433p:plain

unfollowボタンが白くて見えない。

f:id:yukitoku_sw:20191105200552p:plain

loginしているのでボタンがない


演習問題2

ブラウザからHomeページとプロフィールページを表示してみて、統計情報が正しく表示されているか確認してみましょう。

解答

統計情報は一致している

Homeページ

f:id:yukitoku_sw:20191105200915p:plain

プロフィールページ

f:id:yukitoku_sw:20191105200927p:plain


演習問題3

Homeページに表示されている統計情報に対してテストを書いてみましょう。ヒント: リスト 13.28で示したテストに追加してみてください。同様にして、プロフィールページにもテストを追加してみましょう。

解答

Homeページのテストは下記に追加

[test/integration/site_layout_test.rb]
require 'test_helper'

class SiteLayoutTest < ActionDispatch::IntegrationTest
  (中略)
  test "layout links when logged in user" do
    log_in_as(@user)
    get root_path
    assert_select "a[href=?]", root_path, count: 2
    assert_select "a[href=?]", help_path
    assert_select "a[href=?]", about_path
    assert_select "a[href=?]", contact_path
    assert_select "a[href=?]", users_path
    assert_select "a[href=?]", user_path(@user)
    assert_select "a[href=?]", edit_user_path(@user)
    assert_select "a[href=?]", logout_path
    assert_match @user.active_relationships.count.to_s, response.body
    assert_match @user.passive_relationships.count.to_s, response.body
  end
end

プロフィールページは下記に追加

[test/integration/users_profile_test.rb]

require 'test_helper'

class UsersProfileTest < ActionDispatch::IntegrationTest
  include ApplicationHelper

  def setup
    @user = users(:michael)
  end

  test "profile display" do
    get user_path(@user)
    assert_template 'users/show'
    assert_select 'title', full_title(@user.name)
    assert_select 'h1', text: @user.name
    assert_select 'h1>img.gravatar'
    assert_match @user.microposts.count.to_s, response.body
    assert_select 'div.pagination', count: 1
    @user.microposts.paginate(page: 1).each do |micropost|
      assert_match micropost.content, response.body
    end
    assert_match @user.active_relationships.count.to_s, response.body
    assert_match @iser.passive_relationhips.count.to_s, response.body
  end
end


14.2.3 [ Following ] と [ Followers ] ページ

演習問題1

ブラウザから /users/1/followers と /users/1/following を開き、それぞれが適切に表示されていることを確認してみましょう。サイドバーにある画像は、リンクとしてうまく機能しているでしょうか?

解答

following

f:id:yukitoku_sw:20191105214509p:plain

followers

f:id:yukitoku_sw:20191105214547p:plain


演習問題2

リスト 14.29のassert_selectに関連するコードをコメントアウトしてみて、テストが正しく red に変わることを確認してみましょう。

解答

コメントアウト2箇所必要です。
・link_to gravatar...の行のみだと、SyntaxErrorが出てしまうのでeach分ごとコメントアウトする。
・render @usersも忘れずにコメントアウト

[app/views/users/show_follow.html.erb]

<% provide(:title, @title) %>
<div class="row">
  <aside class="col-md-4">
    <section class="uesr_info">
      <%= gravatar_for @user %>
      <h1><%= @user.name %></h1>
      <span><%= link_to "view my profile", @user %></span>
      <span><b>Microposts:</b> <%= @user.microposts.count %></span>
    </section>
    <section class="stats">
      <%= render 'shared/stats' %>
      <% if @users.any? %>
        <div class="user_avatars">
          <%# @users.each do |user| %>
            <%#= link_to gravatar_for(user, size: 30), user %>
          <%# end %>
        </div>
      <% end %>
    </section>
  </aside>
  <div class="col-md-8">
    <h3><%= @title %></h3>
    <% if @users.any? %>
      <ul class="users follow">
        <%#= render @users %>
      </ul>
      <%= will_paginate %>
    <% end %>
  </div>
</div>

テストでassert_selectの行がred(Expected at least 1 element matching "a[href="/users/409608538"]", found 0..)になるのを確認。

最後直すのも忘れずに!


14.2.4 [ Follow ] ボタン(基本編)

演習問題1

ブラウザ上から /users/2 を開き、[Follow] と [Unfollow] を実行してみましょう。うまく機能しているでしょうか?

解答

実行してみましょう!

演習問題2

先ほどの演習を終えたら、Railsサーバーのログを見てみましょう。フォロー/フォロー解除が実行されると、それぞれどのテンプレートが描画されているでしょうか?

解答

フォロー
Rendering users/show.html.erb

Started POST "/relationships" for 127.0.0.1 at 2019-11-05 22:51:57 +0900
Processing by RelationshipsController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"DtJHn4u00RaMzwLppiLdfSHm2mqE8zoN2p4JbZSbtYV1Ngurl+wXDvr8kb2b8wEsh9DY40oBBZRMCpL7gQ8nDQ==", "followed_id"=>"2", "commit"=>"Follow"}
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
   (0.1ms)  begin transaction
  CACHE User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  SQL (1.6ms)  INSERT INTO "relationships" ("follower_id", "followed_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["follower_id", 1], ["followed_id", 2], ["created_at", "2019-11-05 13:51:57.239627"], ["updated_at", "2019-11-05 13:51:57.239627"]]
   (1.2ms)  commit transaction
Redirected to http://localhost:3000/users/2
Completed 302 Found in 15ms (ActiveRecord: 3.6ms)


Started GET "/users/2" for 127.0.0.1 at 2019-11-05 22:51:57 +0900
Processing by UsersController#show as HTML
  Parameters: {"id"=>"2"}
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  Rendering users/show.html.erb within layouts/application
 

フォロー解除
同じく、Rendering users/show.html.erb

Started DELETE "/relationships/88" for 127.0.0.1 at 2019-11-05 22:52:02 +0900
Processing by RelationshipsController#destroy as HTML
  Parameters: {"utf8"=>"", "authenticity_token"=>"n5ZZ1I9/igmIjRJ8losPb4MGgcBztJ5/IN6/2VFF1jfpq2Xr/h5uS39iTzz8o2kRlIHYlJvfQKlUDYpSifoOTw==", "commit"=>"Unfollow", "id"=>"88"}
  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  Relationship Load (0.4ms)  SELECT  "relationships".* FROM "relationships" WHERE "relationships"."id" = ? LIMIT ?  [["id", 88], ["LIMIT", 1]]
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  Relationship Load (0.4ms)  SELECT  "relationships".* FROM "relationships" WHERE "relationships"."follower_id" = ? AND "relationships"."followed_id" = ? LIMIT ?  [["follower_id", 1], ["followed_id", 2], ["LIMIT", 1]]
   (0.1ms)  begin transaction
  SQL (1.1ms)  DELETE FROM "relationships" WHERE "relationships"."id" = ?  [["id", 88]]
   (1.6ms)  commit transaction
Redirected to http://localhost:3000/users/2
Completed 302 Found in 16ms (ActiveRecord: 4.2ms)


Started GET "/users/2" for 127.0.0.1 at 2019-11-05 22:52:02 +0900
Processing by UsersController#show as HTML
  Parameters: {"id"=>"2"}
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  Rendering users/show.html.erb within layouts/application


14.2.5 [ Follow ] ボタン( Ajax編 )

演習問題1

ブラウザから /users/2 にアクセスし、うまく動いているかどうか確認してみましょう。

解答

ブラウザにて動作確認

演習問題2

先ほどの演習で確認が終わったら、Railsサーバーのログを閲覧し、フォロー/フォロー解除を実行した直後のテンプレートがどうなっているか確認してみましょう。

解答

Started POST "/relationships" for 127.0.0.1 at 2019-11-06 22:37:57 +0900
Processing by RelationshipsController#create as JS
  Parameters: {"utf8"=>"", "authenticity_token"=>"quG2QBvRnG9iE+GV/wn9f9GPcma2L5nT+X07bSNILbLRBfp0B4ladxQgcsHC2CEud7lw73jdpkpv6aD7Nty/Og==", "followed_id"=>"2", "commit"=>"Follow"}
  (省略)
   (0.2ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = ?  [["followed_id", 2]]
  Rendered relationships/create.js.erb (6.2ms)
Completed 200 OK in 25ms (Views: 7.6ms | ActiveRecord: 5.6ms)


Started DELETE "/relationships/90" for 127.0.0.1 at 2019-11-06 22:38:00 +0900
Processing by RelationshipsController#destroy as JS
  Parameters: {"utf8"=>"", "authenticity_token"=>"jrAkVxicFuNQQ96quS/UlJwg8zTi2mXGYYFtbJp/uHkgeNtoqU7k1jr0XF934QVDIulqxky9kEISQ8d4XCAXfQ==", "commit"=>"Unfollow", "id"=>"90"}
  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1
(省略)
   (0.1ms)  begin transaction
  SQL (1.0ms)  DELETE FROM "relationships" WHERE "relationships"."id" = ?  [["id", 90]]
   (1.5ms)  commit transaction
  Rendering relationships/destroy.js.erb
  Rendered users/_follow.html.erb (3.3ms)
   (0.4ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = ?  [["followed_id", 2]]
  Rendered relationships/destroy.js.erb (10.7ms)
Completed 200 OK in 29ms (Views: 15.7ms | ActiveRecord: 4.3ms)

jsファイルが表示されている


14.2.6 フォローをテストする

演習問題1

リスト 14.36のrespond_toブロック内の各行を順にコメントアウトしていき、テストが正しくエラーを検知できるかどうか確認してみましょう。実際、どのテストケースが落ちたでしょうか?

解答

まず、現時点でテストがGreenになることを確認する

1行目をコメントアウトする

[app/controllers/relationships_controller.rb]

class RelationshipsController < ApplicationController
  before_action :logged_in_user

  def create
    @user = User.find(params[:followed_id])
    current_user.follow(@user)
    respond_to do |format|
      # format.html { redirect_to @user }
      format.js
    end
  end
 .
 .
 .
end

テスト -> Red

ERROR["test_should_follow_a_user_the_standard_way", FollowingTest, 1.0399349999934202]
 test_should_follow_a_user_the_standard_way#FollowingTest (1.04s)
ActionController::UnknownFormat:         ActionController::UnknownFormat: ActionController::UnknownFormat
            app/controllers/relationships_controller.rb:7:in `create'
            test/integration/following_test.rb:31:in `block (2 levels) in <class:FollowingTest>'
            test/integration/following_test.rb:30:in `block in <class:FollowingTest>'

  73/73: [===========================================================] 100% Time: 00:00:02, Time: 00:00:02

2行目をコメントアウトする

[app/controllers/relationships_controller.rb]

class RelationshipsController < ApplicationController
  before_action :logged_in_user

  def create
    @user = User.find(params[:followed_id])
    current_user.follow(@user)
    respond_to do |format|
      format.html { redirect_to @user }
      # format.js
    end
  end

 .
 .
 .
end

テスト -> Green

1行目の format.html { redirect_to @user }を消すとテストが落ちる

演習問題2

リスト 14.40のxhr: trueがある行のうち、片方のみを削除するとどういった結果になるでしょうか? このとき発生する問題の原因と、なぜ先ほどの演習で確認したテストがこの問題を検知できたのか考えてみてください。

解答

[test/integration/following_test.rb]

  test "should follow a user with Ajax" do
    assert_difference '@user.following.count', 1 do
      # post relationships_path, xhr: true, params: { followed_id: @other.id}
    end
  end

エラー内容

FAIL["test_should_follow_a_user_with_Ajax", FollowingTest, 1.2087310000060825]
 test_should_follow_a_user_with_Ajax#FollowingTest (1.21s)
        "@user.following.count" didn't change by 1.
        Expected: 2
          Actual: 1
        test/integration/following_test.rb:36:in `block in <class:FollowingTest>'

postがないのでもちろんテストはエラー。

なぜ先ほどの演習で確認したテストがこの問題を検知できたのか

3日間調べたが理解できなかったので飛ばします。


14.3.1 動機と計画

演習問題1

マイクロポストのidが正しく並んでいると仮定して (すなわち若いidの投稿ほど古くなる前提で)、図 14.22のデータセットでuser.feed.map(&:id)を実行すると、どのような結果が表示されるでしょうか? 考えてみてください。ヒント: 13.1.4で実装したdefault_scopeを思い出してください。

解答

default_scope -> { order(created_at: :desc) } と設定しているので、マイクロポストの投稿時間を基準に降順で表示される。


14.3.2 フィードを初めて実装する

演習問題1

リスト 14.44において、現在のユーザー自身の投稿を含めないようにするにはどうすれば良いでしょうか? また、そのような変更を加えると、リスト 14.42のどのテストが失敗するでしょうか?

解答

("user_id = ?", id)で自分の投稿を表示しているので削除する

  # ユーザーのステータスフェードを返す 
  def feed
    # Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
    Micropost.where("user_id IN (?)", following_ids)
  end

テストがRedになる
paginationが見つかりませんも検出される(paginationはちゃんと表示されているのに)

% rails test
Running via Spring preloader in process 8714
Started with run options --seed 3649

 FAIL["test_micropost_interface", MicropostsInterfaceTest, 1.0278030000044964]
 test_micropost_interface#MicropostsInterfaceTest (1.03s)
        Expected at least 1 element matching "div.pagination", found 0..
        Expected 0 to be >= 1.
        test/integration/microposts_interface_test.rb:12:in `block in <class:MicropostsInterfaceTest>'

 FAIL["test_feed_should_have_the_right_posts", UserTest, 2.1715810000314377]
 test_feed_should_have_the_right_posts#UserTest (2.17s)
        Expected false to be truthy.
        test/models/user_test.rb:110:in `block (2 levels) in <class:UserTest>'
        test/models/user_test.rb:109:in `block in <class:UserTest>'

  74/74: [===========================================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.50530s
74 tests, 339 assertions, 2 failures, 0 errors, 0 skips


演習問題2

リスト 14.44において、フォローしているユーザーの投稿を含めないようにするにはどうすれば良いでしょうか? また、そのような変更を加えると、リスト 14.42のどのテストが失敗するでしょうか?

解答

演習1の逆バージョン

  # ユーザーのステータスフェードを返す 
  def feed
    Micropost.where("user_id = ?", id)
    # Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
    # Micropost.where("user_id IN (?)", following_ids)
  end

テストがRedになる paginationは自身の投稿に紐づいていることになっているので今回はエラーが出ない

% rails test
Running via Spring preloader in process 8827
Started with run options --seed 5233

 FAIL["test_feed_should_have_the_right_posts", UserTest, 1.2993419999838807]
 test_feed_should_have_the_right_posts#UserTest (1.30s)
        Expected false to be truthy.
        test/models/user_test.rb:106:in `block (2 levels) in <class:UserTest>'
        test/models/user_test.rb:105:in `block in <class:UserTest>'

  74/74: [===========================================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.69392s
74 tests, 347 assertions, 1 failures, 0 errors, 0 skips


演習問題3

リスト 14.44において、フォローしていないユーザーの投稿を含めるためにはどうすれば良いでしょうか? また、そのような変更を加えると、リスト 14.42のどのテストが失敗するでしょうか? ヒント: 自分自身とフォローしているユーザー、そしてそれ以外という集合は、いったいどういった集合を表すのか考えてみてください。

解答

全ての投稿を表示すればいいので

  # ユーザーのステータスフェードを返す 
  def feed
    Micropost.all
    # Micropost.where("user_id = ?", id)
    # Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
    # Micropost.where("user_id IN (?)", following_ids)
  end

フォローしていないユーザーの投稿は表示しない。という部分でエラーが出る

% rails test
Running via Spring preloader in process 8864
Started with run options --seed 36471

 FAIL["test_feed_should_have_the_right_posts", UserTest, 1.5234350000391714]
 test_feed_should_have_the_right_posts#UserTest (1.52s)
        Expected true to be nil or false
        test/models/user_test.rb:114:in `block (2 levels) in <class:UserTest>'
        test/models/user_test.rb:113:in `block in <class:UserTest>'

  74/74: [===========================================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.82366s
74 tests, 383 assertions, 1 failures, 0 errors, 0 skips


14.3.3 サブセレクト

演習問題1

Homeページで表示される1ページ目のフィードに対して、統合テストを書いてみましょう。リスト 14.49はそのテンプレートです。

解答

  test "feed on Home page" do
    get root_path
    @user.feed.paginate(page: 1).each do |micropost|
      assert_match CGI.escapeHTML(micropost.content), response.body
    end
  end


演習問題2

リスト 14.49のコードでは、期待されるHTMLをCGI.escapeHTMLメソッドでエスケープしています (このメソッドは11.2.3で扱ったCGI.escapeと同じ用途です)。このコードでは、なぜHTMLをエスケープさせる必要があったのでしょうか? 考えてみてください。ヒント: 試しにエスケープ処理を外して、得られるHTMLの内容を注意深く調べてください。マイクロポストの内容が何かおかしいはずです。また、ターミナルの検索機能 (Cmd-FもしくはCtrl-F) を使って「sorry」を探すと原因の究明に役立つはずです。

解答

おわり