4.4 Rubyにおけるクラス
Rubyではあらゆるものがオブジェクト
4.4.1 コンストラクタ
ダブルクォートを使って文字列のインスタンスを作成しましたが、
これは文字列のオブジェクトを暗黙で作成するリテラルコンストラクタ。
>> s = "foobar" # ダブルクォートは実は文字列のコンストラクタ => "foobar" >> s.class => String >> f = 2 => 2 >> f.class => Integer >> g = 1.1 => 1.1 >> g.class => Float
文字列がclassメソッドに応答しており、
その文字列が所属するクラスを単に返していることがわかります。
なお配列でも、文字列と同様にインスタンスを生成できます。
>> a = Array.new([1, 3, 2]) => [1, 3, 2]
ハッシュの場合は若干異なります。配列のコンストラクタである
Array.new は配列の初期値を引数に取りますが、
Hash.new はハッシュのデフォルト 値を引数に取ります。
これは、キーが存在しない場合のデフォルト値です。
>> h = Hash.new => {} >> h[:foo] # 存在しないキー (:foo) の値にアクセスしてみる => nil >> h = Hash.new(0) # 存在しないキーのデフォルト値をnilから0にする => {} >> h[:foo] => 0
メソッドがクラス自身 (この場合はnew) に対して呼び出されるとき、
このメソッドをクラスメソッドと呼びます。
クラスのnewメソッドを呼び出した結果は、そのクラスのオブジェクトであり、
これはクラスのインスタンスとも呼ばれます。
lengthのように、インスタンスに対して呼び出すメソッドは
インスタンスメソッドと呼ばれます。
演習
1:1から10の範囲オブジェクトを生成するリテラルコンストラクタは何でしたか? (復習です)
a = 1..10
=> 1..10
2:今度はRangeクラスとnewメソッドを使って、1から10の範囲オブジェクト
を作ってみてください。ヒント: newメソッドに2つの引数を渡す必要があります
>> b = Range.new(1,10)
=> 1..10
3:比較演算子==を使って、上記2つの課題で作ったそれぞれのオブジェクトが
同じであることを確認してみてください。
a == b
=> true
superclassメソッドを使ってクラス階層を調べてみるとよくわかります。
>> s = String.new("foobar") => "foobar" >> s.class # 変数sのクラスを調べる => String >> s.class.superclass # Stringクラスの親クラスを調べる => Object >> s.class.superclass.superclass # Ruby 1.9からBasicObjectが導入 => BasicObject >> s.class.superclass.superclass.superclass => nil
“Rubyではあらゆるものがオブジェクトである” ということの技術的な意味。
Wordクラスを作成し、その中にある単語を前からと後ろからのどちらから読んでも同じ (つまり回文になっている) ならばtrueを返すpalindrome?メソッドを作成してみましょう。
> class Word >> def palindrome?(string) >> string == string.reverse >> end >> end => :palindrome?
このクラスとメソッドは次のように使うことができます。
>> w = Word.new # Wordオブジェクトを作成する => #<Word:0x22d0b20> >> w.palindrome?("foobar") => false >> w.palindrome?("level") => true
文字列を引数に取るメソッドを作るためだけに、わざわざ新しいクラスを作るのは変で単語は文字列なので、↓のようにWordクラスは Stringクラスを継承するのが自然で
(次のリストを入力する前に、古いWordクラスの定義を消去するために、Railsコンソールをいったん終了)。
コンソールでWordクラスを定義
class Word < String #WordクラスはStringクラスを継承する def palindrome? #文字列が回文であればtrueを返す self == self.reverse #selfは文字列自身を表します self == reverse #これでも動くよ.selfは省略可なので end end => :palindrome?
↑のコードは継承のためのRubyのWord < String記法。こうすることで、新しいpalindrome?メソッドだけではなく、Stringクラスが扱えるすべてのメソッドがWordクラスでも使えるようになる。
Stringクラスの内部では、メソッドや属性を呼び出すときのself.も省略可能です。self == reverse #これでも動きます。.selfは省略可なので
演習
1:Rangeクラスの継承階層を調べてみてください。同様にして、
HashとSymbolクラスの継承階層も調べてみてください。
>> s.class => Word >> s.class.superclass => Range >> s.class.superclass.superclass => Object >> s.class.superclass.superclass.superclass => BasicObject
2: にあるself.reverseのselfを省略し、reverseと書いてもうまく動くことを確認してみてください。
Rubyでは組み込みの基本クラスの拡張が可能なのです。
Rubyのクラスはオープンで変更可能であり、
クラス設計者でない開発者でもこれらのクラスにメソッドを自由に追加することが許されています。
class String #文字列が回文であればtrueを返す
def palindrome? self == self.reverse end end :String "deified".palindrome? true
Rubyで組み込みクラス(最初から予約されているクラス)にメソッドを追加できるということは実にすごいこと真に正当な理由がない限り、組み込みクラスにメソッドを追加することは無作法。
Railsの場合、組み込みクラスの変更を正当化できる理由がいくつもあります。
例えば Web アプリケーションでは、変数が絶対に空白にならないようにしたくなることがよくあります(ユーザー名などはスペースやその他の空白文字になって欲しくないものです) ので、
Railsはblank?メソッドをRuby に追加しています。
Railsの拡張は自動的にRailsコンソールにも取り込まれるので、
次のようにコンソールで拡張の結果を確認できます。
(注意: 次のコードは純粋な irb では動作しません)
>> "".blank? => true >> " ".empty? => false >> " ".blank? => true >> nil.blank? => true
スペースが集まってできた文字列は空 (empty) とは認識されませんが、
空白(blank)であると認識されていることがわかります。
nilは空白と認識されることに注意してな。nilは文字列ではないので、
Railsが実はblank?メソッドをStringクラスではなく、そのさらに上の基底クラスに追加していることが推測できます。
その基底クラスとは、(この章の最初で説明した)Object自身です。
RailsによってRubyの組み込みクラスに追加が行われている例については、あとで説明あります。
演習
1:palindrome?メソッドを使って、“racecar”が回文であり、“onomatopoeia”が回文でないことを確認してみてください。南インドの言葉「Malayalam」は回文でしょうか?
ヒント: downcaseメソッドで小文字にすることを忘れないで。
class String def palindrome? if self == reverse puts "palindrome!" else puts "nonepalindrome!" end end end s = String.new("racecar") s.palindrome? s = String.new("onomatopoeia") s.palindrome? s = String.new("Malayalam") s.downcase.palindrome?
2:Stringクラスにshuffleメソッドを追加してみてください。
ヒント:前やった演習が参考になります。??を埋めてください。
class String def shuffle self.?('').?.? end end "foobar".shuffle "borafo" split shuffle join
3:のコードにおいて、self.を削除してもうまく動くことを確認してください。
class String def shuffle split('').shuffle.join end end "foobar".shuffle "borafo"
4.4.4 コントローラクラス
class StaticPagesController < ApplicationController def home end def help end def about end end
StaticPagesControllerはApplicationControllerを継承して定義される
homeやhelp、aboutアクションを見ていく。
RailsコンソールはセッションごとにローカルのRails環境を読み込むので、
コンソール内で明示的にコントローラを作成したり、
そのクラス階層を調べたりすることができる。
Railsコンソールでは、その中からコントローラのアクション
(実はメソッド) を呼ぶこともできます。
>> controller.home => nil
ここでは、homeアクションの中身は空なのでnilが返されます。
第3章では一度もStaticPagesController.newを実行しませんでした。
実は、Railsは確かにRubyで書かれていますが、既にRubyとは別物なのです。
Railsのクラスは、普通のRubyオブジェクトと同様に振る舞うものもありますが、多くのクラスにはRailsの魔法の粉が振りかけられています。
演習
1:第2章で作ったToyアプリケーションのディレクトリでRailsコンソールを開き、
User.newと実行することでuserオブジェクトが生成できることを確認してみよう。
>>User.new => #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
2:生成したuserオブジェクトのクラスの継承階層を調べてみてください。
User => ApplicationRecord(abstract)=> ActiveRecord::Base=> Object=> BasicObject
ユーザークラス
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 :name, :email
ユーザー名とメールアドレス(属性: attribute)に対応するア
クセサー(accessor)をそれぞれ作成します。
アクセサーを作成すると、そのデータを取り出すメソッド(getter)と、データに代入するメソッド(setter)をそれぞれ定義してくれます。具体的には、この行を実行したことにより、インスタンス変数@nameとインスタンス変数@emailにアクセスするためのメソッドが用意されます。
Railsでは、インスタンス変数をコントローラ内で宣言するだけでビューで使えるようになる、といった点に主な利用価値があります。
ただ一般的には、そのクラス内であればどこからでもアクセスできる変数として使われます(これについては後で詳しく説明します)。
そしてインスタンス変数は常に@記号で始まり、まだ定義されていなければ値がnilになります。
initializeは、Rubyの特殊なメソッドで,これは User.newを実行すると
自動的に呼び出されるメソッドです。この場合のinitializeメソッドは、
次のようにattributesという引数を1つ取ります。
def initialize(attributes = {}) @name = attributes[:name] @email = attributes[:email] end
上のコードで、attributes変数は空のハッシュをデフォルトの値として持つため、
名前やメールアドレスのないユーザーを作ることができます。
(存在しないキーに対してハッシュはnilを返すので,:nameキーがなければattributes[:name]はnilになり、同じことがattributes[:email]にも言える)
最後に、formatted_emailメソッドを定義します。このメソッドは、
文字列の式展開を利用して、@nameと@emailに割り当てられた値を
ユーザーのメールアドレスとして構成します。
def formatted_email "#{@name} <#{@email}>" end
@記号によって示されているとおり、
@nameと@emailは両方ともインスタンス変数なので
自動的にformatted_emailメソッドで使えるようになります。
Railsコンソールを起動し、example_userのコードをrequireして、
自作したクラスを試しに使ってみましょう。
require './example_user' #example_userのコードを読み込む方法 =>true example = User.new =>#<User:0x224ceec @email=nil, @name=nil> example.name # attributes[:name]は存在しないのでnil =>nil example.name = "Example User" #名前を代入する =>"Example User" example.email = "user@example.com" #メールアドレスを代入する =>"user@example.com" example.formatted_email =>"Example User <user@example.com>"
requireのパスにある’.’は、Unixの “カレントディレクトリ”を表し
’./example_user’というパスは、カレントディレクトリからの相対パスでexample_userファイルを探すようにRubyに指示します。
次のコードでは空のexample_userを作成します。
次に、対応する属性にそれぞれ手動で値を代入することで、
名前とメールアドレスを与えますよattr_accessorを使っているので、
これで代入できるようになります)。
次のコードは、
example.name = "Example User"
@name変数に”Example User”という値を設定します。同様にemail属性にも値を設定します。これらの値はformatted_emailメソッドで使われます。
最後のハッシュ引数の波カッコを省略できることを説明しました。
それと同じ要領でinitializeメソッドにハッシュを渡すことで、
属性が定義済みの他のユーザを作成することができます。
>> user = User.new(name: "Michael Hartl", email: "mhartl@example.com") => #<User:0x225167c @email="mhartl@example.com", @name="Michael Hartl"> >> user.formatted_email => "Michael Hartl <mhartl@example.com>"
これは一般にマスアサインメント(mass assignment)と呼ばれる技法で、
Railsアプリケーションでよく使われます。例えば第7章は、実際にハッシュ
引数を使ってオブジェクトを初期化するコードがあります。
演習
1:Userクラスで定義されているname属性を修正して、
first_name属性とlast_name属性に分割してみましょう。
また、それらの属性を使って “Michael Hartl”といった文字列を返す
full_nameメソッドを定義してみてください。
最後に、formatted_emailメソッドのnameの部分を、
full_nameに置き換えてみましょう
(元々の結果と同じになっていれば成功です)
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 @full_name = "#{@first_name} #{@last_name}" end def formatted_email "#{@full_name} <#{@email}>" end end
rails console
>>require './example_user' => true >>user = User.new(first_name: "Michael", last_name: "Hartl", email: "mhartl@example.com") => #<User:0x00000002d21bd8 @first_name="Michael", @last_name="Hartl", @email="mhartl@example.com"> >>user.full_name => "Michael Hartl" >>user.formatted_email => "Michael Hartl <mhartl@example.com>"
2:”Hartl, Michael” といったフォーマット (苗字と名前がカンマ+半角スペースで区切られている文字列) で返すalphabetical_nameメソッドを定義してみましょう。
def alphabetical_name @alphabetical_name = "#{@last_name}, #{@first_name}" end
3:full_name.splitとalphabetical_name.split(‘, ‘).reverseの結果を比較し
、同じ結果になるかどうか確認してみましょう。
>> user.full_name.split => ["Michael", "Hartl"] >> user.alphabetical_name.split(', ').reverse => ["Hartl", "Michael" full_name.split == alphabetical_name.split(', ').reverse
ポイントがあるとするなら、テキストエディタで変更してセーブした場合、またrequireメソッドを使わないと変更が反映されないことに注意!あとインスタンス生成しないと意味がない
rm example_user.rb
4章のまとめ
1:Rubyは文字列を扱うためのメソッドを多数持っている 2:Rubyの世界では、すべてがオブジェクトである 3:Rubyではdefというキーワードを使ってメソッドを定義する 4:Rubyではclassというキーワードを使ってクラスを定義する 5:Railsのビューでは、静的HTMLの他にERB(埋め込みRuby:Embedded RuBy)も使えるRubyの組み込みクラスには配列、範囲、ハッシュなどがある 6:Rubyのブロックは (他の似た機能と比べ) 柔軟な機能で、添え字を使った データ構造よりも自然にイテレーション(繰り返し)ができる 7:シンボルとはラベルである。追加的な構造を持たない(代入などができない) 文字列みたいなもの 8:Rubyではクラスを継承できる 9:Rubyでは組み込みクラス(予約済みのクラス)ですら内部を見たり修正したりできる