LoginSignup
5
7

More than 1 year has passed since last update.

【初学者向け】Rubyにおける@nameとself.nameの違い 〜なぜ@なしで puts"#{name}"としてもちゃんと出力されるの?〜

Last updated at Posted at 2021-04-26

概要

@nameself.nameの挙動の違いをまとめます。
初学者の方向けの記事です。

以下のようなコードをたまに見かけることがありました。

class User
  attr_reader :name
  def initialize(name) 
    @name = name
  end

  def introduce
    puts "#{name}です" #@がなくても参照できてる!
  end
end

user = User.new ("川上")
user.introduce
出力結果
川上です

「introduceメソッドの中のnameはローカル変数なの??@が付いてないのに,なぜ値が入っているの?」
と思ってた謎が解けたので,まとめます。

結論

上記の"#{name}です"は,実は"#{self.name}です"の略。
self.nameは,値を参照するnameメソッド(ゲッターメソッド)を呼び出している。
nameメソッド(ゲッターメソッド)の戻り値は@nameなので,initializeメソッドで@nameに格納しておいた値が返る。
(なんのことかサッパリですよね^^; 詳しくは後述します)

それに対し,@nameはクラスの中で共有されるインスタンス変数。

同じ挙動をするように見えるけど,全くの別物。

@name(インスタンス変数)を使用する書き方

まず,よく見るインスタンス変数を使った書き方から見てみます。

@nameを使用する書き方
class User

  def initialize(name)
    @name = name
  end

  def introduce
    puts "#{@name}です" 
  end
end

user = User.new ("川上")
user.introduce
出力結果
川上です

インスタンスを生成した際,initializeメソッド内で@nameに実引数の値川上がセットされます。

クラスの中ではインスタンス変数の値が保持されるので,introduceメソッド内の@nameの値も川上です。

こちらは問題なさそうです。

self.nameを使用する書き方

self.nameを使用する書き方
class User
  attr_accessor :name
  def initialize(name)
    self.name = name
  end

  def introduce
    puts "#{self.name}です" 
  end
end

user = User.new ("川上")
user.introduce
出力結果
川上です

introduceメソッドの中だけでなく,initializeメソッドの中もself.nameとなっています。

すっかり忘れていたのですが,ProgateのRubyの演習ではこの形式で書かれているそうです。
(教えていただき見直してみると,確かにそうなっています。※2021年4月26日時点)

ここで注意したいのが,さりげなくattr_accessor :nameと書かれている点です。
実は,この記述がない場合はエラーになってしまうんです。

もし,attr_accessor :nameがなかったらどうなるか試してみます。

self.nameを使用する書き方
class User
  # attr_accessor :name # コメントアウト
  def initialize(name)
    self.name = name
  end

  def introduce
    puts "#{self.name}です" 
  end
end

user = User.new ("川上")
user.introduce

実行してみると,以下のエラーが出ました。(2つのうちの1つ目)

出力結果
Traceback (most recent call last):
        2: from user.rb:12:in `<main>'
        1: from user.rb:12:in `new'
user.rb:4:in `initialize': undefined method `name=' for #<User:0x00007f7eec13c348> (NoMethodError)

name=メソッドが定義されていない,という内容ですね!

self.name = はname=メソッド(セッターメソッド)を呼び出している

では,name=メソッドを追加してみましょう。

ちなみにname=メソッド(セッターメソッド)は何かというと,@nameに値をセットするメソッドです。

self.nameを使用する書き方(セッターメソッドを追加)
class User

  def initialize(name)
    self.name = name # 実はセッターメソッドを呼び出している
  end
  # セッターメソッドを追加
  def name=(name)
    @name = name
  end

  def introduce
    puts "#{self.name}です" 
  end
end

user = User.new ("川上")
user.introduce

これでもう一度実行すると,上記のエラーに関してはなくなります。

実は,initializeメソッドの中のself.name =name=メソッド(セッターメソッド)を呼び出しています。
self.name =name=と両方イコールがついている点に注意してください)

name=メソッドが定義されていないと,呼び出すことができないのでエラーになっていました。

self.name =name川上という値が入った変数)を実引数としてname=メソッドを呼び出す
@name川上という値がセットされる,という流れです。

では,この状態でもう一度実行してみましょう。
すると,次は以下のエラー(2つ目のエラー)が出ます。

出力結果
Traceback (most recent call last):
        1: from user.rb:17:in `<main>'
user.rb:12:in `introduce': undefined method `name' for #<User:0x00007fbcf209ff90 @name="川上"> (NoMethodError)
Did you mean?  name=

今度はnameメソッドが定義されていない,という内容です。

self.nameはnameメソッド(ゲッターメソッド)を呼び出している

では,nameメソッド(ゲッターメソッド)を定義してみましょう。

nameメソッド(ゲッターメソッド)は@nameを戻り値とするメソッドです。

self.nameを使用する書き方(ゲッターメソッドを追加)
class User

  def initialize(name)
    self.name = name # 実はセッターメソッドを呼び出している
  end
  # セッターメソッド
  def name=(name)
    @name = name
  end

  # ゲッターメソッドを追加
  def name
    @name #戻り値
  end

  def introduce
    puts "#{self.name}です" # 実はゲッターメソッドを呼び出している
  end
end

user = User.new ("川上")
user.introduce

これでエラーはなくなります。

出力結果
川上です

introduceメソッドの中のself.namenameメソッド(ゲッターメソッド)を呼び出しています。
(こちらはイコールなしです)

こちらも,メソッドが定義されていなかったので,呼び出すことができずエラーになっていました。

self.namenameメソッド(ゲッターメソッド)を呼び出す
@nameに格納されていた川上という値が戻り値としてself.nameに入る,という流れです。

