Ruby on Rails
基礎4

  • 前回までセキュリティ対策として、パスワードの暗号化と、バリデーションの設定を行いました。
  • 次は「パーシャルファイル」というものを学んでいきます。
  • 前回の最後らへんで、newファイルにはバリデーションエラーの表示をしたが、editページは後回しにしましたよね?
    • 「パーシャルファイル」を使うと、サクッと出来ます。
  • 「パーシャル」とは「一部」みたいな意味で、「一部を、色んな場所で再利用して使う」ためのファイルがパーシャルファイルです。
    • 実際にやってみましょう。
  • まず、app/views/books/ に新しいファイルを作ります。※「booksフォルダ」を右クリック→「Add file」です。
    • 名前は「_form.html.erb」にしてください。
    • ※最初の文字は _(アンダーバー)です。一般的なキーボードの場合、Shiftを押しながら「ろ」のキーを押せば出ると思います。

パーシャルファイル

  • パーシャルファイルは必ず最初の文字が「 _(アンダーバー)」から始まるので注意してください。
    • これは名前というより「これはパーシャルファイルぞ」という印(しるし)です。
  • では、次は book の newファイル の「フォームの部分」を、丸ごと「_form」ファイルに移します。
    • つまりコピペしてから、 削除します。以下のようになればOKです。

パーシャルファイル

_form.html.erbファイル

長いので省略してますが、from_withが始まって、終わるまで、

まるっと移します。

new.html.erbファイル

newの方は必要なくなるので削除します。

  • この時点、newページを表示すると、以下のようにフォームがなくなって、ほぼまっさらの状態になりますね。

パーシャルファイル

  • 次に、newファイルに以下のコードを追加します。
  • 「render」は、コントローラーでも使いましたが、ファイルを読み込む関数で、そこに partial(パーシャル)とすることで、パーシャルファイルを読み込んでくれます。 次へ。
  • これで、newページを開いてみると、ちゃんとフォームが表示されています。
  • さらに、このパーシャルファイルを editページからも読み込むことが出来ます!
  • つまり後回しにしていた「バリデーションエラーの表示」を editファイルに追加しなくてもOKになります。
    • このようにコードの一部を別にしておくことで、別のページなどから再利用して使えるようになります。これがパーシャルファイルです。
  • とはいえ、new と edit はまったく同じフォームではありません。
    • 「登録後に開くURL」「HTTPメソッド」「ボタンの文字が"登録"か、"編集"か」の、この3か所は new と edit で、内容が違います。
  • つまり「ファームは使いまわしたいが、一部だけ変更したい」ということですが、これも簡単にできます。
    • _formファイルの「登録後に開くURL」「HTTPメソッド」の部分を、以下のようにします。

パーシャルファイル

  • 「 locals 」に{}(ハッシュ)で指定すると、パーシャルファイルにローカル変数を作り、値を渡すことができます。
  • 上の場合だと「url変数」「method変数」「btn_text変数」が作られ、それぞれに値が渡されます。
  • 後は、パーシャルファイル内で、先ほどの渡したローカル変数を使えばOKです。(変更する行だけ画像にしています)

パーシャルファイル

  • これで、一部を変更して、パーシャルファイルを呼び出す準備ができましたので、次は editページのフォームを消して(form_withの始まりから、end までを全部削除です)、以下のコードに変更してください。
  • これで、editページも完成です。
    • 一応ちゃんと動くか試しておいてください。
    • ファームがちゃんと表示されるか?だけじゃなく、editページでもバリデーションエラーのメッセージが表示されるかも確認しておきましょう。

パーシャルファイル

  • 次は、user のフォームもパーシャルファイルにしておきましょう。
    • やることはほぼ同じなので、自分でチャレンジしてみましょう。
  • パーシャルファイルに移すフォームは newページ からコピーしましょう。(editの方はエラーメッセージの表示をしていないため)
    • パーシャルファイル名は book と同じ「_form.html.erb」で良いですが、作る場所は「app/views/users」の中なので注意してください。
    • フォームをコピーした後、すぐに元のフォームを消すと、URLやメソッドがわからなくなりがちなので、メモっておくなどしてください。
  • 以下のようになればOKです。※_form.html.erbファイルは次のページにのせています。

パーシャルファイル

user の newファイル

user の editファイル

  • user の_form.html.erbファイルはこうなっていればOKですね。

