LoginSignup
0
0

More than 5 years have passed since last update.

railsチュートリアル 第十四章 

Last updated at Posted at 2019-03-27

はじめに

この記事では、railsチュートリアル第14章の復習として行程をようやくしたものとなっています。

ユーザーをフォローする

第14章では他のユーザーをフォロー (およびフォロー解除) できるソーシャルな仕組みの追加と、フォローしているユーザーの投稿をステータスフィードに表示する機能を追加していく。

Relationshipモデル

ユーザーをフォローする機能を実装する第一歩は、データモデルを構成することとなる。

「1人のユーザーが複数のユーザーをhas_manyとしてフォローし、1人のユーザーに複数のフォロワーがいることをhas_manyで表す」

といった方法でも実装できそうだが、これでは問題が出てきてしまう。

上記のようにテーブル構造すると、非常に無駄が多く更新も大きな手間となってしまう事になる。

この問題の根本は、必要な抽象化を行なっていないことであり、正しいモデルを見つけ出す方法の1つは、Webアプリケーションにおける following の動作をどのように実装するかを観察することである。

ここを観察すると、フォローまたはアンフォローで作成または削除されるのは、つまるところ2人のユーザーの「関係 (リレーションシップ)」であることがわかる。

リレーションシップを経由することによって1人のユーザーは1対多の関係を持つことができ、さらにユーザーは多くのfollowing (またはfollowers) と関係を持つことができるということになる。

また、Facebookのような友好関係 (Friendships) では本質的に左右対称のデータモデルが成り立つが、Twitterのようなフォロー関係では左右非対称の性質がある。

このような左右非対称な関係性を見分けるために、それぞれを能動的関係 (Active Relationship)受動的関係 (Passive Relationship)と呼ぶことにする。

まずは、フォローしているユーザーを生成するために、能動的関係に焦点を当てていく。

フォローしているユーザーはfollowed_idがあれば識別することができるので、active_relationshipsテーブルを作り、そこを経由することによって効率的なモデル構造を作る。

このデータモデルを実装するために、まずは次のようなマイグレーションを生成する。

$ rails generate model Relationship follower_id:integer followed_id:integer

このリレーションシップは今後 follower_idfollowed_id で頻繁に検索することになるから、それぞれのカラムにインデックスを追加しておく。

db/migrate/[timestamp]_create_relationships.rb
class CreateRelationships < ActiveRecord::Migration[5.0]
  def change
    create_table :relationships do |t|
      t.integer :follower_id
      t.integer :followed_id

      t.timestamps
    end
    add_index :relationships, :follower_id
    add_index :relationships, :followed_id
    add_index :relationships, [:follower_id, :followed_id], unique: true
  end
end

↑のマイグレーションファイルの、

add_index :relationships, [:follower_id, :followed_id], unique: true

という複合キーインデックスは、follower_idとfollowed_idの組み合わせが必ずユニークであることを保証する仕組みであり、これにより、あるユーザーが同じユーザーを2回以上フォローすることを防ぐ。

relationshipsテーブル を作成するために、いつものようにデータベースのマイグレーションを行う。

$ rails db:migrate

User/Relationshipの関連付け

フォローしているユーザーとフォロワーを実装する前に、UserとRelationshipの関連付けを行う。

1人のユーザーにはhas_many (1対多) のリレーションシップがあり、このリレーションシップは2人のユーザーの間の関係となるから、フォローしているユーザーとフォロワーの両方に属す。 (belongs_to)

今回のケースではフォローしているユーザーを follower_id という外部キーを使って特定しなくてはならない。

また、followerというクラス名は存在しないので、ここでもRailsに正しいクラス名を伝える必要がある。

能動的関係に対して1対多 (has_many) の関連付けを実装する

app/models/user.rb
class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
  has_many :active_relationships, class_name:  "Relationship",
                                  foreign_key: "follower_id",
                                  dependent:   :destroy
  .
  .
  .
