らくだ🐫にもできるRailsチュートリアル|4.4(と4.5)

4.4 Rubyにおけるクラス

4.4.1 コンストラクタ

リテラルコンストラクタとは何ぞ

リテラル → コードに直接書かれた文字や数字
コンストラクタ → インスタンスを作成した時に自動的に実行されるメソッド

うーん。これは雰囲気でふわっと掴んでれば良さそう感?(?

ターミナル(コンソール)
>> s = "foobar"  #""は文字列(Stringクラス)のオブジェクトを作成するコンストラクタ(詳細な動作は下記)
=> "foobar"
>> s.class         #変数sのクラスを調べる
=> String          #()

#動作の内容をはっきりと分かりやすく書いたコード↓
>> s = String.new("foobar")   #Stringクラスのインスタンスを作る(中身は文字列のfoobar)
=> "foobar"
>> s.class
=> String
>> s == "foobar"
=> true

配列とハッシュでインスタンスを生成

・配列のコンストラクタ →Array.new
・ハッシュのコンストラクタ →Hash.new

ターミナル(コンソール)
>> a = Array.new([1, 3, 2])  #引数には配列の初期値が入る
=> [1, 3, 2]

>> h = Hash.new     
=> {}               #空のハッシュが生成される
>> h[:hoo]          #存在しないキーにアクセス
=> nil              #存在しないのでnilが返る
>> h = Hash.new(0)  #引数にはハッシュのデフォルト値が入る→ デフォルト値を0に
=> {}               #空のハッシュが生成される
>> h[:hoo]          #存在しないキーにアクセス
=> 0                #デフォルト値に設定した0が返ってくる

クラスメソッド → クラス自身に対して呼び出すメソッド(上記のnew)
クラスメソッドで呼び出された結果 → そのクラスのオブジェクト(クラスのインスタンス)
クラスの中でインスタンスに対して呼び出すメソッド → インスタンスメソッド

演習

  1. 1から10の範囲オブジェクトを生成するリテラルコンストラクタは何でしたか? (復習です)
  2. 今度はRangeクラスとnewメソッドを使って、1から10の範囲オブジェクトを作ってみてください。ヒント: newメソッドに2つの引数を渡す必要があります
  3. 比較演算子==を使って、上記2つの課題で作ったそれぞれのオブジェクトが同じであることを確認してみてください。

Rangeのクラスメソッド → 第一引数と第二引数までの範囲オブジェクトを返す

ターミナル(コンソール)
#1.
>> a = 1..10
=> 1..10

#2.
>> b = Range.new(1, 10)
=> 1..10

#3.
>> a == b
=> true

4.4.2 クラス継承

superclassメソッド →クラスの親クラスが返ってくる

ターミナル(コンソール)
>> s = String.new("foobar") #変数sに "foobar"の値を持つStringクラスのインスタンスを代入
=> "foobar"
>> s.class                        # 変数sのクラスを調べる
=> String
>> s.class.superclass             # Stringクラスの親クラスを調べる
=> Object
>> s.class.superclass.superclass  # Stringクラスの親クラスのさらに親クラスを調べる
=> BasicObject
>> s.class.superclass.superclass.superclass  # Stringクラスの親クラスの親クラスのさらに親クラスを調べる
=> nil                            #BasicObjectより上のクラスはない

全てのRubyオブジェクトはBasicObjectクラスを継承している!

ターミナル(コンソール)
>> class Word         #Wordクラス
>>   def palindrome?(string)      #引数stringを持ったpalindrome?メソッド
>>      string == string.reverse  #回文であればtrueが返る
>>   end
>> end
=> :palindrome?                   #Wordクラスの中身はpalindrome?メソッド(と言う事?)
>> w = Word.new                   #変数wに生成したWordクラスのインスタンスを代入
=> #<Word:0x0000000002d30bd8>     #Wordクラスのインスタンス:インスタンスの情報が保存されている場所
>> w.palindrome?("foobar")        #wの中でpalindrome?メソッドを呼び出す(値は"foobar")
=> false                          #回文になっていないのでfalse
>> w.palindrome?("level")         #wの中でpalindrome?メソッドを呼び出す(値は"level")
=> true                           #回文になっているのでtrue

文字列を引数に取るならStringクラスを継承するのが自然

ターミナル(コンソールを再起動させる)
>> class Word < String        #WordクラスはStringクラスを継承
>>   def palindrome?               #文字列が回文であればtrueを返すalindrome?メソッド
>>     self == self.reverse        #selfは値自身を表す(ここではWordクラス自身)
>>   end
>> end
=> :palindrome?
>> s = Word.new("level")
#変数Sに "level"という文字列を値に持ったWordクラスのインスタンスを生成して代入
#Wordクラスにはコンストラクタが無いので、継承元のStringのコンストラクタである""を呼び出している
#つまり 実質Wordが継承しているString.new("level") (考えるな感じろメソッド)
=> "level"
>> s.palindrome?                   #sに対してpalindrome?メソッドを呼び出す
=> true
>> s.length                     #Stringクラスを継承しているためlengthも呼び出せる
=> 5

また、Stringクラスを継承している為、superclassメソッドを使ってクラス階層を確認すると
「Word < String < Object < BasicObject」であることが確認できる

self.の省略

なお、Stringクラスの内部では、メソッドや属性を呼び出すときのself.も省略可能です。

ターミナル(コンソール)
>> class Word < String
>>   def palindrome? 
>>     self == reverse       #メソッドや属性(この場合reverseメソッド)を呼び出すself.を省略
>>   end
>> end
>> r = Word.new("hogehoge")
=> "hogehoge"
>> r.palindrome?
=> false
>> s = Word.new("level")
=> "level"
>> s.palindrome?
=> true

演習

  1. Rangeクラスの継承階層を調べてみてください。同様にして、HashとSymbolクラスの継承階層も調べてみてください。
  2. リスト 4.15にあるself.reverseのselfを省略し、reverseと書いてもうまく動くことを確認してみてください。
ターミナル(コンソール)
#1.
>> Range.class
=> Class
>> Range.class.superclass
=> Module
>> Range.class.superclass.superclass
=> Object
>> Range.class.superclass.superclass.superclass
=> BasicObject
#HashクラスとSymbolクラスも同じく

2.は上記(self.の省略)の通り上手く動く

4.4.3 組み込みクラスの変更

palindrome?メソッドをStringクラス自身に追加して
リテラルな文字列に対して直接実行することは可能か?
→可能(ただし、正当な理由が無い場合は無作法に当たる)

ターミナル(コンソール)
>> class String    #Stringクラスに追加
>>   def palindrome?
>>     self == reverse  #reverseを呼び出すself.は省略可能
>>   end
>> end
=> :palindrome?
>> "deified".palindrome?
=> true

#Railsはblank?メソッドをRubyの組み込みクラスに追加している
#よってRailsコンソール上では下記のコードが実行できる
>> "".blank?
=> true
>> "      ".empty?
=> false
>> "      ".blank?
=> true
>> nil.blank?
=> true

演習

  1. palindrome?メソッドを使って、“racecar”が回文であり、“onomatopoeia”が回文でないことを確認してみてください。南インドの言葉「Malayalam」は回文でしょうか? ヒント: downcaseメソッドで小文字にすることを忘れないで。
  2. リスト 4.16を参考に、Stringクラスにshuffleメソッドを追加してみてください。ヒント: リスト 4.12も参考になります。
  3. リスト 4.16のコードにおいて、self.を削除してもうまく動くことを確認してください。
ターミナル(コンソール・上記の続きに)
#1.
>> "racecar".palindrome?
=> true
>> "onomatopoeia".palindrome?
=> false
>> "Malayalam".downcase.palindrome?
  #大文字(M)と小文字(m)は区別されるため、すべて小文字に直してからpalindrome?メソッドを呼び出す
=> true

#2.
>> class String
>>   def shuffle
>>     self.split('').shuffle.join
>>   end
>> end
=> :shuffle
>> "foobar".shuffle
=> "obaorf"

#3.
>> class String
>>   def shuffle
>>     split('').shuffle.join
>>   end
>> end
=> :shuffle
>> "foobar".shuffle
=> "roabof"

4.4.4 コントローラクラス

  • Railsのcontrollerにもクラスがあり、階層になっているし継承されている
  • controllerに書かれたアクションはメソッド
ターミナル(コンソール)
>> controller.home
=> nil                   #中身は空なのでnil

→Railsのアクションでは戻り値はないし返される値は重要ではない
RailsとRubyは別物!

演習

  1. 第2章で作ったToyアプリケーションのディレクトリでRailsコンソールを開き、User.newと実行することでuserオブジェクトが生成できることを確認してみましょう。
  2. 生成したuserオブジェクトのクラスの継承階層を調べてみてください。
ターミナル
#1.
ec2-user:~/environment/sample_app (rails-flavored-ruby) $ cd ..  #sample_appを抜ける
ec2-user:~/environment $ cd toy_app/                             #toy_appに移動
ec2-user:~/environment/toy_app (master) $ rails c                #コンソールを起動
>> user = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>

#2.
>> user.class
=> User(id: integer, name: string, email: string, created_at: datetime, updated_at: datetime)
>> user.class.superclass
=> ApplicationRecord(abstract)
>> user.class.superclass.superclass
=> ActiveRecord::Base
>> user.class.superclass.superclass.superclass
=> Object
>> user.class.superclass.superclass.superclass.superclass
=> BasicObject
>> user.class.superclass.superclass.superclass.superclass.superclass
=> nil

4.4.5 ユーザークラス

sample_appで使用するUserクラスを作成する

ターミナル
#sample_appに戻る
ec2-user:~/environment/toy_app (master) $ cd ..
ec2-user:~/environment $ cd sample_app/
ec2-user:~/environment/sample_app (rails-flavored-ruby) $ touch example_user.rb
#ルートdirにexample_user.rbファイルを作成
#なんでこんなところに作るんだろう?って思うじゃん??最終的に削除します!(練習用のファイル)
/sample_app/example_user.rb
#内容の詳しい解説は別途枠外にて class User    attr_accessor :name, :email def initialize(attributes = {}) @name = attributes[:name] @email = attributes[:email] end def formatted_email "#{@name} <#{@email}>" end end

attr_accessorとは何ぞ

「attr_reader」→ゲッター・データを取り出すためのメソッド
「attr_writer」→セッター・データを代入するためのメソッド
この2つを合わせたメソッド→「attr_accessor」
→属性に対応するゲッターとセッターをそれぞれ定義してくれる便利なメソッド

attr_accessor :name, :email
#属性は,で区切って複数指定できる)

このコードによりインスタンス変数@nameとインスタンス変数@emailにアクセスできるようになる(インスタンス変数とは後程どこかで)

initializeメソッドとは何ぞ

インスタンス生成時に自動的に呼び出されるメソッド・引数も渡せる

def initialize(attributes = {})
#空のハッシュがデフォルト値のattributes変数を引数に持つinitializeメソッド
  @name  = attributes[:name]  #@nameにattributesの:nameを代入
  @email = attributes[:email] #@emailにattributesの:emailを代入
  #空のハッシュをデフォルトに設定した為、nameやemailを持たないユーザーを作ることも出来る
end

formatted_emailメソッドを定義

呼び出したときにユーザーの名前とemailが表示される様にメソッドを定義する
@nameと@emailはUserクラスのインスタンス変数なのでUserクラス内で自動的に使用できる

def formatted_email
  "#{@name} <#{@email}>"  #変数展開され @nameの値 <@emailの値> の形で出力される
end

コンソールで試してみる

ターミナル(コンソール)
>> require './example_user'  #./example_userの読み込み(カレントディレクトリ直下)
=> true
>> example = User.new             #Userクラスのインスタンスをexampleに代入(中身は空っぽ)
=> #<User:0x00000000031629b8 @name=nil, @email=nil>
>> example.name = "Example User"  #@nameに値を設定
=> "Example User"
>> example.email = "user@example.com"   #@emailに値を設定
=> "user@example.com"
>> example.formatted_email              #exampleに対してformatted_emailを呼び出す
=> "Example User <user@example.com>"

#ハッシュを渡すことで属性と値が定義されたインスタンスを作成することが出来る
#ハッシュの{}は省略可能!
>> user = User.new(name: "Michael Hartl", email: "mhartl@example.com")
=> #<User:0x0000000003266e90 @name="Michael Hartl", @email="mhartl@example.com">
>> user.formatted_email
=> "Michael Hartl <mhartl@example.com>"

演習

  1. Userクラスで定義されているname属性を修正して、first_name属性とlast_name属性に分割してみましょう。また、それらの属性を使って “Michael Hartl” といった文字列を返すfull_nameメソッドを定義してみてください。最後に、formatted_emailメソッドのnameの部分を、full_nameに置き換えてみましょう (元々の結果と同じになっていれば成功です)
  2. “Hartl, Michael” といったフォーマット (苗字と名前がカンマ+半角スペースで区切られている文字列) で返すalphabetical_nameメソッドを定義してみましょう。
  3. full_name.splitとalphabetical_name.split(’, ’).reverseの結果を比較し、同じ結果になるかどうか確認してみましょう。
/sample_app/example_user.rb
#先にexample_user.rbの該当箇所を追加修正
class User
  attr_accessor :first_name, :last_name, :email

  def initialize(attributes = {})
    @first_name  = attributes[:first_name]
    @last_name = attributes[:last_name]
    @email = attributes[:email]
  end
  
  def full_name
    "#{@first_name} #{@last_name}"
  end
  
  def alphabetical_name
    "#{@last_name}, #{@first_name}"
  end

  def formatted_email
    "#{full_name} <#{@email}>"
  end
end
ターミナル(コンソールを再起動)
>> require './example_user'  #example_user.rbを再度読み込み
=> true
>> user = User.new(first_name: "Michael", last_name: "Hartl", email: "mhartl@example.com")
=> #<User:0x0000000003b369a8 @first_name="Michael", @last_name="Hartl", @email="mhartl@example.com">
>> user.formatted_email  
=> "Michael Hartl <mhartl@example.com>"
  #full_nameメソッドが展開されている(表示は変わらない)
>> user.alphabetical_name
=> "Hartl, Michael"
>> user.full_name.split == user.alphabetical_name.split(', ').reverse
#user.full_nameをスペースで区切った配列 == user.alphabetical_nameを', 'で区切った配列を逆順に並び替え
=> true

4.5 最後に

4.4.5で作成したexample_user.rbファイルは今後使わないので、削除してください。

ターミナル(コンソールは終了させておく)
$ rm example_user.rb

適宜commit

本文ではmasterブランチにマージだけどプルリクの練習↓
ブランチのままGitHubにpush
GitHub上でプルリク→マージ
fetchしてmasterにcheckoutしてpullの流れ

以降まとめに付き本文ママ

まとめとか感想

長かった!やっと終わった!!4章!!
勉強感が強くて苦手な4章ですが、流さないように意識しつつ頑張りました!
1週目の時よりかなり頭に入ったと思います。頑張った!!

らくだ🐫にもできるRailsチュートリアルとは

「ド」が付く素人のらくだ🐫が勉強するRailsチュートリアルの学習記録です。
自分用に記録していますが、お役に立つことがあれば幸いです。

調べたとはいえらくだ🐫なりの解釈や説明が含まれます。間違っている部分もあるかと思います。そんな所は教えて頂けすと幸いなのですが、このブログにはコメント機能がありません💧お手数おかけしますがTwitterなどでご連絡いただければ幸いです