Rubyのoptparseについて。

皆様こんにちは、Amiaです。

今回はタイトルにもある通りRubyの『optparseライブラリ』についてまとめていきたいと思います。
インプットしたら忘れないうちにアウトプット!!

よろしければ最後までご覧になっていただければと思います。

⚫︎optparseライブラリとは

Rubyの標準ライブラリの1つ。
コマンドラインのオプションを取り扱うためのライブラリ。
optparseライブラリを使う場合は、基本的に下記のステップで使用します。
optparserequireする。
OptionParserオブジェクトoptを生成する。
.onメソッドを用いてオプションを取り扱うブロックをoptに登録する。
.parseメソッドを用いて、与えられたARGVparse(パース)する。

⚫︎使い方について

Rubyのリファレンスに記載されているサンプルコードを使用して挙動を確認していきたいと思います。
sample.rb

require 'optparse'
opt = OptionParser.new

opt.on('-a') {|v| p v }
opt.on('-b') {|v| p v }

opt.parse!(ARGV)
p ARGV

実行結果

["-a", "foo", "bar", "-b", "baz"]
true
true
["foo", "bar", "baz"]

.onメソッドは、optオフジェクトに登録するオプションを定義しています。
ARGVにはコマンドラインで渡されたオプション、引数が配列として渡されます。parse(パース)することで引数だけ取り出すことができます。
ARGVからオプションを取り除く必要がない場合は、破壊的メソッドは使用せずにopt.parse(ARGV)とすれば良いです。

上記のsample.rbプログラムではオプションをa,bとして定義しましたが、ヘルプオプションと(h)はデフォルトで定義されています。

ruby sample.rb -h
["-h"]
Usage: sample [options]
    -a
    -b

⚫︎ロングオプションを定義

ショートオプションだけでなくロングオプションを定義したい場合は、.onメソッドでオプション定義する時にショートオプションと一緒にロングオプションとして渡したい値を指定します。
下記のように指定することでショート、ロングのどちらでもオプションを指定できるようになります。

require 'optparse'
opt = OptionParser.new

opt.on('-a', '--all') {|v| p v }
opt.on('-b', '--before') {|v| p v }
opt.parse(ARGV)
p ARGV
ruby sample.rb --all foo --before bar
true
true
["--all", "foo", "--before", "bar"]

またヘルプオプションについてもショート、ロングのどちらでも表示されていることがわかります。

ruby sample.rb -h
Usage: sample [options]
    -a, --all
    -b, --before

⚫︎オプションの説明を明記

ヘルプオプションでヘルプを表示した時に使用できるオプションだけでなく、そのオプションの説明を追加することができます。その場合はロングオプション同様に.onメソッドに説明文を渡します。

require 'optparse'
opt = OptionParser.new

opt.on('-a', '--all', 'description:a') {|v| p v }
opt.on('-b', '--before', 'description:b') {|v| p v }
opt.parse(ARGV)
p ARGV

ヘルプオプションを指定した時に説明文が表示されるようになります。

ruby sample.rb -h
Usage: sample [options]
    -a, --all                        description:a
    -b, --before                     description:b

⚫︎どのオプションが指定されたか記憶させる

OptionParser自体はどのオプションが指定されたかは記憶していません。そのためどのオプションがコマンドラインで渡されてきたか覚えさせておく必要がある場合は、コンテナに格納しておきます。

require 'optparse'
opt = OptionParser.new

option = {}
opt.on('-a', '--all', 'description:a') {|v| option[:a] = v }
opt.on('-b', '--before', 'description:b') {|v| option[:b] = v }
opt.parse(ARGV)
p ARGV
p option

オプションが指定された場合は、コンテナにkey,valueが保存されます。オプションが指定されなかった場合は、nilが返ります。

ruby sample.rb -a foo -b bar
["-a", "foo", "-b", "bar"]
{:a=>true, :b=>true}

ruby sample.rb -a foo
["-a", "foo"]
{:a=>true}

⚫︎オプションに引数が必須か否か指定する

  • 引数が必須の場合
    .onメソッドでオプションの末尾に文字列を追加します。
    ・下記例の例ですとVALを追加しています。

  • 引数が省略可能な場合
    .onメソッドでオプションの末尾に[]で囲った文字列を追加します。
    ・下記例ですと[VAL]を追加しています。

require 'optparse'
opt = OptionParser.new

option = {}
# 引数なし(通常)の記述
opt.on('-a', '--all', 'description:a') {|v| option[:a] = v }
# 引数必須の記述
opt.on('-b VAL', '--before', 'description:b') {|v| option[:b] = v }
# 引数必省略可能の記述
opt.on('-c [VAL]', '--ccc', 'description:c') {|v| option[:c] = v }
opt.parse!(ARGV)

