Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Railsで質問・回答アプリを作ってください(実装編)の課題提出 #1

Merged
merged 65 commits into from Feb 4, 2022

Conversation

Shun712
Copy link
Owner

@Shun712 Shun712 commented Dec 19, 2021

質問回答アプリの実装をしました。
コードレビューお願い致します。

苦労したこと

  1. レイアウトデザインで使用するbootstrapを使った実装に時間がかかってしまった。
  2. slimではインデント数の違いでビューが表示されないこと。
  3. 画像のサイズをvariantメソッドを使って表示させること。
  4. メール通信機能の実装でアドレスをダブルクォーテーションを囲っていなくて、メールが送れないエラーを解決するのに、時間がかかってしまった。

疑問点

  1. app/controllers/questions_controller.rbのindexメソッド内の条件分岐で、真偽値であるtrue・falseをシングルクォーテーションで囲まなければならないのか?
  2. 検索で無記入で検索するとすべてのリソースが対象になるのか?

できなかったこと

  1. ユーザー登録画面でプロフィール画像のデモ画面が表示されないこと。
  2. app/views/questions/index.html.slimで解決・未解決のタブボタンをbootstrapのサンプル通りに実装できなかった。
    Checkbox and radio button groups
  3. app/views/questions/show.html.slimで質問内容のテキストを左寄せにできなかった。
  4. app/views/questions/show.html.slimで、回答内容が空だったときにエラーメッセージが表示されないこと。

工夫したところ

  1. 極力部分テンプレートを使用した。
  2. N+1問題が発生するところで、includesメソッドを使用した。
  3. ユーザーがわかりやすいようなデザインにした。

Comment on lines 48 to 51
def destroy
@question.destroy
redirect_to questions_url, notice: "質問「#{@question.title}」を削除しました。"
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここも同じく、current_userで絞り込みましょう!

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

上記と同じです。

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

上記と同じ間違いです 🙇‍♂️ 🙇‍♂️ 🙇‍♂️
すみません!!!

Comment on lines 16 to 18
def edit
@user = User.find(params[:id])
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

current_userで絞り込んであげたほうがよさそうです!

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

以下に書き換えました。

before_action :set_user, only: %i[show edit update destroy]

private
    def set_user
      @user = current_user
    end

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GOOD!!!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ちなみに、自分で指摘をしておきながら、何か違和感がある感じがしなくもないので、違ったらすみません!

end

def update
@user = User.find(params[:id])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

current_userで絞り込んであげたほうがよさそうです!

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

上記と同じです。

end

def destroy
@user = User.find(params[:id])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

current_userで絞り込んであげたほうがよさそうです!

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

上記と同じです。

class Question < ApplicationRecord
validates :title, presence: true, length: { maximum: 30 }
validates :content, presence: true
before_validation :set_titleless_title

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

おお、before_validation、いいですね!

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

今回は参考書通りですが、他でも意識できるようにしたいです。

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before_validation初めて聞きました。勉強させていただきます。

Comment on lines +11 to +13
def set_titleless_title
self.title = 'タイトルなし' if title.blank?
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こういう親切設計、いいと思います!

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

上記と同じです。

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

あまり意識していなかったので見習います!

@@ -0,0 +1,14 @@
class Question < ApplicationRecord
validates :title, presence: true, length: { maximum: 30 }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここだけじゃないですが、validationをきちんと考える姿勢、いいですね!

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(参考書通りですが)ありがとうございます!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

参考書を参考にするの、大事!!!

table.table.table-hover
thread.thread-default
tr
th= User.human_attribute_name('投稿者')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

単純に'投稿者'としてもよい気がしますが、ymlファイルの方で後ほど定義するのを視野に入れているのであれば、一旦この形でもいいかと思います!

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほど、リリースする段階ではきちんと変数で定義したほうがいいのですね。

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

と言いますか、

Question.human_attribute_name(:title)

こういう書き方するなら、意図がすごくよく分かるんですけれども、

User.human_attribute_name('投稿者')

こうやって書いている場合、普通に'投稿者'と書けるのに、なぜあえてこうしているんだろうと気になるという感じですね。

config/locales/ja.ymlで定義をするか、それか'投稿者’を直書きにするかのどちらかがいいと思います!

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

config/locales/ja.ymlに定義して統一します。

.container
.row
p.text-left
= auto_link(simple_format(h(@question.content), {}, sanitize: false, wrapper_tag: "div"))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

auto_link、初めて知りました!

似たようなgemとして、rinkuというのもあるみたいですね!
https://github.com/vmg/rinku

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ご紹介してくださりありがとうございます。
調べて活用してみたいと思います。

@@ -35,6 +35,9 @@

config.action_mailer.perform_caching = false

config.action_mailer.default_url_options = { host: 'localhost:3000' }
config.action_mailer.delivery_method = :letter_opener_web

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

おお、lettter_opener_web、いいですね!

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

簡単にメールを確認できますよね。

get 'search'
end
end
post '/questions/:id/answers', to: 'answers#create'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