セッターとゲッターをあわせてattr_accesorと書き換えることができる

まず,セッターメソッドは以下のように書き換えることができます。(class User の直下)

self.nameを使用する書き方(セッターメソッドを書き換え)
class User
  attr_writer :name # セッターメソッドを呼び出す
  def initialize(name)
    self.name = name
  end
  # セッターメソッド
  # def name=(name)
  #   @name = name
  # end

  # ゲッターメソッド
  def name
    @name
  end

  def introduce
    puts "#{self.name}です" 
  end
end

user = User.new ("川上")
user.introduce

(追記:コメントで教えていただいたので,「# 裏でセッターメソッドを呼び出す」という表記から「# セッターメソッドを呼び出す」という表記に変更いたしました!次のゲッターメソッドの書き換えについても同様です)

そして,ゲッターーメソッドは以下のように書くことができます。

self.nameを使用する書き方(ゲッターメソッドを書き換え)
class User
  attr_writer :name # セッターメソッドを呼び出す
  attr_reader :name # ゲッターメソッドを呼び出す
  def initialize(name)
    self.name = name
  end
  # セッターメソッド
  # def name=(name)
  #   @name = name
  # end

  # ゲッターメソッド
  # def name
  #   @name
  # end

  def introduce
    puts "#{self.name}です" 
  end
end

user = User.new ("川上")
user.introduce

attr_writer :nameattr_reader :nameをあわせてattr_accessor :nameと書くことができます。

これが「self.nameを使用したコード」に出てきた書き方になります。

self.nameを使用する書き方(attr_accessorに書き換え)
class User
  attr_accessor :name # セッターとゲッターを呼び出す
  def initialize(name)
    self.name = name
  end
  # セッターメソッド
  # def name=(name)
  #   @name = name
  # end

  # ゲッターメソッド
  # def name
  #   @name
  # end

  def introduce
    puts "#{self.name}です" 
  end
end

user = User.new ("川上")
user.introduce
出力結果
川上です

【補足1】selfはインスタンス自身を表している

では,「このselfって何?」というところなんですが,selfuserというインスタンスを表しています。

クラスの外でインスタンスメソッドを呼び出す際,例えば

user.introduce

というように,インスタンス.インスタンスメソッドという形で書きますが,クラスの中ではインスタンス部分をself(インスタンス自身)と書きます。

クラスの中で

self.name

と書くと,userというインスタンス自身に対してnameメソッドを呼び出していることになります。

言われてみれば「なるほど!」と納得できますが,なかなか見かけないので馴染みがありませんでした^^;

【補足2】self.nameself.は省略することができる

self.nameself.は,意味が曖昧にならない限り省略することができます。
ただし,self.name =self.は省略することができません。(ローカル変数nameへの代入とみなされます)

self.を省略する書き方
class User
  attr_accessor :name
  def initialize(name) 
    self.name = name # self.を省略できない
  end

  def introduce
    puts "#{name}です" # self.を省略できる
  end
end

user = User.new ("川上")
user.introduce

ちなみに,私が疑問に思っていた冒頭のコードは,@nameへの値のセットはセッターを介さずに行い,値を参照する時だけゲッターを介している形でした。

値を参照する時だけself.nameを使用する書き方
class User
  attr_reader :name #ゲッターのみ
  def initialize(name) 
    @name = name
  end

  def introduce
    puts "#{name}です" # self.を省略している
  end
end

user = User.new ("川上")
user.introduce

謎が解けてスッキリ!

(ただ,この書き方をされている場合,意図してこうされているわけではなく,「@をつけ忘れているけど,他の目的でattr_reader :nameと書いてるから偶然参照できている」ケースが多いのかな,と思いました)

【補足3】@nameとself.nameは全く異なるもの

とても極端でありえない例ですが,attr_readerを使わずにnameメソッドを書くとき,メソッドの中が

def name
  @name
end

ではなく,例えば以下のような値だった場合,

def name
  1000
end

self.namenameメソッドを呼ぶと

1000

が戻り値として返ります。

このように,純粋な挙動を見ると,self.nameは単に「nameメソッドを呼ぶ」という処理をするだけで,メソッドの中身は関係ありません。
(同様に,self.name =も,全く異なる名前のインスタンス変数(たとえば@age)に値をセットすることができてしまいます)

attr_accessorを使用すれば上記のようにメソッドの中身がおかしくなることはありませんが,ゲッターメソッド(もしくはセッターメソッド)を呼ぶ,という挙動自体は変わりません。

インスタンス変数を使用する場合は,@nameを直接定義するし,一度定義されたら@name自身が値を保持します。(追記:コメント頂き,以下のように修正いたします)

@nameはメソッドを呼ぶものではなく,インスタンス変数です。

両者は全くの別物です。

【補足4】同じ使用法であれば,@nameを使用したほうが良い

クラスの中で共有する値をセットしたり参照する場合は,普通に@nameを使用したほうが良いです。
self.name =で値をセットしたりself.nameで値を取得する場合,それぞれセッター,ゲッターを呼び出す必要があり,その分の処理が増えるからです。

逆に,self.nameを使用したほうが良いケースが思い浮かばず・・ありましたらぜひ教えていただけたら幸いです🙇‍♀️
追記:コメント欄にてself.name =を使用するケースを教えていただきました!ありがとうございます。

終わりに

以上,私自身が疑問に思っていた部分だったのでまとめました。
もし内容に間違いがありましたら,ご指摘いただけますと幸いです☺️

参考

  • Progate RubyⅣ (有料部分なのでURL添付なし)
  • ドットインストール Ruby入門 #20 アクセサを使ってみよう (同上)
  • やんばるエキスパート 講座
5
7
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
7