引数必須な場合に引数を渡さないと下記のようなエラーが発生します。

ruby sample.rb -b
sample.rb:8:in `<main>': missing argument: -b (OptionParser::MissingArgument)

また、オプションと引数を一緒に渡すことで引数をそのままハッシュ(Hash)にkeyとvalueを格納できます。 引数なしのオプションには真偽値が返り、オプションb,cに引数を渡すと引数がハッシュに入ります。

ruby sample.rb -a -b test -c test
{:a=>true, :b=>"test", :c=>"test"}

⚫︎受け取る引数のクラスを指定する

あるオプションの引数で受け取るクラスを限定したい場合は、下記のように記述します。

require 'optparse'
opt = OptionParser.new

option = {}
# 引数はIntegerのみ
opt.on('-a', '--all=N', Integer, 'description:a') {|v| option[:a] = v }
# 引数はStringのみ
opt.on('-b', '--before=S', String, 'description:b') {|v| option[:b] = v }
opt.on('-c [VAL]', '--ccc', 'description:c') {|v| option[:c] = v }
opt.parse!(ARGV)
p option

Intergerクラスを指定すると、文字列が引数として渡った場合エラーを返します。また、Stringクラスを指定すると数値を引数に渡した場合でも文字列として受け取ります。

 ruby sample.rb -a 1 -b aaa
{:a=>1, :b=>"aaa"}

ruby sample.rb -a aaa
sample3.rb:7:in `<main>': invalid argument: -a aaa (OptionParser::InvalidArgument)

ruby sample.rb -b 1
{:b=>"1"}

⚫︎ARGVの機能

ARGVコマンドラインから渡された引数が配列として渡されます。
ARGVでオプションを定義することができます。
オプションの指定はOptionParser::Arguableモジュールのgetoptsメソッドでオプションを指定することができます。getoptsメソッド使うとワンラインでコードでオプションと引数を指定することができ、受け取ったものをハッシュとして受け取ることができます。

getoptsメソッドにはショートネームのオプション、またはロングネームのオプションを指定することができます。引数が必要なオプションにはコロンをオプションの後ろにつけます。オプションはopt.onの時と異なり1つ1つ指定するのではなくまとめて記述するため、オプションや引数をシンプルに扱うだけであればgetoptsメソッド使うと楽になります。

◎ショートネームのオプション

  • ショートネームのオプションを文字列で指定します。
  • オプションが引数を取る場合は直後に:を付けます。
    ・aという引数を取るオプションを追加する場合:a:
    ・aとbという引数を取るオプションを追加する場合:a:b:
    ・aという引数を取らないオプションとbという引数を取るオプションを追加する場合:ab:

◎ロングネームのオプション

  • ショートネームのオプションを文字列で指定します。
  • オプションが引数を取る場合は直後に:を付けます。
  • デフォルト値は:の直後に指定します。
    ・bufsizeという引数を取らないオプションを追加する場合:bufsize
    ・bufsizeという引数を取るオプションを追加する場合:bufsize:
    ・デフォルト値1024のbufsizeという引数を取るオプションを追加する場合:bufsize:1024
require 'optparse'

# 引数なしのオプションa, 引数ありのオプションbを指定
params = ARGV.getopts("ab:")
p params
ruby sample2.rb -h
Usage: sample2 [options]
    -a
    -b VAL

ruby sample2.rb -a -b bbb
{"a"=>true, "b"=>"bbb"}

ロングネームのオプションを使用したい場合も同様に引数が必要なオプションには:をオプションの後ろに付けます。ですが、ショートネームのオプションの時のようにまとめてオプションは指定せずに分けて書きます。ショートネームのオプションが不要な場合は、""のように空文字を渡してあげる必要があります。

require 'optparse'
# ショートオプションなし、ロングオプション引数なし or あり
params = ARGV.getopts("", "all", "before:")
p params
ruby sample2.rb -h
Usage: sample2 [options]
        --all
        --before=VAL

ruby sample2.rb --all --before 1
{"all"=>true, "before"=>"1"}

また、オプションのデフォルト値も指定することが可能です。

require 'optparse'
# ショートオプションなし、ロングオプション引数なし、ロングオプション引数あり、デフォルト値あり
params = ARGV.getopts("", "all", "before:", "maxsize:1024")
p params
ruby sample2.rb -h
Usage: sample2 [options]
        --all
        --before=VAL
        --maxsize=1024