パーシャルファイル

  • これで、パーシャルファイルに関してはOKです。
  • パーシャルファイルは、フォームだけでなく色々な場所で使えるので、覚えておきましょう。
  • 次は、「ストロングパラメータ」というものを導入したいと思います。
  • ストロングパラメータとは、「フォームから意図しないパラメータを送信されても、無視する」ための方法です。
    • つまり、フォームのセキュリティを向上するためのものです。
  • 一応、現時点で意図しないパラメータは保存されないようにしていますが、ストロングパラメータを使うとコードがスッキリと書けるため、今では「ストロングパラメータを使うのが基本」となっていますので、やっておきましょう。
  • では、まずは book の方からやっていきます。
  • bookコントローラーを開いて、色々並んでいるメソッドの一番下に、以下の赤枠のコードを追加してください。

ストロングパラメータ

  • 次に、createメソッドの中身を変更します。以下のようにしてください。

ストロングパラメータ

変更前

変更後

  • 今まで、@bookに対して、一つのカラムずつ = で代入していたのを、一度にまとめて@bookに入れている、という感じです。
  • 非常にスッキリしますよね?
  • ちょっと説明すると、最初に追加した「 book_params 」メソッドを、「 book.save(book_params) 」のところで呼び出しています。
  • この「 book_params 」メソッドの中の「 params.require~ 」が「ストロングパラメータ」と呼ばれる部分で、「指定したカラムのパラメータしか受け取らない」ための仕組みになっています。
  • 今のコードだと「name, date, star, impression, user_id」の四ると、最初に追加した「 book_params 」メソッドを、「 book.save(book_params) 」のところで呼び出しています。
  • この「 book_params 」メソッドの中の「 params.require~ 」が「ストロングパラメータ」と呼ばれる部分で、「指定したカラムのパラメータしか受け取らない」ための仕組みになっています。
  • 今のコードだと「name, date, star, impression」の4つだけが指定されています。

ストロングパラメータ

  • もし、フォームから送られてきた params(パラメータ)に、何かしらの理由で別のカラムのデータが混ざっていたとしても、ここで指定したカラムのデータ以外は受け取らないようになっています。(指定していないデータは捨てられる。エラーにはならない)
  • まぁ、つまりは前の = で、代入しているのと同じようになるという事です。
    • ※逆に言うと「 = で1つずつ代入せずに、paramsのデータを一気に保存する方法」もあって、その場合はストロングパラメータを使わないと、勝手な値がデータベースに入れられてしまいます。
  • ちなみに「 user_id 」だけはフォームから送信しているデータではなく、コントローラーで入れているデータなので、ストロングパラメータの中には含まれていません。
  • あくまで、ストロングパラメータは「paramsとして送られてくるデータの内、受け取るデータを指定する」ものです。
  • create以外にも、フォームから params を受け取っているのは updateメソッド なので、こちらも変更しておきます。
    • 以下のようにしてください。

ストロングパラメータ

  • 「save」メソッドを「update」メソッドに変更しています。
  • この二つは、データベースに値を保存するという意味では同じですが、saveメソッド の場合は「 @book.save(book_params) 」 みたいに引数を指定して一部を保存する、という事はできず、@bookをまるごと保存しようとします。
    • なのでストロングパラメータを使う場合は、updateメソッド を使う必要があります。
  • ここまで出来たら、念のため「本の新規作成」「本の編集」を試してみましょう。

ストロングパラメータ

  • book が出来たので、次は user もストロングパラメータを使う方法へ変更します。
  • これもやる事は同じなので、自分でやってみてください。
    • 正解は次のページ。

ストロングパラメータ

  • 以下のようにすれば良いですね。

ストロングパラメータ

  • ちなみにですが、先ほど作った「 user_paramsメソッド 」は、return(リターン:戻り値を返す)が省略されていることに気づいたでしょうか?
  • Ruby では、メソッドの中に return がない場合は、「最後に評価(計算)された式」が自動的に返されます。

ストロングパラメータ

メソッドの実行

return が書いてなくても、最後の式(行)が返される

  • つまりは、以下のように書いてるのと同じです。
  • 他のプログラミング言語だとあまりない動きなので、注意してください。
    • (例えば returnがない場合、Pythonでは None が、JavaScriptでは undefined が返されます)
  • 以前バリデーション(フォームの値を検証するやつ)をしたときに「パスワードを変えたいだけなのに、ユーザー名も変えないと "その名前は設定済です。別に名前にしてください。" というメッセージが出て、パスワードだけを変更することができない」というバグが残っていたと思いますが、今回の変更でそのバグも解決しています。
  • updateメソッドを使うことで、フォームの"名前"の部分を空白の場合は、nameカラム に対して変更処理が行われなくなります。
    • そのため、 name に対してのバリデーションも動かないので、パスワードだけの変更が出来るようになりました。
    • 逆に言うと、saveメソッド は変更がない場合でも、バリデーションが発生してしまうため、編集の時は updateメソッド を使うべきです。
  • ただし、名前だけを変更したい場合でも、パスワードの入力は必要です。
    • (パスワードは変更しなくても良いが、入力は必要)
    • これは、バリエーションをさらにカスタムしてすれば解決しますので、やってみましょう。
      • 次のページ。

