rubyに入門する

にあえん

January 15, 2024

Rubyってどうなの?ナナオです。

今回はあまり触ってこなかったRubyに入門してみようと思います。

きっかけとしてはある会社と面談したときに面白そうな会社だな~~と思ったのですが、Rubyをメインで使っている会社だったからです。

私のRubyのレベルは若干しかできないくらいのレベルなので、基礎はできるようにしておこうと思い、今回備忘録もかねて入門してみようと思います。

Rubyのセットアップ

まずはRubyのインストールを行っていきましょう。

バージョン管理はしっかりやっておきたいので、rbenvを使います。

私の開発環境はWindowsですが、anyenvを使いたかったのでWSL2で作業はしています。

また、すでにanyenvをダウンロード済みの環境ですが、anyenvのインストールは公式リポジトリを参照してください。

ではanyenvでrbenvをセットアップしていきます。

anyenv install rbenv

これで問題なくrbenvはインストールできます。

あとは適当に安定版の3.2.2くらいをインストールします。

rbenv install 3.2.2

ただここでエラーが起きてしまいました。かなしいね。

==> Downloading openssl-3.1.4.tar.gz...
-> curl -q -fL -o openssl-3.1.4.tar.gz https://dqw8nmjcqpjn7.cloudfront.net/840af5366ab9b522bde525826be3ef0fb0af81c6a9ebd84caa600fea1731eee3
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 14.8M  100 14.8M    0     0  10.0M      0  0:00:01  0:00:01 --:--:-- 10.0M
==> Installing openssl-3.1.4...
-> ./config "--prefix=$HOME/.anyenv/envs/rbenv/versions/3.2.2/openssl" "--openssldir=$HOME/.anyenv/envs/rbenv/versions/3.2.2/openssl/ssl" zlib-dynamic no-ssl3 shared
-> make -j 24

BUILD FAILED (Ubuntu 22.04 on x86_64 using ruby-build 20231225-4-g33168b3)

You can inspect the build directory at /tmp/ruby-build.20240115225829.19003.NXfhFA
See the full build log at /tmp/ruby-build.20240115225829.19003.log

ログを見てみると、zlibがインストールされていないのが原因だったようです。

インストールします。

sudo apt install libz-dev

再チャレンジしましたが、またエラーが出てしまいました。

==> Downloading openssl-3.1.4.tar.gz...
-> curl -q -fL -o openssl-3.1.4.tar.gz https://dqw8nmjcqpjn7.cloudfront.net/840af5366ab9b522bde525826be3ef0fb0af81c6a9ebd84caa600fea1731eee3
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 14.8M  100 14.8M    0     0  10.1M      0  0:00:01  0:00:01 --:--:-- 10.1M
==> Installing openssl-3.1.4...
-> ./config "--prefix=$HOME/.anyenv/envs/rbenv/versions/3.2.2/openssl" "--openssldir=$HOME/.anyenv/envs/rbenv/versions/3.2.2/openssl/ssl" zlib-dynamic no-ssl3 shared
-> make -j 24
-> make install_sw install_ssldirs
==> Installed openssl-3.1.4 to /home/nanao/.anyenv/envs/rbenv/versions/3.2.2
==> Downloading ruby-3.2.2.tar.gz...
-> curl -q -fL -o ruby-3.2.2.tar.gz https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.2.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 19.5M  100 19.5M    0     0   9.8M      0  0:00:01  0:00:01 --:--:--  9.8M
==> Installing ruby-3.2.2...
-> ./configure "--prefix=$HOME/.anyenv/envs/rbenv/versions/3.2.2" "--with-openssl-dir=$HOME/.anyenv/envs/rbenv/versions/3.2.2/openssl" --enable-shared --with-ext=openssl,psych,+
-> make -j 24
*** Following extensions are not compiled:
fiddle:
        Could not be configured. It will not be installed.
        /tmp/ruby-build.20240115230256.23220.MIDrdY/ruby-3.2.2/ext/fiddle/extconf.rb:73: missing libffi. Please install libffi or use --with-libffi-source-dir with libffi source location.
        Check /tmp/ruby-build.20240115230256.23220.MIDrdY/ruby-3.2.2/ext/fiddle/mkmf.log for more details.
openssl:
        Could not be configured. It will not be installed.
        /tmp/ruby-build.20240115230256.23220.MIDrdY/ruby-3.2.2/ext/openssl/extconf.rb:101: OpenSSL library could not be found. You might want to use --with-openssl-dir=<dir> option to specify the prefix where OpenSSL is installed.
        Check /tmp/ruby-build.20240115230256.23220.MIDrdY/ruby-3.2.2/ext/openssl/mkmf.log for more details.