ruby sample2.rb
{"all"=>false, "before"=>nil, "maxsize"=>"1024"}

デフォルト値はコマンド実行時に上書きすることも可能です。

ruby sample2.rb --maxsize=100
{"all"=>false, "before"=>nil, "maxsize"=>"100"}

⚫︎最後に

今回はRubyのoptparseについて学習する中で調べたことを簡単にですが記事としてまとめてみました。
Rubyもですがoptparseについてはまだまだ理解の浅い部分が多いため、復習したいと思います。

最後までご覧いただきありがとうございました。

⚫︎参考文献

docs.ruby-lang.org

docs.ruby-lang.org

Happiness Chainで約8ヶ月学習した感想

これは「Happiness Chain Advent Calendar 2023」の10日目の記事です。
Happiness Chain Advent Calendar 2023 - Adventar

皆様こんにちは、Amiaです。

私は「Happiness Chain」というオンラインのプログラミングスクールに入会して勉強しています。

そして「Happiness Chain」に入会してから約8ヶ月が経ちました。
今回は「Happiness Chain」で約8ヶ月学習した私の感想等を記事として書いていきたいと思います。

自己紹介

改めましてAmia(あみあ)と申します。
3回ほど転職をして現在は経理という名の総務事務をやっております。🥹
年齢は20代後半、趣味は動画配信サービスでの動画視聴です。
ITに関する知識、スキルはもちろん特筆する他の知識やスキルはございません。
受験勉強もしないような勉強嫌いでした。

Webエンジニアを目指そうと思った理由

①年収を上げたい
まずはお金ですね。とても大事です。お金があれば大抵のことはなんとかなると思います。年収が上がれば今よりも良い場所に住めますし、欲しいものも買えるようになります。
②スキルを身に付けた分だけ年収が上がっていくこと
現在の職場へ転職するにあたり約1年かけて簿記の資格を取得しました。1年後には簿記の資格を活かした仕事を教えてもらえる予定でした。現在の職場には1年と少しいますが簿記の資格を活かした仕事はやらせてもらえません。
今まで楽な方へ楽な方へと進んできたので当たり前の結果ですね。また転職するにしても経験がないため自身の思うような職場には中々いくことができないでしょう。
そこで3つ目の理由につながるのですが、「どうせなら将来的にスキルを身に付けた分だけ年収が上がり、場所の制約ない職業」に転職しようと考えました。
③働く場所に制約がないこと
PCと環境さえ整えていれば働く場所に制約がないことは将来的にも魅力的だと感じました。職場へわざわざ通勤する必要がないためその分時間を有意義に使えますよね。

挫折と再帰

私は恥ずかしながら一度挫折し、「Happiness Chain」を退会しています。
ですが、どうしても人生を変えたいと考えて再度入会しました。
一度挫折してしまった理由は先に何ヶ月先もの学習時間を設定して「1日1日目標時間分の学習は絶対に行わなくては」と根を詰めていたからですね。
再度「Happiness Chain」に入会してからは下記に2つのことを行い挫折しないようにしています。
①目標をなるべく低く設定すること
もちろん目標学習時間を設定することは大事なことですが、始めのうちは尚のこと低く設定することが挫折しないコツなのではないかと思います。
今はなるべく日々の目標を低く設定して学習を行なっています。
②適度に休息日を設ける
月に何日か決めて趣味に全力を尽くす日を設けています。
毎日学習を続けていくよりも休息日を設けることで結果、その次の日から集中して学習できるようになることが多かったです。

以上の2点を行うようになってからは挫折することは今のところありません。

現段階までの学習時間と学習内容

1ヶ月目の学習内容(約66時間)

⚫︎Progateでの学習
(途中まで)

2ヶ月目の学習内容(約88時間)

⚫︎Progateでの学習
(先月の途中から)
⚫︎エンジニアとしての基礎を学ぶ
⚫︎Web技術の基本を学ぶ
⚫︎VSCodeについて
⚫︎Vimについて
⚫︎Linuxについて

3ヶ月目の学習内容(約88時間)

⚫︎Linuxについて
⚫︎Git & GitHub

4ヶ月目の学習内容(約74時間)

⚫︎HTML/CSSについて

5ヶ月目の学習内容(約83時間)

⚫︎Web開発の基礎を学ぶ
⚫︎Docker
(途中まで)

6ヶ月目の学習内容(約82時間)

⚫︎Docker
(先月の途中から)

7ヶ月目の学習内容(約70時間)

⚫︎Docker
⚫︎Git & GitHub
⚫︎Ruby
(途中まで)

8ヶ月目の学習内容(約73時間)

