Ruby.学習その8

#### 投稿目的
- 個人の学習目的で投稿していきます。
- Rubyの入門を読みながら自分なりの思考で記述しています。

#### 参考資料

[ゼロからわかる Ruby 超入門 (かんたんIT基礎講座)](https://www.amazon.co.jp/ゼロからわかる-Ruby-超入門-かんたんIT基礎講座-五十嵐/dp/4297101238/ref=sr_1_3?ie=UTF8&qid=1549614126&sr=8-3&keywords=ruby+入門)

## プログラムでWebへアクセス
- WebページにアクセスするにはHTTPまたはHTTPSを利用します。
- Rubyが用意しているnet/httpライブラリを利用して、指定したURLへHTTPやHTTPSでアクセスする。

```ruby
require "net/http" # ライブラリ"net/http"を読み込み
require "uri" # ライブラリ"uri"を読み込み

uri = URI.parse("http://example.com/") # URIオブジェクトを作成
puts Net::HTTP.get(uri) # GETメソッドでHTMLで戻り値を表示
```
- 1行目でRubyで用意されているライブラリ"net/http"を読み込み。
- 2行目でRubyで用意されているライブラリ"uri"を読み込み。
- 3行目は2行目でURIモジュールが使用できるよになり、URIに.parseメソッドで引数にURLを文字列で渡すことでURIオブジェクト(URI::HTTPSオブジェクト)を作成しています。
    - URI::HTTPSは名前空間でURIモジュールの中にあるHTTPSクラスを指している。
    - URL = 住所: URLはWeb上に様々なファイルがありどこに位置するのかを表したもの。
    - URN = Web側で認識されている名前: Web上で対象を特定するための固有のシリアルナンバーみたいなイメージ(一般ユーザの目に触れることはほぼない)。
    - URL(住所)とURN(名前)の総称がURIです。
- 4行目は1行目でnet/httpを読み込んだのでNet::HTTPクラスが使用できるようになり、getメソッドの引数にuriを渡すことでURIオブジェクトが示す先にWebサーバへHTTP GETメソッドでリクエストを送り、Webサーバが返したレスポンスHTMLが戻り値となりpメソッドで表示されます。
    - Net::HTTPは名前空間でNetモジュールの中にあるHTTPクラスを指しています。

#### 標準添付ライブラリ
- 標準添付ライブラリとはRubyが用意しているライブラリ.
    - net/httpはHTTPを簡単に扱うことのできるライブラリ.
    - URIはURIを扱うためのライブラリ.

## WebページへアクセスしてJSONを取得
- HTMLはブラウザで見ることを主目的であるため、HTMLデータの中から目当てのデータを取得する用途には向いていない。
- データををやりとりすることを目的とした別の形式としてJSONが存在する。
    - JSON(JavaScript Object Notation)は元々、JavaScriptで扱われていたファイル形式で非常に軽量で直感的にデータを扱うことができるため幅広い言語で利用されている。
        - Rubyでは、他の言語と連携するためのAPIを作成する時に利用されます。
         
#### API
- API(Application Programming Interface)とはソフトウェアの機能を共有することができる、それによってソフトウェアの一部をWEB上に公開することによって、誰でも外部から利用することができる。
- しかし、外部で利用するにも「この機能はこう使ってください」、「これはこの用に利用できません」など機能や仕様が普通はわかりませんがAPIはその仕様やルールをまとめてWeb上に公開をしている。

```ruby
require "net/http" # ライブラリ"net/http"を読み込み
require "uri" # ライブラリ"uri"を読み込み

uri = URI.parse("https://igarashikuniaki.net/example.json") # json形式にした文字列を引数に渡す
p result = Net::HTTP.get(uri) # GETメソッドでJson形式で戻り値を表示
```

- jsonはRubyのハッシュと似た形式で、ライブラリjsonを利用することでハッシュへ変換することができます。

```ruby
require "net/http"
require "uri"
require "json" # ライブラリjsonを読み込み

uri = URI.parse("https://igarashikuniaki.net/example.json") 
result = Net::HTTP.get(uri)
hash = JSON.parse(result) # jsonからハッシュへ変換

p hash # 結果: {"caffe latte"=>400}
p hash["caffe latte"] # 400
``` 

- jsonへ変換することもできます。

```ruby
require "json"
p ({mocha: 400}.to_json) # 結果: {\"mocha\":400}
```
- 

## WebページへHTTP POSTメソッドでリクエスト
- WebページへリクエストするときやリクエストするプログラムではHTTP GETメソッドを使っていました、リクエスト先の状態を変更しない時に利用します。
- リクエスト先の状態を変更する場合はHTTP POSTメソッドを利用します、例えば入力フォームに情報を入れて登録ボタンを押下した際に利用します。

```ruby
require "net/http"
require "uri"
require "json"

uri = URI("https://www.example.com")
result = Net::HTTP.post(uri, {mocha: 400}.to_json, "Content-Type" => "application/json") 
p result # 結果: #<Net::HTTPOK 200 OK readbody=true>
```

- 「{mocha: 400}.tp_json」は送るjson形式データ、「"Content-Type" => "application/json"」は送るデータ形式としてjsonを指定しています。

## 例外とは
- 例外処理とは想定通り処理が進まなかったときやエラーが起きた時など、例外的な処理を書く時に使います。
- getsはキーボード入力を取り込むメソッド。
- to_iは文字列を整数に変換メソッド。

```ruby
puts "金額を入力してください"
bill = gets.to_i # 100円
puts "割り勘する人数を入力してください"
number = gets.to_i # 4人

warikan = bill / number # 100円を4人で割る
puts "1人あたり#{warikan}円です" # 結果: 一人あたり25円です
```

- 上記のプログラムの人数の部分を0と入力した結果が下記です。

```shell:ターミナル
Traceback (most recent call last):
        1: from warikan1.rb:6:in `<main>'
warikan1.rb:6:in `/': divided by 0 (ZeroDivisionError)
tame:Ruby_2019.02.14 tame$
```

- 想定外の例外が発生するとRubyはエラーメッセージを表示してプログラムを停止します。
- エラーメッセージに書かれている(ZeroDivisionError)は例外の種類です。
    - (ZeroDivisionError)は0で割り算を行ったときに発生する例外です。
    - (ArgumentError)はメソッドの引数の数が誤っている時に発生する例外です。
    - (NameError)は変数名を間違ったときに発生する例外です。
- 上記の用に様々な例外クラスが存在します。

## 例外を処理
- 例外処理を行うにはbegin~rescueを利用します。
- 下記が例外処理の定義です。
 
```ruby
begin
    # 例外が発生する可能性がある処理
rescue 例外クラス名
    # 例外が発生した時に実行する処理
end
```

- rescueは例外が発生した場合のみ実行されます。
- 下記が例外処理を追加したプログラムです。

```ruby
puts "金額を入力してください"
bill = gets.to_i # 100円
puts "割り勘する人数を入力してください"
number = gets.to_i # 0人

begin # 例外が起きる可能性
    warikan = bill / number
    puts "1人あたり#{warikan}円です"
rescue ZeroDivisionError　# 例外が起きた時の処理
    puts "おっと、0人では割り勘できません"
end

# 結果: "おっと、0人では割り勘できません"

```

- 先ほどは0で割ることで(ZeroDivisionError)のエラーメッセージが表示されましたが今回はrescueの処理が実行されるので"おっと、0人では割り勘できません"と表示されます。

- メソッド内で例外処理を記述する場合はbegin、endを省略することができます。

```ruby
def warikan(bill, number) # warikan関数を定義
    warikan = bill / number
    puts "1人あたり#{warikan}円です"
rescue ZeroDivisionError # 例外が発生した場合の処理
    puts "おっと、0人では割り勘できません"
end

warikan(100, 0) # 結果: "おっと、0人では割り勘できません"
warikan(100, 1) # 結果: "１人あたり100円です"
warikan(100, 2) # 結果: "1人あたり50円です"
```

- Ruby2.5以降では、ブロック内でもbegin、endを省略することができます。

```ruby
bill = 100
numbers = [0, 1, 2]

numbers.each do |number| 
    warikan = bill / number
    puts "1人あたり#{warikan}円です"
rescue ZeroDivisionError
    puts "おっと、0人では割り勘できません"
end

# 結果: "おっと、0人では割り勘できません"
# 結果: "１人あたり100円です"
# 結果: "1人あたり50円です"
```

#### 例外の詳しい情報を得る
- 下記が例外の詳しい情報を得るための具体例です。
- ファイル操作には組み込みライブラリのFileクラスを使用します。
    - File.open(filename)はcatメソッドが呼び出された際に引数で指定されたファイルを開き、ファイル操作のためのFileオブジェクトを作ってfile変数に代入します。
- each_lineは配列のeachに似たメソッドでファイルの先頭から1行ずつ読み込み、繰り返しline変数に代入して、読み込んだ各行はputsメソッドで画面に表示して、操作が終わると開いたファイルが閉じられます。
- SystemCallErrorはファイル操作になどに失敗した際に発生する例外クラスで、例外クラスの後に=> e(変数)という記述を加えて受け取った例外オブジェクトがe変数に代入されます。
- e.classで例外オブジェクトのクラス名を表示。
- e.messageで例外メッセージを表示する。
- filename = ARGV.firstでターミナルで指定したファイル名を読み込みます、ARGVはRubyが用意した特別な定数で、ターミナルで指定した引数を要素として持つ配列で、firstで先頭の要素を取得します。

```ruby:cat.rb
def cat(filename)
    # ファイル内容を表示
    File.open(filename) do |file| # 受け取ったファイルをfile変数に代入
        file.each_line{|line|  # 1行ずつ読み込みline変数に代入
            puts line # 1行ずつ表示
        }
    end # 繰り返し処理が終わったらファイルを閉じる
rescue SystemCallError => e # ファイルの操作を失敗した際の例外オブジェクトをe変数に代入
    puts "--- 例外が発生しました ---"
    puts "例外クラス: #{e.class}" # e変数に代入された例外オブジェクトのクラスを表示
    puts "例外メッセージ: #{e.message}" # e変数に代入された例外のメッセージを表示
end

filename = ARGV.first # ターミナルで引数を指定できる
cat(filename) 
```

- 下記は適当なテキストファイル.

```shell:menu.txt
カフェラテ
カプチーノ
```

- ターミナルで引数を指定するには「ARGV.first」が必要。

```shell:ターミナル
$ ruby cat.rb menu.txt 
## menu.txtの中身がcat関数で表示されます
```

- 直接cat関数を呼び出す際に引数に文字列でファイル名を渡してcat.rbを実行してもファイルを表示することができる。

```ruby:cat.rb
def cat(filename)
    File.open(filename) do |file| 
        file.each_line{|line|  
            puts line 
        }
    end 
rescue SystemCallError => e 
    puts "--- 例外が発生しました ---"
    puts "例外クラス: #{e.class}" 
    puts "例外メッセージ: #{e.message}"
end

cat("menu.txt")  # 引数に文字列でファイル名を入力
```

- ターミナルでcat.rbファイルを実行.

```shell:ターミナル
$ ruby cat.rb 
## menu.txtファイルが表示される
```

- 存在しないファイルを入力して操作を失敗すると設定したエラーの詳細が表示されます。

```shell:ターミナル
$ ruby cat.rb test.rb 
## 存在しないファイルを読み込んで操作を失敗すると下記の用にrescueが実行されます。
--- 例外が発生しました ---
例外クラス: Errno::ENOENT
例外メッセージ: No such file or directory @ rb_sysopen - test.txt 
```

#### 自分で例外を発生
- raiseを利用して例外だった場合のメッセージを表示できます。

```ruby:type1.rb
def type(age) # 年齢から成年か未成年かを判定するメソッド
    if age < 0
        raise "年齢がマイナスです(#{age}才)" # ageがマイナスの時は例外を発生させる
    elseif age < 20
        "未成年"
    else
        "成年"
    end
end

age = ARGV.first.to_i # ターミナルの引数を整数に変換して変数ageに格納する
type = type(age)
puts "#{age}才は#{type}です"
```

```shell:ターミナル
$ ruby type1.rb -1
## 引数が0より小さい場合は例外が発生して下記のメッセージが表示される
Traceback (most recent call last):
        1: from type1.rb:15:in `<main>'
type1.rb:5:in `type': 年齢がマイナスです(-1才) (RuntimeError)
```

###### 補足: 下記の用に特定の例外クラスを指定することも可能


```ruby
raise ScriptError, "エラーが発生しました"
```

#### 例外の有無に関わらず必ず処理を実行
- ensureからendの間に書いた内容は例外の発生有無に関わらず処理必ず実行されます。

```ruby:type2.rb
def type(age)
    if age < 0
        raise "年齢がマイナスです(#{age}才)" 
    elseif age < 20
        "未成年"
    else
        "成年"
    end
end

begin # 例外が発生する可能性のある処理
    age = ARGV.first.to_i 
    puts "#{age}才は#{type(age)}です"
rescue => e # 例外が発生した時に実行する処理
    puts "例外が発生しました: #{e.message}"
ensure # 例外の発生有無に関わらず実行される処理
    puts "ご利用ありがとうございました"
end
```

- 正の数であれば例外は発生しませんがensureは実行される

```shell:ターミナル
$ ruby type2.rb 25
## 例外が発生しなくても"ご利用ありがとうございました"と表示
25才は成年です
ご利用ありがとうございました
```

- 負の数だと例外が発生するがensureは実行される

```shell:ターミナル
$ ruby type2.rb -1
## 例外が発生しても"ご利用ありがとうございました"と表示
例外が発生しました: 年齢がマイナスです(-1才)
ご利用ありがとうございました
```