やること
Rubyについて知る
4.1 動機
トピックブランチ作成
$ git switch -c rails-flavored-ruby
4.1.1 組み込みヘルパー
リスト4.1 レイアウト
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
railsの組み込み関数stylesheet_link_tagを使ってapplication.cssを全てのメディアタイプで使えるように
戸惑う可能性のあること: railsの組み込み関数、カッコを使わないメソッド呼び出し、シンボル、ハッシュ
4.1.2 カスタムヘルパー
新しく作ったメソッドをカスタムヘルパーと呼ぶ
ページタイトルが指定されていないときは基本タイトルを表示するfull_titleというヘルパーを作る
リスト4.2 full_titleヘルパーの中身
ヘルパーを使ってコードを短くできる
リスト4.3
リスト4.4 タイトル用にテスト更新
リスト4.6 homeページ更新
4.2 文字列とメソッド
railsコンソールを使っていく
コンソールはdevelopment環境で起動する
(他2つはtest環境とproduction環境)
気になるコードがあればRuby APIやRubyリファレンスマニュアル(るりま)をみる
#でコメントを使える
4.2.1 文字列
文字列(string)
""で囲んだものを文字列リテラルと呼ぶ
+演算子で結合できる
#{}で式展開できる。代入された変数を埋め込むと #{}の中で展開される
putsで文字列を出力できる
puts "foo"
foo
=> nil
副作用("foo"をコンソールに表示させた)が重要
似たようなprintメソッドだと結果が改行されない
""と''の違いは、''だと式展開を行わない
4.2.2 オブジェクトとメッセージ受け渡し
あらゆるものがオブジェクト
オブジェクトはメッセージに応答する(例:文字列にlengthというメッセージを送ると文字数を返す)
オブジェクトに渡されるメッセージを一般にメソッドと呼ぶ
疑問符を使えばtrueかfalseという論理値(boolean)を返す。処理の流れを変えるのに有効
演算子 &&and ||or !notで表すこともできる
メソッドチェーンという手法を使えばnilにメソッドを渡せる
nil.to_s.empty?
=> true
論理値がfalseになるのはfalse自身とnillの2つだけ
4.2.3 メソッドの定義
呼び出し時に引数とかっこを省略できる
→引数にデフォルト値を設定しているため
Rubyのメソッドには暗黙の戻り値がある
メソッド内で最後に評価された式の値が自動的に返される
メソッドで引数の変数名にはどんな名前を使っても問題ない
4.2.4 titleヘルパー、再び
リスト4.11 application_helperのfull_titleヘルパーの解説
1行目の module Applicationについて
Rubyのモジュールは互いに関連する複数のメソッドをまとめる方法の1つ
includeメソッドでモジュールを読み込める(ミックスインという)
railsでは自動的にヘルパーモジュールを読み込むのでinclude行を書く必要がない
このfull_titleメソッドは自動的に全てのビューで利用できる
4.3 他のデータ構造
いくつかのrubyのデータ構造について見ていく
4.3.1 配列と範囲演算子
配列(array)とは、特定の順序を持つ要素のリスト
splitメソッドを使うと、文字列を自然に変換した配列を得られる
"foo bar baz".split #文字列を3つの要素を持つ配列に分割する
=> ["foo", "bar", "baz"]
文字を指定して区切ることも可能
"fooxbarxbaz".split('x')
["foo", "bar", "baz"]
ゼロオリジン
配列の最初の要素のインデックスが0から始まり、0,1,2...と数えていく
a = [42, 8, 17]
a[0] == 42
a[2] == 17
[]以外でも要素にアクセス可能
a.first == 42
a.second == 8
lengthメソッドやempty?メソッドにも対応している
a.length 3
a.empty? false
a.include?(42) true
a.sort [8, 17, 42]
a.reverse [17, 8, 42]
どのメソッドを実行した場合にもaの中身は変更されていない
変更したい場合には、元のメソッドの末尾に!を付ける
a [42, 8, 17]
a.sort! [8, 17, 42]
a [8, 17, 42]
pushメソッド(もしくは<<演算子)で配列に要素を追加できる
a = [42, 8, 17]
a.push(6)
[42, 8, 17, 6]
a << 7
[42, 8, 17, 6, 7]
a << "foo" << "bar"
[42, 8, 17, 6, 7, "foo", "bar"]
rubyでは異なる型が配列の中で共存できる(整数と文字列など)
joinメソッドで配列を文字列に変換できる
a [42, 8, 17, 6, 7, "foo", "bar"]
a.join
"4281767foobar"
a.join(', ') #カンマとスペースを使って連結する
"42, 8, 17, 6, 7, foo, bar"
範囲(range)は配列と密接に関係している
0..9
0..9.to_a
9に対してto_aを使ったためエラー
(0..9).to_a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
範囲は配列の要素を取り出すのに便利
a = %w[foo bar baz quux] # %wで文字列の配列に変換
["foo", "bar", "baz", "quux"]
a[0..2]
["foo", "bar", "baz"]
インデックスに-1という値を指定できる。-1を使うと配列の長さを知らなくても配列の最後の要素を指定できる
配列の特定の開始位置の要素から最後の要素までを一度に選択できる
a = (0..9).to_a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
a[2..(a.length-1)] # 明示的に配列の長さを使って選択
[2, 3, 4, 5, 6, 7, 8, 9]
a[2..-1] # 添字に-1を使って選択
[2, 3, 4, 5, 6, 7, 8, 9]
文字列に対しても範囲オブジェクトが使える
('a'..'e').to_a
["a", "b", "c", "d", "e"]
4.3.2 ブロック
配列と範囲はいずれも、ブロックを伴うさまざまなメソッドに応答できる
(1..5).each { |i| puts 2 * 1}
2
4
6
8
10
上のコードでは、範囲オブジェクトの(1..5)に対してeachメソッドを呼び出している
メソッドに渡されている{ |i| puts 2 * i }の部分がブロック
iを囲む||は、ブロック整数に対して使うrubyの構文で、ブロックを操作するときに使う変数を指定する
この場合、範囲オブジェクトのeachメソッドは、iという1つのローカル変数を使ってブロックを操作できる
範囲に含まれるそれぞれの値をこの変数に次々に代入してブロックを実行する
ブロックは{}で囲むが、doとendで囲んで示すこともできる
(1..5).each do |i|
puts 2 * i
end
ブロックには複数の行を記述できる
railsチュートリアルでは、短い1行のブロックには{}を使い、長い1行や複数行のブロックにはdo..end記法を使う
(1..5).each do |number|
puts 2 * number
puts '__'
end
2
__
4
__
6
__
8
__
10
__
iの代わりにnumberを使っている。ブロック変数の名前は固定されていない
mapメソッドを使ったブロック例
3.times {puts "Betelgeuse!"} # 3.timesではブロックに変数を使っていない
"Betelgeuse!"
"Betelgeuse!"
"Betelgeuse!"
(1..5).map { |i| i**2 }
[1, 4, 9, 16, 25]
%w[a b c]
["a", "b", "c"]
%w[a b c].map { |char| char.upcase}
["A", "B", "C"]
%w[A B C].map {|char| char.downcase}
["a", "b", "c"]
mapメソッドは渡されたブロックを配列や範囲オブジェクトの各要素に対して適用し、その結果を返す
後半の2つの例では、mapのブロック内で宣言した引数(char)に対してメソッドを呼び出している
省略記法 &:メソッド名の記法を"symbol-to-proc"
%w[A B c].map {|char| char.downcase}
["a", "b", "c"]
%w[A B C].map(&:downcase)
["a", "b", "c"]
最後のブロックの例として単体テストを見る
テストコードにdoというキーワードがあり、テストの本体がそもそもブロックでできている
このtestメソッドは文字列(説明文)とブロックを引数にとり、テストが実行されるときにブロック内の文が実行される
('a'..'z').to_a.shuffle[0..7].join
a~zの英小文字の配列を作る
シャッフルして最初の8つの要素を取り出す
取り出した要素を結合して1つの文字列にする
4.3.3 ハッシュとシンボル
ハッシュは本質的には配列と同じ。違いはインデックスとして整数値以外のものも使える
ハッシュのインデックス(キーと呼ぶ)は何らかのオブジェクト
user = {}
{}
user["first_name"] = "Michael"
Michael
user["last_name"] = "Hartl"
"Hartl"
user["first_name"]
"Michael"
user
{"last_name"=>"Hartl", "first_name"=>"Michael"}
ハッシュはキーと値のペアを{}で囲んで表記する。キーと値のペアを持たない{}は空のハッシュ
ハッシュの{}とブロックの{}は全くの別物
ハッシュと配列は似ているが、並び順が保証されない違いがある
順序が重要な場合は配列を使う
ハッシュの1要素を[]を使って定義する代わりに、キーと値をハッシュロケット(=>)によって表現する方が簡単
user = {"first_name" => "Michael", "last_name" => "Hartl"}
{"last_name"=>"Hartl", "first_name"=>"Michael"}
Rubyの慣習で、{}の内側の最初と最後にスペースを追加している。このスペースはあってもなくてもいい
ハッシュのキーとして文字列を使っていたが、railsでは文字列よりもシンボルを使う方が普通
シンボルは文字列と似ているが、''で囲む代わりに:が前に置かれている点が異なる
ハッシュのキーとしてシンボルを採用する場合、userのハッシュは次のように定義
user = { :name => "Michael Hartl", :email => "michael@example.com" }
{:name=>"Michael Hartl", :email=>"michael@example.com"}
user[:name]
"Michael Hartl"
user[:password]
nil
未定義のハッシュ値はnilになる
ハッシュではシンボルをキーとして使うのが一般的
h1 = { :name => "Michael Hartl", :email => "michael@example.com" }
{:name=>"Michael Hartl", :email=>"michael@example.com"}
h2 = { name: "Michael Hartl", email: "michael@example.com" }
{:name=>"Michael Hartl", :email=>"michael@example.com"}
h1 == h2
true
2つ目の記法はキーの名前の前でなく後に:を置き、その後に値が続くように置き換えたもの
:name => "Michael Hartl" と name: "Michael Hartl"は同じ意味になる
リスト4.13 ハッシュの中にハッシュを使うことも可能
ハッシュのハッシュをネストされたハッシュという
ハッシュもeachメソッドに応答する
flash = { success: "It worked!", danger: "It failed." }
{:success=>"It worked!", :danger=>"It failed."}
flash.each do |key, value|
puts "Key #{key.inspect} has value #{value.inspect}"
end
Key :success has value "It worked!"
Key :danger has value "It failed."
配列のeachメソッドではブロックの変数は1つだけだが、ハッシュのeachメソッドではブロックの変数はキーと値の2つになっている
したがってハッシュに対してeachメソッドを実行すると、ハッシュ内にある「キーと値のペア」ごとに処理を繰り返す
inspectメソッドは要求されたオブジェクトを表現する文字列を返す
puts (1..5).to_a # 配列を文字列として出力
1
2
3
4
5
puts (1..5).to_a.inspect # 配列のリテラル(数値や文字列を直接記述したもの)を出力
[1,2,3,4,5]
puts :name, :name.inspect
name
:name
puts "It worked!", "It worled!".inspect
It worked!
"It worked!"
pメソッドというショートカットもある
p :name == puts :name.inspect
4.3.4 CSS、再び
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
上のコードは
<%= style_sheet_link_tag("application", { "data-turbo-track": "reload" }) %>
と同じ意味
メソッド呼び出しの()と、ハッシュがメソッド呼び出しの最後の引数である場合の{}は省略可能
stylesheet_link_tag "application", "data-turbo-track": "reload"はメソッドに2つの引数を渡している
最初の引数はスタイルシートへのパスを指定する文字列
次の引数はハッシュで、Turboの設定を含んでいる
リスト4.14
ソースコードを表示するとスタイルシートを読み込むのに必要なHTMLが表示される
16進数の長い文字列があるが、これは自動的に挿入した文字列でサーバー側で変更された場合にブラウザがCSSを再読み込みするのに使う
4.4 Rubyにおけるクラス
Rubyではメソッドをまとめるのにクラスを使う。クラスからインスタンスが生成されることでオブジェクトが作成される
4.4.1 コンストラクタ
””を使って文字列のインスタンスを作成したが、これは文字列のオブジェクトを暗黙で作成するリテラルコンストラクタ
s = "foobar"
"foobar"
s.class
String
文字列がclassメソッドに応答しており、その文字列が所属するクラスを返している
明示的に同等の名前付きコンストラクタを使える
名前付きコンストラクタはクラス名に対してnewメソッドを呼び出す
s = String.new("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[:foo] # 存在しないキー (:foo) の値にアクセスしてみる
nil
h = Hash.new(0) # 存在しないキーのデフォルト値をnilから0にする
{}
h[:foo]
0
メソッドがクラス自身(new)に対して呼び出されるときクラスメソッドと呼ぶ
クラスのnewメソッドを呼び出した結果はそのクラスのオブジェクトでありクラスのインスタンスと呼ばれる
インスタンスに対して呼び出すメソッドはインスタンスメソッドと呼ばれる
4.4.2 クラスの継承
superclassメソッドを使ってクラス階層を調べる
s = String.new("foobar")
"foobar"
s.class
String
s.class.superclass
Object
s.class.superclass.superclass
BasicObject
s.class.superclass.superclass.superclass
nil
図4.1 Stringクラスの継承階層
Rubyにおけるクラスは最終的にスーパークラスを持たないBasicObjectクラスを継承している
→Rubyではあらゆるものがオブジェクト
クラスを作成してみる
wordクラスを作成し、回文かチェックするpalindrome?メソッド作成
class Word
def palindrome?(string)
string == string.reverse
end
end
:palindrome?
次のように使う
w = Word.new
#Word:0x00007f74f947e580
w.palindrome?("foobar")
false
w.palindrome?("level")
true
上の文は不自然に書かれている
文字列を引数にとるメソッドを作るためだけにわざわざ新しいクラスを作るのは変
WordクラスはStringクラスを継承するのが自然
リスト4.15 Wordクラスを定義
こうすることでStringクラスが使える全てのメソッドがWordクラスでも使えるようになる
class Word < String
s = Word.new("level") # 新しいWordを作成し、"level" で初期化する
"level"
s.palindrome? # Wordが回文かどうかを調べるメソッド
true
s.length # WordはStringで扱える全てのメソッドを継承している
5
selfキーワードを使うと自分自身を指定できる
Wordクラスの中ではselfはオブジェクト自身を指す
self == self.reverse
単語が回文か確認できる
self == reverse 省略可
4.4.3 組み込みクラスの変更
継承を使わずにpalindrome?メソッドをStringクラス自身に追加すれば、Wordクラスを作らずpalindrome?をリテラル文字列に対して直接実行できるはず
Rubyのクラスは変更可能
class String
def palindrome?
self == self.reverse
end
end
:palindrome?
"deified".palindrome?
=> true
組み込みクラスにメソッドを追加するには正当な理由が必要
Railsの場合、変数が空白にならないようにするためblank?メソッドをRubyに追加している
4.4.4 コントローラクラス
図4.3 StaticPagesコントローラの継承階層
railsコンソールではコントローラのアクション(メソッド)を呼べる
controller.home
nil
railsのアクションには戻り値がない
railsはRubyで書かれているがRubyとは別物
4.4.5 ユーザークラス
完全なクラスを作成する
リスト4.17
example_user.rbの中身
attr_accessor :name, :email
ユーザー名とメールアドレス(属性:attribute)に対応するアクセサー(accessor)をそれぞれ作成する
アクセサーを作成するとそのデータを取り出すメソッド(getter)と、データに代入するメソッド(setter)をそれぞれ定義してくれる
この行を実行すると、インスタンス変数@name,@emailにアクセスするためのメソッドが用意される
railsでは、インスタンス変数をコントローラ内で宣言するだけでビューで使えるようになる
そのクラス内であればどこからでもアクセスできる変数として使われる
インスタンス変数は@から始まる、定義されていなければ値はnil
def initialize(attributes = {})
@name = attributes[:name]
@email = attributes[:email]
end
initialize(初期化)はRubyの特殊なメソッド
User.newを実行すると自動的に呼び出される
この場合initializeメソッドはattributes引数を1つとる
上のコードのattributes変数は空のハッシュをデフォルト値として持つため、名前やメールアドレスのないユーザーを作れる
存在しないキーに対してハッシュはnilを返すので、:nameキーがなければattributes[:name]はnilになる。attributes[:email]も同じ
formatted_emailメソッド
これは文字列の式展開を利用して、@nameと@emailに割り当てられた値をユーザーのメールアドレスとして構成する
railsコンソールで自作したUserクラスを使ってみる
requireで読み込む
require './example_user'
true
example = User.new
#
example.name
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ファイルを探すよう指示している
空のexample_userを作成し、対応する属性にそれぞれ手動で値を代入することで、名前とメールアドレスを与える
attr_accessorを使っているので代入できるようになっている
@name変数に"Example User"という値を設定。同様にemail属性にも設定。これらはformattedメソッドで使われる
initializeメソッドにハッシュを渡すことで属性が定義済みの他のユーザーを作成できる
user = User.new(name: "Michael Hartl", email: "mhartl@example.com")
#<User:0x00007f4040dbadf8 @email="mhartl@example.com",
@name="Michael Hartl">
user.formatted_email
"Michael Hartl mhartl@example.com"
これはマスアサイメント(mass assignment)と呼ばれる
(フォームから送られてきたパラメーターをひとつにまとめて、一度に保存できるRailsの機能)
4.5 最後に
example_user.rbは削除しておく
$ rm example_user.rb
リポジトリにコミットしてmainブランチにマージする
$ git commit -am "Add a full_title helper"
$ git switch main
$ git merge rails-flavored-ruby
gitにpushする前にテストしておく
$ rails test
$ git push
4.5.1 本章のまとめ
Rubyは文字列は扱うためのメソッドを多数持つ
Rubyではすべてがオブジェクト
Rubyではdefというキーワードを使ってメソッドを定義する
Rubyではclassというキーワードを使ってクラスを定義する
Railsのビューでは、静的HTMLの他にERB(埋め込みRuby: Embedded Ruby)も使える
Rubyの組み込みクラスには配列、範囲、ハッシュなどがある
Rubyのブロックは、添え字を使ったデータ構造よりも自然にイテレーション(一連の工程を短期間で繰り返す、開発サイクルのこと)できる
シンボルではラベル。代入などができない文字列みたいなもの
Rubyではクラスを継承できる
Rubyでは組み込みクラスですら内部をみたり修正できる