⚫︎Ruby
(先月の途中から)

Happiness Chainの特徴

①プロのエンジニアの方がコードレビューをしてくださる
独学だとたとえ正解であったとしても他に最適解があったとしても中々わかりませんよね。もっとコードを短くできたりメンテナンス性が低かったりetc..
将来稼げるエンジニアになるためにはとても重要なことです。
②課題のレベルが高い
実務を意識した課題となっています。インプットした内容をきちんと理解していないとクリアできないと思います。そのため課題に取り組んでいくことで自ずと力がついていくのではと思います。
③モダンな技術を学ぶことができる
バックエンド、フロントエンド、クラウド等で最先端の技術を広く深くそれぞれ学ぶことができます。

最後に

学習をしていく中で、お金や自分の望んだ物を手に入れるためには努力が必要だということを痛感しました。
私は一度「Happiness Chain」を退会しましたが、どうしても人生を変えたいと思い再度入会しました。
仕事をしながらの学習は中々思うようにいかないこともありますが、挫折しないための対策を行ったことで解決できました。
これからはこれまで以上に厳しい道のりになると思いますが、稼ぐことができるエンジニアになることができるように日々学習を継続していきたいです。

Happiness Chainに入会して8ヶ月が経ちました(11月月報)

皆様こんにちは、Amiaです。

私は「Happiness Chain」というオンラインのプログラミングスクールに入会して勉強しています。

そして「Happiness Chain」に入会してから8ヶ月が経ちました。

それでは8ヶ月目に学習した内容を書いていきたいと思います。

⚫︎8ヶ月目の学習内容(約73時間)

Ruby

①書籍でのインプット(Rubyのプログラミング初心者向けの超入門講座)(先月からの続き)
②コーディング課題(1)

⚫︎学習時間について

・1ヶ月目の学習内容(約66時間)
・2ヶ月目の学習内容(約88時間)
・3ヶ月目の学習内容(約88時間)
・4ヶ月目の学習内容(約74時間)
・5ヶ月目の学習内容(約83時間)
・6ヶ月目の学習内容(約82時間)
・7ヶ月目の学習内容(約70時間)
・8ヶ月目の学習内容(約73時間)
・Happiness Chain入会後累計学習時間(約648時間)

⚫︎振り返ってみての感想

先月は本当にRubyのみで終わってしまいました。読むのに時間をかけてしまいました..
まだ書籍の内容を完全には理解していないため、読み込んで深い知識としていきたいです。
しばらくは隙間時間を見つけては読んでいくような形にしたいですね。

それではまた遅くて一ヶ月後ぐらいにお会いしましょう🖐️

『プロを目指す人のためのRuby入門』を読みました。

皆様こんにちは、Amiaです。

私はここ何週間かでタイトルにもある通り『プロを目指す人のためのRuby入門』を読んでいました。
じっくりと読んでいましたが、一週目ですとやはり理解の浅い部分があります。
そのためアウトプットと定着率を上げるためにも今回は学んだこと等を記事としてまとめていきたいと思います。

よろしければ最後までご覧になっていただければと思います。

⚫︎良かった点

1.実際に手を動かしながら学べる

サンプルコードや例題が各章で用意されているため、実際にコーディングしながら学ぶことができます。
そのため、ただ読むだけの時よりも定着率が格段に上がると思います。

2.他の言語と異なる部分が記載されている

Rubyでのコーディング方法やRuby特有の仕様と他の言語の異なる部分が比較して記載されているため、他の言語の経験者の方でも混乱することなく進められるのではないかと思います。

3.現場目線の実践的知識が記載されている

筆者の方は「あのとき、こんなふうに説明してくれたら良かったのに!」や「このテクニックはもっと早く知っておきたかった!」と感じたポイントをに盛り込んで執筆されたとのことです。
現場で通用する実践的な知識・ノウハウが至る所に散りばめられており、使用頻度頻度の多い部分や少ない部分に触れられていました。そのためしっかり覚えておくべき部分と頭の片隅に入れておく程度よい部分がわかり減り張りを付けて読むことができます。

⚫︎学んだこと

「第2章 Rubyの基礎を理解する」

この章では主に下記について学びました。始めからかなりのボリュームがありました。
Rubyに関する基礎知識
・文字列
・数値
・真偽値と条件分岐
・メソッドの定義
・その他の基礎知識
基礎となる重要な部分が記載されているため、あやふやな部分がなくなるまで読み返したい章でした。

「第3章 テストを自動化する」