end

上記では、ユーザーを削除したら、ユーザーのリレーションシップも同時に削除される必要あるため、関連付けにdependent: :destroyも追加している。

followerの関連付けについては現段階では使わないが、followerfollowed を対称的に実装しておくことで、構造に対する理解は容易になる。

app/models/relationship.rb
class Relationship < ApplicationRecord
  belongs_to :follower, class_name: "User"
  belongs_to :followed, class_name: "User"
end

上記で定義した関連付けにより下のようなメソッドが使用できるようになった。

メソッド 用途
active_relationship.follower フォロワーを返す
active_relationship.followed フォローしているユーザーを返す
user.active_relationships.create(followed_id: other_user.id) userと紐付けて能動的関係を作成/登録する
user.active_relationships.create!(followed_id: other_user.id) userを紐付けて能動的関係を作成/登録する (失敗時にエラーを出力)
user.active_relationships.build(followed_id: other_user.id) userと紐付けた新しいRelationshipオブジェクトを返す

Relationshipのバリデーション

ここで存在性のバリデーションを与えておく。

app/models/relationship.rb
class Relationship < ApplicationRecord
  belongs_to :follower, class_name: "User"
  belongs_to :followed, class_name: "User"
  validates :follower_id, presence: true
  validates :followed_id, presence: true
end

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

followingとfollowersに関して今回はhas_many throughを使う。

デフォルトのhas_many throughという関連付けでは、Railsはモデル名 (単数形) に対応する外部キーを探す。

has_many :followeds, through: :active_relationships

上のコードの場合、Railsは「followeds」というシンボル名を見て、これを「followed」という単数形に変え、 relationshipsテーブルのfollowed_idを使って対象のユーザーを取得してくる。

ただ、user.followedsという名前は英語として不適切となる。

代わりに、user.followingという名前を使うことにする。
そのためには、Railsのデフォルトを上書きする必要があり、ここでは:sourceパラメーターを使って「following配列の元はfollowed idの集合である」ということを明示的にRailsに伝える必要がある。
Userモデルにfollowingの関連付けを追加する↓

app/models/user.rb
class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
  has_many :active_relationships, class_name:  "Relationship",
                                  foreign_key: "follower_id",
                                  dependent:   :destroy
  #追加
  has_many :following, through: :active_relationships, source: :followed
  .
  .
  .
end

次に、followingで取得した集合をより簡単に取り扱うために、followunfollow といった便利メソッドを追加する。
"following" 関連のメソッド ↓

app/models/user.rb
class User < ApplicationRecord
  .
  .
  .
  def feed
    .
    .
    .
  end

  # ユーザーをフォローする
  def follow(other_user)
    following << other_user
  end

  # ユーザーをフォロー解除する
  def unfollow(other_user)
    active_relationships.find_by(followed_id: other_user.id).destroy
  end

  # 現在のユーザーがフォローしてたらtrueを返す
  def following?(other_user)
    following.include?(other_user)
  end

  private
  .
  .
  .
end

フォロワー

ここでuser.followersメソッドを追加していく。

これは上のuser.followingメソッドと対になり、フォロワーの配列を展開するために必要な情報は、relationshipsテーブルに既にある。

よって、follower_idとfollowed_idを入れ替えるだけで、フォロワーについてもフォローする場合と全く同じ方法が活用できる。
受動的関係を使ってuser.followersを実装する↓

app/models/user.rb
class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
  has_many :active_relationships,  class_name:  "Relationship",
                                   foreign_key: "follower_id",
                                   dependent:   :destroy
  #追加
  has_many :passive_relationships, class_name:  "Relationship",
                                   foreign_key: "followed_id",
                                   dependent:   :destroy
  has_many :following, through: :active_relationships,  source: :followed
  #追加
  has_many :followers, through: :passive_relationships, source: :follower
  .
  .
  .
end

