- 投稿日:2019-11-15T23:27:45+09:00
Windows環境でVSCodeでのRuby on Railsの開発環境構築手順
※ Rubyの推奨環境はLinux?とはいえWindowsしか持ってないよ!って人向けです
1.Rubyのインストール
Rubyで開発する上で、最も必要なものはRubyのインストールなので
https://rubyinstaller.org/downloads/
からRuby + Devkitをダウンロードするダウンロードしたらexeファイルを実行してインストールをする
インストールしたら、
ruby -v
コマンドを実行、バージョンが表示されたならばOK
バージョンが表示されなければ、Rubyをインストールしたパスの下の/binフォルダにパスを通す2.Git bashのインストール
https://gitforwindows.org/
からgitをダウンロードし、インストールする3.VSCodeのインストール、環境整備
https://code.visualstudio.com/
より、VisualStudioCodeをダウンロードし
exeファイルを実行してインストールするEXTENSIONSを選択し、Rubyを選択してこれもインストールする
その後、VSCode左下の歯車をクリック
検索欄に、「terminal.integrated.shell.windows」と入力して
表示される「Edit in setting.json」をクリックする"terminal.integrated.shell.windows": "C:\\Program Files\\Git\\bin\\bash.exe"と入力する
すると、VSCode下部のターミナルがGit Bashに変更される
- 投稿日:2019-11-15T22:47:51+09:00
やさしい図解で学ぶ 中間テーブル DB データベース joinモデル 概念【初級者用】
中間テーブルって?
中間テーブルとは
"join table もしくは junction table"とも呼ばれ、2つのテーブル(エンティティ)を結びつける、結合テーブルを指します。・・・・・・?
とりあえず、下のER図を見てみましょう。
テーブルとテーブルの間にそれぞれを繋げるテーブルがあります。これが"中間テーブル"です。
(ER図の表記一覧はこちら)2つのテーブル、この場合coursesテーブルとusersテーブルの間に
" 多対多 "
の関係性が生まれた時、マルチカラムアトリビュートを避けるため、この中間テーブルを用いると言われています。
・・・・・・・・・?
現時点でなにをいってるかわからなくても大丈夫。
これから詳しく図解を使って紹介しますのでちょっとずつイメージしていきましょう。1.まずはイメージ。
例えば、
下の画像のようなプログラミング学習サイトを製作しているとします。
このサイトではユーザーは学習したいプログラミングコースをカスタマイズして選ぶことができるとします。ここでまずは2つのテーブル
①usersテーブル (ユーザー情報用のテーブル)
②coursesテーブル (学べる言語情報用のテーブル)
があるとします。
1userは複数のコースを持っている →→→ "対多"
1courseは複数のユーザーを持っている →→→ "対多"
ということはusersテーブルとcoursesテーブルは
" 多対多 "
という関係が成り立ちます。
ちょっとまだ多対多の意味がよくわからないという方もいると思いますが、次の章で実際にテーブルをみてみましょう。
2."多対多"とは
(とりあえずユーザー3人と言語コース3つで表示しています。)
1 userは複数の言語コースとつながり、
一方、
1 courseは複数のユーザーに繋がっています。つまり、
多対多とは
対するテーブルのレコードを複数お互いに持っているということです。
そして、この関係性では図のカラムの他に外部キー用のカラムが必要です。
では外部キーを持たせたテーブルをみてみましょう。3. 実際にテーブルをみてみよう
ちなみに1カラムの中にデータは1つずつしか入れられないので下の図のようなことはできません。
★coursesテーブルの中身
・coursesテーブルにはとりあえず5人分のuser_idカラムを持たせています。
(カラムのuser_id1~5というのはこの言語コースがもつ1~5人目のユーザーという意味です。)★usersテーブルの中身
・usersテーブルには8コース分のcourse_idを持ちます。
(カラムのcourse_id1~8というのはユーザーがもつ1~8つ目の言語コースという意味です。)どこがいけないのかわからない場合はnilのカラムにとりあえず注目してみてください。
なんだかボコボコ、ガタガタで、使われていない空のカラムが多数ありますよね。
このような不要なカラムが多数、発生するようなテーブルは
Multi Column Attribute(マルチカラムアトリビュート)
とよばれ、DB設計においてアンチパターンであるとされています。
では、どうしたらいいのか。そんな時用いるのが
中間テーブルです。
4.中間テーブルの構造
では、実際に中間テーブルがどんな構造になっているのかもう一度最初のER図をみてみましょう。
よく見ると
中間テーブル course_usersテーブル は
それぞれのテーブルの外部キー
・user_id
・course_id
を持っています。ちなみに中間テーブルの命名は関係性をみて自分で作ることもできます。言語によっても多少、可否が異なるかもしれませんが、
(基本的にはアルファベット順で〇〇〇__〇〇〇sテーブルとするようです。なので今回の場合は、course_usersテーブルとしています。)
2." 多対多 "で用いた図を使って中間テーブルを作成してみましょう。
↓下のような構造になっています。
例えばid:2のユーザーが「HTMLとCSSも学びたい!」となってもuser_idとcourse_idを図のように足していけば、nilカラムを生み出すことなく追加することができますよね。
要するに今後、userの数もしくがcourseの数が増えたとしても
この中間テーブルがあれば、nilを出すことなく、レコードを追加することができます。以上
今回はコードについて触れると長くなってしまうので概念のみです。
後日、Railsの中間テーブル作成方法は記事にする....かもしれません。もちろん中間テーブルを使わずに設計をしていく方法もあり、様々ですが、DB設計をしていて、
「あれ? もしかしてこのテーブルって中間テーブルとかいうやつかな?」と思ったらこの記事で概念を見直してみてはいかがでしょうか。またなにかいい例があれば修正、更新いたします。
- 投稿日:2019-11-15T22:47:51+09:00
やさしい図解で学ぶ 中間テーブル 多対多 DB データベース joinモデル 概念 【初級者用】
中間テーブルって?
"join table もしくは junction table"とも呼ばれ、2つのテーブル(エンティティ)を結びつける、結合テーブルを指します。
とりあえず、下のER図を見てみましょう。
テーブルとテーブルの間にそれぞれを繋げるテーブルがあります。
これが"中間テーブル"です。(ER図の見方はこちら)2つのテーブル、この場合coursesテーブルとusersテーブルの間に
" 多対多 "
の関係性が生まれた時、マルチカラムアトリビュートを避けるため、1つの解決方法としてこの中間テーブルを用います。
現時点でなにをいってるかわからなくても大丈夫。
これから詳しく図解を使って紹介しますのでちょっとずつイメージしていきましょう。1.まずはイメージ。
例えば、
下の画像のようなプログラミング学習サイトを製作しているとします。
このサイトではユーザーは学習したいプログラミングコースをカスタマイズして選ぶことができるとします。ここでまずは2つのテーブル
①usersテーブル (ユーザー情報用のテーブル)
②coursesテーブル (学べる言語情報用のテーブル)
があるとします。
簡単な図にすると以下のような関係性がイメージできます。1userは複数のコースを持っている →→→ "対多"
1courseは複数のユーザーを持っている →→→ "対多"
ということはusersテーブルとcoursesテーブルは
" 多対多 "
という関係が成り立ちます。
ちょっとまだ多対多の意味がよくわからないという方もいると思いますが、次の章で実際にテーブルをみてみましょう。
2."多対多"とは
(とりあえずユーザー3人と言語コース3つで表示しています。)
1 userは複数の言語コースとつながり、
一方、
1 courseは複数のユーザーに繋がっています。つまり、
多対多とは
対するテーブルのレコードを複数お互いに持っているということです。
そして、この関係性では図のカラムの他に外部キー用のカラムが必要です。
では外部キーを持たせたテーブルをみてみましょう。
多対多の関係性の一例ですが、こんな感じになるはずです。
ではもう少し詳しくみてみましょう。3. 実際にテーブルをみてみよう
ちなみに前提として1カラムの中にデータは1つずつしか入れられません。
つまり、下の図のように1カラムがuser_idを複数もつことはできません。★coursesテーブルの中身
・coursesテーブルにはとりあえず5人分のuser_idカラムを持たせています。
(カラムのuser_id1~5というのはこの言語コースがもつ1~5人目のユーザーという意味です。)★usersテーブルの中身
・usersテーブルには8コース分のcourse_idを持ちます。
(カラムのcourse_id1~8というのはユーザーがもつ1~8つ目の言語コースという意味です。)どこがいけないのかわからない場合はnil(からっぽ)のカラムにとりあえず注目してみてください。
なんだかボコボコ、ガタガタで、使われていないからっぽのカラムが多数ありますよね。
このような不要なカラムが多数、発生するようなテーブルは
Multi Column Attribute(マルチカラムアトリビュート)
とよばれ、DB設計においてアンチパターンであるとされています。
では、どうしたらいいのか。そんな時用いるのが
中間テーブルです。
4.中間テーブルの構造
では、実際に中間テーブルがどんな構造になっているのかもう一度最初のER図をみてみましょう。
よく見ると
中間テーブル course_usersテーブル は
それぞれ接続先のテーブルの外部キー
・user_id
・course_id
を持っています。ちなみに中間テーブルの命名は関係性をみて自分で作ることもできます。言語によっても多少、可否が異なるかもしれませんが、
(基本的にはアルファベット順で〇〇〇__〇〇〇sテーブルとするようです。なので今回の場合は、course_usersテーブルとしています。)*
[2.]で用いた図を再び使って中間テーブルを作成してみましょう。
この図の関係性から中間テーブルは
↓下のような構造になります。例えば、
id:2のユーザーが「HTMLとCSSも学びたい!」となってもuser_idとcourse_idを図のように足すことができます。要するに今後、userの数もしくがcourseの数が増えたとしても
この中間テーブルがあれば、nil(空)を出すことなく、レコードを追加することができます。まとめ
中間テーブルの特徴まとめ
・多対多の関係性があるテーブルの間に設置
・接続する2テーブルの外部キーをもつ
・nilカラムを出さないDB設計をしたいとき
・命名は〇〇〇__〇〇〇s (基本的にはアルファベット順)もしくは新しく命名状況によっては中間テーブルをつくったほうがいいケースばかりではありませんが、中間テーブルや多対多の概念について、もやもやしている方はそれがちょっとでもクリアになってもらえたなら嬉しいです。
今回はコードについて触れると長くなってしまうので概念のみです。
またなにかいい例があれば修正、更新いたします。
- 投稿日:2019-11-15T22:47:51+09:00
やさしい図解で学ぶ 中間テーブル 多対多 DB データベース joinモデル 概念編
中間テーブルって?
"join table もしくは junction table"とも呼ばれ、2つのテーブル(エンティティ)を結びつける、結合テーブルを指します。
↓ER図のテーブルとテーブルの間にそれぞれを繋げるテーブルがあります。
これが"中間テーブル"です。(ER図の見方はこちら)2つのテーブルの間に
" 多対多 "
という関係性が生まれた時、ある問題点を解決する1つの方法としてこの中間テーブルを配置します。
これから詳しく図解を使って紹介しますのでちょっとずつイメージしていきましょう。
1.まずは多対多のイメージ
例えば、
下の画像のようなプログラミング学習サイトを製作しているとします。
このサイトではユーザーは学習したいプログラミングコースをカスタマイズして選ぶことができるとします。ここでまずは2つのテーブル
①usersテーブル (ユーザー情報用のテーブル)
②coursesテーブル (学べる言語情報用のテーブル)があるとします。
各テーブルのレコードを基点に簡単な図にすると以下のようなテーブル間の関係性がイメージできます。①usersテーブル
1userは複数のコースを持っている →→→ "対多"
②coursesテーブル
1courseは複数のユーザーを持っている →→→ "対多"
ということはusersテーブルとcoursesテーブルは
対多 + 対多 == " 多対多 "
という関係が成り立ちます。
まだよくわからないという方もいると思いますが、実際にテーブルの形で例をみてみましょう。
2. 実際に多対多のテーブルをみてみよう
とりあえずユーザー3人と言語コース3つで表示しています。
それぞれのテーブルのレコードどちらからも複数のつながりが見えますつまり、
" 多対多 "とは対するテーブルのレコードを複数お互いに持っているということです。
そして、この関係性では下図のように外部キー用のカラムが必要なので
外部キーを持たせたテーブルをみてみましょう。
多対多の関係性の一例ですが、こんな感じになるはずです。
ではもう少し詳しくみてみましょう。3. さらに詳しくみてみよう
*注意:ちなみに前提として1カラムの中にデータは1つずつしか入れられません。
つまり、下の図のように1カラムがuser_idを複数もつことはできません。★coursesテーブルの中身
・coursesテーブルにはとりあえず5人分のuser_idカラムを持たせています。
(カラムのuser_id1~5というのはこの言語コースがもつ1~5人目のユーザーという意味です。)★usersテーブルの中身
・usersテーブルには8コース分のcourse_idを持ちます。
(カラムのcourse_id1~8というのはユーザーがもつ1~8つ目の言語コースという意味です。)どこがいけないのかわからない場合はnil(からっぽ)のカラムにとりあえず注目してみてください。
なんだかボコボコ、ガタガタで、使われていないからっぽのカラムが多数ありますよね。
このような不要なカラムが多数、発生するようなテーブルは
Multi Column Attribute(マルチカラムアトリビュート)
とよばれ、DB設計においてアンチパターンであるとされています。
では、どうしたらいいのか。そんな時用いるのが
中間テーブルです。
4.中間テーブル
では、実際に中間テーブルがどんな構造になっているのかもう一度最初のER図をみてみましょう。
よく見ると
中間テーブル course_usersテーブル は
それぞれ接続先のテーブルの外部キー
・user_id
・course_id
を持っています。[2.]で用いた図を再び使って中間テーブルを作成してみましょう。
この2つのテーブルの間にの中間テーブルを設置すると下のような構造になります。
中間テーブル: course_usersテーブル
例えば、
id:2のユーザーが「HTMLとCSSも学びたい!」となってもuser_idとcourse_idを図のように足すことができます。要するに今後、userの数もしくがcourseの数が増えたとしても
この中間テーブルがあれば、nil(空)を出すことなく、レコードを追加することができます。まとめ
中間テーブルの特徴まとめ
・多対多の関係性があるテーブルの間に設置
・接続する2テーブルの外部キーをもつ
・nilカラムを出さないDB設計をしたいとき
・命名は〇〇〇__〇〇〇s (基本的にはアルファベット順)もしくは新しく命名状況によっては中間テーブルをつくったほうがいいケースばかりではありませんが、中間テーブルや多対多の概念について、もやもやしている方はそれがちょっとでもクリアになってもらえたなら嬉しいです。
またなにかいい例があれば修正、更新いたします。
補足
ちなみに中間テーブルの命名は関係性をみて自分で作ることもできます。言語によっても多少、可否が異なるかもしれませんが、
(基本的にはアルファベット順で〇〇〇__〇〇〇sテーブルとするようです。なので今回の場合は、course_usersテーブルとしています。)*
- 投稿日:2019-11-15T22:18:35+09:00
gem 'devise'に苦戦 プログラミング 37日目
gem'devise'使う上で考慮すべきこと
1.URLかぶり
例えばdeviseをuserモデルに紐づけてinstallしたりすると以下のような(一部)routesが自動的にできます。
URLかぶりとは??
userのアカウント情報を表示させるusers/showを作ろうと、get "users/:id" => "users#show"をroutesの設定しました。そうするとそれまでできていたlogoutがうまくいかなくなりました。その原因として考えた結論が、users/:idとdeviseで設定した他のusers/~~~が被ってしまっているということです。(あっているかは定か)usersはやめpages/:idにすることで解決しました。
new_user_session GET /users/sign_in(.:format) devise/sessions#new user_session POST /users/sign_in(.:format) devise/sessions#create destroy_user_session DELETE /users/sign_out(.:format) devise/sessions#destroy new_user_password GET /users/password/new(.:format) devise/passwords#new edit_user_password GET /users/password/edit(.:format) devise/passwords#edit user_password PATCH /users/password(.:format) devise/passwords#update PUT /users/password(.:format) devise/passwords#update POST /users/password(.:format) devise/passwords#create cancel_user_registration GET /users/cancel(.:format) devise/registrations#cancel new_user_registration GET /users/sign_up(.:format) devise/registrations#new edit_user_registration GET /users/edit(.:format) devise/registrations#edit user_registration PATCH /users(.:format) devise/registrations#update PUT /users(.:format) devise/registrations#update DELETE /users(.:format) devise/registrations#destroy POST /users(.:format) devise/registrations#create root GET /2.いちいち編集ページなどは作らず既存のやつを使う方がいい
そしてviewも手順どうりに進めるとdeviseフォルダができます。deviseのフォルダにはメールアドレス情報やパスワードを変更するページもあるためいちいち編集ページを作る必要はありません。ただ名前を登録するところがないのが不便です。
- 投稿日:2019-11-15T22:01:12+09:00
Model名とController名が異なっていてもデータ移動はできるらしい 学習37日目
〇〇モデルのデータは□□コントローラー上でも使える
UserモデルのデータはUsersコントローラーで,Pageモデルのデータはpagesコントローラーでという大きな思い込みをしていた。(冷静に考えれば、application.html.erbはいろんなモデルからデータを取ってきている)
勘違いしていたこと
1.モデルとコントローラーのデータのやり取りは、同じ名前同士で行う。←そんなことない!
UserモデルのデータはUsersコントローラーで,Pageモデルのデータはpagesコントローラーでという大きな思い込みをしていた。(冷静に考えれば、application.html.erbはいろんなモデルからデータを取ってきている)
2.controllerの内容を継承したければコピペする←それだけではない!
確かにuserscontrollerの内容をそのままpagescontrollerでも使いたければ、コピペも1つの手段になる。しかしめんどくさいのでclassを継承をすれば良い(pagescontroller<userscontroller)
下記の例はpagesと名付けたshowのviewである。ここには@userを用いている。そしてその下はpagesコントローラーである。
show.html.erb(pages)<div class="container"> <div class="profile-card"> <div class="profile-card__inner"> <div class="profile-thumb"> <img src="<%="/user_images/#{@user.image_name}" %>" class="user-image" alt="アイコン"> </div> <div class="profile-content"> <span class="profile-name"><%=@user.name%></span> <span class="profile-job"><%=@user.email %></span> <span class="profile-intro"><%=@user.intro %></span> <%=link_to("編集する",edit_user_registration_path, method: :get) %> </div> </div> </div> </div>class PagesController < UsersController def index @users = User.all end def show @user = User.find_by(id: params[:id]) end def update @user = User.find_by(id: params[:id]) @user.name = params[:name] @user.intro = params[:intro] if params[:image] @user.image_name = "#{@user.id}.jpg" image = params[:image] File.binwrite("public/user_images/#{@user.image_name}", image.read) end if @user.save flash[:notice] = "ユーザー情報を編集しました" redirect_to("/pages/#{@user.id}") else render("users/edit") end end def edit @user = User.find_by(id: params[:id]) end end↑↑Pagescontroller<Userscontrollerしてるからコピペしなくていい(しちゃってる悪い例)
@user = User.allは別にpagescontrollerでも使える
- 投稿日:2019-11-15T21:32:37+09:00
【AWS EC2】bundler -v -bash: bundler: コマンドが見つかりません
rbenvうっかり入れ忘れなので、下記のコマンドを入力すればOK。
バージョンはご自身の環境に合わせてください。$ rbenv install 2.5.1 $ rbenv global 2.5.1 $ rbenv rehash #rehashを行う $ ruby -v # バージョンを確認上記のコマンドでダメだった場合は、下記の記事を試してみてください。
【rails】PCからrvenvだけを間違ってアンインストールしてしまって鼻血が出た話
https://qiita.com/kaorioka09jm/items/77f3b64027d8ed05bb21
- 投稿日:2019-11-15T20:56:27+09:00
Rails Tutorialの知識から【ポートフォリオ】を作って勉強する話 #14 ユーザ投稿表示, ページネーション編
こんな人におすすめ
- プログラミング初心者でポートフォリオの作り方が分からない
- Rails Tutorialをやってみたが理解することが難しい
前回:#13 パスワード再設定編
次回:準備中今回の流れ
- 投稿用のモデルをつくる
- サンプル投稿を表示する
- テストをつくる
Tutorial13章(第4版)に突入しました。
今回はユーザ投稿の表示とページネーションを実装します。
(投稿機能は#15(準備中)で紹介します。)投稿用のモデルをつくる
ここでの手順は以下の通りです。
- Micropostモデルをつくる
- Userモデルを編集する
- バリデーションを追加する
- Micropostモデルを改良する
- エラー時の日本語化を行う
Micropostモデルをつくる
まずは投稿のためのMicropostモデルをつくりましょう。
ユーザが投稿できる項目は以下の通りです。
- 動画視聴時間
- メモ
- 画像
加えて投稿はユーザがいて初めて成立するので、Userモデルに所属させます。
そのためには生成時にuser:referencesを加えます。
以上を踏まえてMicropostモデルを生成しましょう。bash$ rails g model Micropost time:integer memo:text picture:string user:references投稿を表示する際、twitterのように最新のものが上部に来てほしいものです。
その準備として、マイグレーションにインデックスを加えます。db/migrate/[timestamp]_create_microposts.rbclass CreateMicroposts < ActiveRecord::Migration[5.2] def change create_table :microposts do |t| t.integer :time t.text :memo t.string :picture t.references :user, foreign_key: true t.timestamps end add_index :microposts, [:user_id, :created_at] end enduser_idは投稿したユーザ、create_atは投稿時間を管理しています。
これらを複合キーとすることで、望み通り取り出すことが可能です。Userモデルを編集する
MicropostモデルはUserモデルに所属しました。
この実装はbelongs_to :userにより、user_idとして形になりました。
今度はUserモデルにMicropostモデルを所有してもらいましょう。app/models/user.rbclass User < ApplicationRecord has_many :microposts # 中略 endhas_manyによりUserはMicropostと1対多の関係になりました。
こうすることでmicropostsを指定する時、こんな書き方が可能です。user = User.new user.microposts慣習的にも正しいので、以上の作業は忘れずに行いましょう。
バリデーションを追加する
ここでは以下のバリデーションを追加します。
- user_idが空の場合、Micropostを生成しない
- いずれの値も空の場合のみ(user_idを除く)Micropostを生成しない
app/models/micropost.rbclass Micropost < ApplicationRecord belongs_to :user validates :user_id, presence: true validates :memo, length: { maximum: 255 } validates :only_user_id, presence: true private def only_user_id time.presence or memo.presence or picture.presence end endいずれかという条件を実装するためにonly_user_idメソッドを定義しました。
バリデーションで『いずれか』を実装する詳しい方法↓
「いずれかのカラムが空でなければ良い」というバリデーションMicropostモデルを改良する
ここでは以下の機能を追加します。
- 新しい投稿を先にソートする
- ユーザが削除されたら投稿も削除する
app/models/micropost.rbclass Micropost < ApplicationRecord belongs_to :user default_scope { order(created_at: :desc) } # 中略 endapp/models/user.rbclass User < ApplicationRecord has_many :microposts, dependent: :destroy # 中略 endエラー時の日本語化を行う
あとはエラー時の言語を日本語にしましょう。
日本語化にはgemと設定が必要です。#6やRailsのバリデーションエラーのメッセージの日本語化を参考に設定を行なってください。お済みの方は以下のようにファイルを編集してください。
config/locales/models/ja.ymlja: activerecord: models: user: ユーザ micropost: 投稿 attributes: user: name: 名前 email: メールアドレス password: パスワード password_confirmation: パスワード(再入力) micropost: time: 記録時間 memo: メモ picture: 画像 user_id: ユーザID only_user_id: 入力欄後ほどビューをつくります。
その際エラー表示を確認するので、ひとまずここで終了です。
最後にデータベースを更新します。bash$ rails db:migrate
サンプル投稿を表示する
Micropostモデルは整いました。次は投稿を表示しましょう。
ここでの手順は以下の通りです。
- サンプル投稿を生成する
- サンプル投稿を表示する
- ページネーションを実装する
サンプル投稿を生成する
サンプル投稿を生成するにはfakerを使用します。
gemfile+ gem 'faker'
bash$ bundle installそれではサンプル投稿を生成します。
ついでにサンプルユーザも生成しておきます。db/seeds.rbUser.create!( name: "Example User", email: "example@railstutorial.org", password: "foobar", password_confirmation: "foobar", activated: true ) 5.times do |n| name = Faker::Name.name email = "example_#{n+1}@railstutorial.org" password = "password" User.create!( name: name, email: email, password: password, password_confirmation: password, activated: true ) end users = User.order(:created_at).take(3) 50.times do memo = Faker::Lorem.sentence(6) users.each { |user| user.microposts.create!(memo: memo) } end最後にデータベースを再構築しましょう。
bash$ rails db:migrate:reset $ rails db:seedこれでサンプルユーザとサンプル投稿が生成されました。
サンプル投稿を表示する
生成を終えたら投稿を表示しましょう。
ここでの手順は以下の通りです。
- usersコントローラを編集する
- ビューを編集する
- ページネーションを追加する
usersコントローラを編集する
投稿を表示させるビューはshow.html.erbです。
つまりこれに対応するコントローラにMicropostを渡す必要があります。
対応するのはusersコントローラです。編集しましょう。app/controllers/users_controller.rbclass UsersController < ApplicationController # 中略 def show @user = User.find(params[:id]) @microposts = @user.microposts.page(params[:page]).per(10) endこの1文はページネーションを実装する記述です。
@microposts = @user.microposts.page(params[:page]).per(10)ページネーションしたい対象に.page〜以下を加えることで動作します。
per(10)は1ページの表示数を表します。今回は10にしました。ビューを編集する
続いてはビューです。
パーシャルを使用しつつ実装しましょう。app/views/users/show.html.erb<% provide(:title, @user.name) %> <div class="container show-container"> <div class="row"> <div class="col bg-primary"> form </div> <div class="col bg-secondary"> figure </div> </div> <div class="row"> <div class="col"> <%= render 'layouts/log' %> </div> </div> </div>app/views/layouts/_log.html.erb<h1 class="log-title col-2">Logs</h1> <% if @user.microposts.any? %> <div class="container"> <ol class="microposts"> <% @microposts.each do |micropost| %> <li id ="micropost-<%= micropost.id %>"> <span class="row log-list"> <span class="col-2 log-timestamp d-none d-md-inline-block log-timestamp-block"> <span class="log-timestamp"><%= time_ago_in_words(micropost.created_at) %>前</span> </span> <span class="col-md-10 col-log-memos"> <div class="log-time-and-edit"> <div class="row"> <span class="log-time col-3"><%= micropost.time %>分</span> <span class="col-7 log-timestamp log-timestamp-inline"><%= time_ago_in_words(micropost.created_at) %>前</span> <span class="log-edit col-2"><%= link_to image_tag('edit.png', class: "log-edit-image"), '#' %></span> </div> </div> <span class="log-memo"><%= micropost.memo %></span> <span class="log-picture"><%= micropost.picture %></span> </span> </span> </li> <% end %> </ol> </div> <%= paginate @microposts %> <% else %> <span>まだ投稿がありません</span> <% end %>app/assets/stylesheets/application.scss.erb// max-width = 767px @media (max-width: 767px) { .nav-item-extend { margin-top: 0.6rem; } .log-title { padding: 0; } .log-list { border-left: 1px solid $lantern-dark-white; } } @media (min-width: 768px) { .log-timestamp-inline { visibility: hidden; } } // 中略 // logs ol { padding: 0; list-style: none; } .log-title { margin: 1.5rem 0; text-align: center; } .log-list { margin-bottom: 1rem; .log-timestamp { color: gray; } .log-timestamp-block { text-align: center; padding-bottom: 2rem; margin-top: 0.1rem; border-right: 1px solid $lantern-dark-white; } .col-log-memos { padding: 0 1rem 0 1rem; .log-time-and-edit { .log-time { font-size: 1.2rem; color: $lantern-yellow; } .log-timestamp-inline { text-align: right; margin-top: 0.1rem; padding: 0; } .log-edit { text-align: right; margin-top: 0.1rem; .log-edit-image { width: 1rem; } } } .log-memo { } .log-picture { } } } // paginate .pagination { margin-top: 1.6rem; .page-item { .page-link { border: 1px solid $lantern-light-white; background-color: $lantern-light-white; color: $lantern-blue; } } .page-item.active .page-link { background-color: $lantern-blue; border-color: $lantern-blue; color: $lantern-light-white; } } // 中略_log.html.erbのこの部分もページネーションを実装するための記述です。
<%= paginate @microposts %>コントローラに引き続き、2つの記述が見受けられました。
これらはkaminariというgemにより動作します。
準備は以上です。後はgemを挿れるだけです。ページネーションを追加する
それではkaminariを追加し、ページネーションを動作させましょう。
Bootstrap4と日本語化の適用も行います。gemfile+ gem 'kaminari' + gem 'kaminari-bootstrap'
bash$ bundle install $ rails g kaminari:views bootstrap4config/locales/models/ja.ymlja: # 中略 views: pagination: first: "« 最初" last: "最後 »" previous: "‹ 前" next: "次 ›" truncate: "..."これらを終えるとビューはこんな感じになります。
PC版↓
スマホ版↓
参考になります↓
【Ruby on Rails】gem(Kaminari)でページネーション機能を追加してBootstrapを適用する。
【Rails初心者】ページネーションを実装して自分好みにデザインを変えるテストをつくる
最後にテストを完成させます。
ここでの手順は以下の通りです。
- FactoryBotを整える
- テストを書く
FactoryBotを整える
まずはテストを行う前の準備をしましょう。
今回新たにMicropostモデルが生成されました。
それに伴うテストを行いたいので、FactoryBotで導入しましょう。ここでの手順は以下の通りです。
- テスト用のMicropostモデルを生成する
- テスト用のUserモデルを編集する
spec/factories/microposts.rbFactoryBot.define do factory :memos, class: Micropost do trait :memo_1 do time { 240 } memo { "I just ate an orange!" } picture { nil } user_id { 1 } created_at { 10.minutes.ago } end trait :memo_2 do time { 180 } memo { "Check out the @tauday site by @mhartl: http://tauday.com" } picture { nil } user_id { 1 } created_at { 3.years.ago } end trait :memo_3 do time { 59 } memo { "Sad cats are sad: http://youtu.be/PKffm2uI4dk" } picture { nil } user_id { 1 } created_at { 2.hours.ago } end trait :memo_4 do time { 207 } memo { "Writing a short test" } picture { nil } user_id { 1 } created_at { Time.zone.now } end association :user, factory: :user end endspec/factories/users.rbFactoryBot.define do factory :user do name { "Michael Example" } sequence(:email) { |n| "michael_#{n}@example.com" } password { "password" } password_confirmation { "password" } activated { true } end factory :other_user, class: User do name { "Sterling Archer" } sequence(:email) { |n| "duchess_#{n}@example.gov" } password { "foobar" } password_confirmation { "foobar" } activated { true } end factory :no_activation_user, class: User do name { "No Activation" } sequence(:email) { |n| "no_#{n}@activation.co.jp" } password { "foobar" } password_confirmation { "foobar" } activated { false } end endmicroposts.rbではtraitを使って、Micropostモデル内を区切りました。
users.rbではsequenceを使って、メールアドレスの一意性を保つよう番号をつけました。
以上で準備は完了です。※ 変更により他のテストが失敗する可能性があるので、適宜変更を加えてください。
Micropostモデルのテスト
それではいよいよテストに入りましょう。
このテストでは以下を確認します。
- モデルが正しく生成されているか
- いずれの値も空の場合(user_idを除く)、Micropostは存在しないか
- カラムが最新のものから順に並んでいるか
- user_idが存在しないMicropostは存在しないか
- memoが255文字を超えないか
spec/models/micropost_spec.rbrequire 'rails_helper' RSpec.describe Micropost, type: :model do let(:user) { create(:user) } let(:micropost) { user.microposts.build(time: 240, memo: "Lorem ipsum", user_id: user.id) } describe "Micropost" do it "should be valid" do expect(micropost).to be_valid end it "should not be valid" do micropost.update_attributes(time: 1, memo: " ", picture: nil, user_id: user.id) expect(micropost).to be_valid micropost.update_attributes(time: nil, memo: " ", picture: nil, user_id: user.id) expect(micropost).to be_invalid end it "should be most recent first" do create(:memos, :memo_1, created_at: 10.minutes.ago) create(:memos, :memo_2, created_at: 3.years.ago) create(:memos, :memo_3, created_at: 2.hours.ago) memo_4 = create(:memos, :memo_4, created_at: Time.zone.now) expect(Micropost.first).to eq memo_4 end end describe "user_id" do it "should not be present" do micropost.user_id = nil expect(micropost).to be_invalid end end describe "memo" do it "should not be at most 255 characters" do micropost.memo = "a" * 255 expect(micropost).to be_valid micropost.memo = "a" * 256 expect(micropost).to be_invalid end end endUserモデルのテスト
このテストでは以下を確認します。
- ユーザが削除されたら投稿も削除されるか
spec/models/user_spec.rbrequire 'rails_helper' RSpec.describe User, type: :model do # 中略 it "destroys assosiated microposts" do user.microposts.create!(memo: "Lorem Ipsum") expect{ user.destroy }.to change{ Micropost.count }.by(-1) end以上でテストは終了です。
次回は投稿機能を実装します。前回:#13 パスワード再設定編
次回:準備中
- 投稿日:2019-11-15T20:10:33+09:00
メモ:minitest-reporters導入方法
書くことが無いので、rails testの結果が見やすくなるgem、「minitest-reporters」の導入方法をメモしておこうと思う。
導入方法
Gemfileの「group :test do」の中に以下のように導入。
group :test do gem 'minitest' gem 'minitest-reporters' end加えて、test_helper.rbに以下の二行を追加。
test_helper.rbrequire 'rails/test_help' require "minitest/reporters" #1行目 Minitest::Reporters.use! #2行目gem 'minitest'にどういう役割があるのか、調べてもイマイチよくわからなかったが...
どうやらRailsのテストの方法はいくつかあるようで、minitestというのはそのうちの一つらしい。
今のところはminitest-reportersの前提modならぬ前提gem的な存在だと思っておくことにする(適当)。
- 投稿日:2019-11-15T17:38:08+09:00
#Rails + #rspec OPTIONS request test by ActionDispatch::Integration::Session.proccess
example
describe 'options' do subject { process :options, '/' } before { subject } it { ... } endRef
Perform HTTP OPTIONS request from Rails 5 for CORS pre-flight or otherwise - Stack Overflow
ActionDispatch::Integration::Session
Original by Github issue
- 投稿日:2019-11-15T16:50:53+09:00
eager_load + find_by でクエリが2回発行される件の対策
結論
よくある1:NのRelationがある時
class User < ApplicationRecord has_many :comments end class Comment < ApplicationRecord belongs_to :user endUser.eager_load(:comments).find_by(id: 1)とかすると
1. User.find_by(id: 1) のSQL
2. User.eager_load(:comments) のSQL
の2回SQLが発行されるUser.eager_load(:comments).where(id: 1).load.firstとかするとクエリが1回になる。
(.load
は.to_a
とかでもOK)ruby 2.5.1 rails 5.0.6/6.0.1(下記ログは6.0.1のものです)で確認済み。
詳細
普通にやってみたりしたけど、どうしてもただのfindっぽいクエリと、
まとめて情報をとってくるクエリの2回が発行されたUser.eager_load(:comments).find(1) # SELECT DISTINCT `users`.`id` FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 LIMIT 1 # SELECT `users`.`id` AS t0_r0, `users`.`name` AS t0_r1, `users`.`created_at` AS t0_r2, `users`.`updated_at` AS t0_r3, `comments`.`id` AS t1_r0, `comments`.`user_id` AS t1_r1, `comments`.`created_at` AS t1_r2, `comments`.`updated_at` AS t1_r3 FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 AND `users`.`id` = 1 User.eager_load(:comments).find_by(id: 1) # SELECT DISTINCT `users`.`id` FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 LIMIT 1 # SELECT `users`.`id` AS t0_r0, `users`.`name` AS t0_r1, `users`.`created_at` AS t0_r2, `users`.`updated_at` AS t0_r3, `comments`.`id` AS t1_r0, `comments`.`user_id` AS t1_r1, `comments`.`created_at` AS t1_r2, `comments`.`updated_at` AS t1_r3 FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 AND `users`.`id` = 1 User.eager_load(:comments).where(id: 1).first # SELECT DISTINCT `users`.`id` AS alias_0, `users`.`id` FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 ORDER BY `users`.`id` ASC LIMIT 1 # SELECT `users`.`id` AS t0_r0, `users`.`name` AS t0_r1, `users`.`created_at` AS t0_r2, `users`.`updated_at` AS t0_r3, `comments`.`id` AS t1_r0, `comments`.`user_id` AS t1_r1, `comments`.`created_at` AS t1_r2, `comments`.`updated_at` AS t1_r3 FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 AND `users`.`id` = 1 ORDER BY `users`.`id` ASC
WHERE `users`.`id` = 1 AND `users`.`id` = 1
とかめっちゃ冗長そうなクエリがある。
2回目のクエリだけでほしい情報は取れるはず
なんとかして1回のクエリにしたい![]()
User.eager_load(:comments).where(id: 1)
の場合は、クエリが1回なので、
これの時点でクエリを無理やり発火させてから、firstを取ればいいのでは?User.eager_load(:comments).where(id: 1).load.first # SELECT `users`.`id` AS t0_r0, `users`.`name` AS t0_r1, `users`.`created_at` AS t0_r2, `users`.`updated_at` AS t0_r3, `comments`.`id` AS t1_r0, `comments`.`user_id` AS t1_r1, `comments`.`created_at` AS t1_r2, `comments`.`updated_at` AS t1_r3 FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1キタ━━━━(゚∀゚)━━━━!!
おわり。
クエリ整形ver
User.eager_load(:comments).find(1) # SQL (4.0ms) SELECT DISTINCT `users`.`id` FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 LIMIT 1 # SQL (2.8ms) SELECT `users`.`id` AS t0_r0, `users`.`name` AS t0_r1, `users`.`created_at` AS t0_r2, `users`.`updated_at` AS t0_r3, `comments`.`id` AS t1_r0, `comments`.`user_id` AS t1_r1, `comments`.`created_at` AS t1_r2, `comments`.`updated_at` AS t1_r3 FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 AND `users`.`id` = 1 # -- pretty format. # SELECT # DISTINCT `users`.`id` # FROM # `users` # LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` # WHERE # `users`.`id` = 1 # LIMIT # 1; # # SELECT # `users`.`id` AS t0_r0, # `users`.`name` AS t0_r1, # `users`.`created_at` AS t0_r2, # `users`.`updated_at` AS t0_r3, # `comments`.`id` AS t1_r0, # `comments`.`user_id` AS t1_r1, # `comments`.`created_at` AS t1_r2, # `comments`.`updated_at` AS t1_r3 # FROM # `users` # LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` # WHERE # `users`.`id` = 1 # AND `users`.`id` = 1; #=> #<User id: 1, name: "hogehoge", created_at: "2019-11-15 04:54:42", updated_at: "2019-11-15 04:54:42"> User.eager_load(:comments).find_by(id: 1) # SQL (1.0ms) SELECT DISTINCT `users`.`id` FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 LIMIT 1 # SQL (2.7ms) SELECT `users`.`id` AS t0_r0, `users`.`name` AS t0_r1, `users`.`created_at` AS t0_r2, `users`.`updated_at` AS t0_r3, `comments`.`id` AS t1_r0, `comments`.`user_id` AS t1_r1, `comments`.`created_at` AS t1_r2, `comments`.`updated_at` AS t1_r3 FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 AND `users`.`id` = 1 # SELECT # DISTINCT `users`.`id` # FROM # `users` # LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` # WHERE # `users`.`id` = 1 # LIMIT # 1; # # SELECT # `users`.`id` AS t0_r0, # `users`.`name` AS t0_r1, # `users`.`created_at` AS t0_r2, # `users`.`updated_at` AS t0_r3, # `comments`.`id` AS t1_r0, # `comments`.`user_id` AS t1_r1, # `comments`.`created_at` AS t1_r2, # `comments`.`updated_at` AS t1_r3 # FROM # `users` # LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` # WHERE # `users`.`id` = 1 # AND `users`.`id` = 1; # # SELECT # `users`.`id` AS t0_r0, # `users`.`name` AS t0_r1, # `users`.`created_at` AS t0_r2, # `users`.`updated_at` AS t0_r3, # `comments`.`id` AS t1_r0, # `comments`.`user_id` AS t1_r1, # `comments`.`created_at` AS t1_r2, # `comments`.`updated_at` AS t1_r3 # FROM # `users` # LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` # WHERE # `users`.`id` = 1 # AND `users`.`id` = 1; #=> #<User id: 1, name: "hogehoge", created_at: "2019-11-15 04:54:42", updated_at: "2019-11-15 04:54:42"> User.eager_load(:comments).where(id: 1).first # SQL (0.6ms) SELECT DISTINCT `users`.`id` AS alias_0, `users`.`id` FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 ORDER BY `users`.`id` ASC LIMIT 1 # SQL (0.6ms) SELECT `users`.`id` AS t0_r0, `users`.`name` AS t0_r1, `users`.`created_at` AS t0_r2, `users`.`updated_at` AS t0_r3, `comments`.`id` AS t1_r0, `comments`.`user_id` AS t1_r1, `comments`.`created_at` AS t1_r2, `comments`.`updated_at` AS t1_r3 FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 AND `users`.`id` = 1 ORDER BY `users`.`id` ASC # # SELECT # DISTINCT `users`.`id` AS alias_0, # `users`.`id` # FROM # `users` # LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` # WHERE # `users`.`id` = 1 # ORDER BY # `users`.`id` ASC # LIMIT # 1; # # SELECT # `users`.`id` AS t0_r0, # `users`.`name` AS t0_r1, # `users`.`created_at` AS t0_r2, # `users`.`updated_at` AS t0_r3, # `comments`.`id` AS t1_r0, # `comments`.`user_id` AS t1_r1, # `comments`.`created_at` AS t1_r2, # `comments`.`updated_at` AS t1_r3 # FROM # `users` # LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` # WHERE # `users`.`id` = 1 # AND `users`.`id` = 1 # ORDER BY # `users`.`id` ASC; #=> #<User id: 1, name: "hogehoge", created_at: "2019-11-15 04:54:42", updated_at: "2019-11-15 04:54:42"> User.eager_load(:comments).where(id: 1).load.first # SQL (1.0ms) SELECT `users`.`id` AS t0_r0, `users`.`name` AS t0_r1, `users`.`created_at` AS t0_r2, `users`.`updated_at` AS t0_r3, `comments`.`id` AS t1_r0, `comments`.`user_id` AS t1_r1, `comments`.`created_at` AS t1_r2, `comments`.`updated_at` AS t1_r3 FROM `users` LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` WHERE `users`.`id` = 1 # SELECT # `users`.`id` AS t0_r0, # `users`.`name` AS t0_r1, # `users`.`created_at` AS t0_r2, # `users`.`updated_at` AS t0_r3, # `comments`.`id` AS t1_r0, # `comments`.`user_id` AS t1_r1, # `comments`.`created_at` AS t1_r2, # `comments`.`updated_at` AS t1_r3 # FROM # `users` # LEFT OUTER JOIN `comments` ON `comments`.`user_id` = `users`.`id` # WHERE # `users`.`id` = 1; #=> #<User id: 1, name: "hogehoge", created_at: "2019-11-15 04:54:42", updated_at: "2019-11-15 04:54:42">
- 投稿日:2019-11-15T16:47:52+09:00
AWS Elastic Beanstalkで定期処理実行(cron)
Heroku Schedulerでできたことを、Elastic Beanstalkでも行いたい
運用しているRailsアプリの本番環境を、HerokuからElastic Beanstalkに移行させました。
その際タスクの定期処理を行う方法で少し苦労したので、メモ的にシェアしておきます。以前はHeroku Schedulerでrakeコマンドを呼び出せましたが、EBでそれを行うにはEC2内でcronを使うか、ワーカー環境でPOSTメソッドを呼び出す方法があるみたいです。今回は安く済ませたいので、前者を選びました。
後者の方法はこちらが参考になります。
https://qiita.com/tomoeine/items/38a9b2123e3afa1d5cd0方法
このファイルを.ebextensionsに置くだけでOKです。あとは
eb deploy
すれば、自動でcronのファイルを作成し自動実行してくれます。
eb ssh
で/etc/cron.dを確認してみてください。.ebextensions/cron-sample.configfiles: # filesは、EC2上にファイルを作成する。パスはアプリのルートではなくLinuxのルート。 "/etc/cron.d/hello_world": mode: "000644" owner: root group: root content: | 0 4 * * * root /usr/local/bin/hello_world.sh # 指定した頻度で、↓で作成するシェルスクリプトを実行。これは毎日午前4時 "/usr/local/bin/hello_world.sh": mode: "000755" owner: root group: root content: | #!/bin/bash . /opt/elasticbeanstalk/support/envvars # アプリの環境変数読み込み cd /var/app/current /opt/rubies/ruby-2.5.7/bin/bundle exec /opt/rubies/ruby-2.5.7/bin/rake hello_world >> /var/log/hello_world.log 2>&1 # rakeコマンド打つ。例ではhello_worldというrakeメソッドをすでに定義しているとする。 # ログも出力。 exit 0 commands: remove_old_cron: command: "rm -f /etc/cron.d/hello_world.bak" # バックアップを削除参考
Linux サーバーでのソフトウェアのカスタマイズ
https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/customize-containers-ec2.html#linux-filesRuby on Railsの環境構築をElastic Beanstalkで行う
https://qiita.com/yuyasat/items/4d93b4ad4f86a6e13d50#cron%E3%81%A7rails-runner%E3%82%84rake%E3%82%BF%E3%82%B9%E3%82%AF%E3%82%92%E5%8B%95%E3%81%8B%E3%81%99
- 投稿日:2019-11-15T16:47:02+09:00
またお前か mysql2 gemのインストール失敗
問題
これと関連がある記事を今まで2回作成しましたが、また新たな環境でmysql2 gemのインストールに失敗しました。
既存の方法ではないやり方で解決したので、記事を作成しました。Installing mysql2 0.5.2 with native extensions Gem::Ext::BuildError: ERROR: Failed to build gem native extension. current directory: /Users/jhpark/works/practice/js/react_with_rails/vendor/bundle/ruby/2.6.0/gems/mysql2-0.5.2/ext/mysql2 /usr/local/var/rbenv/versions/2.6.5/bin/ruby -I /usr/local/var/rbenv/versions/2.6.5/lib/ruby/2.6.0 -r ./siteconf20191115-56779-10cp3mw.rb extconf.rb checking for rb_absint_size()... yes checking for rb_absint_singlebit_p()... yes checking for rb_wait_for_single_fd()... yes ----- Using mysql_config at /usr/local/bin/mysql_config ----- checking for mysql.h... yes checking for errmsg.h... yes checking for SSL_MODE_DISABLED in mysql.h... yes checking for SSL_MODE_PREFERRED in mysql.h... yes checking for SSL_MODE_REQUIRED in mysql.h... yes checking for SSL_MODE_VERIFY_CA in mysql.h... yes checking for SSL_MODE_VERIFY_IDENTITY in mysql.h... yes checking for MYSQL.net.vio in mysql.h... yes checking for MYSQL.net.pvio in mysql.h... no checking for MYSQL_ENABLE_CLEARTEXT_PLUGIN in mysql.h... yes checking for SERVER_QUERY_NO_GOOD_INDEX_USED in mysql.h... yes checking for SERVER_QUERY_NO_INDEX_USED in mysql.h... yes checking for SERVER_QUERY_WAS_SLOW in mysql.h... yes checking for MYSQL_OPTION_MULTI_STATEMENTS_ON in mysql.h... yes checking for MYSQL_OPTION_MULTI_STATEMENTS_OFF in mysql.h... yes checking for my_bool in mysql.h... no ----- Don't know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load ----- ----- Setting libpath to /usr/local/Cellar/mysql/8.0.18_1/lib ----- creating Makefile current directory: /Users/jhpark/works/practice/js/react_with_rails/vendor/bundle/ruby/2.6.0/gems/mysql2-0.5.2/ext/mysql2 make "DESTDIR=" clean current directory: /Users/jhpark/works/practice/js/react_with_rails/vendor/bundle/ruby/2.6.0/gems/mysql2-0.5.2/ext/mysql2 make "DESTDIR=" compiling client.c compiling infile.c compiling mysql2_ext.c compiling result.c compiling statement.c linking shared-object mysql2/mysql2.bundle ld: library not found for -lssl clang: error: linker command failed with exit code 1 (use -v to see invocation) make: *** [mysql2.bundle] Error 1 make failed, exit code 2 Gem files will remain installed in /Users/jhpark/works/practice/js/react_with_rails/vendor/bundle/ruby/2.6.0/gems/mysql2-0.5.2 for inspection. Results logged to /Users/jhpark/works/practice/js/react_with_rails/vendor/bundle/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/mysql2-0.5.2/gem_make.out An error occurred while installing mysql2 (0.5.2), and Bundler cannot continue. Make sure that `gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/'` succeeds before bundling. In Gemfile: mysql2肝心のところはここ。
ld: library not found for -lsslmysql2のコンパイルに必要なopensslのライブラリーを探せないということです。
brewでopensslをインストールしようとしたんですが、なぜかインストールできませんでした。
代わりにopenssl@1.1をインストールしましたが、問題は解決できず。解決
.zshrcファイルでLIBRARY_PATHがopensslのままだったので、openssl@1.1で修正したら無事にコンパイルされてインストールできました。
export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/opt/openssl@1.1/lib/この世界は色々と変化が早いですね。
opensslも後ろにバージョンが付いたformulaが出たんですから、また他のバージョンが付いたformulaも出ると思います。要はmysql2 gemはopensslが必要でopensslのlibrary pathをシェルの設定ファイルに書かなければならない。
opensslはバージョンアップによってlibrary pathが変更される可能性があるため、その都度正しいパスで修正しなければならない。以上。
- 投稿日:2019-11-15T16:32:57+09:00
link_to をブロックで囲って each で回す方法(link_to do ~ end)
user/index.html.rb<% @users.each |user| do %> <%= link_to user_path(user) do %> <%= @user.name %> <% end %> <% end %>上記のように、link_to do〜end で囲えば良い。
- 投稿日:2019-11-15T14:00:46+09:00
[メモ]2019/11/15 Ruby on Rails
Railsでページを表示するのには以下の3つのファイルが必要
ページの作成に必要なもの
・ビュー(View)
・コントローラ(Controller)
・ルーティング(routing)
ビュー(View)
ビュー(View)とはページの見た目を作るHTMLファイル。
ブラウザとのやり取りの中で,Railsからビューが返されページが表示される。コントローラ(Controller)
ページを表示するときに、Railsではコントローラを経由してビューをブラウザに返す。
「rails g controller home top」を実行したとき、「home_controller.rb」というコントローラファイルが生成され、ファイルの中に「topメソッド」が追加される。
コントローラーのメソッドを「アクション」と呼ぶ。メソッドとは、「オブジェクト(もの)の操作を定義したもの」
例)オブジェクト:マリオとすると
メソッド 操作
・ジャンプする。
・走る
・しゃがむetc...
3. ルーティング(routing)
ブラウザとコントローラを繋ぐ。ページが表示されるまでに、
ルーティング→コントローラ→ビューという順番で処理が行われる。
- 投稿日:2019-11-15T13:47:33+09:00
railsの復習
Railsでの処理のポイント
- ルーティング
- コントローラー
- モデル
- ビュー
モデル、ビュー、コントローラーをまとめて
MVCという。HTTPメソッド
- GET ページを表示する操作
- POST データを登録する操作
- PUT データを変更する操作
- DELETE データを削除する操作
コントローラ アクション
- index 一覧表示リクエストアクション
- new 新規投稿リクエストアクション
- create データ投稿リクエストアクション
- show 個別詳細表示リクエストアクション
- edit 投稿編集表示リクエストアクション
- update データ編集リクエストアクション
- destroy データ削除リクエストアクション
部分テンプレート
renderメソッドを使う。
renderメソッドのpartialオプションでファイルの指定を行う。
部分テンプレートのファイル名の先頭は「_(アンダーバー)」で始める。★以下、省略表示が鬼。順々に教えてもらいたいところ。部分テンプレートの理解が浅いうちから最終省略形見せられたら大混乱間違いなしだわ・・・。でも改めて見ると、やりたいことをササッとやってくれ、って考えると最終形の方が直感的に分かるか、って気がしないでもないが、経緯を理解した上で使ってかないと、部分テンプレートがどれなのか分からなくなりそう。
<% @users.each do |user| %> <%= render partial: "user", locals: { user: user } %> <% end %><%= render partial: "user", collections: "@users" %><%= render @users%>フォーム入力データの取得
form-tagに入力されたデータは、params.permit(フォームの名前定義)で取得。
form-withに入力されたデータは、params.require(モデル名).permit(フォームの名前定義)で取得。
form-withの場合、form-tagに比較して深い階層に保存される為、require()が必要になる。フォームの入力データは、プライベートメソッドにて取得することで、Classの外部から呼び出されないようにする。バリデーション 一定の制約をかける
空のデータを登録できないようにするような場合に使用。
validates フォームの名前定義, presence: truelink_to
link_to 表示するテキスト, prefixもしくは、URIパターン, HTTPメソッド
HTTPメソッドを省略した場合は、GETで実行されるので注意する。ridirect_to
コントローラのビューに関わらず、特定のページにとばす。
ridirect_to アクション名 (実行条件?)コードの記述(試験対策)
スコープの意識は・・・まだあんまりしなくていいのかな。
コードの書き順はよく確認すること。routes.rbのコードの書き順に注意。
いつもは頭に書いてるのが末尾にあったり、
先に定義していないといけないコードを、後ろに書いてあったりする。
railsのエラー修正問題ヤベェな。
どないしょ。
- 投稿日:2019-11-15T12:42:48+09:00
ApplicationControllerでStandardErrorをrescue_fromするときに少しでも開発しやすく
背景
Ruby on RailsでAPIサーバーを実装するとき、予期しないバグでアプリケーションに例外が起きたときに、500エラーレスポンスで固定のメッセージを返したいときがあります。
ApplicationController
に、次のような関数呼び出しを書いて、アプリケーションで共通の例外処理を実装するでしょう。rescue_from StandardError, with -> do render json: { message: 'Something wrong' }, status: :internal_server_error end問題
問題1: 開発中も例外が表示されない
開発中に出た例外が起きたときも、固定メッセージしか情報が得られません。
デバッグのためにいちいち上記の実装をコメントアウトするのも手間です。解法: 環境変数を見て例外処理をスキップ
Ruby on Railsは環境変数で開発環境と本番環境を切り替えます。
このルールに乗り、次のように開発中の場合は例外処理をスキップします。rescue_from StandardError, with -> do # 開発中は例外をキャッチしません。 raise exception if Rails.env.development? render plain: 'Something wrong', status: :internal_server_error end問題2: テストではレスポンスコードやメッセージを本番と同じにしたい
RSpec
などのテストコードでは本番の振る舞いを確認したいです。
ですので開発中環境のように、例外処理そのものをスキップする実装は望ましくありません。解法: 例外の内容を標準出力に
例外処理の実装はそのままに、例外の内容を標準出力に書き出します。
本場環境では余計な情報が出力されないように、ここでも環境変数を見て出力するかどうかを切り替えます。rescue_from StandardError, with ->(exception) do puts exception.backtrace if Rails.env.test? render json: { message: 'Something wrong' }, status: :internal_server_error end問題3: 例外のバックトレースが長すぎて読みにくい
通常
RSpec
を実行する時は、複数のテストケースを実行します。
予期しない例外が起きる時は、複数のテストケースで起きることがあるでしょう。
そのときにバックトレースをすべて表示すると標準出力の表示が流れすぎて見るのが大変です。解法: ActiveSupport::BacktraceCleanerで出力するバックトレースの情報を減らす
Ruby on RailsにはActiveSupport::BacktraceCleanerというバックトレースの情報を減らすためのクラスが用意されています。
これを使って、gem
(依存ライブラリ)とRSpec
に関するバックトレースの表示をしません。rescue_from StandardError, with ->(exception) do if Rails.env.test? bc = ActiveSupport::BacktraceCleaner.new bc.add_silencer { |line| line =~ %r{gems|/rspec} } puts bc.clean exception.backtrace end render json: { message: 'Something wrong' }, status: :internal_server_error end問題3: 例外の色が黒字だと出力から例外情報をみつけられない
通常
RSpec
を実行する時は、複数のテストケースを実行します。
予期しない例外が起きる時は、複数のテストケースで起きることがあるでしょう。
たくさんの標準出力の内容から例外を目grepするのは大変です。解法: 例外表示の1行目を赤くする
多くのターミナルはANSI escape codeを使うと出力文字に色をつけられます。
これを使って例外名に色を付けます。
またRSPecのエラーと合わせて赤字にすると、情報が必要以上に増えず読みやすくなります。rescue_from StandardError, with ->(exception) do if Rails.env.test? puts "\e[31m", exception.class, "\t#{exception.message}\e[0m" bc = ActiveSupport::BacktraceCleaner.new bc.add_silencer { |line| line =~ %r{gems|/rspec} } puts bc.clean exception.backtrace end render json: { message: 'Something wrong' }, status: :internal_server_error end問題4: 独自例外が出力される
アプリケーション固有の例外処理を独自例外を使って共通化することがあります。
例外処理の対象をStandardError
にしていると、これらの独自例外も標準出力に表示します。
また、共通の例外処理が上手く実装できているか確認するためのテストコードを書くこともあります。
その場合は、テストを実行すると常に独自例外が標準出力に表示されます。
バックトレースまで表示するので、馬鹿になりません。
偽陽性に慣れると、本当の例外まで見逃すようになります。解法: 独自例外の時は標準出力に表示しない
例外の出力処理に、キャッチした例外が自作の独自例外か確認するガード条件を追加します。
rescue_from StandardError, with ->(exception) do if Rails.env.test? # MyErrorは、意図した例外なので出力しません。 puts_detail_of exception unless exception.is_a? MyError end render json: { message: 'Something wrong' }, status: :internal_server_error end def puts_detail_of(exception) bc = ActiveSupport::BacktraceCleaner.new bc.add_silencer { |line| line =~ %r{gems|/rspec} } puts "\e[31m", exception.class, "\t#{exception.message}\e[0m" puts bc.clean exception.backtrace endおわりに
良き開発を、例外とともに
参考文献リスト
- 投稿日:2019-11-15T12:37:41+09:00
独学でプログラミング学習を始めて1.5か月の振り返りと独学者へのアドバイス
独学でプログラミング学習を始めて1.5か月ほどがたちました。転職、ポートフォリオ等なにも成果がない状態ですが、私自身の振り返りとアウトプットの習慣付けのために学習内容や反省点、これから独学される方へのアドバイスなどを投稿します。技術的なことは書いていません。
対象読者
・独学でプログラミング学習を始める方
・独学で挫折しそうな方自己紹介
・24歳、新卒1年目、メーカー営業職で工場研修中(長い)
・過去2度プログラミングの独学挫折(HTML,CSS,PHP)
・2109年9月末から3度目の独学スタートなぜ独学か
・お金がないからです。
学習内容
・HTML(復習)
・CSS(復習)
・Ruby
・Ruby on Rails
・MySQL
・Bootstrap
・AWS
・Docker
チュートリアルやドットインストール、YouTubeなどの教材を利用し、手を動かしながら学習しています。現在はVM上のcentOS環境にDocker×Rails×MySQLでポートフォリオ製作中です。学習時間
・平日2時間
・休日4~10時間
平日は仕事前に0~1時間、帰宅後1~2時間という感じです。睡眠時間を削ることもありましたが翌日の体調が著しく悪化するため睡眠優先してます。その他、通勤・休憩などでPCを触れない自由時間が4時間以上あるので、その時間に技術書を読んだり、用語の理解に努めたりしています。
土曜日は朝から晩まで。日曜日は最低半日(約4時間)で後はリフレッシュに時間を使うようにしています。成長したこと
・独学で1.5か月継続している!!
過去2回は2週間ほどで辞めてます。「エラーが出ることと向き不向きは関係ない」と考えるようになったのが一番の要因だと思います。・コンピューター、プログラムとは何かということを学んだ
「コンピューターにはどんな機能があるのか」、「プログラムとは何か」を理解しないままプログラミングやってみたいと言っていた自分から卒業しました。・公式ドキュメントを参考にするようになった。
Qiitaファーストでコピペとかしてましたが、用語や概念を少しずつ理解していくうちに公式ドキュメントに抵抗がなくなりました。(幸い英語のリーディングには抵抗がありません。)反省
・プログラムを書く時間が少ない。
一番の反省点は、肝心のRuby, Rails, SQLへの理解が深まる前からDockerやAWSにも手を出してしまったことです。ただでさえ学習効率が悪いにも関わらず、DockerやAWSでの環境構築に時間がかかるうちに学習したことが頭から抜けていくという状態でした。・アウトプット不足
エラーを解決した経験、隙間時間に学んだ用語などをQiitaに投稿する習慣が大切だと分かっていながらなかなかできませんでした。今の悩み
・ポートフォリオがRailsチュートリアルのサンプルに寄せられていく。デザインに気を遣わないとチュートリアルと何が違うのかわからなくなります。
今後の方針
・基礎固め
ポートフォリオ製作を通して今まで学んだ技術への理解を深めていきます。新しい技術には手を出しません。・アウトプットの習慣化
独学の最大のデメリットはアウトプット→フィードバックの機会が少ないために学習効率が悪いことだと感じています。(自走というより自歩です。)
自らアウトプット+フィードバックを得られる場を確保しなければインプットの質も下がり、自己満の勉強になってしまいます。
MENTAなどのサービスでメンターお願いします。独学で学習を始めた方へアドバイス(自戒を込めて)
・エラーは解決できる
個人的に過去の挫折の原因は「エラーが出た→プログラミングに向いてない...」と考えてしまうことがでした。
「エラー発生→対応→解決しない」ということがよくあると思います。「対応して解決しなかった」ということは、プログラミングの向き不向きとは関係がありません(少なくとも始めて数か月は)。「うまくいかない方法を1つ見つけた」ということです。つまり解決に近づいているのです。初心者が出すエラーなど大したことはありません。検索して、ひたすら試せば解決できます。
「初心者がやるエラーはだいたい経験したかな」と思えるまでやってみてください。・とりあえずPC立ち上げましょう
高すぎる目標やハードルを定めることは挫折のもとです。初めはとりあえず毎日PC立ち上げればオッケーぐらいの気持ちでいいかもしれません。私はたまにサボりそうになりますが、そのときはPC立ち上げることを目標にします。結局触りだすと楽しくなります。時にはハードルを思いっきり下げることも継続の秘訣です。おわりに
スクールに通わないと決めた以上、自分がもつリソースを最大限活用して学習効率を上げていくのみです。少しずつ技術的なこと、サンプルアプリなどもアウトプットしていきます。
アドバイスいただけると幸いです。
- 投稿日:2019-11-15T11:53:08+09:00
Ruby on Rails チュートリアル第8章 一時cookieによるログイン機構
一時cookieによるログイン機構
この記事ではログインの基本的な仕組みを実装する。ログインの基本的な仕組みとは、ブラウザがログインしている状態を保持し、ユーザーによってブラウザが閉じられたら状態を破棄するといった仕組み (認証システム (Authentification System))である。
セッション
HTTPはステートレス (Stateless) なプロトコルである。文字通り「状態 (state)」が「ない (less)」ので、HTTPのリクエスト1つ1つは、それより前のリクエストの情報をまったく利用できない。この特性のため、ブラウザのあるページから別のページに移動したときに、ユーザーのIDを保持しておく手段がHTTPプロトコル内「には」まったくない。ユーザーログインの必要なWebアプリケーションでは、セッション (Session) と呼ばれる半永続的な接続をコンピュータ間 (ユーザーのパソコンのWebブラウザとRailsサーバーなど) に別途設定する。
Railsでセッションを実装する方法として最も一般的なのは、cookiesを使う方法である。cookiesとは、ユーザーのブラウザに保存される小さなテキストデータあり、あるページから別のページに移動した時にも破棄されないので、ここにユーザーIDなどの情報を保存できる。アプリケーションはcookies内のデータを使って、例えばログイン中のユーザーが所有する情報をデータベースから取り出すことができる。今回はsessionというRailsのメソッドを使って一時セッションを作成する。HTTPはリクエストの情報を全く保存出来ないので、ユーザーのブラウザに保存されるcookiesという小さなテキストデータにユーザーIDなどの情報を保存する。このcookieを用いて、セッション (Session) と呼ばれる半永続的な接続をコンピュータ間 (ユーザーのパソコンのWebブラウザとRailsサーバーなど) に設定する。今回はsessionというRailsのメソッドを使ってブラウザを閉じると破棄される一時セッションを作成する。
セッションをRESTfulなリソースとしてモデリングできると、他のRESTfulリソースと合わせて理解しやすい。ログインページではnewで新しいセッションを出力し、そのページでログインするとcreateでセッションを実際に作成して保存し、ログアウトするとdestroyでセッションを破棄する、といった風に。ただしUsersリソースではバックエンドでUserモデルを介してデータベース上の永続的データにアクセスするのに対し、Sessionリソースでは代わりにcookiesを保存場所として使うという違いがある。セッションコントローラ
ログインとログアウトの要素を、Sessionsコントローラの特定のRESTアクションにそれぞれ対応付ける。ログインのフォームは、newアクションで処理する。createアクションにPOSTリクエストを送信すると、実際にログインし、destroyアクションにDELETEリクエストを送信すると、ログアウトする。
$ rails generate controller Sessions newまず、上のコードでセッションコントローラとnewアクションを作成する。このnewアクションに対応するビューでログインフォームのページを作成する。
Usersリソースのときは専用のresourcesメソッドを使ってRESTfulなルーティングを自動的にフルセットで利用できるようにしたが、Sessionリソースではフルセットはいらないので、以下のように「名前付きルーティング」だけを使う。
config/routes.rbRails.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 endこれで、getリクエストを/loginに発行すればセッションコントローラのnewアクションを呼び出せる(名前つきルートはlogin_path)などといった操作が実現される。
ログインフォーム
コントローラとルーティングを定義したので、今度は新しいセッションで使うビュー、つまりログインフォームを整える。ログインフォームとユーザー登録フォームの違いは、4つあったフィールドが [Email] と [Password] の2つに減っていることだけである。
ユーザー登録フォームでform_forヘルパーを使い、ユーザーのインスタンス変数@userを引数にとっていた。<%= form_for(@user) do |f| %> . . . <% end %>しかし、セッションにはSessionモデルというものがなく(ユーザー登録においてはユーザーモデルが存在した)、そのため@userのようなインスタンス変数に相当するものもない。したがって、新しいセッションフォームを作成するときには、form_forヘルパーに追加の情報を独自に渡さなければならない。
form_for(@user)Railsではユーザーモデルがある場合は上のように書くだけで、「フォームのactionは/usersというURLへのPOSTである」と自動的に判定する、セッションの場合はリソースの名前とそれに対応するURLを以下のように具体的に指定する必要がある。
form_for(:session, url: login_path)以上を踏まえると、ログインフォームのコードは以下のようになる。
app/views/sessions/new.html.erb<% provide(:title, "Log in") %> <h1>Log in</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for(:session, url: login_path) do |f| %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.submit "Log in", class: "btn btn-primary" %> <% end %> <p>New user? <%= link_to "Sign up now!", signup_path %></p> </div> </div>ユーザーがすぐクリックできるように、ユーザー登録ページのリンクを追加してある。フォームに入力した値が、paramsハッシュのparams[:session][:email]とparams[:session][:password]となる。
なお、paramsは次のような入れ子ハッシュになっている。ハッシュの中にハッシュがある構造である。{ session: { password: "foobar", email: "user@example.com" } }ユーザーの検索と検証
最初に、セッションコントローラーのcreateアクションを完成させていく。createアクションではユーザー認証に必要なあらゆる情報をparamsハッシュから取り出せる。
app/controllers/sessions_controller.rbclass SessionsController < ApplicationController def new end def create user = User.find_by(email: params[:session][:email].downcase) #フォームに入力したメールアドレスと同じメールアドレスをもつユーザーをデータベースから探す if user && user.authenticate(params[:session][:password]) #フォームに入力したパスワードをハッシュ化したものと、先ほど探したユーザのpassword_digestと一致するかを確認する # ユーザーログイン後にユーザー情報のページにリダイレクトする else # エラーメッセージを作成する render 'new' end end def destroy end end
- 投稿日:2019-11-15T11:53:08+09:00
Ruby on Rails チュートリアル第8章 一時cookieによるログイン機構
一時cookieによるログイン機構
この記事ではログインの基本的な仕組みを実装する。ログインの基本的な仕組みとは、ブラウザがログインしている状態を保持し、ユーザーによってブラウザが閉じられたら状態を破棄するといった仕組み (認証システム (Authentification System))である。
セッション
HTTPはステートレス (Stateless) なプロトコルであり、リクエストの情報を全く保存出来ないので、ユーザーのブラウザに保存されるcookiesという小さなテキストデータにユーザーIDなどの情報を保存する。このcookieを用いて、セッション (Session) と呼ばれる半永続的な接続をコンピュータ間 (ユーザーのパソコンのWebブラウザとRailsサーバーなど) に設定する。今回はsessionというRailsのメソッドを使ってブラウザを閉じると破棄される一時セッションを作成する。
セッションをRESTfulなリソースとしてモデリングできると、他のRESTfulリソースと合わせて理解しやすい。ログインページではnewで新しいセッションを出力し、そのページでログインするとcreateでセッションを実際に作成して保存し、ログアウトするとdestroyでセッションを破棄する、といった風に。ただしUsersリソースではバックエンドでUserモデルを介してデータベース上の永続的データにアクセスするのに対し、Sessionリソースでは代わりにcookiesを保存場所として使うという違いがある。セッションコントローラ
ログインとログアウトの要素を、Sessionsコントローラの特定のRESTアクションにそれぞれ対応付ける。ログインのフォームは、newアクションで処理する。createアクションにPOSTリクエストを送信すると、実際にログインし、destroyアクションにDELETEリクエストを送信すると、ログアウトする。
$ rails generate controller Sessions newまず、上のコードでセッションコントローラとnewアクションを作成する。このnewアクションに対応するビューでログインフォームのページを作成する。
Usersリソースのときは専用のresourcesメソッドを使ってRESTfulなルーティングを自動的にフルセットで利用できるようにしたが、Sessionリソースではフルセットはいらないので、以下のように「名前付きルーティング」だけを使う。
config/routes.rbRails.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 endこれで、getリクエストを/loginに発行すればセッションコントローラのnewアクションを呼び出せる(名前つきルートはlogin_path)などといった操作が実現される。
ログインフォーム
コントローラとルーティングを定義したので、今度は新しいセッションで使うビュー、つまりログインフォームを整える。ログインフォームとユーザー登録フォームの違いは、4つあったフィールドが [Email] と [Password] の2つに減っていることだけである。
ユーザー登録フォームでform_forヘルパーを使い、ユーザーのインスタンス変数@userを引数にとっていた。<%= form_for(@user) do |f| %> . . . <% end %>しかし、セッションにはSessionモデルというものがなく(ユーザー登録においてはユーザーモデルが存在した)、そのため@userのようなインスタンス変数に相当するものもない。したがって、新しいセッションフォームを作成するときには、form_forヘルパーに追加の情報を独自に渡さなければならない。
form_for(@user)Railsではユーザーモデルがある場合は上のように書くだけで、「フォームのactionは/usersというURLへのPOSTである」と自動的に判定する、セッションの場合はリソースの名前とそれに対応するURLを以下のように具体的に指定する必要がある。
form_for(:session, url: login_path)以上を踏まえると、ログインフォームのコードは以下のようになる。
app/views/sessions/new.html.erb<% provide(:title, "Log in") %> <h1>Log in</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for(:session, url: login_path) do |f| %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.submit "Log in", class: "btn btn-primary" %> <% end %> <p>New user? <%= link_to "Sign up now!", signup_path %></p> </div> </div>ユーザーがすぐクリックできるように、ユーザー登録ページのリンクを追加してある。フォームに入力した値が、paramsハッシュのparams[:session][:email]とparams[:session][:password]となる。
なお、paramsは次のような入れ子ハッシュになっている。ハッシュの中にハッシュがある構造である。{ session: { password: "foobar", email: "user@example.com" } }ユーザーの検索と検証
最初に、セッションコントローラーのcreateアクションを完成させていく。createアクションではユーザー認証に必要なあらゆる情報をparamsハッシュから取り出せる。
app/controllers/sessions_controller.rbclass SessionsController < ApplicationController def new end def create user = User.find_by(email: params[:session][:email].downcase) #フォームに入力したメールアドレスと同じメールアドレスをもつユーザーをデータベースから探す if user && user.authenticate(params[:session][:password]) #フォームに入力したパスワードをハッシュ化したものと、先ほど探したユーザのpassword_digestと一致するかを確認する # ユーザーログイン後にユーザー情報のページにリダイレクトする else # エラーメッセージを作成する render 'new' end end def destroy end end入力されたメールアドレスを持つユーザーがデータベースに存在し、かつ入力されたパスワードがそのユーザーのパスワードである場合のみ、if文がtrueになる。
ログイン
この節では、cookiesを使った一時セッションでユーザーをログインできるようにする。このcookiesは、ブラウザを閉じると自動的に有効期限が切れるものを使う。
sessionを実装するには、Sessionsコントローラを生成した時点で既に自動生成されているセッション用ヘルパーモジュールを、Applicationコントローラにこのモジュールを読み込ませる必要がある。app/controllers/application_controller.rbclass ApplicationController < ActionController::Base protect_from_forgery with: :exception include SessionsHelper endlog_in メソッド
Railsで事前定義済みのsessionメソッドを使って、単純なログインを行えるようにする (なお、これは先ほど生成したSessionsコントローラとは無関係である)。このsessionメソッドはハッシュのように扱えるので、次のように代入する。
session[:user_id] = user.id上のコードを実行すると、ユーザーのブラウザ内の一時cookiesに暗号化済みのユーザーIDが自動で作成される。この後のページで、session[:user_id]を使ってユーザーIDを元通りに取り出すことができる。sessionメソッドで作成された一時cookiesは、ブラウザを閉じた瞬間に有効期限が終了する。
同じログイン手法を様々な場所で使い回せるようにするために、Sessionsヘルパーにlog_inという名前のメソッドを定義する。
app/helpers/sessions_helper.rbmodule SessionsHelper # 渡されたユーザーでログインする def log_in(user) session[:user_id] = user.id end endsessionメソッドで作成した一時cookiesは自動的に暗号化され、上のコードは保護される。そして重要な事には、攻撃者がたとえこの情報をcookiesから盗み出すことができたとしても、それを使って本物のユーザーとしてログインすることはできない。ただし今述べたことは、sessionメソッドで作成した「一時セッション」にしか該当せず。cookiesメソッドで作成した「永続的セッション」には当てはまらない。
以下にlog_inメソッドを含むcreateアクションを示す。app/controllers/sessions_controller.rbclass SessionsController < ApplicationController def new end def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) log_in user redirect_to user else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end def destroy end endこのコードにより、ユーザーが認証に成功すると、log_inメソッドにより一時セッションにユーザーIDが保存されログインし、ユーザーのプロフィールページにリダイレクトされる。
- 投稿日:2019-11-15T10:26:15+09:00
enumで、キーの文字列に数字を含める方法
*前提*
schema.rbt.integer "sex" t.integer "age"通常のenum(文字列のみ)の書き方は以下の通り。
user.rbenum sex: { 未選択: 0, 男性: 1, 女性: 2 }enumで数字を含めるには以下のように
user.rbenum age: { 未選択: 0, "10代": 1, "20代": 2, "30代": 3, "40代": 4, "50代": 5, "60代": 6, "70代": 7, "80代": 8, "90代": 9 }キーをダブルクオーテーション("")で囲えば良い。
むしろ本来こんなふうに書くべきなのかもしれない。
- 投稿日:2019-11-15T04:39:30+09:00
【初学者向け】 Railsのform_forでプルダウン(セレクトボックス)を実装してみる
概要
今回はrailsアプリケーションにおいてフォームを送信する際に、以下のような簡単なプルダウンを実装できるようにします。
例
上記の例は以下のようなコードで実装できます。
hoge/view/new.html.erb<%= form_for @hoge do |f| %> # 以下の一行がプルダウンを実装するためのコード <%= f.select :faculty, [["理工学部", "理工学部"], ["経済学部", "経済学部"], ["法学部", "法学部"], ["医学部", "医学部"]], include_blank: "選択して下さい" %> <% end %>一般形
コードを一般形に直すと以下のようになります。
hoge/view/new.html.erb<%= form_for @hoge do |f| %> # 以下の一行がプルダウンを実装するためのコード <%= f.select :保存先のカラム名, [["選択肢1", "実際にDBに保存させる内容"], ["選択肢2", "実際にDBに保存させる内容"]], include_blank: "選択して下さい" %> <% end %>「保存先のカラム名」には、プルダウンで選択した値を送信する際に保存したいカラムの名前を記述します。
「選択肢」と「実際にDBに保存させる内容」は基本一緒にして大丈夫です。
(「実際にDBに保存させる内容」を変えれば、"経済学部"を選択して送信した際に保存される内容を例えばtrueや2などの整数にする、といったことができます。)参考記事
- 投稿日:2019-11-15T04:39:30+09:00
Railsのform_forでプルダウン(セレクトボックス)を実装してみる
概要
今回はrailsアプリケーションにおいてフォームを送信する際に、以下のような簡単なプルダウンを実装できるようにします。
例
上記の例は以下のようなコードで実装できます。
hoge/view/new.html.erb<%= form_for @hoge do |f| %> # 以下の一行がプルダウンを実装するためのコード <%= f.select :faculty, [["理工学部", "理工学部"], ["経済学部", "経済学部"], ["法学部", "法学部"], ["医学部", "医学部"]], include_blank: "選択して下さい" %> <% end %>一般形
コードを一般形に直すと以下のようになります。
hoge/view/new.html.erb<%= form_for @hoge do |f| %> # 以下の一行がプルダウンを実装するためのコード <%= f.select :保存先のカラム名, [["選択肢1", "実際にDBに保存させる内容"], ["選択肢2", "実際にDBに保存させる内容"]], include_blank: "選択して下さい" %> <% end %>「保存先のカラム名」には、プルダウンで選択した値を送信する際に保存したいカラムの名前を記述します。
「選択肢」と「実際にDBに保存させる内容」は基本一緒にして大丈夫です。
(「実際にDBに保存させる内容」を変えれば、"経済学部"を選択して送信した際に保存される内容を例えばtrueや2などの整数にする、といったことができます。)参考記事
- 投稿日:2019-11-15T04:39:30+09:00
【Rails】 form_for でプルダウン(セレクトボックス)を実装してみる
概要
今回はrailsアプリケーションにおいてフォームを送信する際に、以下のような簡単なプルダウンを実装できるようにします。
例
上記の例は以下のようなコードで実装できます。
hoge/view/new.html.erb<%= form_for @hoge do |f| %> # 以下の一行がプルダウンを実装するためのコード <%= f.select :faculty, [["理工学部", "理工学部"], ["経済学部", "経済学部"], ["法学部", "法学部"], ["医学部", "医学部"]], include_blank: "選択して下さい" %> <% end %>一般形
コードを一般形に直すと以下のようになります。
hoge/view/new.html.erb<%= form_for @hoge do |f| %> # 以下の一行がプルダウンを実装するためのコード <%= f.select :保存先のカラム名, [["選択肢1", "実際にDBに保存させる内容"], ["選択肢2", "実際にDBに保存させる内容"]], include_blank: "選択して下さい" %> <% end %>「保存先のカラム名」には、プルダウンで選択した値を送信する際に保存したいカラムの名前を記述します。
「選択肢」と「実際にDBに保存させる内容」は基本一緒にして大丈夫です。
(「実際にDBに保存させる内容」を変えれば、"経済学部"を選択して送信した際に保存される内容を例えばtrueや2などの整数にする、といったことができます。)参考記事
- 投稿日:2019-11-15T02:13:08+09:00
フォロー機能 User.rbとRelationship.rb
Relationship.rbclass Relationship < ApplicationRecord belongs_to :follower, class_name: "User" belongs_to :followed, class_name: "User" endRelationshipモデル(中間モデル)には、
follower (フォローする人)
followed(フォローされる人)
が存在している。user.rbhas_many :follower, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy # フォロー取得。Relationshipモデルのfollower_idにuser_idを格納 has_many :followed, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy # フォロワー取得。followed_idにuser_idを格納上2行は、user主体で得る情報。
特定のアクションが起きた際、user_idが格納される場所をforeign_keyで指定することで、follower_id および followed_id の適切な方にuser_idを格納。また、フォロー数等のときは、ユーザ情報の中身を必要としないので
<% current_user.follower.each do |user| %>
<%= user.count %>
<% end %>のように、followerをとってよい(フォロー数取得)。
(フォロワー数を取得する場合は、followed)
user.rbhas_many :following_user, through: :follower, source: :followed #following_userを命名。自分がフォローしているユーザ情報を取得。 has_many :follower_user, through: :followed, source: :follower #自分をフォローしているユーザ情報を取得。ユーザ情報を詳しく必要とする場合は、こちら(emailなど)。
<% current_user.following_user.each do |user| %>
<%= user.email %>
<% end %>following_userで、followerテーブルを通じて、followedテーブルを参照。
- 投稿日:2019-11-15T01:42:01+09:00
ユーザ一覧で"フォローする"ボタンを押したときの挙動 備忘録
user/index/html.erb<% if user != current_user %> <% if current_user.following?(user) %> <%= link_to 'フォロー解除', unfollows_path(user.id), method: :POST %> <% else %> <%= link_to 'フォローする', follows_path(user.id), method: :POST %> <% end %> <% end %>上の 'フォローする' ボタンを押すと、follows_path が読み込まれる
follows_pathは
routes.rbpost 'follows/:id' => 'relationships#follows', as: "follows" # フォローする。follows_pathの作成。上の as "follows"で名前付きパスをつけたもの。
follows_path が呼び出されると、
relationships#follows(relationshipsコントローラのfollowsアクション)
が呼び出されるrelationships.controller.rb# ユーザーをフォローする def follows(user_id) follower.create(followed_id: user_id) endフォロー完了。
- 投稿日:2019-11-15T01:37:18+09:00
【rails】jQueryでページカレントをつけようとして読み込み問題でハマった話
まずは完成図gif
書き加えたコードはこれ
sidebar.js$(window).load(function () { //.loadでページが完全に読み込まれたら実行されるようにする var href = $(".main-header__left-box__current-group").data("group-id") $(".group").each(function (i, ele) { if ($(ele).data("group-id") === href) { //group__side_currentが色の変わるclass $(this).addClass("group__side_current") //data-id一致でclassをつける } else { $(this).removeClass("group__side_current")//data-id一致しなければclass削除 } }); });上記のjqueryの動きを図解?するとこんな感じです。
タイトル部分にお勉強中にdata-idをグループ名部分につけるくだりがあり、再利用しました。
問題はこのコード
application.js//= require turbolinks
このコードがあることによって、
な ぜ か 挙動が不安定になりました。
その時のgifがこちら
- クリックすると何もおこらない
- リロードするとなぜかカレントがつく
この時コンソールに入れて確認しましたが、これは普通に動作しました。
turbolinks = Ajaxによるページ遷移の高速化のためのライブラリ
~中略~
遷移にあたって今のページと共有するリソース(css/img/js)の再読込が不要となるため、読み込みが速くなる。
なお、フックイベントを使って遷移をキャンセルできる。
turbolinksチートシートより抜粋再読み込みを不要にしてるため、ページが変わったことによるcssの付与やjsの再試行が行われてなく、うまく動作してないみたいですね。
turbolinksにおいて、リンクがクリックからページ表示までの一連の流れをVisitと呼ぶ。
Visitは大きく二種類:
Application Visits = リンククリックによるページ遷移
Restoration Visits = ブラウザの戻る/進むボタンによるページ遷移
turbolinksチートシートより抜粋リロードすると動作しているのは、「turbolinks」を通したブラウザの動作でないため、
読み込みの挙動は通常通りに行われている。だからカレントがついたわけなんですね。うわぁ。なんてことだ。
犯人確定の証言記事じゃないか...たまげたな。今後の開発で気をつけようと思った点
現時点では「turbolinks」を削除しても動作に問題はない。
世に出回るサービスのように複雑でないし、アクセスがたくさんあるわけでもない、画像もあまり表示されていない。
大したことしてないから「速い」ので「turbolinks」の恩恵が正直わかりませんでした。しかしサービスを作るとなると、素早くスムーズに動くことは必須なので、
プロジェクトで「これを使おう!」ってなった際には、個別に読み込みさせて影響範囲を絞ってあげることが必要かと思いました。Railsで、任意のJavaScriptやCSSだけを読み込む
https://qiita.com/Oakbow/items/2e712e05bb4bbf68faf5↑こういうのとかで
参考記事
ナビゲーションをカレント表示する方法まとめ【jQueryとかWordPress】
https://webkcampus.com/201904/1598/【jQuery】「$(document).ready()」と「$(window).load()」の違いを極める!
https://on-ze.com/archives/1851