この章では主に下記について学びました。テストを自動化するためにはテスト用のフレームワーク(テスティングフレームワーク)を利用します。本書ではMinitestを利用しました。
・Minitestの基本
FizzBuzzプログラムのテスト自動化

「第4章 配列や繰り返し処理を理解する」

この章では主に下記について学びました。この章もかなりのボリュームがありました。
・配列
・ブロック
・範囲(Range)
・さまざまな繰り返し処理
・繰り返し処理用の制御構造
配列やブロックはRubyにおいて利用頻度が非常に高い機能だそうです。配列に用意された豊富なメソッドを使いこなせば使いこなすほど、簡潔なRubyプログラムが書けるようになるはずとのとことなので理解するまで読み込んでいきたいですね。

「第5章 ハッシュやシンボルを理解する」

この章では主に下記について学びました。
・ハッシュ
・シンボル
ハッシュも配列と同様に利用頻度が非常に高いとのことです。こちらの章もしっかりと知識を定着できるように読み返したいですね。

「第6章 正規表現を理解する」

この章では主に下記について学びました。
正規表現そのものについて
Rubyにおける正規表現オブジェクト
正規表現については書籍の筆者の方が書いた下記の記事がとても勉強になります。
正規表現についてよくわからない方は是非一読ください。
qiita.com qiita.com qiita.com qiita.com

「第7章 クラスの作成を理解する」

この章では主に下記について学びました。
オブジェクト指向プログラミングの基礎知識
・クラスの定義
・selfキーワード
・クラスの継承
・メソッドの可視性
・定数
・さまざまな種類の変数
・クラス定義やRubyの言語仕様に関する高度な話題
クラスの定義はオブジェクト指向言語にとって根幹となる機能なため、こちらも中々ボリュームがありましたね。

「第8章 モジュールを理解する」

この章では主に下記について学びました。
・モジュールの概要
・モジュールを利用したメソッド定義(includeとextend)
・モジュールを利用した名前空間の作成
・関数や定数を提供するモジュールの作成
・状態を保持するモジュールの作成
・モジュールに関する高度な話題
大規模なプログラムやオープンソースライブラリの開発に関わる場合はモジュールの知識は必須とのことでした。まだ理解が浅い部分があるため再度読み返したいと思います。

「第9章 例外処理を理解する」

この章では主に下記について学びました。
・例外の捕捉
・意図的に例外を発生させる方法
・例外処理のベストプラクティス
例外処理は「ここぞ!」というときにだけ使う機能で、やみくもにrescueを連発したり捕捉すべきでない例外を捕捉したりしてはいけないということがよくわかりました。
これは、かえって問題の多いプログラムができあがってしまうためですね..

「第10章 yieldとProcを理解する」

この章では主に下記について学びました。
・ブロックを利用するメソッドの定義とyield
・Procオブジェクト
この章は「プログラミング初心者にとってはちょっとハードルが高いかもしれません」と書かれている通り私自身難しく感じました。

「第11章 パターンマッチを理解する」

この章では主に下記について学びました。
・パターンマッチの基本
・パターンマッチの利用パターン
・ガード式や1行パターンマッチなどの応用的な使い方
パターンマッチはcase文によく似た、ver3.0から本格的に導入された新しい構文です。前章とは打って変わり理解しやすい内容となっていました。

「第12章 Rubyデバッグ技法を身につける」

この章では主に下記について学びました。
・バックトレースの読み方
・よく発生する例外クラスとその原因
・プログラムの途中経過を確認する方法
・汎用的なトラブルシューティング方法
この章はデバッグ方法やトラブルシューティング方法等が記載されていました。プログラミングにエラーやバグは付きものなため、何度も読み返し解消するためのテクニックを身に付けていきたいです。

「第13章 Rubyに関するその他のトピック」

この章では主に下記について学びました。
・日付や時刻の扱い
・ファイルやディレクトリの扱い
・特定の形式のファイルを読み書きする
環境変数や起動時引数の取得
・非推奨機能を使ったときに警告を出力する
・eval、バッククオートリテラル、sendメソッド
・Rake
・gemとBundler
Rubyにおける型情報の定義と型検査(RBS、TypeProf、Steep)
・「Railsの中のRuby」と「素のRuby」の違い
この章では様々な内容が記載されていました。まだ全ては理解できていないためまた読み返したいと思います。

⚫︎難しく感じた部分

本書は入門書と書かれていますが、筆者の方も仰っているようにプログラミングの基礎部分は理解している前提で書かれています。そのためプログラミング完全初学者には読み進めるのが難しい内容ではないかと思います。
私はプログラミング学習サイトで一度大まかな全体像や基礎を学んでから読みました。それでもやはり難しいと感じる部分はありました。

