Rails-tutorial自分用まとめ4.8(Ruby 演習)

4.5から続く

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では組み込みクラス(予約済みのクラス)ですら内部を見たり修正したりできる

その5に続きます

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です