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な関係になれれば万々歳だなーとおもっています。

 

よろしくお願いします。