ストロングパラメータ

  • バリエーションと言えば、モデルファイルなので「 models/user.py 」を開いて、以下の赤枠部分を追加してください。

ストロングパラメータ

  • バリエーションのオプションとして「 if: :条件を返すメソッド」と書くと、そのバリエーションを行う条件が指定できます。
    • 今回は「 password_digest_changed? 」という条件か、「 new_record? 」のどちらかがtureの場合だけ、パスワードのバリデーションが実行されるようになっています。
    • 「 password_digest_changed? 」はフォームに入力したパスワードが、ハッシュ化(暗号化)されたか?を判定しており、パスワード欄を空白にしておけば false になります。(何かしら入力してたら true です)
    • 「 new_record? 」は、「新しいレコード(データベースに新しいデータが新規作成されたか?)」を判定しており、つまり update の時は、false で、create の時は true になります。
  • 「 || 」の部分は「または」の意味なので、まとめると「パスワード欄に何か入力したか、または、新規作成の時だけバリエーションをする」という意味になっています。
  • このようにすることで、「パスワード欄には何も入力せず、編集画面であればバリエーションしない」という意味になります。

ストロングパラメータ

  • ちなみに「 password_digest_changed? 」と「 new_record? 」も、Railsなどで用意されている組み込みメソッドです。
  • Rails ではこのような組み込みメソッドが非常に多く用意されてますが、覚えきれないので「なんかこんな判定できるメソッドないかな?」っと思ったらネット検索して探してみましょう。
  • 次は「画像のアップロード」を出来るようにし、本に画像を登録していきましょう。
  • まず、画像をアップロードする仕組みを作る前に考えておくべきことがあります。
    • それは「画像をどこに保存しておくのか?」です。
  • 画像といえば、文字などのデータに比べると圧倒的に大きいサイズですよね?
  • つまり「サーバの保存容量」を大きく使うことになりますし、「保存したり、取り出したりする場合の通信量」も大きくなります。
    • ここらへんのシステム設計は、最初のうちにミスると後々かなり面倒なことが多いです。
  • 基本的にベストな方法としては、「画像や動画など専用の保存場所を用意しておく」という事です。
  • 分けて用意しておくことで「画像が増えていったら、画像の保存領域だけ徐々に大きくしていく」みたいに、カスタマイズがしやすくなります。
  • ただ、今はあくまで学習のためのサイトですし、専用の保存場所は費用が掛かることがほとんどなので、現時点では気にしないことにします。
  • 「じゃあ画像もデータだし、データベース(DB)サーバに入れておくか」と、なりがちですが、実は画像など大きいデータをDBサーバに保存するのはオススメできません。(やろうと思えばできるし、そうしてるサイトもあるけど)
  • 基本的にDBサーバは、数字や文字データのような小さいデータを大量に管理するために作られていますので、画像または動画のような大きなデータの保存は向いておらず、全体的に遅くなってしまいます。
  • 大きいサイズのデータは、通常の「ファイルシステム」(普通のパソコンで使われているシステム)の方が向いていますので、DBサーバ以外のサーバに置いておきます。次へ。

画像のアップロード

  • 「DBサーバ以外のサーバ」と言っても、意味がわからないと思いますので、ここらで「システム(サイト)全体」の話をしておきます。
  • まず現在のシステムには、3つのサーバがあります。
  • 「サーバ」とは一言で言うと「クライアント(ブラウザなど)からのリクエスト(要求)に、レスポンス(反応)するもの」を言います。
    • あくまで「機能(ソフト)」のことですので、「1つのパソコン(コンピュータ)」に「複数のサーバ」が動いていることも良くあります。
    • 現在も1つのコンピュータに3つのサーバが動いています。
    • その3つとは【WEBサーバ】【アプリケーションサーバ(APサーバ)】【データベースサーバ(DBサーバ)】です。
  • それぞれの役割を説明しておくと、サイトを開く時には、HTTPリクエストというものが送られますが、そのリクエストに対してサイトの情報(HTML)を返してくれるのが WEBサーバ です。
    • ソフトで言うと、Apache(アパッチ)、Nginx(エンジンエックス)が有名です。※ソフト名などは覚える必要はありません。
    • このWEBサーバは、基本的に静的ファイル(何も変化しないファイル)を返すことがメインです。
    • つまり、今のサイトのように「同じ showファイルだけど、中身が違う(本の名前などが違う)」というファイルは作れません。
  • 動的にファイルを生成したり、何かしらのプログラミング的な処理をするためのものが アプリケーションサーバ(APサーバ)です。
    • ソフトで言うと Puma(プーマ) や Unicorn(ユニコーン)などが有名です。
    • このAPサーバの上で、Rails が動いている感じです。
  • (ただ、Puma には簡易的なWEBサーバの機能もありますので、今の repit の環境では Puma が WEBサーバとしても、APサーバとしても動いています)