⚫︎最後に

「学んだこと」の部分で今回記載した内容については全て挙げるとボリュームが膨大になってしまうため一部に過ぎません。
私自身まだ本書の内容を全てを完璧に理解できていません。再度読み返し深い知識にしていきたいと思います。
気になった方は是非購入を検討してみてはいかがでしょうか。

Happiness Chainに入会して7ヶ月が経ちました(10月月報)

皆様こんにちは、Amiaです。

私は「Happiness Chain」というオンラインのプログラミングスクールに入会して勉強しています。

そして「Happiness Chain」に入会してから7ヶ月が経ちました。

それでは7ヶ月目に学習した内容を書いていきたいと思います。

⚫︎7ヶ月目の学習内容(約70時間)

・Docker

①任意のインプット課題

・Git & GitHub

①インプット教材での復習

Ruby

①書籍でのインプット(Rubyのプログラミング初心者向けの超入門講座)

⚫︎学習時間について

・1ヶ月目の学習内容(約66時間)
・2ヶ月目の学習内容(約88時間)
・3ヶ月目の学習内容(約88時間)
・4ヶ月目の学習内容(約74時間)
・5ヶ月目の学習内容(約83時間)
・6ヶ月目の学習内容(約82時間)
・7ヶ月目の学習内容(約70時間)
・Happiness Chain入会後累計学習時間(約556時間)

⚫︎振り返ってみての感想

先月はほとんどRubyのみで終わってしまいました。読むのに時間をかけてしまいました..
まだ書籍の内容を完全には理解していないため、読み込んで深い知識としていきたいです。
しばらくは隙間時間を見つけては読んでいくような形にしたいですね。

それではまた遅くて一ヶ月後ぐらいにお会いしましょう🖐️

Happiness Chainに入会して6ヶ月が経ちました(9月月報)

皆様こんにちは、Amiaです。

私は「Happiness Chain」というオンラインのプログラミングスクールに入会して勉強しています。

そして「Happiness Chain」に入会してから6ヶ月が経ちました。

段々と恒例になってきましたね(笑)
それでは6ヶ月目に学習した内容を書いていきたいと思います。

⚫︎6ヶ月目の学習内容(約82時間)

・Docker

①動画でのインプット
(先月の途中から)
②Web教材でのインプット
③dockerで開発環境を構築する。
④dockerで開発環境を構築する方法についてブログに記載する。

⚫︎学習時間について

・1ヶ月目の学習内容(約66時間)
・2ヶ月目の学習内容(約88時間)
・3ヶ月目の学習内容(約88時間)
・4ヶ月目の学習内容(約74時間)
・5ヶ月目の学習内容(約83時間)
・6ヶ月目の学習内容(約82時間)
・Happiness Chain入会後累計学習時間(約484時間)

⚫︎振り返ってみての感想

先月はDockerのみで終わってしまいました。色々と調べながら学習を行なっていたため予想よりも時間が掛かってしまいましたね...。
Dockerについてはもう少しインプットすることがあるため、それが終わり次第またGitについて復習しようと考えています。

それではまた遅くて一ヶ月後ぐらいにお会いしましょう🖐️

Docker composeで既存のRailsアプリをdocker化する方法について

皆様こんにちは、Amiaです。

私はここ一ヶ月くらいDockerについて学習をしていました。
そのためアウトプットも兼ねてタイトルにある通り『既存のアプリをdockerする方法』ついて今回はブログを書いていきたいと思います。

よろしければご覧になっていただければと思います。

⚫︎今回の開発環境について

◎開発環境
・CPU:Apple M2
・OS:macOS 13.4.1

◎各種バージョン
ruby:3.2.2
rails:7.0.6
・postgres:12.0

※今回私が環境構築するにあたり使用したエディタおよびターミナルはVScodeです。

⚫︎手順全体像

詳細な手順に触れる前にまずは大まかな流れから確認していきましょう。
①今回対象のアプリを置くディレクトリを用意する。
②ターミナルを開き、アプリを置くディレクトリへ移動する。
③今回対象のRailsプロジェクトをGithubからクローン(コピー)してくる。
④③でクローンしてきたディレクトリの直下にDockerfileを作成する。
⑤④で作成したDockerfileを編集する。
⑥同ディレクトリ内でdocker-compose.ymlを作成する。
⑦⑥で作成したdocker-compose.ymlを編集する。
⑧configディレクトリ内にあるdatabase.ymlを変更する。
⑨ターミナルでDocker imageを作成する。
⑩ターミナルでコンテナを起動する。
⑪上記までとは別のターミナル画面を開く。
⑫ターミナルでデータベースを作成する。
Webブラウザ上でURL:http://localhost:3000にアクセスする。
⑭ターミナルでアプリケーションを終了させる。