:followers属性 の場合、Railsが「followers」を単数形にして自動的に外部キーfollower_idを探してくれるから :sourceキー を省略してもイイが has_many :following との類似性を強調させるため残しておく。

[Follow] のWebインターフェイス

この節では、フォロー/フォロー解除の基本的なインターフェイスを実装、また、フォローしているユーザーと、フォロワーにそれぞれ表示用のページを作成する。

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

前の章のときと同じように、サンプルデータを自動作成する rails db:seed を使って、データベースにサンプルデータを登録しておく。

先にサンプルデータを自動作成できるようにしておけば、Webページの見た目のデザインから先にとりかかることができ、バックエンド機能の実装を後に回すことができるというメリットがある。

サンプルデータにfollowing/followerの関係性を追加する↓

db/seeds.rb
#省略

# リレーションシップ
users = User.all
user  = users.first
following = users[2..50]
followers = users[3..40]
following.each { |followed| user.follow(followed) }
followers.each { |follower| follower.follow(user) }

ここでは、最初のユーザーにユーザー3からユーザー51までをフォローさせ、それから逆にユーザー4からユーザー41に最初のユーザーをフォローさせる。

データベース上のサンプルデータを作り直すために、いつものコマンドを実行する。

$ rails db:migrate:reset
$ rails db:seed

統計と [Follow] フォーム

これでサンプルユーザーに、フォローしているユーザーとフォロワーができた。

プロフィールページHomeページを更新して、これを反映する。

次にUsersコントローラにfollowingアクションとfollowersアクションを追加する↓

config/routes.rb
Rails.application.routes.draw do
  root   'static_pages#home'
  get    '/help',    to: 'static_pages#help'
  get    '/about',   to: 'static_pages#about'
  get    '/contact', to: 'static_pages#contact'
  get    '/signup',  to: 'users#new'
  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'
  #追加
  resources :users do
    member do
      get :following, :followers
    end
  end

  resources :account_activations, only: [:edit]
  resources :password_resets,     only: [:new, :create, :edit, :update]
  resources :microposts,          only: [:create, :destroy]
end

↑によって生成されるルーティングテーブルを下に示す

HTTPリクエスト URL アクション 名前付きルート
GET /users/1/following following following_user_path(1)
GET /users/1/followers followers followers_user_path(1)

ルーティングを定義したので、統計情報のパーシャルを実装する。

app/views/shared/_stats.html.erb
<% @user ||= current_user %>
<div class="stats">
  <a href="<%= following_user_path(@user) %>">
    <strong id="following" class="stat">
      <%= @user.following.count %>
    </strong>
    following
  </a>
  <a href="<%= followers_user_path(@user) %>">
    <strong id="followers" class="stat">
      <%= @user.followers.count %>
    </strong>
    followers
  </a>
</div>

<% @user ||= current_user %>
このコードは、@userがnilでない場合 (つまりプロフィールページの場合) は何もせず、nilの場合 (つまりHomeページの場合) には@userにcurrent_userを代入するコードとなる。

また、
<strong id="following" class="stat">
...
</strong>

こうしておくと、Ajaxを実装するときに便利となる。

これをHomeページに表示する。

app/views/static_pages/home.html.erb
<% if logged_in? %>
  <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/feed' %>
    </div>
  </div>
<% else %>
  .
  .
  .
<% end %>

また必要に応じてSCSSにスタイルを加える。

次の行程として[Follow] / [Unfollow] ボタン用のパーシャルも作成しておく。

app/views/users/_follow_form.html.erb
<% unless current_user?(@user) %>
  <div id="follow_form">
  <% if current_user.following?(@user) %>
    <%= render 'unfollow' %>
  <% else %>
    <%= render 'follow' %>
  <% end %>
  </div>
<% end %>

このコードは、followunfollowのパーシャルに作業を振っているだけになるからRelationshipsリソース用の新しいルーティングを追加し、フォロー/フォロー解除用のパーシャルを個別に用意する必要がある。