システム全体の話

  • つまり、現在のシステム全体はこんな感じです。

WEBサーバ

APサーバ

DBサーバ

サイトを使う人

1つのコンピュータの中にある

システム全体の話

Rails

  • もちろん、それぞれのサーバを別のコンピュータにすることも可能です。
  • 何かサイト(サービス)を作る時には「開発中の環境」と「本番の環境」で、2種類の考え方があって、「開発中の環境」ではそれぞれのサーバを同じコンピュータ内に置いておくことで、開発しやすくしたり、費用が掛かりにくくします。
  • 逆に「本番の環境」では、それぞれのサーバを分けて、個別に管理しやすくしたり、速度やセキュリティを重視した作りにします。
  • 特にDBサーバなどは別にした方が、メンテナンスやカスタマイズがしやすいので、ある程度大きい規模のサイトになれば別にする方が多いと思います。
  • WEBサーバとAPサーバは大体同じコンピュータ上に置かれることが多いですが、本番の環境では「WEBサーバは、Nginx、APサーバは Puma」みたいにソフトだけでも分けた方が良いです。
  • 画像や動画などの大きいデータに関しても、それを保存するためだけの「ストレージサーバ(またはメディアサーバ)」を用意して、そこに保存させておくのがベストです。
    • ただ、今は開発中というかただの勉強中なので、APサーバなどと同じ場所に保存しておきます。
  • ちなみに「●●サーバ」という「サーバの名前」に関しては別に決まりがあるわけでないので、「画像だけ置いとくサーバだから画像サーバと名付けるか」みたいに、勝手に決めて良いです。
    • もちろん、名前だけで何をするサーバなのか、がわかる名前がベストですが。

システム全体の話

  • ということで、画像の話に戻りましょう。
  • 「DBサーバに画像を保存しない」とは言いましたが、「この本は、この画像」のように「本のカラムの一つとして、画像を設定」しないといけません。
  • なので「画像データはDBサーバの外で保存する」けど、その「画像の起き場所や名前(パス)は、DBサーバに保存する」という形になります。ちょっとややこしいですが、とりあえず、画像の本体と、そのファイル名と、2つのデータの保存が必要になるということです。
  • まずは、画像のファイル名(パス)ですが、これはDBサーバに保存するので、新しいカラムが必要ですね。
  • 以下のコマンドで img_path というカラムを追加しましょう。

画像のアップロード

  • 追加できたら「 rake db:migrate 」も忘れずに実行しておきましょう。
  • 次にフォームに「画像アップロード用のフィールド」を用意します。
  • どこでも良いですが、とりあえずタイトルの下に追加しておきます。

画像のアップロード

  • これでフォームに関してはOKです。
    • 次へ。
  • これでアップロードする準備はできました。
  • 次ですが、アップロードされた画像を「画像の本体」と「画像のパス」と分けて保存する、という処理が必要ですね。
  • ただ、画像のアップロード機能には、他にも気にしないといけないことが多く、1つ1つ作っていくと中々大変です。
  • そのため、一般的には gem(Railsに追加して使えるアプリ的なやつ)をインストールして、画像のアップロード機能を作ることが多いです。
  • ここでも gem を利用して画像のアップロード機能を作っていきたいと思います。
  • いくつか有名な gem があるのですが、ここでは carrierwave(キャリアウェーブ) という gem を使っていきます。
  • gem を追加するには、まずは「 Gemfile 」に追加しないといけないので、Gemfileファイルを開いてください。

画像のアップロード

  • 以下のように「 gem carrierwave 」を追加してください。

画像のアップロード

  • Gemfileを変更したら、必ず「 bundle install 」も必要ですので、行っておきましょう。
  • 次に、Shell で以下のコマンドを実行してください。
  • これで carrierwave の実行に必要なファイルが作成されます。

画像のアップロード

  • 次に、モデルファイルに、以下の1行を追加してください。