⚫︎手順

それでは手順の詳細について記載していきたいと思います。

①今回対象のアプリを置くディレクトリを用意する。

こちらの工程については特に詳細な説明は不要かと思います。
アプリを置きたい場所にディレクトリを新しく作成するなり既存の場所に作成するなりお好きなようにしていただけたらと思います。

②ターミナルを開き、アプリを置くディレクトリへ移動する。

下記コマンドをターミナルで入力して①で用意したディレクトリに移動します。

cd <ディレクトリパス>
③今回対象のRailsプロジェクトをGithubからクローン(コピー)してくる。

下記のコマンドをターミナルで入力して今回対象のRailsアプリをクローンします。

git clone <リポジトリパス>
④③でクローンしてきたディレクトリの直下にDockerfileを作成する。

下記のコマンドをターミナルで入力して③でクローンしてきたディレクトリの直下にDockerfileを作成します。

touch Dockerfile
⑤④で作成したDockerfileを編集する。

Dockerfileを下記のように編集します。

FROM ruby:3.2.2
RUN apt-get update && apt-get install -y \
  build-essential \
  libpq-dev \
  nodejs \
  postgresql-client \
  yarn

  WORKDIR /rails-docker
  COPY Gemfile Gemfile.lock /rails-docker/
  RUN bundle install

今回のコードについて詳細をまとめていきたいと思います。

FROM ruby:3.2.2

FROMで作成するコンテナのベースとなるイメージを指定しています。今回の場合は「ruby」ですね。

RUN apt-get update && apt-get install

RUNでコンテナ内で実行されるコマンド指定します。apt-get updateでインストール可能なパッケージの一覧を更新します。apt-get installで指定したパッケージをインストールします。

WORKDIR /rails-docker

WORDKDIRを指定することで、以降の指令がそのディレクトリ内で実行されます。今回の場合ですと以降は作業ディレクトリが「/rails-docker」に変更されます。

COPY Gemfile Gemfile.lock /rails-docker/

COPYでbuild contextの中にあるDockerfile以外のファイルやフォルダをDocker Imageに組み込み、コンテナ内で使用できます。今回の場合ですとbuild contextの中にあるGemfileとGemfile.lockをコンテナ内の「/rails-docker/」配下にコピーしています。

RUN bundle install

bundle installはライブラリをインストールするコマンドです。また、ここでインストールするものがアプリ内で使えるかどうかをチェックしています。

⑥同ディレクトリ内でdocker-compose.ymlを作成する。

下記のコマンドをターミナルで入力して同ディレクトリ内にdocker-compose.ymlを作成します。

touch docker-compose.yml
⑦⑥で作成したdocker-compose.ymlを編集する。

docker-compose.ymlを下記のように編集します。

version: '3'

volumes:
 db-data:

services:
  web:
    build: .
    command: bash -c "rails db:migrate && rails s -p 3000 -b '0.0.0.0'"
    ports:
      - '3000:3000'
    volumes:
      - '.:/rails-docker'
    environment:
    - 'DATABASE_PASSWORD=postgres'
    tty: true
    stdin_open: true
    depends_on:
      - db
    links:
      - db

  db:
    image: postgres:12.0
    volumes:
      - 'db-data:/var/lib/postgresql/data'
    environment:
      - 'POSTGRES_USER=postgres'
      - 'DATABASE_PASSWORD=postgres'
      - 'POSTGRES_HOST_AUTH_MEtHOD=trust'

※インデントの位置に気をつけてください。私はこのせいでエラーが出てしまい、少し悩まされました(笑)

今回のコードについて詳細をまとめていきたいと思います。

version: '3'

docker-composeで使用するバージョンを定義します。

volumes:
 db-data:

ここでのvolumesはDocker Composeにボリュームを作成するように指示します。また、ここで定義されたボリュームはサービス内のvolumesで使用することができます。

services:

servicesでは全体の構成をするコンテナの設定をします。
今回では「web」と「db」ですね。

webサービスについて

build: .

webサービスのDocker imageをビルドする際の設定をします。
用意したDockerfileよりビルドする場合、build:へdocker-compose.ymlファイルが置いてあるディレクトリ(カレントディレクトリ)からDockerfileが置いてあるディレクトリへの相対パスを記述します。
build: .でdocker-compose.ymlと同じフォルダにDockerfileがあることを示します。

