値渡し・参照渡し・参照の値渡し

値渡し・参照渡し・参照の値渡しとは?

[値渡し・参照渡し・参照値渡し]について分かりやすいサイトがあったので、そちらの記事を載せさせていただきます。

railsで以下のような検証を行うと、[参照渡し]が実感できます。

[1] pry(main)> a = "1234"
=> "1234"
[2] pry(main)> b = a
=> "1234"
[3] pry(main)> b.reverse!
=> "4321"
[4] pry(main)> a
=> "4321"

また以下のように値を再度代入した場合は、違うメモリを参照するので、代入元は影響を受けない。

[1] pry(main)> a = "1234"
=> "1234"
[2] pry(main)> b = a
=> "1234"
[3] pry(main)> b = "4321"
=> "4321"
[4] pry(main)> a
=> "1234"

railsが値参照により実際にアプリケーションに影響を与えてしまう例を以下に記載します。

Image from Gyazo

上記のようなプロフィール編集画面において、名前をバリデーションエラー(空欄で保存されないように)になるように更新すると、右上の名前が表示される部分がバリデーションエラーになってDBに保存されていないはずなのにバリデーションエラーの内容で表示されてしまう。

Image from Gyazo

上記のようになってしまう流れは、以下のようになる。

[app/controllers/profiles_controller.rb]
  before_action :set_user, only: %i[show edit update]
  def update
    if @user.update(user_params)
      redirect_to profile_url, success: (t 'defaults.user_update')
    else
      flash.now[:danger] = (t '.fail')
      render 'edit'
    end
  end

  private

  def set_user
    @user = current_user
  end

①@user = current_userにより、@userには、DBから取得した現在のユーザーが格納される。

②バリデーションエラーになるようにユーザー情報を更新すると、if @user.update(user_params)でassign_attributesにより、DBへの変更は行わないが更新された値を取得して@userに格納される。

③上記の@userの内容(バリデーションエラーになる新しいユーザー情報)でsaveするとバリデーションエラーになり、コールバックされ@userの値が②の状態(@userの値がassign_attributesにより@userが更新したい内容になる)まで戻る。

④上記で@userの値が変更されており、@userは@user = current_userにより参照渡しでcurrent_userのメモリアドレスを参照しているので、@userの値が変更されている状態なので、current_userのメモリアドレスも引っ張られて値が変わる。

⑤バリデーションエラーにより、render 'edit'により再度プロフィール編集画面がレンダーされる。

⑥レンダー後のプロフィール編集画面の右上に表示される名前が変わる。(右上に表示される名前は、<%= current_user.decorate.full_name %>というコードで記載しているため、current_userの値が変わっているので、表記が変わる。)

※ポイント
updateメソッドは、assign_attributesの後にsaveを実行する。
if @user.update(user_params)で、バリデーションエラーになるが、saveで発生しているため。バリデーションエラーによりコールバックされ、その前のassign_attributesまでは処理されており、@userの値が更新される。
※ assign_attributes:DBには保存せず値のみ更新する。

参考記事:

値渡しと参照渡しの違いを理解する

assign_attributesなのに保存されてしまう - 箱のプログラミング日記。