画像のアップロード

  • これは「 carrierwave 」のメソッドで、img_path カラムに保存されるデータに対して、carrierwave の処理が働くようにする指定です。
  • これで画像アップロードの準備はほぼ終わりですが、最後にストロングパラメータで、フォームから img_path のパラメータを受け取れるようにしないといけません。
    • これは、一旦自分でチャレンジしてみてください。答えは次へ。
  • bookコントローラーを以下のようにすれば良いですね。

画像のアップロード

  • これで、画像のアップロードは出来るようになったので、試しに何か本を新規作成か、編集する時に画像を設定して登録してみてください。
  • この時点では、エラーが出なければOKです。
  • 何かしらのエラーが出来る時は、Railsを再起動してから試してください。
  • アップロードした画像は「 public/uploads/book/img_path/[本のid番号] 」というフォルダに保存されていますので、確認しておきましょう。
  • 「 carrierwave 」を使うと、このように整理された形で画像が保存されます。

画像のアップロード

  • これで「画像の保存」は出来ましたが、「表示」はまだ前の画像のままなので、ちゃんと設定した画像が表示されるようにします。
  • まずは book の showファイル を開いて、以下のように変更しましょう。

画像のアップロード

  • 出来たら、画像を設定した本の showページ を開いて確認しましょう。
  • ただし、このままだとちょっと別の問題が生まれてしまいます。
  • 画像を登録している本だと良いですが、画像を登録していない本の場合、showページ を開いた時にエラーになってしまいます。
    • これは「 img_path 」が空っぽだからです。
  • 画像を登録していない場合でも、エラーにならず、別の画像を表示されるようにしましょう。以下のようにします。

画像のアップロード

この名前は、前に表示させていた画像のファイル名

このスライドとは別のファイルを使っていた場合は、その名前にする

  • ほとんど説明は必要のないコードですが、一応捕捉しておくと「 @book.img_path.present? 」の「 .present? 」は「 @book.img_path 」の中身があるか?を判定するメソッドです。
  • これで登録してない本でも、エラーにならないようになりました。

画像のアップロード

画像がない本

画像がある本

  • 後は、他のページにも同じことをすれば良いですね。次へ。
  • 他に本の画像が表示されるのは「 book の index 」と「 user の show 」です。
    • それぞれちゃんと画像が表示されるように変更してみてください。答えは次へ。
    • ヒント:基本的には、元々画像を表示させてた部分にコピペするだけですが、インスタンス変数(@book)とローカル変数(book)がありますので、注意してください。

画像のアップロード

  • 以下のようにすれば良いですね。まずは「 book の index 」の方です。

画像のアップロード

  • 「 user の show 」の方です。

画像のアップロード

  • これで画像のアップロードが出来るようになりましたね。
  • ユーザーも同じように画像を登録できるようにしてみましょう。
  • もう carrierwave はインストールされているので後は
    • 「 userテーブルにカラムを追加する」
    • 「 modelsファイルに、追加したカラムを carrierwave で処理する設定を追加する 」
    • 「テーブルに追加したカラムを、ストロングパラメータに追加する」
    • 「画像を表示できるようにビューを変える」だけです。
  • まずはカラムの追加です。以下のコマンドを実行しましょう。

画像のアップロード

  • 「 rake db:migrate 」の実行も忘れずに行いましょう。
  • 次は「 modelsファイルに、追加したカラムを carrierwave で処理する設定を追加」します。
  • 「 app/models/user.py 」ファイルに以下を書き込みます。

画像のアップロード

  • 次は「 ストロングパラメータに追加 」します。

画像のアップロード

  • 後は「ビューを変更する」だけです。
    • これは自分でチャレンジしてみましょう。
    • ヒント:変更するファイルは userの「index」「show」「_form」の三か所です。
  • 以下のようにすると良いですね。まずは「 _form 」ファイルです。※一部だけを画像にしていますので、写ってない部分も削除しないでください。

画像のアップロード

  • 「 index 」「 show 」は以下のようにすれば良いですね。

画像のアップロード

indexファイル

showファイル

  • これで画像のアップロード機能が出来ましたね。
  • では、今回のスライドの内容を、もう一つのサイトにも同じようにしてみてください。
    • 出来たら次へ進みましょう。

画像のアップロード

  • 今回は「パーシャルファイル」「ストロングパラメータ」「画像のアップロード機能」を行いました。
  • ほとんど基本的な機能は出来たので、スライドも次回が最終回になります。
  • 次回、最終回では「画像のバリデーション」を行いたいと思います。

お疲れ様です