勘違いかもしれないですが、ここと重複してそうです!

  resources :questions do
    resources :answers, only: %i[create]

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

申し訳ございません。消し忘れです。
気をつけます。

db/schema.rb Outdated
Comment on lines 36 to 42
create_table "answers", force: :cascade do |t|
t.integer "question_id", null: false
t.integer "user_id", null: false
t.text "content", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

外部キーにindexを貼ってあげると、検索が早くなるかと思います!
(データ量が少ないと、体感変わらないですが)

この記事がベストか微妙ですが、少し調べてみてください!
DBのインデックスと複合インデックス - Qiita

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

存在は知っていましてが、理解が足りなかったので実装しませんでした。
ご紹介してくださりありがとうございます。

db/schema.rb Outdated

create_table "questions", force: :cascade do |t|
t.string "title", limit: 30, null: false
t.text "content"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

textにlimitをかけてあげてもいいかもしれないです!
(titleよりも、contentの方がより多くの文字数を入れ込まれる可能性があり、DBを圧迫されるかもしれないので)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DBについても配慮が必要ですね。
気をつけます。
db:rollbackして追記した場合、schema.rbに
t.text "content", limit: 500, null: false
が反映されましたが、
rails g migration ChangeQuestionsContentLimit500
をした場合、schema.rbに
ActiveRecord::Schema.define(version: 2021_12_24_215427) do
にしか違いが反映されませんでした。
どちらも違いないと思っていたのですが、前者のほうが適切ですか?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

db:rollbackして追記した場合、schema.rbに
t.text "content", limit: 500, null: false
が反映されましたが、
rails g migration ChangeQuestionsContentLimit500
をした場合、schema.rbに
ActiveRecord::Schema.define(version: 2021_12_24_215427) do
にしか違いが反映されませんでした。

migrationファイルの中身を見ないと、何とも言えないですね!
どういった中身でした?

Copy link
Owner Author

@Shun712 Shun712 Dec 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

db:rollbackして追記した場合
20211209100927_create_questions.rbに下記を追記

class CreateQuestions < ActiveRecord::Migration[5.2]
  def change
    create_table :questions do |t|
      t.string :title, limit: 30, null: false
      t.text :content, limit: 500, null: false
      t.boolean :solved_check, default: false, null: false

      t.timestamps
    end
  end
end
create_table "questions", force: :cascade do |t|
    t.string "title", limit: 30, null: false
    t.text "content", limit: 500, null: false
    t.boolean "solved_check", default: false, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.integer "user_id", null: false
    t.boolean "admin", default: false, null: false
    t.index ["user_id"], name: "index_questions_on_user_id"
  end

rails g migration ChangeQuestionsContentLimit500をした場合
マイグレーションファイルに下記を作成

class ChangeQuestionsContentLimit500 < ActiveRecord::Migration[5.2]
  def change
    def up
      change_column :questions, :content, :text, limit: 500
    end

    def down
      change_column :questions, :content, :text
    end
  end
end

schema.rb

ActiveRecord::Schema.define(version: 2021_12_29_205612) do
.
.
  create_table "questions", force: :cascade do |t|
    t.string "title", limit: 30, null: false
    t.text "content"
    t.boolean "solved_check", default: false, null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.integer "user_id", null: false
    t.boolean "admin", default: false, null: false
    t.index ["user_id"], name: "index_questions_on_user_id"
.
.

