Doiya’s blog

私が所属しているプログラミングスクールの進捗を書くブログ

基礎編 課題12 その1

まだ解決していないので、とりあえず現状を記載します。

まずcommentのモデルとコントローラを作成したよ。 comment.rb(アソシエーションでuserとboardを関連づけてます。)

class Comment < ApplicationRecord
  belongs_to :user
  belongs_to :board
  validates :body, presence: true, length: { maximum: 65_535 }
end

board.rb

has_many :comments, dependent: :destroy

user.rb

has_many :boards, dependent: :destroy
has_many :comment, dependent: :destroy

マイグレ

class CreateComments < ActiveRecord::Migration[5.2]
  def change
    create_table :comments do |t|
      t.text :body
      t.references :user, foreign_key: true
      t.references :board, foreign_key: true

      t.timestamps  null:false
    end
  end
end

comment_controller.rb(boardのコントローラーと資料をもとに作成した。)

class CommentsController < ApplicationController
  def create
     comment = current_user.comments.build(comment_params)
     if comment.save
       redirect_to board_path(comment.board), success: t('defaults.message.created', item: Comment.model_name.human)
     else
       redirect_to board_path(comment.board), danger: t('defaults.message.not_created', item: Comment.model_name.human)
     end
  end

   private

   def comment_params
     params.require(:comment).permit(:body).merge(board_id: params[:board_id])
   end
end

次にboard.controller.rbのshowの部分を追記した

def show
    @board = Board.find(params[:id])
    @comment = current_user.comment.new
    @comments = @board.comments.includes(:user).order(created_at: :desc)
  end

ルーティングのネストする (入れ子にする)

resources :boards, only: %i[index new create show] do
    resources :comments, only: %i[create], shallow: true
 end

浅いネストはshallowオプションを使う。

bin/rails routesで現状以下の感じになってる。(出来てないかも)

board_comments POST   /boards/:board_id/comments(.:format)                                                     comments#create
                   boards GET    /boards(.:format)                                                                        boards#index
                          POST   /boards(.:format)                                                                        boards#create
                new_board GET    /boards/new(.:format)                                                                    boards#new
                    board GET    /boards/:id(.:format)                                                                    boards#show

あと、rspecで訳のわからないエラーが出ている。

Since there is no EDITOR or BETTER_ERRORS_EDITOR environment variable, using Textmate by default.

訳:editorまたはbetter_errors_editor環境変数がないため、デフォルトでtextmateを使用します。

viewファイルは今、考えている最中なので省きます。

また進捗があれば記載します。

参照: パーシャルを使って掲示板一覧を表示する - Ruby on Rails Learning Diary

コメント機能の実装 - Qiita

【Rails】 paramsって一体何?使い方を徹底解説! | Pikawaka - ピカ1わかりやすいプログラミング用語サイト

Rails のルーティング - Railsガイド

基礎編 課題11 後編

いつものごとくエラー解決からいきましょうか。

The asset "default.png" is not present in the asset pipeline.

アセットパイプラインってやつみたい。何をミスしてたかというと app/uploders/board_image_uploders.rb

def default_url
    'board_default.png '# app/assets/images/default.png を返す
 end

ここでdefault.pngと記載したから、エラーが出た。

次に中々画面が出なかったのよ。 しばらく考えて、結論が出た。 board.rb

mount_uploader :board_image, ImageUploader

これってboardで画像を出してたよなあと、なので BoardImageUploderしないといけなくないかって。 それで

image_uploders.rbをboard_image_uploders.rbに修正した。
BoardImageUploderに修正した。
マイグレも Addを二つで書いてたから、また作り直した

そしたら、画像は出せたよ。

じゃあ、本題。 デフォルト画像の設定がされていること

一般的に画像が投稿されていない場合でも、デフォルト画像が表示されるため。 ・uploaders/board_image_uploder.rb

def default_url
  'sample.jpg'
end

上記に伴ってView側で画像設定有無の判定や、未設定時に表示させる画像の指定を行っていないこと。 default_urlを設定しているため、elseの処理を記載する必要はありません。

# GOOD
<%= image_tag @board.board_image.url, class: 'card-img-top img-fluid', size: '300x200' %>

# BAD
<% if @board.board_image.present? %>
  <%= image_tag @board.board_image.url, class: 'card-img-top img-fluid', size: '300x200' %>
<% else %>
  <%= image_tag 'sample.jpg', class: 'card-img-top img-fluid', size: '300x200' %>
<% end %>

次に下記の文はアップロードに失敗した際もファイルが消えないようにするために必要である。

<%= f.hidden_field :board_image_cache %>

gitignoreを追記する。(ローカル環境でアップロードした画像ファイルはアップロードしないようにするため)

すでにファイルをコミットしてからgitignoreを追記してもファイルが管理対象に含まれているため、以下のコマンドでコマンドでgitの管理対象外に設定すること

git rm --cached [ファイル名]  

また不要なファイルをpushしてしまった場合は、以下のコマンドでリモートリポジトリから削除する

git rm [ファイル名] 

このような対応が起きないように、コミット対象のファイルはpush前に都度確認するようにする。

プロの進め方

掲示板作成のテストに、画像ファイルの指定を追加する
アップロード用のライブラリをインストールする
アップローダーを作成する
Boardテーブルに画像のカラムを追加する
ControllerとViewに、画像ファイルのフィールドを追加する

参照:

https://www.notion.so/11-8bbcfad1a16c408d8113d99645cb83ee

https://matazoukun.hatenablog.com/entry/2020/10/22/145809

基礎編 課題11 前編

今日は今までの流れを記載するので、正解かどうかわからんよ。 いくよー。

1:以下の二つをインストールしたで。

gem 'carrierwave'
gem 'mini_magick'

2:boardのboard_imageというカラムを新しく追加したで。

g migration Add AddBoardImageToBoards

マイグレの時、名前をつけんの気をつけた方がいいらしい。名前が衝突する可能性があるから。無難に「Add〇〇」にした方がいいと思う。 bin/rails db:migrateはお忘れなく。そんで、出来たファイルに以下の感じでやる。

class Add < ActiveRecord::Migration[5.2]
  def change
    add_column :boards, :board_image, :string
  end
end

3 このカラムに対して画像をアップロードして、それをアップローダーが処理す ることになるため、 カラムとアップローダーの関連付けを行う。

class Board < ApplicationRecord
  mount_uploader :board_image, ImageUploader
end

4  まずアップローダーを作成

bin/rails g uploader image

続いてアップローダーにminimagick経由で加工するため追記。 image_uploader.rb

class ImageUploader < CarrierWave::Uploader::Base 
  include CarrierWave::MiniMagick
  process resize_to_fit: [300, 200]
end

5 画像投稿のviewを編集して、画像用のフォームを追加 。

view/boards/new.html.erb(これは正しいかどうかわからない)

<%= f.label :board_image, t('.thumbnail') %>
<%= f.file_field :board_image, class: 'form-control' %>

6 画像の拡張子を制限

image_uploader.rb

def extension_whitelist
    %w(jpg jpeg gif png)
end

今回はここまでです。また次回、進捗があればブログ書きます。

参照:

2020/12/31 carrier_wave - チワワかわいいブログ

railsで画像アップロードの方法 - Qiita

基礎編 課題10

今回は以下の記事を参考にした。 偶然、前の課題で見かけた。

study-diary.hatenadiary.jp

formのテンプレートからエラーメッセージ表示のパーシャルを呼び出すようにする。

・app/views/boards/_form.html.erb

<%= form_with model: board, local: true do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  <div class="form-group">
    <%= f.label :title %>

・shared/_error_messages.html.erb

<% if object.errors.any? %>
  <div class="alert alert-danger">
    <ul class="mb-0">
      <% object.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
    </ul>
  </div>
<% end %>

f.objectって?? f.objectとするとそのフォーム(ここではboard)を作るときに参照データを引っ張ってくれる

object: f.object 

左のobject(名前は何でもいい。hogeでも可)は、パーシャル内で使う変数"object"を定義している

つまり、パーシャルで使う変数"object"は、boardを作るときに参照したデータが入る

object: @userやboardでも動くけど、他のページに同じコードを入れたとき、その変数を使えるかどうかは異なる。

f.objectならどのページでも使える。

参照:

https://railsguides.jp/active_record_validations.html#%E3%83%90%E3%83%AA%E3%83%87%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%A8%E3%83%A9%E3%83%BC%E3%82%92%E3%83%93%E3%83%A5%E3%83%BC%E3%81%A7%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B

現場Rails 写経で得た事(4章) 後編

とりあえず、自分が特に学んだものを列挙していきます。

1 モデルの仕組み Railsにおけるモデルの検証は基本的に「モデルオブジェクト(レコード)をデータベースに登録・更新する前に検証を行い、エラーがあれば登録・更新をしないで差し戻す」と言う仕組みになっています。 saveメソッド:データベースの登録更新を行う前に自動で検証する。エラーがあればfalseを返す。詳細はerrorsでわかる。この時、検証失敗以外の予期せぬエラーがあれば、例外を発生する。 save!メソッド:検証エラー時にfalseを返すのではなく例外を発生させるメソッドです。 valid? :検証処理単独で呼ぶ。 00.errors.full_messages: 完成された検証エラーメッセージの形

2 必要なデータが入っているか?

validates :○○, presence: true

3 以下の文章の流れ

def create
   @task = current_user.tasks.new(task_params)  ③

   if @task.save ①
     redirect_to @task, notice: "タスク「#{@task.name}」を登録しました。"
   else
     render :new ②
   end
  end

一 登録のために用いるメソッドの変更です。今回、name属性が入っていなくてはならないという検証を追加したので、ユーザーの入力次第では、検証エラーによって登録が失敗するようになりました。そこで、save!ではなくsaveを使うようにしてそも戻り値によって制御を変える。 二 検証エラーの時の処理の追加です。検証エラーがfalseだった時はrender:newによって、登録用のフォーム画面を再び表示して、ユーザーに再入力 を促しています。 三 Taskオブジェクトを@taskというインスタンス変数に代入するようにしていることです。これは、検証エラーがあってもう一度新規登録画面を表示する際に、ビューに検証を行った現物のTaskオブジェクトを渡す必要があるから。 @taskをビューに伝えることによって、次の2点の効果が生まれる。 三の一 Taskオブジェクトには直前にユーザーがフォーム送信したデータが入っているため、前回操作したままの値をフォーム内に引き継いで表示できます。もしこれを行わないと、検証エラーが発生するたびに全てのデータを新たに入力しなおす必要がある。ユーザーがイライラするらしい。 三の二 Taskオブジェクトのかかえる検証エラーの内容をユーザーにして表示する。 三の三 以下の文でエラーメッセージが出る。

- if user.errors.present?
 ul#error_explanation
  -user.errors.full_messages.each do |message|
   li = message

4 ユーザーのパスワードをdigestを生成・保存。 has_secure_password

password: ユーザーが入力した生のパスワードを一時的に格納するための属性です。 password_confirmation 確認パスワードを一時的に保存する。

5 ログイン画面を表示するためのアクションに対してもlogin_requiredフィルタが実行されて、どのURLをリクエストしても、無限にredirectしないための対策で以下の一文を加える。

sessions_controllers.rb

skip_before_action:login_required

6 ログインしているユーザーに紐づくTaskだけを表示する。

@tasks = current_user.tasksイコール@tasks = Task.where(user_id: current_user.id)

7 最初の管理ユーザーを作る時 bin/rails c で以下の一文やるか、seed(初期データを投入するやり方)を利用する。