psych:
        Could not be configured. It will not be installed.
        Check /tmp/ruby-build.20240115230256.23220.MIDrdY/ruby-3.2.2/ext/psych/mkmf.log for more details.
readline:
        Could not be configured. It will not be installed.
        /tmp/ruby-build.20240115230256.23220.MIDrdY/ruby-3.2.2/ext/readline/extconf.rb:62: Neither readline nor libedit was found
        Check /tmp/ruby-build.20240115230256.23220.MIDrdY/ruby-3.2.2/ext/readline/mkmf.log for more details.

BUILD FAILED (Ubuntu 22.04 on x86_64 using ruby-build 20231225-4-g33168b3)

You can inspect the build directory at /tmp/ruby-build.20240115230256.23220.MIDrdY
See the full build log at /tmp/ruby-build.20240115230256.23220.log

これはわかりやすいですね。

またいくつかのパッケージをインストールしてあげます。

また、パッケージ管理にはbrewを使用しているのですが、これのダウンロード方法についてはこちらを参照してください。

brew install openssl@1.1 libyaml

opensslは.zshrcに設定の追記が必要です。

# .zshrc

# opensslの設定
export PATH="/home/linuxbrew/.linuxbrew/opt/openssl@1.1/bin:$PATH"

あとはreadlineもインストールしておきます。

sudo apt install libreadline-dev

再度インストールしてみたところ、成功しました!

デフォルトのバージョンを3.2.2にしておきます。

rbenv global 3.2.2

これで問題なくirbコマンドが使えるようになりました!

❯ irb
irb(main):001:0> puts 3
3
=> nil

Rubyの基礎

ここを参考にします。

20分ではじめるRuby

適当にrubyのプロジェクトを作成していきます。

# プロジェクトの初期化
mkdir ruby-playground && cd ruby-playground
bundle init
# 適当に実行できるrubyファイルを作成
touch free_1.rb

ではまずは関数定義からやっていきましょう!

def hi
    puts "Hello ruby!"
end

hi()

pythonと同じくdefを使い、終わりにはendを入れます。

実行は以下のように行います。

> ruby free_1.rb
Hello ruby!

想定通りに出力されましたね!

またpythonとは違い、引数がない関数呼び出しはカッコが必要ないようです。

面白いですね。

def hi
    puts "Hello ruby!"
end

hi

毎回コマンド実行するのは面倒なので、VSCode上から実行できるように以下の拡張機能を入れておきましょう。

Code Runner - Visual Studio Marketplace

あとついでに以下のRubyにかかわる拡張機能も入れておきましょう。

Ruby - Visual Studio Marketplace

次に引数付きの関数を定義します。

def hi(name)
    puts "Hello #{name}!"
end

hi("test")

Rubyでは、特殊記号を付けなくてもテンプレート文字列を使えるようです。

引数はpythonと同じくデフォルト値を設定できるようです。

def hi(name = "world")
    puts "Hello #{name}!"
end

hi

さらに関数呼び出し時、引数が明確であればカッコなしでスペースで呼び出しもできるとあります!

Haskellっぽくてテンション上がります!!

hi "World"

さらに続けてクラスの定義をしてみます。

挨拶する人というクラスで、コンストラクタとメソッドを実装します。

class Greeter
    # Rubyにおけるコンストラクタ
    def initialize(name = "world")
        @name = name
    end

    def say_hi
        puts "Hi #{@name}"
    end

    def say_bye
        puts "Hi #{@name}, come back soon"
    end
end

def main
    greeter = Greeter.new # Greeter.initializeでは呼び出せないので注意!
    greeter.say_hi
    greeter.say_bye
end

main

これを実行してみます。

Hi world
Hi world, come back soon

定義したとおりに出力しました。

initializeで登場している@のついた変数はインスタンス変数であり、デフォルトでプライベートになっています。

@nameをパブリック変数にする場合、クラスにattr_accessorを定義して明示しておく必要があります。

class Greeter
    attr_accessor :name # コロン付けるの忘れずに!!

    # ...中略...
end

def main
    greeter = Greeter.new
    greeter.name = "test"
    puts greeter.name # 追加
end

これを実行した結果は以下の通りです。

test

変更後のインスタンス変数の値が出力されています。

ではさらにこのGreeterを発展させたクラスを実装します。

あまりにも一つのインスタンス変数が多重責務を負っているのでちょっとめまいがしてきますが、チュートリアルなのでこの辺はしょうがないですね。。