Comment on lines +1 to +8
User.create!(
name: '管理者',
nickname: 'マネージャー',
email: 'manager@example.com',
password: 'password',
password_confirmation: 'password',
admin: true
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こんな感じでシードデータまで用意したのはポイント高いですね!
Shunさん以外、たぶんほぼいないんじゃないかと思います!

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ユーザーや質問があった場合のレイアウトを見てみたかったので使いました。


def destroy
@question.destroy
redirect_to questions_url, notice: "質問「#{@question.title}」を削除しました。"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

勘違いだったらすいません、、、削除が終わったら/admin/questionsではなく/questionsにリダイレクトされるということでよろしいでしょうか?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ご指摘の通り私のミスです。
正しくはadmin_questions_pathに書き直しました。
ありがとうございます。

@@ -0,0 +1,27 @@
class Admin::QuestionsController < ApplicationController
before_action :required_admin
before_action :set_q, only: %i[index search]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

変数宣言の共通化できていてすごいです!自分はできていなかったので見習います、、、

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

おお、レビューありがとうございます。
DRY大切ですよね!実装し忘れてしまうことも多いですが、、


def required_admin
redirect_to root_path unless current_user.admin?
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

app/controllers/admin/questions_controller.rbにもrequired_adminメソッドが定義されていたと思います。
2箇所に全く同じメソッドが定義されているので共通化した方が良いと思うのですが、ベースコントローラーに定義すると1箇所だけに定義するだけで済むと思います!

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ご指摘ありがとうございます。おっしゃる通りです。
共通化いたします。


def create
@question = Question.find(params[:question_id])
@answer = @question.answers.build(user_id: current_user.id, content: params[:answer][:content])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

せっかく書いたanswer_paramsメソッドがどこにも使われていないように見受けられます!
もし使うのであれば(というかストロングパラメータなので使った方が良いと思います)、こういう書き方でもいいかもしれません!
mergeを使う書き方は賛否両論あるかもしれませんがご了承ください、、、

@answer = @question.answers.build(answer_params.merge(user_id: current_user.id))

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ストロングパラメータは確かに必要ですね!
実装いたします。

end

def update
@question.update!(question_params)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update!だと何かエラーが起きたときに例外が発生するので、バリデーションのエラーなどが返ってこないと思います!(自分も指摘されたことがあります、、、)

↓参考
Railsアプリケーションにおけるエラー処理(例外設計)の考え方 - Qiita

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

例外処理という言葉きくとドキッとしますが、目を背けず勉強したいと思います。

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

例外処理という言葉きくとドキッとします

エラー画面が嫌いってことなんですかね?笑

@user_from = params[:user_from]
@user_to = params[:user_to]
@question = params[:question]
mail(to: "#{@user_to}.email", subject: "#{@user_from}さんが質問を投稿しました。")
Copy link

@keit1722 keit1722 Dec 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こちらの↓「クォーテーションを付けないとメール送信ができなかった件」なのですが、
https://github.com/keit1722/q_and_a_app/pull/1/files#r775154806

もう一度メール部分のコードを読ませていただきました!
@user_toには送信先のユーザー全員情報が配列で入っていように思えます。
その場合、送信先(to:)に配列が指定されているので上手く送信できなかったのではないかと推測しています!
ちなみに、宛先に指定したユーザー全員に送信できていますか?

宛先ユーザーの配列をループして一人ひとりのemail宛に送信すれば上手くいくのではないかと思いました!(クォーテーションがなくても送信できるかも?)

間違っていたらすいません、、、

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keitさんの仮説が正しそう。

2パターンやりようがありますね。

一つ目
ユーザーの数分send_when_questionを呼ぶ。

receivers.each do |user|
  SampleMailer.with(user_from: current_user, 
                        user_to: user,
                        question: @question).send_when_question.deliver_now
end

二つ目
toにはメールアドレスの配列を渡すこともできるのでそれを活用する

mail(to: @user_to.map { |user| user.email } ", subject: "#{@user_from}さんが質問を投稿しました。")

ですが二つ目は基本やっちゃダメですね。
理由としてはメールの宛先が他の人にもバレるので。
例えばAさんとBさんにメールを送ったとして、Aさんの受信メールを見るとTo(宛先)にBさんも含まれていることがわかってしまう。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ご指摘ありがとうございます。
eachメソッドで一人ひとりに送るという方法も考えたのですが、別で実装したいと思ったのでこの形になりました。確認してみましたが、一人にしかメール通知できていなかったので実装を変えたほうがいいかもしれません。

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

一人にしかメール通知できていなかった

これも宛先のアドレスが不正なので送られないはず(letter_openerでは宛先がなんであれ送られてるように見えるけど)

Copy link

@satoshitodaka satoshitodaka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

コメントしましたのでよろしくお願いします!

Comment on lines +11 to +13
def set_titleless_title
self.title = 'タイトルなし' if title.blank?
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

あまり意識していなかったので見習います!

t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id", null: false
t.boolean "admin", default: false, null: false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

理解できてなくてすみません。
Questionsにはどうしてadminカラムを設けるんですか?
ざっとみた感じ、使っていないような気がしました。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

すみません、自分でもなぜ設定したのかわかりませんでした。
参考書に従って設定したのかもしれませんが削除します。
ご指摘ありがとうございます!

body.text-center
.navbar.navbar-expand-md.navbar-light.bg-light
.container-fluid
.navbar-brand= link_to(image_tag('/logo.png', size:'75x50'), root_url)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

オリジナルのロゴ使うの良いですね!!

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ネットのフリー素材から拝借してきたものです。

t.string "email", null: false
t.string "password_digest", null: false
t.string "nickname"
t.string "picture"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

自信がないのですが。。。(違ったらごめんなさい)
Active Strageを使って画像をUserに紐付ける場合、カラムを設ける必要はありましたっけ?

カラムを設定する、という記事も見かけましたが
実際のところは必要ないんじゃないかと思います。(記事が誤っているか古い?)
Railsガイドでも触れていないような気がします。

https://railsguides.jp/active_storage_overview.html

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ご指摘ありがとうございます!
確かにUsersテーブルにカラムを設ける記載が見当たりませんね!
実際にカラムを削除してアバター画像が登録できるが試しました。

class RemovePictureFromUsers < ActiveRecord::Migration[5.2]
  def up
    remove_column :users, :picture
  end

  def down
    add_column :users, :picture, :string
  end
end

このマイグレーションファイルを実行してカラムを削除し、アバター画像が登録できました。
やはり画像用のカラムは必要ありませんでした。
非常に勉強になりました!!

@Shun712 Shun712 merged commit 9021a01 into main Feb 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants