T_Y_CODE

プログラミング学習記録

学習記録 11日目

11日目の学習記録をまとめていきます。

学習計画

学習内容

オブジェクト指向設計実践ガイド 5章

  • ダックタイピングとは「もじオブジェクトがアヒルのように鳴き、アヒルのように歩くのなら、そのクラスが何であれ、それはアヒルである」という表現に由来する。Rubyでは引数にするオブジェクトのクラスが何であれ同じロジックで動かすことが出来る。
def add(a, b)
  a + b
end

add(1, 2) # => 3 Integer型の引数で計算出来る
add(1.0, 2.0) # => 3.0 Float型の引数でも計算できる
  • 以下のコードにおいてTripクラス内prepareメソッドは、引数のmechanicに含まれるprepare_byciclesメッセージを送っている。Mechanicクラスという指定はないためprepare_bycicleメソッドのあるクラスならどのクラスでもよい。
class Trip
  attr_reader :bicycles, :customers, :vehicle
  def prepare(mechanic)
    mechanic.parepare_bicycles(bicycles)
  end
  #...
end

class Mechanic
  def prepare_bicycles(bicycles)
    bicycles.each { |bicycle| prepare_bicycle(bicycle) }
  end

  def prepare_bicycle(bicycle)
  #...
  end
end

f:id:t_y_code:20201016091522p:plain

  • 上記アプリケーションの要件が変わり、Mechanicに加えTripCoordinator, Driverが追加になった。
  • 以下のコードには依存が多くある。prepareメソッドは3つのクラスの存在でcaseしているしそれぞれのメソッドの存在を知ってしまっている。クラスやメソッドの変更により壊れる可能性が高い危険なメソッドになってしまっている。
  • 更にシーケンス図を見るとかなり複雑になってしまっていることが確認出来る。
class Trip
  attr_reader :bicycles, :customers, :vehicle
  def prepare(pareparers)
    preparers.each do |preparer|
      case prepare
      when Mechanic
        preparer.prepare_bicycles(bicycles)
      when TripCoordinator
        preparer.buy_food(customers)
      when Driver
        preparer.gas_up(vehicle)
        preparer.fill_water_tank(vehicle)
      end
    end
  end
end

class TripCoordinator
  def buy_food(customers)
  #...
  end
end

class Driver
  def gas_up(vehicle)
  #...
  end

  def fill_water_tank(vehicle)
  #...
  end
end

class Mechanic
  def prepare_bicycles(bicycles)
    bicycles.each { |bicycle| prepare_bicycle(bicycle) }
  end

  def prepare_bicycle(bicycle)
  #...
  end
end

f:id:t_y_code:20201016095144p:plain

  • このクラスは旅行の準備をするものが新たに登場すると更に複雑化してしまう。
  • 解決策としてダックを見つけることである。Tripクラスのprepareメソッドは単一の目的を果たすためにあるのでその引数も単一の目的を達成するために渡されてくるということを認識する。prepareメソッドは旅行の準備をすることを望みます。prepareメソッドが引数の動作を信頼すれば設計はより簡単になる。(つまり、prepareメソッドは引数を信頼して引数のクラスやメソッドを知らない状態にする)
  • まずはシーケンス図から変更してみる。ずっとシンプルになりました。Preparerは具象的な存在ではない。抽象的な存在であり設計上の想像でしかない。

f:id:t_y_code:20201016100056p:plain

  • シーケンス図からコードを作成する。各クラスはprepare_tripメソッドに応答するダックになっている。
class Trip
  attr_reader :bicycles, :customers, :vehicle
  def prepare(pareparers)
    preparers.each { |preparer| preaparer.prepare_trip(self) }
  end
end

class TripCoordinator
  def prepare_trip(trip)
    buy_food(trip.customers)
  end

  def buy_food(customers)
  #...
  end
end

class Driver
  def prepare_trip(trip)
    vehicle = trip.vehicle
    gas_up(vehicle)
    fill_water_tank(vehicle)
  end

  def gas_up(vehicle)
  #...
  end

  def fill_water_tank(vehicle)
  #...
  end
end

class Mechanic
  def prepare_trip(trip)
    trip.bicycles.each { |bicycle| prepare_bicycle(bicycle) }
  end
  #...
end
  • 最初の例は具象クラスに依存しており、コードの理解はしやすいが拡張には危険が伴う。
  • 直前の例はダックタイプに依存しており、コードの理解力が求められるが拡張性が増している。
  • ダックタイプを使うことでコードは具象的なものから抽象的なものへと変化していく。
  • 以下の文はダックタイプに置き換えることが出来る。
    • クラスで分岐するcase文(直前の例)
    • kind_of?とis_a?
    • responds_to?
  • is_a?(継承しているクラス, モジュールに引数が入っているか調べる)は直前例と同様のダックタイプが可能。本質的には同じ。
  • respods_to?(インスタンスやクラスが引数のメソッドを含んでいるか調べる)を使用すればクラスへの依存を無くす...ように見えるが実際には具象的なクラスのメソッドを想定しているため結局依存している。ダックタイプ化すべきである。

Ruby on Railsチュートリアル 1章

  • やり直し含め今回が3回目の1章になります。今回は勉強のためにCloud9ではなく、あえてローカル環境で開発をしたいと思います。
1.2.2 Railsをインストール
  • ローカルにRailsをインストールします。
$ gem install rails -v 6.0.3
...
Done installing documentation for ... rails after 49 seconds
40 gems installed
$ rails -v
Rails 6.0.3
  • Yarnのインストールを行います。ちなみにYarnはJavaScriptのパッケージマネージャらしい。
$ brew install yarn
...
$ yarn -v
1.22.10
1.3 最初のアプリケーション
  • 開発用フォルダを作成します。
$ cd ~
$ mkdir environment
$ cd environment/
  • Railsアプリケーションの新規作成を行います。
$ rails _6.0.3_ new hello_app
...
Webpacker successfully installed
1.3.1 Bundler
  • GemfileのバージョンをRailsチュートリアル指定のものに合わせてgemをインストールしていきます。
$ cd hello_app
$ bundle install
...
If you are updating multiple gems in your Gemfile at once,
try passing them all to `bundle update`
  • アップデートしろと言われたのでアップデートします。( 大体毎回言われるのでもう慣れましたね笑 )
$ bundle update
An error occurred while installing puma (4.3.4), and Bundler cannot continue.
Make sure that `gem install puma -v '4.3.4' --source 'https://rubygems.org/'` succeeds before bundling.
  • pumaだけインストール出来なかったっぽい。指示されたコマンドを入力して手動インストールします。
$ gem install puma -v '4.3.4' --source 'https://rubygems.org/'
Gem files will remain installed in ...
  • もう一度アップデートします。
$ bundle update
An error occurred while installing puma (4.3.4), and Bundler cannot continue.
Make sure that `gem install puma -v '4.3.4' --source 'https://rubygems.org/'` succeeds before bundling.
  • まだ解決してないっぽい... ググって以下の記事を発見。どうもXcodeが悪さをしてるらしい。以下のコマンドで解決出来るらしいので試す。

qiita.com

$ bundle config build.puma --with-cflags="-Wno-error=implicit-function-declaration"
  • アップデートします。無事通ったのでbundle installもします。
$ bundle update
$ bundle install
1.3.2 rails server
  • Railsサーバを起動してちゃんとインストールされたか確認します。サーバ起動後 http://localhost:3000 にアクセスします。
$ rails s
  • 起動できました。

f:id:t_y_code:20201016121426p:plain

演習

1. デフォルトのRailsページに表示されているものと比べて、今の自分のコンピュータにあるRubyのバージョンはいくつになっていますか? コマンドラインruby -vを実行することで簡単に確認できます。

  • 上記画像ではRubyのバージョンは2.7.1p83になっています。ローカルのバージョンを調べます。同じですね。
$ ruby -v
ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-darwin19]

2. 同様にして、Railsのバージョンも調べてみましょう。調べたバージョンはリスト 1.2でインストールしたバージョンと一致しているでしょうか?

  • 上記画像ではRailsのバージョンは6.0.3です。Gemfileでも6.0.3を指定しています。