class MegaGreeter
    attr_accessor :names

    def initialize(names = "world")
        @names = names
    end

    def say_hi
        if @names.nil? # 返却される値がtrue/falseな関数はクエスチョンマークがつく
            puts "..."
        elsif @names.respond_to?("each")
            @names.each do |name| # eachの返却値から反復処理してる
                puts "Hello #{name}!"
            end
        else
            puts "Hello #{name}!"
        end
    end

    def say_bye
        if @names.nil?
            puts "..."
        elsif @names.respond_to?("join") # join関数があるか判定する
            puts "Goodbye #{@names.join(", ")}. Come back soon!"
        else
            puts "Goodbye #{@names}. Come back soon!"
        end
    end
end

def main
    greeter = MegaGreeter.new
    greeter.names = ["test_1", "test_2"] # 配列定義はpythonと一緒
    greeter.say_hi
    greeter.say_bye
end

# pythonで言う`if __name__ == "__main__":`みたいなやつ!
if __FILE__ == $0
    main
end

面白い特徴についてはコメントしていますが、実際現場にこんなコードあったら大変ですね。

pythonとの比較

公式にありました!

PythonからRubyへ

上で書いたこと以外で気になったところでいうと

いくつか気になったところについて深堀ってみます。

Procオブジェクト、ブロック、ラムダ式とは

ちょっと複雑になってきますが、ブロックは{}もしくはdo ~~ endで囲われた範囲のことと定義されています。

【これで完璧!】Rubyのブロックの使い方、使い道まとめ (do~end {}) | 侍エンジニアブログ

またブロックに関して面白いコードを定義している記事がありました。

Rubyのブロック、Proc、Lambdaって何? #Ruby - Qiita

class Sample
  define_method :output do
    x + y
  end
end

define_methodというのはクラスにメソッドを定義するための関数で、第一引数にメソッド名、第二引数に実処理を実装します。

[Ruby]define_methodを使えるようにしておきましょうか。 #Ruby - Qiita

つまり上のサンプルコードでは、defを使わずにdefine_methodとブロックを使って関数定義をしているんですね。

クラスの定義の中に関数の実行を直接できるって部分は、Rubyの面白い特徴だと思いました。

ただブロックはオブジェクトではないので、単体で存在できるわけじゃないんですね。

そこで使用するのがProcです。

block = Proc.new {
    |x, y| x + y
}
puts block.call(1, 2)

また、この時Proc.newはlambdaに置き換えることができます。

これがlambda式になります。

block = lambda {
    |x, y| x + y
}
# もしくはこう
block = ->(x, y) { x + y }
puts block.call(1, 2)

yieldキーワードの振る舞いについて

pythonではジェネレータとして活躍しているyieldですが、rubyではどのように変わるのでしょうか?

簡単にまとめてくれているサイトがありました。

Rubyのyieldって結局何なの?|よしだ

# 以下のような定義が
def hogehoge( x, &proc )
    proc.call if block_given?
    return x + 2
end

p hogehoge( 3 )
p hogehoge( 5 ){ p "foo" }

# yieldを使用することで簡潔になる!
def hogehoge( x )
    yield if block_given?
    return x + 2
end

p hogehoge( 3 )
p hogehoge( 5 ){ p "foo" }

つまりこの関数では、yieldを使うことで{}で宣言しているp "foo"の呼び出しを簡潔に行えるようになったわけですね。

pythonのジェネレータとはだいぶ役割が変わってくるなぁ。。

さらに実用的なコードを見つけました。

【Ruby】yieldとは?使いどころを具体例を用いて解説します - エンジニアの寝言

def process_page(url = nil)
 get url if url  # URLにアクセス

 wait_loading # 画面描画の完了まち
 check_error_page  # 画面読み込み後、エラー画面が表示されていた場合のハンドリング

 yield if block_given?
end

def login_page
 process_page('https://engineer-negoto.com/') do
   # ID, PWを入力しログインボタンをクリック
   input_id
   input_password
   click_button('login')
 end
end

def top_page
 # 詳細画面へ遷移できるのボタンをクリックする
 process_page { click_button('detail') } 
end

def detail_page
 # HTMLをダウンロードする
 process_page { download_html }
end

この例では、process_page関数の実行後に行ってほしい処理をブロックで指定しています。

つまり、rubyのyieldはpythonでいうwith句みたいなやつという認識でいいでしょう。

(間違っていたらコメントください)

まとめ

今回チュートリアルをやってみて、かなり面白いなと思いました。

railsなどの構築もまたやってみたいです!

それではまた。

参考

rbenvでのRubyインストール失敗対応(cannot load such file -- psych) #Ruby - Qiita