Ruby初心者によるRuby初心者のためのRuby基礎
Rubyはいろいろ柔軟すぎてまったくコードが読めなかったので備忘録も兼ねてざっと整理してみました。
まだRubyやRailsを触り始めて2〜3週間程度なのでなにか間違っていた場合はご指摘お願いします。
※ちょくちょく余談で深めな部分に言及してますがあまり気にしないでください。
文字列
- ダブルクォート(")、シングルクォート(')どちらでも括れる
- シングルクォート(')では変数が文字列展開されない
- カラ文字はカラ文字扱い
>> "foo" => "foo" >> 'foo' => "foo" >> "" => ""
- +で結合できる
>> "foo" + "bar" => "foobar" >> "foo" + "" => "foo"
変数
変数の初期化
- 宣言 -> 必要なし。どこでも使い始められる。
- 型の指定 -> 必要なし
- 数値、文字列ともに代入可能
- 命名規則 -> 小文字のみを使う。単語は_で繋ぐ(snake_case)
#変数の宣言、型の宣言は必要なし #代入と共にいきなり使える >> valu = 1 => 1 >> char = "foo" => "foo"
変数の文字列展開
- #{変数名}でダブルクォート内(" ")でも格納している文字列を展開できる
- シングルクォート内(' ')では展開できない
- 「#{変数名}」を出力したい場合は
'#{変数名}'
もしくは"\#{変数名}"
>> char = "foo" => "foo" >> char + "bar" => "foobar" >> "char" + "bar" => "charbar" >> "#{char}" + "bar" => "foobar" >> "#{char}bar" => "foobar" >> '#{char}bar' => "\#{char}bar" >> "\#{char}bar" => "\#{char}bar"
変数のスコープ
>> def puts_name >> name = "Tanaka Taro" >> puts name >> end => :puts_name >> puts_name Tanaka Taro => nil >> puts name Traceback (most recent call last): 1: from (irb):11 NameError (undefined local variable or method `name' for main:Object)
メソッド内で初期化された変数は初期化されたメソッド内からしか呼び出せない。
上の例ではputs_name
メソッドで初期化された変数name
はputs_name
内では呼び出せるが、puts_name
外から呼び出そうとするとエラーになる。
※余談
- printメソッドやputsメソッドでは"\n"が改行を意味する
- 「\」もしくは「''」で普通表示されない特殊文字を表示できる
>> print "foo" foo=> nil >> print "foo\n" foo => nil >> print "foo\\n" foo\n=> nil >> print 'foo\n' foo\n=> nil
>> puts "foobar" foobar => nil >> puts "foo\nbar" foo bar => nil >> puts "foo\\nbar" foo\nbar => nil >> puts 'foo\nbar' foo\nbar => nil
配列
配列の基本
- 変数同様宣言や型の指定は必要ない
- 配列の中にどんなオブジェクトも格納可能
- 数値や文字列、nilを同時に格納可能
[]
やArray.new
で初期化できる- 要素への参照は要素番号(0~)を指定
>> [] #中身のないカラの配列 => [] >> Array.new #ArrayはArrayクラスのオブジェクト => [] >> [] == Array.new => true >> array = [1, 2, "three", 4, "five", nil] => [1, 2, "three", 4, "five", nil] >> array[0] => 1 >> array[2] => "three" >> array[5] => nil
配列の大きさ取得
- 配列の大きさは
length
もしくはsize
で取得
>> array = [1, 2, "three", 4, "five", nil] => [1, 2, "three", 4, "five", nil] >> array.length => 6 >> array.size => 6
配列の各要素への繰り返し処理
each
もしくはmap
を使う- 処理の書き方は
{}
もしくはdo-end
で
>> array = [1, 2, "three", 4, "five", nil] => [1, 2, "three", 4, "five", nil] >> >> # {}を使った記述 >> array.each{ |value| puts "value = #{value}"} value = 1 value = 2 value = three value = 4 value = five value = nil => [1, 2, "three", 4, "five", nil] >> >> # do-end を使った記述 >> array.each do |value| >> puts "value = #{value}" >> end value = 1 value = 2 value = three value = 4 value = five value = nil => [nil, nil, nil, nil, nil, nil]
※余談
each
とmap
の違い
※2018/8/25追記
each
とmap
についてはこちらの記事にもまとめました。
each
処理1
>> def hoge >> (1..10).to_a.each do |i| >> unk = i + 1 >> end >> end => :hoge >> ihr = hoge => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
処理2
>> array = [1, 2, 3, 4, 5] >> i = 0 >> array.each do |value| >> puts "value = #{value}" >> puts "i = #{i}" >> i = value + 1 >> puts "i = value + 1" >> puts "i = #{i}" >> puts"-------------------" >> end value = 1 i = 0 i = value + 1 i = 2 ------------------- value = 2 i = 2 i = value + 1 i = 3 ------------------- value = 3 i = 3 i = value + 1 i = 4 ------------------- value = 4 i = 4 i = value + 1 i = 5 ------------------- value = 5 i = 5 i = value + 1 i = 6 ------------------- => [1, 2, 3, 4, 5]
eachメソッドは、配列の要素の数だけブロックを繰り返し実行します。繰り返しごとにブロック引数には各要素が順に入ります。戻り値はレシーバ自身です。
map
処理1
>> def huga >> (1..10).to_a.map do |i| >> unk = i + 1 >> end >> end => :huga >> ihr2 = huga => [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
処理2
>> array = [1, 2, 3, 4, 5] >> i = 0 >> array.map do |value| >> puts "value = #{value}" >> puts "i = #{i}" >> i = value + 1 >> puts "i = value + 1" >> puts "i = #{i}" >> puts"-------------------" >> end value = 1 i = 0 i = value + 1 i = 2 ------------------- value = 2 i = 2 i = value + 1 i = 3 ------------------- value = 3 i = 3 i = value + 1 i = 4 ------------------- value = 4 i = 4 i = value + 1 i = 5 ------------------- value = 5 i = 5 i = value + 1 i = 6 ------------------- => [nil, nil, nil, nil, nil]
参考:map, map! (Array) | Rubyリファレンス
mapメソッドは、要素の数だけ繰り返しブロックを実行し、ブロックの戻り値を集めた配列を作成して返します。collectメソッドの別名です。
each
では配列の要素(処理1のi
, 処理2のvalue
)を返す。処理1では配列の要素i
を返し、処理2ではvalue
を返す。いずれの処理においても処理内で要素自身を更新していないため処理後に返る値は入力された配列のまま。
一方map
ではブロックの戻り値を返す。処理1ではhoge
メソッドが返す値unk
を返し、処理2ではブロックをメソッド化していないためブロックは値を返さない。よって処理1では元の配列に1が足され、処理2ではnil
が返る。
ちなみにmap
はどうやらメソッドの最後に評価した値を返すようで、上記処理2の例ではputs "-------------------"
の評価値を返している。puts
はnil
を返すため、配列はnil
が入った状態で返ってくる。
よってmap
処理の最後の行にj = 1
という処理を追加した場合、返る配列は以下のように[1, 1, 1, 1, 1]
になる。
>> array = [1, 2, 3, 4, 5] >> i = 0 >> array.map do |value| >> puts "value = #{value}" >> puts "i = #{i}" >> i = value + 1 >> puts "i = value + 1" >> puts "i = #{i}" >> puts"-------------------" >> j = 1 >> end ... ... (省略) ... => [1, 1, 1, 1, 1]
メソッド
メソッドの定義
- クラス内で定義されたメソッドはそのクラスから作成されたオブジェクト(インスタンス)に対してのみ使用できる。
- 引数の()は省略可能
def メソッド名(引数1, 引数2, ..., 引数n) 実行する処理1 実行する処理2 ... end
メソッドの呼び出し
- オブジェクトに続いて
.
で繋ぐことで、そのオブジェクトのクラスで定義されているメソッドを呼び出すことができる。 - 引数の()は省略可能
メソッド名(引数1, 引数2, ..., 引数n) メソッド名 引数1, 引数2, ..., 引数n オブジェクト名.メソッド名(引数1, 引数2, ..., 引数n) オブジェクト名.メソッド名 引数1, 引数2, ..., 引数n
- メソッド命名規則
- 一般的に全て小文字
- 単語間は_で繋ぐ(snake_case)
>> def sum_puts(num1, num2) >> puts(num1 + num2) >> end => :sum_puts >> sum_puts(1, 2) 3 => nil >> sum_puts 1, 2 3 => nil
- メソッドの定義位置はメソッドの呼び出し前
- 定義前に呼び出すとエラーになる
>> sum_puts(1, 2) Traceback (most recent call last): 1: from (irb):1 NoMethodError (undefined method `sum_puts' for main:Object) >> def sum_puts(num1, num2) >> puts num1 + num2 >> end => :sum_puts >> sum_puts(1, 2) 3 => nil
※余談
値渡しと参照渡し
要はrubyではメソッド引数での値の渡し方は値渡しである、ということ。
値渡しと参照渡しについては以下の記事が詳しく解説している。
簡単にサンプルコードを書いてみた。
>> def sample(num_method) >> i = num_method >> puts "i = num_method" >> puts "---------------------" >> puts "num_method = #{num_method}" >> puts "i = #{i}" >> puts "---------------------" >> puts "num_method id = #{num_method.object_id}" >> puts "i id = #{i.object_id}" >> puts "---------------------" >> i = i + 1 >> puts "i = i + 1" >> puts "---------------------" >> puts "num_method = #{num_method}" >> puts "i = #{i}" >> puts "---------------------" >> puts "num_method id = #{num_method.object_id}" >> puts "i id = #{i.object_id}" >> puts "---------------------" >> num_method = num_method + 3 >> puts "num_method = num_method + 3" >> puts "---------------------" >> puts "num_method = #{num_method}" >> puts "i = #{i}" >> puts "---------------------" >> puts "num_method id = #{num_method.object_id}" >> puts "i id = #{i.object_id}" >> end => :sample >> num = 10 => 10 >> num.object_id => 21 >> sample(num) i = num_method --------------------- num_method = 10 i = 10 --------------------- num_method id = 21 i id = 21 --------------------- i = i + 1 --------------------- num_method = 10 i = 11 --------------------- num_method id = 21 i id = 23 --------------------- num_method = num_method + 3 --------------------- num_method = 13 i = 11 --------------------- num_method id = 27 i id = 23 => nil >> num => 10 >> num.object_id => 21
sample
メソッド内の num_method
,i
ともに始めのうちは引数num
と同じ値、同じobject_id
を持っている。
その後メソッド内で変数の中身が書き換わると同時にそれぞれの変数のobject_id
が変化している。
最終的にどちらも元の変数num
と違うobject_id
となり、num
の値は元のものから変化しない。
破壊的メソッドによる値の渡し方
強制的にレシーバーの値を変更する破壊的メソッドではどのように値を渡しているのかを検証。
破壊的メソッドを使用する前後でのobject_id
を調べる。
>> # sample メソッド >> def sample(str) >> puts "str = #{str}" >> puts "str id = #{str.object_id}" >> end => :sample >> >> # sample_upcase! メソッド >> def sample_upcase(str) >> puts "str = #{str}" >> puts "str id = #{str.object_id}" >> puts "str.upcase = #{str.upcase}" >> puts "str.upcase id = #{str.upcase.object_id}" >> i = str.upcase >> puts "i = #{i}" >> puts "i id = #{i.object_id}" >> puts "str = #{str}" >> puts "str id = #{str.object_id}" >> end => :sample_upcase >> >> # sample_upcase! メソッド >> def sample_upcase!(str) >> puts "str = #{str}" >> puts "str id = #{str.object_id}" >> puts "str.upcase! = #{str.upcase!}" >> puts "str.upcase! id = #{str.upcase!.object_id}" >> i = str.upcase! >> puts "i = #{i}" >> puts "i id = #{i.object_id}" >> puts "str = #{str}" >> puts "str id = #{str.object_id}" >> end => :sample_upcase! >> >> # str 変数に "hello" を代入、object_id 確認 >> str = "hello" => "hello" >> puts "str id = #{str.object_id}" str id = 70154707881220 => nil >> >> # sample メソッド呼び出し >> sample(str) str = hello str id = 70154707881220 => nil >> >> # str 変数に格納されている文字列と object_id の確認 >> puts "str = #{str}" str = hello => nil >> puts "str id = #{str.object_id}" str id = 70154707881220 => nil >> >> #sample_upcase メソッド呼び出し >> sample_upcase(str) str = hello str id = 70154707881220 str.upcase = HELLO str.upcase id = 70154697977560 i = HELLO i id = 70154719397600 str = hello str id = 70154707881220 => nil >> >> # str 変数に格納されている文字列と object_id の確認 >> puts "str = #{str}" str = hello => nil >> puts "str id = #{str.object_id}" str id = 70154707881220 => nil >> >> # sample_upcase! メソッド呼び出し >> sample_upcase!(str) str = hello str id = 70154707881220 str.upcase! = HELLO str.upcase! id = 8 i = i id = 8 str = HELLO str id = 70154707881220 => nil >> >> # str 変数に格納されている文字列と object_id の確認 >> puts "str = #{str}" str = HELLO => nil >> puts "str id = #{str.object_id}" str id = 70154707881220 => nil
この例からわかること
str
の参照するobject_id
は破壊的メソッド(upcase!
)使用前後で変わらない。- 破壊的でないメソッドではレシーバー(
str
)とは違うobject_id
に値を返す。
肝心のsample_upcase!
メソッドの挙動ですが、i
に空文字かnil
が入っているなど少し挙動がおかしいようなのでもう少し検証。
>> str = "hello" => "hello" >> str.object_id => 70287705130100 >> str.upcase! => "HELLO" >> str.upcase! => nil >> str => "HELLO" >> str.object_id => 70287705130100 >> str.upcase!.object_id => 8 >> nil.object_id => 8
破壊的メソッドupcase!
によって大文字変換を行う際に、対象の文字列がすでにすべて大文字だとnil
が返ってくる。
そしてnil
オブジェクトのobject_id
が8。
つまり先ほどの例ではsample_upcase!
メソッド内のputs "str.upcase! = #{str.upcase!}"
処理によってstr
内の文字列が大文字に変換され、それ以降の処理puts "str.upcase! id = #{str.upcase!.object_id}"
, i = str.upcase!
ではnil
が参照されていたことになる。
それでは肝心の破壊的メソッドupcase!
が実行される瞬間はどこを参照しているのかというと、
>> str = "hello" => "hello" >> str.object_id => 70287705130100 >> str.upcase!.object_id => 70287705130100 >> str => "HELLO" >> str.object_id => 70287705130100
きちんとレシーバーであるstr
のobject_id
を参照している。
結論、
- 非破壊的メソッド
upcase
はレシーバーと異なるobject_id
で文字列変換を行う - 破壊的メソッド
upcese!
はレシーバーと同じobuject_id
で文字列変換を行う - 破壊的メソッドを使用してレシーバーの値から変更がない場合、破壊的メソッドは
nil
を返す
クラス
クラスの定義
- 定義位置はクラスの呼び出し前
- クラスの命名規則
- 頭文字は大文字
- 単語の頭文字を大文字にして単語を区切る(CamelCase)
# クラスの定義 class クラス名 end
>> JapanesePeople.class # classメソッド = オブジェクトのクラスを返すメソッド Traceback (most recent call last): 1: from (irb):1 NameError (uninitialized constant JapanesePeople) # クラスの定義前なのでエラー出力 >> class JapanesePeople >> >> end => nil >> JapanesePeople.class => Class # JapanesePeopleクラスが作成されていることが確認された
オブジェクトの生成
- オブジェクトを生成するとクラス内の
initialize
メソッドが実行される。 initialize
メソッドはクラス定義時に省略可能。initialize
メソッドを使うことでオブジェクト生成時に必ず行うべき処理を実行することができる。
# オブジェクトの生成
オブジェクト名 = クラス名.new()
>> class JapanesePeople >> def initialize(name, age, from) >> @name = name >> @age = age >> @from = from >> puts "name: #{@name}, age: #{@age}, from: #{@from}" >> end >> end => :initialize >> tanaka = JapanesePeople.new("Tanaka", 25, "Tokyo") name: Tanaka, age: 25, from: Tokyo => #<JapanesePeople:0x00007fcd2640abc8 @name="Tanaka", @age=25, @from="Tokyo">
インスタンス変数
- クラス内の全てのメソッドで共有できる変数。
- 変数名の頭に@をひとつ付ける
- クラスから作成されるインスタンスごとに独立した変数
>> class JapanesePeople >> def initialize(name, age, from) >> @name = name >> @age = age >> @from = from >> puts "name: #{@name}, age: #{@age}, from: #{@from}" >> end >> def introduction >> puts "#{@from}出身の#{@name}です。#{@age}歳です。" >> end >> end => :introduction >> tanaka = JapanesePeople.new("Tanaka", 25, "Tokyo") name: Tanaka, age: 25, from: Tokyo => #<JapanesePeople:0x00007fcd2640abc8 @name="Tanaka", @age=25, @from="Tokyo"> >> suzuki = JapanesePeople.new("Suzuki", 30, "Fukuoka") name: Suzuki, age: 30, from: Fukuoka => #<JapanesePeople:0x00007fcd2642b620 @name="Suzuki", @age=30, @from="Fukuoka"> >> tanaka.introduction Tokyo出身のTanakaです。25歳です。 => nil >> suzuki.introduction Fukuoka出身のSuzukiです。30歳です。 => nil
インスタンス変数はtanaka
とsuzuki
で独立しているが、どちらもJapanesePeople
クラスなのでintroduction
メソッドは共通。
クラス変数
- 同じクラスであればインスタンスを超えて参照可能な変数。
- もちろんクラス内のどのメソッドからも参照可能。
- 変数名の頭に@@を付ける。
>> class JapanesePeople >> def initialize(name, age, from) >> @name = name >> @age = age >> @from = from >> @@like = "kome" >> puts "name: #{@name}, age: #{@age}, from: #{@from}, like: #{@@like}" >> end >> def introduction >> puts "#{@from}出身の#{@name}です。#{@age}歳です。#{@@like}が好きです。" >> end >> def like_change_kome >> @@like = "kome" >> end >> def like_change_sakura >> @@like = "sakura" >> end >> end => :like_change_sakura >> tanaka = JapanesePeople.new("Tanaka", 25, "Tokyo") name: Tanaka, age: 25, from: Tokyo, like: kome => #<JapanesePeople:0x00007fcd26b9a118 @name="Tanaka", @age=25, @from="Tokyo"> >> suzuki = JapanesePeople.new("Suzuki", 30, "Fukuoka") name: Suzuki, age: 30, from: Fukuoka, like: kome => #<JapanesePeople:0x00007fcd28c5e2c8 @name="Suzuki", @age=30, @from="Fukuoka"> >> tanaka.introduction Tokyo出身のTanakaです。25歳です。komeが好きです。 => nil >> suzuki.introduction Fukuoka出身のSuzukiです。30歳です。komeが好きです。 => nil >> tanaka.like_change_sakura => "sakura" >> tanaka.introduction Tokyo出身のTanakaです。25歳です。sakuraが好きです。 => nil >> suzuki.introduction Fukuoka出身のSuzukiです。30歳です。sakuraが好きです。 => nil >> suzuki.like_change_kome => "kome" >> tanaka.introduction Tokyo出身のTanakaです。25歳です。komeが好きです。 => nil >> suzuki.introduction Fukuoka出身のSuzukiです。30歳です。komeが好きです。 => nil
クラス変数である@@like
はJapanesePeople
クラスのtanaka
, suzuki
どちらにも常に共通であり、どちら側のメソッドからも参照して値を変更することが可能。
クラスの継承
- クラスの継承方法
class クラス名 < 継承したいクラス名 end
クラスを継承することで、継承元のクラスで定義したメソッドなどを継承先のクラスでも使用することができる。
>> class Person >> def introduction >> puts "私は人間です。" >> end >> end => :introduction >> class Japanese < Person >> end => nil >> Tanaka = Japanese.new() => #<Japanese:0x00007ffb0099f3b0> >> Tanaka.introduction 私は人間です。 => nil
Japanese
クラスはPerson
クラスを継承しているため、Japanese
クラスのインスタンスであるTanaka
はJapanese
クラスで定義されていなくてもPerson
クラスのintroduction
メソッドを使用することができる。
シンボル
:symbol
など、:
が前置された文字列のこと。
変数と違い、シンボル自身に値や文字列を格納することはできない。
シンボルを表すクラス。シンボルは任意の文字列と一対一に対応するオブジェクトです。
文字列の代わりに用いることもできますが、必ずしも文字列と同じ振る舞いをするわけではありません。 同じ内容のシンボルはかならず同一のオブジェクトです。
文字列との違い
文字列は、同じ文字列でも参照するたびにobject_id
の違うオブジェクトが参照されている。
何度参照しても表面上の文字列としては同じものだが、参照するたびにオブジェクトとしては違うものという判定になる。
>> "symbol".object_id => 70193690175060 >> "symbol".object_id => 70193690258700 >> "symbol".object_id => 70193693840280
>> "symbol" == "symbol" => true >> "symbol".object_id == "symbol".object_id => false
一方シンボルは文字列の組み合わせに対して常に同じobject_id
をとる。
何度参照しても常に同じobject_id
となり、同じオブジェクトとみなされる。
>> :symbol.object_id => 392008 >> :symbol.object_id => 392008 >> :symbol.object_id => 392008
>> :symbol == :symbols => true >> :symbol.object_id == :symbol.object_id => true
ハッシュ
ハッシュの定義
- 配列と似た構造
- キーを指定してオブジェクト(値)を格納する。
- 配列番号のみでなく任意の数値や文字列でオブジェクトを取り出せる。
- {}は省略可能
- キーには数値、文字列、シンボルなどを始めあらゆるオブジェクトを採用可能。
nil
やtrue
,false
, クラス, モジュールなど
# キーが数値、文字列の場合 ハッシュ名{キー1 => オブジェクト1, キー2 => オブジェクト2, ...} ハッシュ名 キー1 => オブジェクト1, キー2 => オブジェクト2, ... # キーがシンボルの場合 ハッシュ名{:キー1 => オブジェクト1, :キー2 => オブジェクト2, ...} ハッシュ名 :キー1 => オブジェクト1, :キー2 => オブジェクト2, ... ハッシュ名{キー1: オブジェクト1, キー2: オブジェクト2, ...} ハッシュ名 キー1: オブジェクト1, キー2: オブジェクト2, ... # ハッシュにオブジェクトを追加 ハッシュ名[キー] = オブジェクト # ハッシュの値を参照 ハッシュ名[キー]
- 変数や配列、ハッシュは中身のオブジェクトがキーとして割り当てられる
- よって中身が変わるとエラーとなる
>> greet = "hello" => "hello" >> array = [1, 2, 3, 4, 5] => [1, 2, 3, 4, 5] >> person = {name: "tanaka", age: 25} => {:name => "tanaka", :age => 25} >> hash = {greet => "hoge", array[0] => "fuga", person[:name] => "hogefuga"} => {"hello"=>"hoge, "1=>"fuga", "tanaka"=>"hogefuga"}
ハッシュをハッシュにネストする
ハッシュのオブジェクトにハッシュを持っている状態
>> family = { >> name: family_name = { >> dat: "toshio", >> mam: "kaori", >> son: "yuta", >> daughter: "saori" >> }, >> num: 4 >> } => {:name=>{:dat=>"toshio", :mam=>"kaori", :son=>"yuta", :daughter=>"saori"}, :num=>4}
モジュール
モジュールの定義
- 定義位置はモジュールの呼び出し前
- モジュールの命名規則
- 頭文字は大文字
- 単語の頭文字を大文字にして単語を区切る(CamelCase)
module モジュール名 処理 end
>> module Person >> def introduction >> puts "私は人間です。" >> end >> end => :introduction
モジュールについてはこちらの記事がわかりやすかったのでここでは簡単な紹介程度で。
上記の記事で、
- モジュールはインスタンスを作れない
- モジュールは継承できない
とあったので、これだけ確認しておきます。
モジュールはインスタンス(オブジェクト)を生成できない
- そもそもModuleクラスにはインスタンスを生成するnewメソッドは用意されていない。
>> module Person >> def introduction >> puts "私は人間です。" >> end >> end => :introduction >> Person.new() NoMethodError: undefined method `new' for Person:Module from (pry):10:in `__pry__'
モジュールは継承できない
- モジュールを継承しようとするとエラーが出力される。
>> module Person >> def introduction >> puts "私は人間です。" >> end >> end => :introduction >> module Japanese < Person SyntaxError: unexpected '<' module Japanese < Person ^ >> class Japanese < Person >> end TypeError: superclass must be a Class (Module given) from (pry):6:in `__pry__'
おわりに
何かご指摘やご要望があれば遠慮なくコメントお願いします。