User.create!(name: , email: ,password: ,password_confirmation: ,admin: true)

8 現場rails p176 p177は復習しましょう!

9 URLをリンクとして表示する方法 ・gem 'rails_autolink'をインストールする。 ・tasks/show.html.slimを以下のようにする。

td= auto_link(simple_format(h(@task.description), {}, sanitize: false, wrpper_tag: "div"))

現場rails 写経で得た事(4章) 前編

まずエラーを見ようね。

Migrations are pending. To resolve this issue, run:
        bin/rails db:migrate RAILS_ENV=development

これはマイグレーションのリセットとかもあり得るのかもしれないけど 意外とスペルミスもあり得るかのでその点、確認して方がいいかも。 今回はマイグレーションファイルにスペルミスがあったと思う。

もう一つ

新規登録するとuserカラムないのに「Userを入力してください」

上記のエラーでずっと悩んでいた。 結論を言うと、4−5−11−3の部分をやっていなかったから エラーが出た。要は僕の凡ミスです。 けど、意味はあった。ここをデバックして見つけたから。 見つけた順序としては

1 binding.irbを以下のところでやる。

def create
   @task = current_user.tasks.new(task_params) 
#ここをTask.new(task_params)にしてた。
  binding.irb
   if @task.save
     redirect_to @task, notice: "タスク「#{@task.name}」を登録しました。"
   else
     render :new
   end
  end

2 @task.valid?でfalseだと判明する。

3 @task.errors?でuser_idもnilだと気づく。(userカラムないのに)

4 調べると、Task.new(task_params)はuser_idも代入する必要がことを知る。

5 current_user.tasks.new(task_params) に修正する。

これで解決した。

具体的には、まだ言えないところもあるけど デバックを自力でやったのは初めてかもしれんので 褒める意味を込めて、ブログに書きますね。

あと、今回の写経を通じて 大きく成長したなと感じるのは 「エラーに対して免疫がついたこと」 以前までは、動揺したりイライラしたりすることも多かったけど 今回はなんというか平常心でやれたような気がする。 そこは成長したなと個人的に思うかな。

今回はここで終わります。 また4章を見直して それから記事を書きます。

現場Rails 写経して得たこと(3章)

全部は書けないので、ほんの一部分だけ書きますね。

1こんなエラーが出たよ。

I18n::InvalidLocaleData at /tasks/new
can not load translations from /Users/??/workspace/??/kiso/taskleaf/config/locales/ja.yml: #<Psych::SyntaxError: (<unknown>): could not find expected ':' while scanning a simple key at line 10 column 5>

こういうのが出たら、ymlの部分をみようね。そこが原因でエラーになっている可能性が高い。 余談やけど、ymlを「ヤムル」って読む人もいるみたい。

マイグレーション:データベースにテーブルを追加する で起用するには「rails db:migrate」をやる。

3ルーティング リクエストを処理すべきコントローラ とアクションを特定する。

4 ja.yml  

モデルの名前はmodelsの下 
属性の名前はattributesの下

5 PostとGetの違いは以下の記事がわかりやすかったかも。

blog.senseshare.jp

6 form_with モデルオブジェクトを使ってHTMLのform要素を作成するための メソッド。モデルとフォームは、form_withにmodel:@〇〇という引数を渡すことで対応可。ちなみにパーシャルのところ(現場rails p119) new.html.slim

= render partial: 'form', locals: { task: @task }   ...①

_form.html.slim

= form_with model: task, local: true do |f| ...②

①で記述しているlocalsオプションは、パーシャル内のローカル変数を設定します。locals: { task: @task }と記述すると「インスタンス変数@taskを、パーシャル内のローカル変数taskとして渡す」という意味になります。これにより、②のようにローカル変数taskから利用できる。

補足 p106.107の部分は個人的に大事なところだと思うので、また復習した方がいいと思う。(書く量、多くなるので省略します)