has_manyとhas_many throughでの取得できるデータの違い
class User < ApplicationRecord has_many :posts end
@user.posts
=> @userのidとpostテーブルのuser_idに一致するpostsを取得。
つまり、@userが作成したpost全てを取得
class User < ApplicationRecord has_many :posts, through: :likes end
@user.posts
=> @userのidとlikeテーブルのuser_idに一致するlikesを取得し、そのuser_idに紐付くpost_idからpostをlike経由で取得。
つまり、@userがlikeしたpost全てを取得。
このふたつを併記はできない。(呼び出し方が@user.postsで同じになるため。)
この為呼び出し方を変えての定義が必要。
以下はhas_many :posts, through: :likesを書き換えた場合
class User < ApplicationRecord has_many :posts has_many :like_posts, through: :likes, source: :post # has_many :posts, through: :likes の書き換え # @user.like_postsで呼び出し。 end
1対多のアソシエーション
アソシエーションとは
2つのActive Recordモデル同士のつながりのこと。
モデルとモデルの間に関連付けを行う事で
コードでの共通操作をよりシンプルで簡単描けるようになる。
関連付けの仕方(1対多)
モデル作成時に関連付けしたい他のモデルとの関連付けを行う。
1対多の関係で、1のモデルが既にあり、
多のモデルを作成する際(userが1、postが多)
rails g model Post content:text user:references
とすることで、Postモデルにuser_idカラムが追加される。
また、マイグレーションを実行することで
以下のコードがpost.rbに自動的に追加される。
(post.rb) belongs_to :user
あとはuser.rbにpostとの関連を記述して完了。
(user.rb) has_many :posts
(※バリデーションを記載している場合、has_manyもbelongs_toもvalidaionの上に書く)
便利なオプション
- 親を削除したら子も消える dependent: :destroy
userを削除したら紐づくpostもまとめて削除したい場合、
オプションでdependent: :destroyを渡します。
has_many :posts, dependent: :destroy
- 外部キーの指定 foreign_key
postテーブルのuser_idが"外部キー"、postテーブルが"参照元テーブル"、
userテーブルのidが"主キー"、userテーブルが参照先テーブルと言われ、 外部キー名の基本形は"参照先テーブル名(単数形)_id"。 こうすることで作成されたカラムが外部キーであることが認識される。 もしも、違う名前をつけたい、それを外部キーにしたい、 という場合は明示的にそのカラムが外部キーであることを宣言する必要がある。 それがforeign_keyオプション。参照先と参照元のテーブル両方に宣言する。 user_idではなくuser_noにしたい場合、
has_many :posts, foreign_key: 'user_no' belongs_to :user, foreign_key: 'user_no'
参照:Railsガイド
railsguides.jp
N+1問題
N+1問題の基本
関連付けされている前提。
こーゆーのがあったら、
毎回each文回す度にpostテーブルを参照してしまう。
@users = User.all @users.each do |user| puts user.post.content end
ので、includesメソッドを使ってこう書き換える。
(includesメソッドとは関連テーブルの条件が一致するデータと指定テーブルのデータを取得するメソッド)
@users = User.includes(:post).all @users.each do |user| puts user.post.content end
これがN+1問題解消の基本
postからuserの情報を取得したい時は、関係性を入れ替えればOK
@posts = Post.includes(:user).all
また、postに対して複数のcommentがある場合で、
そのpostを投稿したuserのcommentを取得したい場合があるとすれば
@posts = Post.include(:user).all @posts.each do |post| puts post.user.comments end
これでは、
userとcommentの間でN+1問題が発生してしまう。
解決のためには以下のように書き換える。
@posts = Post.includes(user: :comment).all @posts.each do |post| puts post.user.comments end
その他細々した設定
* order
# posts.titleでの並び替え(title昇順) Post.includes(:user).order(:title) # posts.titleでの並び替え(title降順) Post.includes(:user).order(title: :desc) # 以下も同じ Post.includes(:user).order("title DESC") # posts.titleでの並び替え(user_id昇順、user_idが同じ場合はcreated_at降順) Post.includes(:user).order(:user_id, created_at: :desc) # users.created_atでの並び替え(created_at降順) Post.includes(:user).order(users: {created_at: :desc})
* where
# post.user_idが3のboardを取得 Post.includes(:user).where(user_id: 3) # 以下も同じ Post.includes(:user).where(posts: {user_id: 3}) # user.nameが"後藤"さんのpostを取得 Post.includes(:user).where(users: {name: "後藤"})
ストロングパラメータにmergeする
例)ユーザー(user)と掲示板(board)とコメント(post)
class PostsController < ApplicationController def create post = current_user.posts.new(post_params) post.board_id = params[:board_id] if post.save redirect_to board_path(post.board) else render :new end end private def post_params params.require(:post).permit(:body) end end
は、こう書き換えられる。
class PostsController < ApplicationController def create post = current_user.posts.new(post_params) if post.save redirect_to board_path(post.board) else render :new end end private def post_params params.require(:post).permit(:body).merge(board_id: params[:board_id]) end end
form_withのlocalオプション
form_withではデフォルトでremote :trueになっている。
これを解除するのがlocalオプション。
デフォルトのままでは、 フォームは非同期通信としてJS方式で送られてしまう。
この場合、例えばビューからコントローラにフォームの内容を送って そこから別アクションへリダイレクトする際、 画面遷移されずエラーになる。
これはredirect_toが非同期通信に対応していないため反応しない。
redirect_toを有効にするには、 同期通信としてHTML方式で送る必要があるため form_withのオプションにlocal :trueをつけることで解決。
(/_form.html.erb) <%= form_with model: @post, local: true do |f| %> <%= f.label :body %> <%= f.text_area :body %> <%= f.submit %> <% end %>
(/posts_controller.rb) def new @post = Post.new end def create @post = Post.new(board_params) if @board.save redirect_to posts_path else render :new end end
アクセサメソッドの基本
b>アクセサメソッドとは、
インスタンス変数(@nameとか)の読み書きをするためのメソッド。
"attr_accessor"とか"attr_writer"とか"attr_reader"がアクセサメソッド
(attr_writerはセッターメソッド、attr_readerはゲッターメソッドともいう。)
設定の仕方は
attr_accessor :name
中身としては
def name @name end def name=(value) @name = value end
クラス内で設定した場合の挙動はこんな感じになる。
class User attr_accessor :name end user = User.new user.name = "斉藤さん” user.name => "斉藤さん”
class User attr_accessor :name end
は
class User def name @name end def name=(value) @name = value end end
と同じで、
user.name = ”斉藤さん”
でインスタンスメソッド
def name=(value) @name = value end
を呼び出している。
つまり、valueの部分が ”斉藤さん” なので
user.name = ("斉藤さん")
としてインスタンスメソッドの引数に”斉藤さん”が渡されている。
これでインスタンス変数@nameに”斉藤さん”が代入される。
(つまり、斉藤さんが設定される => セッター)
次に
user.name
では、インスタンスメソッド
def name @name end
が呼び出されている。
先ほど@nameには”斉藤さん”が代入されたため、戻り値は
=> "斉藤さん"
になっている。
(.nameで@nameの中身を取得できる => ゲッター)
また、アクセサメソッドはinitializeメソッドと一緒に使われるケースが多い。
class User attr_accessor :name def initialize(name) @name = name end end
こうすることで
user = User.new("斉藤さん”)
というように.newメソッドに引数を渡せるようにするためである。
(initializeメソッドは特殊なメソッドで、newメソッドを呼び出すと自動的に呼び出される。)
プログラミングのアウトプットブログです。
プログラミングの
アウトプットとしてブログを書いていこうと思います。
趣旨としては
・勉強した内容を整理したい。
・書くことで理解を深めたい。
・復習をしやすいようにしたい。
今まではいつでも見れるようにiphoneのメモアプリ(デフォルトで入ってるやつ)に書いていってたんですけど、量が多くなってきて煩雑になってきたのでブログの方にシフトチェンジしたい、そんなところです。
とりあえず書いてみて、良さそうだったら続けようと思います。
あれこれそんなによくないかも、ってなったらやめます。
完全に自分用なのですが、同じ分野の勉強している一部の人にとっては有益かもしれないし、知識ある人がみてくれれば何かヒントをもらえるかもしれないし、winwinな関係になれれば万々歳だなーとおもっています。
よろしくお願いします。