Relationshipリソース用のルーティングを追加する↓

config/routes.rb
#省略

resources :relationships,       only: [:create, :destroy]
end

ユーザーをフォローするフォーム↓

app/views/users/_follow.html.erb
<%= form_for(current_user.active_relationships.build) do |f| %>
  <div><%= hidden_field_tag :followed_id, @user.id %></div>
  <%= f.submit "Follow", class: "btn btn-primary" %>
<% end %>

ユーザーをフォロー解除するフォーム↓

app/views/users/_unfollow.html.erb
<%= form_for(current_user.active_relationships.find_by(followed_id: @user.id),
             html: { method: :delete }) do |f| %>
  <%= f.submit "Unfollow", class: "btn" %>
<% end %>

これでパーシャルとしてフォロー用フォームをプロフィールページに表示できるようになった。

プロフィールページにフォロー用フォームとフォロワーの統計情報を追加する↓

app/views/users/show.html.erb
<% provide(:title, @user.name) %>
<div class="row">
  <aside class="col-md-4">
    <section class="user_info">
      <h1>
        <%= gravatar_for @user %>
        <%= @user.name %>
      </h1>
    </section>
    #追加
    <section class="stats">
      <%= render 'shared/stats' %>
    </section>

  </aside>
  <div class="col-md-8">
    #追加
    <%= render 'follow_form' if logged_in? %>
    <% if @user.microposts.any? %>
      <h3>Microposts (<%= @user.microposts.count %>)</h3>
      <ol class="microposts">
        <%= render @microposts %>
      </ol>
      <%= will_paginate @microposts %>
    <% end %>
  </div>
</div>

この[Follow] / [Unfollow] ボタンの実装には標準的な方法とAjaxを使う方法の2つがある。

その前に、フォローしているユーザーとフォロワーを表示するページをそれぞれ作成してHTMLインターフェイスを完成させる。

[Following] と [Followers] ページ

フォローしているユーザーを表示するページと、フォロワーを表示するページは、いずれもプロフィールページとユーザー一覧ページを合わせたもののようになる。

ここでの最初の作業は、フォローしているユーザーのリンクとフォロワーのリンクを動くようにすることとなる。

Twitterに倣って、どちらのページでもユーザーのログインを要求するようにする。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update, :destroy,
                                        :following, :followers]
  .
  .
  .
  def following
    @title = "Following"
    @user  = User.find(params[:id])
    @users = @user.following.paginate(page: params[:page])
    render 'show_follow'
  end

  def followers
    @title = "Followers"
    @user  = User.find(params[:id])
    @users = @user.followers.paginate(page: params[:page])
    render 'show_follow'
  end

  private
  .
  .
  .
end

Railsは慣習に従って、アクションに対応するビューを暗黙的に呼び出す。
上記でも、renderを明示的に呼び出し、show_followという同じビューを出力している。したがって、作成が必要なビューはこれ1つですむ。

フォローしているユーザーとフォロワーの両方を表示するshow_followビュー↓

app/views/users/show_follow.html.erb
<% provide(:title, @title) %>
<div class="row">
  <aside class="col-md-4">
    <section class="user_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>

[Follow] ボタン (基本編)

フォローとフォロー解除はそれぞれリレーションシップの作成と削除に対応しているため、まずはRelationshipsコントローラが必要となる。

$ rails generate controller Relationships

次に、logged_in_userフィルターRelationshipsコントローラのアクションに対して追加する。

app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
  before_action :logged_in_user

  def create
  end

  def destroy
  end
end

[Follow] / [Unfollow] ボタンを動作させるためには、フォームから送信されたパラメータを使って、followed_id に対応するユーザーを見つけてくる必要がある。

その後、見つけてきたユーザーに対して適切に follow/unfollowメソッド を使う。