1.3.3 Model-View-Controller (MVC)
$ cd app/
$ ls
assets          controllers     javascript      mailers         views           channels        helpers         jobs            models
  • ブラウザがRailsアプリと通信する際、Webサーバーにリクエストを送信し、controllerに渡される。controllerはmodelと対話しmodelを呼び出した後viewを描画しHTMLとしてブラウザに出力している。
1.3.4 Hello, world!
  • アプリ内にあるcontrollerを確認します。
$ cd app/controllers/
$ ls *_controller.rb
application_controller.rb
  • application_controller.rbにhelloメソッド(アクション)を追加して「hello, world!」というテキストを表示するコードを追記します。
class ApplicationController < ActionController::Base
  def hello
    render html: "hello, world!"
  end
end
  • ルーティングファイル(config/routes.rb)に以下のルーティングを記載する。rootはルートURLのこと。
Rails.application.routes.draw do
  # コントローラ名#アクション名
  root 'application#hello'
end
  • ルートURLを確認します。しっかり反映されてます。

f:id:t_y_code:20201016123537p:plain

演習

1. リスト 1.9のhelloアクションを書き換え、「hello, world!」の代わりに「hola, mundo!」と表示されるようにしてみましょう。

  • application_controller.rbのhelloアクションを書き換えます。
class ApplicationController < ActionController::Base
  def hello
    render html: "hola, mundo!"
  end
end
  • 反映されてますね。

f:id:t_y_code:20201016123751p:plain
2. Railsでは「非ASCII文字」もサポートされています。「¡Hola, mundo!」にはスペイン語特有の逆さ感嘆符「¡」が含まれています(図 1.23)17 。「¡」文字をMacで表示するには、Optionキーを押しながら1キーを押します。この文字をコピーして自分のエディタに貼り付ける方が早いかもしれません。

  • しっかり表示出来てます。
class ApplicationController < ActionController::Base
  def hello
    render html: "¡hola, mundo!"
  end
end

f:id:t_y_code:20201016123935p:plain
3. リスト 1.9のhelloアクションを参考にして、2つ目のアクションgoodbyeを追加しましょう。このアクションは、「goodbye, world!」というテキストを表示します。リスト 1.11のルーティングを編集して、ルートルーティングの割り当て先をhelloアクションからgoodbyeアクションに変更します(図 1.24)。

  • コントローラとルーティングを変更します。
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  def hello
    render html: "¡hola, mundo!"
  end

  def goodbye
    render html: "goodbye, world!"
  end
end
# config/routes.rb
Rails.application.routes.draw do
  root 'application#goodbye'
end
  • しっかり反映されてますね。

f:id:t_y_code:20201016124338p:plain

1.4 Gitによるバージョン管理
$ cd ~/environment/hello_app/
$ git init
  • 初回なので全てのファイルをステージに移動します。(ここの意味がようやく理解出来て嬉しい...)
$ git add -A
  • ステージされたファイルをコミットします。コミットが作成されました。
$ git commit -m "Initialize repository"
$ git log --oneline
897f8e5 (HEAD -> master) initialize repository
$ git remote add origin https://github.com/***/***.git
  • リモートへプッシュします。
$ git push -u origin master
1.4.4ブランチ、編集、コミット、マージ
  • modify-READMEブランチを作成し移動します。
$ git checkout -b modify-README
Switched to a new branch 'modify-README'
  • READMEMEを編集しコミットします。コミットファイルが作成されました。
$ git add -A
$ git commit -m "Improve the README file"
$ git log --oneline
d03bce3 (HEAD -> modify-README) Improve the README file
897f8e5 (origin/master, master) initialize repository
  • masterブランチへ移動しmodify-READMEブランチをマージします。ファストフォワードされましたね。
$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
$ git merge modify-README
Fast-forward
 README.md | 29 +++++++++--------------------
 1 file changed, 9 insertions(+), 20 deletions(-)
  • ブランチは不要になったので削除します。
$ git branch -d modify-README
Deleted branch modify-README (was d03bce3).
  • リモートへプッシュします。初回で-uしてたのでgit pushのみで良くなる。
