【Rails応用】コントローラーとビューを同時に作る方法/要素サイズの設定
ここからは応用的なことを説明していきます。
まずはコントローラーとビューを同時に作る方法を学習しましょう。
今までとコマンドが少しだけ異なります。
コントローラー作成時に、コントローラー名に続けてアクション名を指定します。
rails g controller messages index
こうすることで、
messagesコントローラーにindexアクションが定義され、
index.html.erbファイルが作成されました。
またもう一つ、要素サイズの設定を学びましょう。
paddingなどを使用してサイズを指定した場合、
意図しないサイズになることがあります。
そのためにbox_sizingプロパティを使用します。
CSSに
*{
box-sizing : border-box
}
と記載することでサイズを指定していても、
ボックスサイズに含まれるようになります。
また*というセクレタは、全ての要素に適用させるという意味です。
★【Rails】検索機能(collectionとmember)
今回は検索したテキストを含む投稿のみを
表示させられる機能を追加する方法を説明したいと思います。
トップページにフォームを表示させたいので、
index.html.erbにフォームを追加します。
<%= form_with ( url : search_posts_path , local : true , method : : get , class : "search-form" ) do | form | %>
<%= form. text_field : keyword, placeholder: "投稿を検索する", class: "search-input" %>
<%= form . submit "検索" , class : "search-btn" %>
<% end %>
これで入力フォームと検索ボタンを配置できました。
まだルーティングができていないので、
searchアクションのルーティングを行いましょう。
こちら7つのアクション以外のアクションですね。
ここでルーティングを設定したい際に使える
collectionとmemberを学習します。
これらを使用することによって生成されるルーティングのURLと
実行されるコントローラーを任意にカスタムできます。
この2つの違いは : id が紐づくかどうかです。
collectionは : id 無し
memberは : id 有り
となります。
では実際にルーティングを行います。
今回もポストを関連づける必要があるのでネストを利用しましょう。
また、 : id を使用して特定のページにいく必要はないので、
collectionを使います。
collection do
get ' search '
end
これでルーティングの完了です。
では次にメソッドの定義をしましょう。
メソッド名はsearchメソッドとします。
ビジネスロックという「データに対する処理」に関してはモデルに記載します。
具体的には「データをどのように処理するか」「どのデータを取得するのか」、
「どんな手順で処理していくのか」などを指します。
ということでsearchメソッドをPostモデルに定義しましょう。
そこで、検索したキーワードが含まれている投稿を取得するためには、
whereメソッドとLIKE句を使用します。
whereメソッドはモデルが使用できる
ActiveRecordメソッドの一つです。
モデル. where(条件)と引数に条件を指定すると、
一致したレコードのインスタンスを配列で取得できます。
なので条件には「検索対象となるカラム」を必ず含めましょう。
LIKE句は曖昧な文字列を検索するときに使用し、
whereメソッドと一緒に使用します。
def self.search ( search )
if search ! = " "
Post . where ( ' text LIKE (?) ' , "%#{ search}%" )
else
Post . all
end
end
引数のsearchにはフォームから送信されたパラメーターが入ります。
if search ! = " "というのは「フォームが空欄でなければ」
処理を実行するという意味ですね。
そうでなければPost . allで全てのデータが取得されます。
' text LIKE (?) ' , "%#{ search}%"
そしてここですね。
#{ search}が含まれているタイトルを表しています。
ここでの%は任意の文字列を指します。
textの部分はtitleなどに変更することができます。
ではコントローラーにアクションを定義しましょう、
def search
@posts = Post . search ( params [ : keyword ] )
end
ここでPostモデルに書いたsearchメソッドを呼び出してます。
またsearchメソッドの引数にparams [ : keyword ] を記載して
検索結果を渡しています。
これであとはビューを作成して完成です!
【Rails】ルーティングのネスト
今回、ルーティングのネストという方法を
説明していきたいと思います。
例えばコメント投稿機能を付けたい時、
新してくCommentモデルを作成して、テーブルも作成しますね。
そしてルーティングしたい時、今まで同様にroutes.rbに記述します。
resourcesの中で、コメント機能が実行するアクションはcreateアクションなので、only : createと指定しましょう。
さてここで一つ。
パスを確認すると、どの投稿に対するコメントなのかを示す情報はありません。
コメントする際には必ず紐づく投稿がありますよね。
ではどのようにして、どの投稿に対するコメントなのかを
パスから判断したらいいでしょうか。
ここで使用する方法がルーティングのネストです。
これは入れ子構造で示す方法です。
routes.rbを再度編集しましょう。
resources : posts do
resources : comments , only : : create
end
resources : users , only : : show
こういった形で、ルーティングの中にルーティングを記述します。
ネストを利用することでモデルと結びついている別のモデルのid情報が
送れるようになるのです。
つまりまとめると、ルーティングをネストさせる一番の理由は
アソシエーション先のレコードのidを
paramsに追加してコントローラーに送るためです。
今回はコメントと結びつく投稿のidをparamsに追加することができました。
そしてもちろんcreateアクションを定義する際にも
ストロングパラメーターを使用して保存できるカラムを指定します。
新たにcomment_paramsメソッドを定義しましょう。
def create
comment = Comment . create ( comment_params )
redirect_to " /posts/#{comment . post . id }"
end
private
def comment_params
params.require( : comment ). permit ( :text ) . merge ( user_id : current_user.id , post_id : params [ : post_id ] )
end
user_idカラムにはログインしているユーザーのidを保存し、
post_idカラムはparamsで渡されるようにするので、params [ : post_id ]として保存します。
そしてここ何?!と思ったかた多いかと思いますが、
redirect_to " /posts/#{comment.post.id }"
これは「コメントを保存したら投稿の詳細画面に戻ってね」と指示してます。
redirect_toの後にはルーティングのURLやPrefixを記述したら、
そのアクションが実行されます。
パスの部分はコメントと結びつく投稿のidを記述し、
その投稿の詳細画面に遷移するよう記述されてます。
ここからは追加でコメント機能を実装する際に必要な知識を追記します。
ビューにて対応する投稿の詳細画面で結び付けて表示させたい場合、
ビューを呼び出す前のコントローラーが実行されている時点で、
まずコメントのレコードをデータベースから取得します。
なのでpostsコントローラーのshowメソッドを編集しましょう。
def show
@comment = Comment.new
@comments = @post.comments.include( : user )
end
showは詳細画面を表示するアクションですね。
postsのshow.html.erbにコメントフォームを実装した際に、
comments#createを実行するリクエストを実行するために
インスタンス変数を生成しておきます。
またpostsテーブルとcommentsテーブルはアソシエーションが組まれているので
@post.commentsとすることで@postへ投稿された全てのコメントを取得できます。
さらにビューでは誰のコメントか明らかにするため、
アソシエーションを使ってユーザーのレコードを取得する処理を繰り返します。
このときN+1問題が発生するので、includesメソッドを使って解決しています。
これでコントローラーで、特定の投稿に対するコメントの全レコードを
取得することができたのでビューで表示しましょう。
<div class = " comments">
<h1><コメント一覧></h1>
<% if @comments %>
<% @comments . each do | comment | %>
<p>
<strong><%= link_to comment . user . nickname , "/users/#{ comment . user_id} " %> : </strong>
</p>
<% end %>
<% end %>
<% div %>
@commentsには複数のコメントのレコードが含まれているので、
配列の形にしてeachメソッドを使って表示させます。
またlink_toメソッドで作ったリンクの表示名は
コメントしたユーザーの名前になっています。
そしてパスにはコメントしたユーザーのidを記述しています。
こうすることでコメントを投稿したユーザーのidをparamsで扱えるようにしています。
以上がコメント実装機能で使えるルーティングのネストと
その他の設定でした!
【Rails】アソシエーション/N+1問題
今回は今までの投稿データを取得する時の方法と、
ユーザー情報もくっつけて投稿データを取得するための方法の違いと、
後者のそのやり方についてみていきましょう。
今まではPost.allで投稿の情報を取得していましたね。
ただ今回ユーザー情報も一緒に取得するとなると、また別のテーブルから
情報を引っ張って来る必要が出てきます。
実際にはpostsテーブルから投稿データ、userテーブルからuser_idの取得が
必要になりますね。
これでは記述が多くなってしまうので予めテーブル同士で関連づけて、
テーブル間で情報のやりとちできるようにしておくことができます。
この概念のことをアソシエーションと呼びます。
アソシエーションとはモデルを利用したテーブル同士を関連づけのことです。
アソシエーションをモデルに定義することで、
そのモデルに紐づく別のモデルにアクセスができるようになります。
この時、1対多の関係になるのか1対1の関係になるのか、
この概念が非常に重要となってきます。
例えばUserモデルの視点に立つと、1人のユーザーは複数の投稿を所有します。
この1対多の状態をhas_manyの関係といいます。
この1対多の繋がりがあるということを示すのがhas_manyメソッドです。
Userモデル(user.rb)を編集しましょう。
has_many : posts
ではPostモデルの視点ではどうか。
一つの投稿は必ず一人のユーザーによるもの。つまり1対1の関係です。
この状態はbelongs_toの関係といいます。
同じようにこの繋がりを示しておきましょう。
Postモデル(post.rb)
belongs_to : user
これで双方のモデルを通して情報のやりとりが
できるようになりました!
ここで、このアソシエーションを利用した場合にかぎり
起こってしまう問題を考えてみましょう。
それはデータベースへのアクセス回数が多くなってしまうことです。
Post. allでデータを取得すれば全てのデータを
一度のデータベースへのアクセスで取得できますね。
ただ今回のようにuser_idを紐づけてしまうと、
投稿数と同じ回数だけデータベースへのアクセスが必要になります。
これはアプリケーションのパフォーマンスが著しく下がってしまうことに
繋がってしまうのです。
この問題をN+1問題といいます。
ではこのN+1問題の解決方法をみていきましょう。
利用するのはincludesメソッドです。
引数に関連モデルを指定し、一度のアクセスでまとめてデータを取得します。
こちらを処理を実行するときに指示するので、
コントローラーに記載します。
(postsコントローラー)
def index
@posts = Post . includes ( : user )
end
このincludesメソッドを使用すると全てのデータが取得されるので、
元々記載されていたallメソッドは不要になります。
そして最後に、このアソシエーションによってuser_idが紐づけられ、
投稿者の名前が表示されるようになれば、nameカラムも不要になりますね。
フォームのname欄は消してしまってOKです。
消したらストロングパラメーターも編集して、投稿を保存する際に
nameカラムへの保存は行わないように変更しましょう!
またtweetsテーブルからもnameカラムを削除しましょう。
その際は追加の時と同じでコマンドから削除ができます。
こちらを実行しましょう。
rails g migration RemoveNameFromUsers name : string
以上がアソシエーションの使い方でした。
【Rails】マイページの実装
ここではマイページの実装を通して、アソシエーションやN+1問題に関して
お話ししていこうと思います。
現状だといろんな人の投稿が一覧表示されていますが、
自分だけの投稿に絞って見ることができません。
そこで投稿を保存するときに、
それぞれユーザー情報も一緒に保存する必要があります。
そのためにはまずpostsテーブルにuser_idカラムを追加しましょう。
rails g migration AddUserIdToPosts user_id : integer
これは説明していませんでしたが、
このような形で命名規則にしたがってコマンドを実行すると
自動でコードが記載されたマイグレーションを生成します。
その後、マイグレーションファイルの実行を行いましょう。
これでマイグレーションを編集せずに
postsテーブルにuser_idカラムが追加されました。
そして実際に投稿にuser_idを紐づけていきましょう。
その際に使用するのがcurrent_userメソッドです。
こちらはdevise導入で使用できるメソッドです。
このメソッドで現在ログインしているユーザーの情報を取得することが可能です。
なのでどうしたいか整理すると、今やりたい処理は
投稿データが入ったparamsの情報と、
current_userメソッドで取得したログイン中のユーザーのidを結合させて
テーブルに送られて来るようにすること。ですね。
そこで2つのハッシュを統合するときに使用するのがmergeメソッドです。
ではこれを使って実際にparamsとcurrent_userを結合させてみましょう。
まずはparamsにはどんな情報が入っているでしょう。
params. require( : post ) . permit ( :name , : image , :text )
だとします。
current_userメソッドの中身はどうでしょうか。
user id = { user_id : " 1 " }
だとしましょう。
これをmergeメソッドを使ってくっつけます。
paramsの記述を編集しましょう。
private
def post_params
params. require( : post ) . permit ( :name , : image , :text ) . merge ( user_id : current_user . id)
end
となります。
mergeメソッドを使ってuser_id情報を付け加えただけですね!
【Rails】deviseについて(ログイン機能の実装)/リダイレクト機能
SNSなどにあるようなログイン機能を実装したいと思います。
この機能を実装することで、
ログインするとログインボタンがユーザー名に切り替わったり、
ログインしたユーザーのみが投稿機能を利用できるように
制限をかけることができます。
このログイン機能を実装するために、
ユーザー管理機能を簡単に実装することができるdeviseというGemを
インストールして使用します。
gemをインストールするところは省きます。
インストールしたら
rails g devise : install というコマンドを実行することで、
設定関連に使用するファイルを自動で生成してくれます。
そこまで完了したら
deviseのUserモデルを作成しましょう。
こちらは通常のモデルの作成方法ではなく、
deviseのモデル作成用コマンドを用います。
rails g devise user
と入力して実行しましょう。
もちろんマイグレーションファイルも合わせて生成してくれます。
また自動的にroutes.rbに devise_for : users が追記されます。
この devise_for はユーザー機能に必要な複数のルーティングを
一度に生成してくれているdeviseのメソッドです。
では自動生成されたマイグレーションファイルを使用して
テーブルを作成していきましょう。
マイグレートを行いusersテーブルが作成されたことを確認してください。
そしてrails sも忘れずに。
ここで一つ問題が。
現状、ログインしているいないに関わらず
新規投稿画面にアクセスすることができます。
そこで未ログインユーザーがアクセスした場合に
ルートパスに遷移するよう設定を行いましょう。
こういった、本来受け取ったパスと別のパスに
転送することをリダイレクトと言います。
ではまずどのようにログインしているか判別するか。
この判別にはuser_signed_in?メソッドとunlessを使用します。
user_signed_in?メソッド
こちらはログインしているか判別するためのメソッドです。
これを合わせてunlessを使用しましょう。
ではコントローラーに定義します。
ここでもまずbefore_actionで処理を実行するメソッドをまとめます。
今回は7つのアクションのうち、
未ログインユーザーが使用できるメソッドが index と show になります。
(ログインしていなくても一覧と詳細は表示できる)
なのでこの2つのアクション以外を実行した場合は
ルートパスへのリダイレクトを適用させましょう。
before_action : move_to_index , expect : [ : index , : show ]
private
def move_to_index
unless user_signed_in?
redirect_to action: : index
end
end
unless user_signed_in?
でログインしるユーザーではない時(返り値がfalseの時)
の処理を指定しています。
redirect_to action: リダイレクト先となるアクション名
でリダイレクト処理を指定できます。
またビューに関してですが、
deviseでログイン機能を実装すると
ログイン/サインアップ画面が自動的に生成されます。
ただこれはGem内に存在するビューファイルを読み込んでいるため
ビューファイルとしては生成されません。
ここに変更を加えたい場合にはdeviseのコマンドを実行して
ビューファイルの生成します。
rails g devise : views
これで、ログイン画面(sessionsディレクトリ)と
サインアップ画面(registrationsディレクトリ)の
ビューファイルが生成されました。
違うディレクトリの同じファイル名なので
編集するときには気をつけましょう。
ではサインアップ画面にて入力できる最大文字数を指定してみましょう。
ニックネームをテーブルのカラムに追加し、
ビューファイルにフォームを追加します。
<div class= "field">
<%= f . label : nickname %> < em >( 6 characters maximum )< /em >< br />
<%= f . text_field : nickname , autofocus : true , maxlength : " 6 " %>
</div>
ここで覚えて欲しいのはmaxlengthメソッドです。
これはtext_fieldに使えるオプションで、入力できる最大文字数を指定できます。
そして最後にもう一つ大事なこと。
deviseに関わるコントローラーはGemに記述されており、
編集ができません。
そこで何が問題になるかというと、
ストロングパラメーターの定義ができないということです。
そのため、deviseにストロングパラメーターを設定するときには
特別な記述が必要となります。
その方法は2つあります。
①devise特有のパラメーターを取得する方法
devise_parameter_sanitizerメソッド
deviseにおけるparamsのようなメソッドです。
「ログイン」「新規登録」などのリクエストからパラメーターを取得できます。
これはもうこういうものなんだという感じで覚えて欲しいのですが、
メソッド名にも慣習的なものがあります。
実際に書くとこうです。
private
def configure_permitted_parameters
devise_parameter_sanitizer.permit ( :deviseの処理名, keys: [ : 許可するキー] )
end
メソッド名は本当はなんでもいいのですが、
慣習的なものでこのように定義することが多いです。
そしてdevise_parameter_sanitizer.permitのpermitメソッドですが、
これはparamsの時とちょっと違います。引数が違うんです。
今までは
params.require( : モデル名). permit( : 許可するキー)
でしたが、今回は
devise_parameter_sanitizer.permit ( : deviseの処理名 , keys : [ : 許可するキー] )
と第一引数にdeviseの処理名、
第二引数にKeysというキーに対し、配列でキーを指定して、
許可するパラメーターを追加します。
ではdeviseの処理名にはどんなものがあるのかみてみましょう。
: sign_in(サインイン(ログイン)の処理を行う時)
: sign_up(サインアップ(新規登録)の処理を行う時)
: account_update(アカウントの情報更新の処理を行う時)
これらを使用することができます。
そして第一引数に該当する処理の時、Keysに指定された名前をもつ
パラメーターの取得を許可します。
ではdeviseのコントローラーは編集できないので、
どこに記述すればいいのでしょうか。
application_controller.rbファイルに記述しましょう。
これは全てのコントローラーが継承しているファイルなので、
ここに記述されたことは全てのコントローラーで共通となる処理になります。
今まで作ってきたコントローラーも、全てこのファイルの処理が読み込まれた上で
作られる仕組みになってます。
ではこのファイルにストロングパラメーターを定義して
処理を読み込ませましょう。
今回もbefore_actionを使用して各アクションの事前処理として設定します。
before_action : configure_permitted_parameters, if : : divise_controller?
private
def configure_permitted_parameters
devise_parmeter_sanitizer. permit ( : sign_up , keys : [ : nickname ] )
end
if : : divise_controller?
ここに注目してみましょう。
これは値にメソッド名を指定することで、
戻り値がtrueの時のみ処理を実行する設定です。
つまり今回はdeviseのヘルパーメソッド名を指定して、
もしdeviseに関する処理であればそのメソッドを実行するよう指示しています。
つまり「この処理の対象はdeviseコントローラーだよ」といってます。
もしこれをbefore_actionとして設定していなければ、
別のコントローラーが実行される時にもこの処理が実行されてしまいます。
applicationコントローラーは全てのコントローラーに継承されてしまうので。
長くなりましたが、
ここまでがdeviseの使い方に関する一連の流れです!
いろいろ特殊ですが便利な機能なのでしっかり覚えましょう。
【Rails】before_actionメソッド
今回はまた便利な機能の紹介です。
一通り7つのアクションを定義し終えたコントローラーを
のぞいて見てみてください。
同じような記述がありませんか?
ありますね。
editアクションとshowアクションの定義がどちらも
@post = Post . find (params [ : id ] )
となってます。
ここでプログラミングあるあるのまとめちゃおう作戦です!
このときに使用するのが before_actionメソッド。
このメソッドを使用すると、各アクションが実行される前に
共通の処理を行うことができます。
なのでコントローラーファイルのクラスが定義されてるその中の
一番上に記述します。(上から処理されていくので)
before_action : set_post , only : [ :edit , :show ]
private
def set_post
@tweet = Tweet . find ( params[ :id ])
end
*新たに処理を実行させたいメソッドをprivateメソッドで作成する
→クラス外から呼び出されないように。
editとshowの2つのアクションの前に実行されるように
set_postというメソッドを定義しましょう。
onlyオプションやexceptオプションでどのアクションの実行前に
この処理を実行させるか制限をかけましょう。
そして忘れずに、元々定義していたeditアクションとshowアクションの
中身は空にしておきましょう。
これでbefore_actionメソッドは完成です!