Relationshipsコントローラ↓

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)
    redirect_to user
  end

  def destroy
    user = Relationship.find(params[:id]).followed
    current_user.unfollow(user)
    redirect_to user
  end
end

これにより、フォロー/フォロー解除の機能が完成した。

[Follow] ボタン (Ajax編)

上記ではRelationshipsコントローラの createアクションdestroyアクション を単に元のプロフィールにリダイレクトしていた。

ここで、Ajaxを使えば、Webページからサーバーに「非同期」で、ページを移動することなくリクエストを送信することができる。

WebフォームにAjaxを採用するのは今や当たり前になりつつあるので、RailsでもAjaxを簡単に実装できるようになっている。

form_for

というコードを

form_for ..., remote: true

と置き換えるだけで、Railsは自動的にAjaxを使うようになる。
Ajaxを使ったフォローフォーム↓

app/views/users/_follow.html.erb
<%= form_for(current_user.active_relationships.build, remote: true) do |f| %>
  <div><%= hidden_field_tag :followed_id, @user.id %></div>
  <%= f.submit "Follow", class: "btn btn-primary" %>
<% end %>

Ajaxを使ったフォロー解除フォーム↓

app/views/users/_unfollow.html.erb
<%= form_for(current_user.active_relationships.find_by(followed_id: @user.id),
             html: { method: :delete },
             remote: true) do |f| %>
  <%= f.submit "Unfollow", class: "btn" %>
<% end %>

フォームの更新が終わったので、今度はこれに対応するRelationshipsコントローラを改造して、Ajaxリクエストに応答できるようにする。

こういったリクエストの種類によって応答を場合分けするときは、respond_toメソッドというメソッドを使うようにする。

respond_to do |format|
  format.html { redirect_to user }
  format.js
end

上の (ブロック内の) コードのうち、いずれかの1行が実行される。

RelationshipsコントローラでAjaxに対応させるために、respond_toメソッドをcreateアクションとdestroyアクションに追加する。

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

  def destroy
    @user = Relationship.find(params[:id]).followed
    current_user.unfollow(@user)
    #追加修正
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end
end

ビューで変数を使うため、userが@userに変わっている

今度はブラウザ側でJavaScriptが無効になっていた場合 (Ajaxリクエストが送れない場合) でもうまく動くようにする。

config/application.rb
require File.expand_path('../boot', __FILE__)
.
.
.
module SampleApp
  class Application < Rails::Application
    .
    .
    .
    # 認証トークンをremoteフォームに埋め込む
    config.action_view.embed_authenticity_token_in_remote_forms = true
  end
end

.js.erbファイル

JavaScriptが有効になっていても、まだ十分に対応できていない部分がある。

というのも、Ajaxリクエストを受信した場合は、Railsが自動的にアクションと同じ名前を持つJavaScript用の埋め込みRuby (.js.erb) ファイル (create.js.erbやdestroy.js.erbなど) を呼び出指定しまうからである。

なのでファイルを新たに作成する必要がある。

JS-ERbファイルの内部では、DOM (Document Object Model) を使ってページを操作するため、RailsがjQuery JavaScriptヘルパーを自動的に提供している。

JavaScriptと埋め込みRubyを使ってフォローの関係性を作成する↓

app/views/relationships/create.js.erb
$("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>");
$("#followers").html('<%= @user.followers.count %>');

Ruby JavaScript (RJS) を使ってフォローの関係性を削除する↓

app/views/relationships/destroy.js.erb
$("#follow_form").html("<%= escape_javascript(render('users/follow')) %>");
$("#followers").html('<%= @user.followers.count %>');

これらのコードにより、プロフィールページを更新させずにフォローとフォロー解除ができるようになった。

ステータスフィード

現在のユーザーにフォローされているユーザーのマイクロポストの配列を作成し、現在のユーザー自身のマイクロポストと合わせて表示する。