$ git push
1.5 デプロイする
  • 今回もHerokuにデプロイします。HerokuのデータベースはPostgreSQLのため本番環境にpg gemをインストールする必要がある。また、HerokuはSQLiteをサポートしていないためsqlite3 gemが本番環境にインストールされないようにGemfileを書き換える。
  • 本番環境以外のbundle installを行う。
$ bundle install --without production
  • ファイルの変更を行ったのでリモートにプッシュします。
$ git add -A
$ git commit -m "Update Gemfile for Heroku"
$ git push
  • herokuの設定を行います。herokuがインストールされてなかったのでHomebrewでインストールします。
$ brew tap heroku/brew && brew install heroku
$ heroku --version
heroku/7.46.0 darwin-x64 node-v12.16.2
  • herokuにログインします。--interactiveでブラウザを開かずにログイン出来る。
$ heroku login --interactive
heroku: Enter your login credentials
Email: ***
Password: ***
Two-factor code: ******
Logged in as ***
  • herokuのアプリケーションの実行場所を作成します。
$ heroku create
Creating app... done, ⬢ ***
https://***.herokuapp.com/ | https://git.heroku.com/***.git
  • herokuにプッシュします。
$ git push heroku master
  • プッシュ出来ました。以下実際のデプロイしたサイトです。

https://secure-cliffs-60179.herokuapp.com

演習

1. 1.3.4.1と同じ変更を行い、本番アプリでも「hola, mundo!」を表示できるようにしてください。

  • applicationコントローラのhelloアクションの内容を変更します。先程の演習で変更したルーティングも変更しなおします。
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  def hello
    render html: "hola, mundo!"
  end

  def goodbye
    render html: "goodbye, world!"
  end
end
# config/routes.rb
Rails.application.routes.draw do
  root 'application#hello'
end
  • 変更の確認をします。

f:id:t_y_code:20201016123751p:plain

  • gitとherokuにプッシュします。
$ git add -A
$ git commit -m "answer 1.5.3.1"
$ git push && git push heroku
  • herokuにデプロイしたサイトを確認します。

2. 1.3.4.1と同様、ルートへのルーティングを変更してgoodbyeアクションの結果が表示されるようにしてください。またデプロイ時には、Git pushのmasterをあえて省略し、git push herokuでデプロイできることを確認してみてください。

  • ルーティングを変更します。
# config/routes.rb
Rails.application.routes.draw do
  root 'application#goodbye'
end
  • 変更を確認しgitとherokuにプッシュします。
$ git add -A
$ git commit -m "answer 1.5.3.2"
$ git push && git push heroku
  • herokuにデプロイしたサイトを確認します。
1.5.4 Herokuコマンド
  • renameについては省略します。
演習

1. heroku helpコマンドを実行し、Herokuコマンドの一覧を表示してみてください。Herokuアプリのログを表示するコマンドはどれですか?

  • heroku helpコマンドを実行します。ログを表示するのはheroku logsコマンドですね。
$ heroku help
...
  logs            display recent log output
...

2. 上の演習で見つけたコマンドを使って、Herokuアプリの最近のログ(log)を調べてみましょう。直近に発生したイベントは何でしたか?(このログを調べるコマンドを覚えておくと、本番環境の不具合を見つけるときに役立ちます)

$ heroku logs
...
2020-10-16T04:39:24.033724+00:00 heroku[web.1]: State changed from starting to up
  • 1章は以上で終了です。

本日の総括

  • ダックタイプについて学んだ。以前使用していた言語がC#で静的型付け言語だったので動的型付け言語の強みを知った。
  • 昨日インストールしたPlantUMLを早速使ってみました。シーケンス図がプログラムのように書けるのは便利。

f:id:t_y_code:20201016105514p:plain

  • PlantUMLの記法については以下の記事を読みながらやりました。

qiita.com

  • Railsチュートリアルの復習を今日から始めました。Ruby, Git, SQL, Linuxコマンドについて学んだのでまた違った発見がありそうです。1章はスラスラ読めますね!