command: bash -c "rails db:migrate && rails s -p 3000 -b '0.0.0.0'"

commandwebサービスのコンテナが起動したときに実行するコマンドを指定します。 bash -c "rails db:migrate && rails s -p 3000 -b '0.0.0.0'"でデータベースにデータを反映してRailsのWebサーバーをポート番号3000で起動する。

ports:
      - '3000:3000'

ホストの「ポート番号」とコンテナの「ポート番号」の対応付けを定義します。 ホストのポート番号:コンテナのポート番号で指定します。

volumes:
      - '.:/rails-docker'

ホストのdocker-compose.ymlが配置されているディレクトリをコンテナ上の「/rails-docker」ディレクトリにマウント(共有)させます。ホストにもデータを共有することでデータの永続性を可能にしています。そのためコンテナが削除された場合でもデータは保持されたままになります。

environment:
    - 'DATABASE_PASSWORD=postgres'

Dockerコンテナ内で使用される環境変数を設定するため指定します。
Dockerコンテナはホストの環境と完全に分離されて動作するため、コンテナ内部で環境変数を設定する必要があります。

tty: true

出力を見やすくします。

stdin_open: true

標準入出力とエラー出力をコンテナと結びつける設定を指定します。

depends_on:
    - db

依存関係を定義してコンテナの起動順序を制御します。 今回の場合ですとwebサービスが起動する前にdbサービスが起動するように設定しています。

links:
    - db

コンテナを他のサービスにリンクするように設定します。

・dbサービスについて

image: postgres:12.0

Dockerイメージを指定することで、そのイメージよりコンテナを作成します。
ホストに存在しなければDocker Hubからイメージを取得し、それをもとにコンテナを作成します。
今回の場合ですとDocker Hubからpostgresのver.12.0のイメージを取得してそれをもとにコンテナを作成します。

volumes:
      - 'db-data:/var/lib/postgresql/data'

ホストのdb-dataをコンテナ上の「/var/lib/postgresql/data」ディレクトリにマウント(共有)させます。

environment:
      - 'POSTGRES_USER=postgres'
      - 'DATABASE_PASSWORD=postgres'
      - 'POSTGRES_HOST_AUTH_MEtHOD=trust'

webサービスの時と同様にDockerコンテナ内で使用される環境変数を設定するため指定します。

⑧configディレクトリ内にあるdatabase.ymlを変更する。

database.ymlを下記のように変更します。
⚫︎変更前

default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  <<: *default
  database: myapp_development

test:
  <<: *default
  database: myapp_test

production:
  <<: *default
  database: myapp_production
  username: myapp
  password: <%= ENV["MYAPP_DATABASE_PASSWORD"] %>

⚫︎変更後

default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  user: postgres
  password: <%= ENV.fetch("DATABASE_PASSWORD") %>
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  <<: *default
  database: rails-docker_dev

test:
  <<: *default
  database: rails-docker_test

production:
  <<: *default
  database: rails-docker_prod

今回のコードについて詳細をまとめていきたいと思います。

adapter: postgresql

使用するデータベースの種類を指定します。

encoding: unicode

文字コードを指定します。

host: db

接続先のサーバ名またはIPアドレスを指定します。

user: postgres

ユーザー名を指定します。

password: <%= ENV.fetch("DATABASE_PASSWORD") %>

データベースに接続するパスワードを指定します。

database: rails-docker_dev

接続先のデータベース名を指定します。

⑨ターミナルで下記のコマンドを入力してDocker imageを作成する。
docker-compose build
⑩ターミナルで下記のコマンドを入力してコンテナを起動する。
docker-compose up
⑪上記までとは別のターミナル画面を開く。
⑫ターミナルで下記のコマンドを入力してデータベースを作成する。
docker-compose run --rm rails db:create
Webブラウザ上で下記のURLにアクセスする。

http://localhost:3000

画面が表示されていることをご確認ください。

⑭ターミナルでアプリケーションを終了させる。

ターミナルで下記のコマンドを入力してアプリケーションを終了させます。

docker-compose down

お疲れ様でした。
以上が『Docker composeで既存のRailsアプリをdocker化する方法』についてでした。

⚫︎最後に

Docker composeで既存のRailsアプリをdocker化する方法について記載してきました。今回の記事を書く中でもまだ理解が浅はかな部分があったため調べたりインプット教材を見直したりしました。まだまだ学習が足りないということを痛感しました。

Dockerを使用することで開発環境を簡単に構築することができます。とても便利なため1日でも早く使いこなすことができるようにしていきたいです。

以上、最後までご覧くださりありがとうございました。