ステータスフィードを実装するには現在のユーザーによってフォローされているユーザーに対応するユーザーidを持つマイクロポストを取り出し、同時に現在のユーザー自身のマイクロポストも一緒に取り出すようにする必要がある。

フィードに必要な3つの条件を下に示す。

・フォローしているユーザーのマイクロポストがフィードに含まれていること。
・自分自身のマイクロポストもフィードに含まれていること。
・フォローしていないユーザーのマイクロポストがフィードに含まれていないこと

最初に、このフィードで必要なクエリについて考えてみる。
ここで必要なのは、micropostsテーブル から、あるユーザー (つまり自分自身) がフォローしているユーザーに対応するidを持つマイクロポストをすべて選択 (select) することである。

このクエリを模式的に書くと次のようになる。

SELECT * FROM microposts
WHERE user_id IN (<list of ids>) OR user_id = <user id>

↑を参考に、今回必要になる選択は、上よりも少し複雑で、例えば次のような形になる。

Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)

このfollowing_idsメソッドは、has_many :followingの関連付けをしたときにActive Recordが自動生成したものである。

これにより、user.followingコレクションに対応するidを得るためには、関連付けの名前の末尾に_idsを付け足すだけで済む。

app/models/user.rb
class User < ApplicationRecord
  .
  .
  .
  # パスワード再設定の期限が切れている場合はtrueを返す
  def password_reset_expired?
    reset_sent_at < 2.hours.ago
  end

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

  # ユーザーをフォローする
  def follow(other_user)
    following << other_user
  end
  .
  .
  .
end

サブセレクト

問題点として上記のフィードの実装は、投稿されたマイクロポストの数が膨大になったときにうまくスケールしない、つまり、フォローしているユーザーが5,000人程度になるとWebサービス全体が遅くなる可能性がある。

上に示したコードの問題点は、following_idsでフォローしているすべてのユーザーをデータベースに問い合わせし、さらに、フォローしているユーザーの完全な配列を作るために再度データベースに問い合わせしている点である。

このような問題は、SQLのサブセレクト(subselect)を使うと解決できる。

まずはコードを若干修正し、フィードをリファクタリングすることから始める。

whereメソッド内の変数に、キーと値のペアを使う↓

app/models/user.rb
class User < ApplicationRecord
  .
  .
  .
  # ユーザーのステータスフィードを返す
  def feed
    Micropost.where("user_id IN (:following_ids) OR user_id = :user_id",
     following_ids: following_ids, user_id: id)
  end
  .
  .
  .
end

前者の疑問符を使った文法も便利だが、同じ変数を複数の場所に挿入したい場合は、後者の置き換え後の文法を使う方がより便利になる。

上記ではfollowing_idsをSQLのサブセレクトとして使う。
つまり、「ユーザー1がフォローしているユーザーすべてを選択する」というSQLを既存のSQLに内包させる形になり、結果としてSQLは次のようになる。

SELECT * FROM microposts
WHERE user_id IN (SELECT followed_id FROM relationships
WHERE follower_id = 1)
OR user_id = 1

これでもっと効率的なフィードを実装する準備がきた。
フィードの最終的な実装↓

app/models/user.rb
class User < ApplicationRecord
  .
  .
  .
  # ユーザーのステータスフィードを返す
  def feed
    following_ids = "SELECT followed_id FROM relationships
                     WHERE follower_id = :user_id"
    Micropost.where("user_id IN (#{following_ids})
                     OR user_id = :user_id", user_id: id)
  end
  .
  .
  .
end

(ここに記述されているコードは生のSQLを表す文字列であり、following_idsという文字列はエスケープされているのではなく、見やすさのために式展開している)

これでステータスフィードの実装は完了。

さいごに

これでrailsチュートリアルを全て完走した。
ここで出てくる知識はサービスを作る上で基本になるところだろうからしっかり復習して自分の知識にしようと思う。

ここにプラスアルファで返信機能などもつけることを演習として推奨されているのでまた挑戦してみようと思う。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0