12. ライブラリ

ライブラリの利用

ライブラリはプログラムをする上で便利な機能を提供してくれる機能です。言語で用意されているものや、第三者が作成したクラス、メソッドなどを利用することができます。
ライブラリはrequireメソッド、require_relativeメソッドを利用して読み込みます。
ここで説明するライブラリの種類は以下の3つです。

  • 組み込みライブラリ
  • 標準ライブラリ
  • Gem

組み込みライブラリ

組み込みライブラリはRuby本体に組み込まれているライブラリです。
このライブラリに含まれるクラスやモジュールは、requireを書かなくても使うことができます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
p Math::PI                      #=> πの値を示す

p Math::E                       #=> 自然対数の底eの値

p Math::sqrt(2)                 #=> √2

p Rational(1, 3)                #=> (1/3)

p Complex(2, 3)                 #=> (2+3i)

deg = 30                        #=> 30度
rad = ( deg * Math::PI/ 180.0 ) #=> 「度」から「ラジアン」に変換

p Math.sin (rad)                #=> sin(θ) 正弦関数

p Math.asin (rad)               #=> asin(θ) 逆正弦関数

標準ライブラリ

下記はRubyに標準でインストールされているライブラリの一例です。
require することによってその機能を使用することができます。

ライブラリ 機能
erb 埋め込みRuby(ERB)
uri URIライブラリ
webrick 汎用HTTPサーバーフレームワーク。HTTPサーバが簡単に作れる。
date 日付クラス・日時クラス
json JSON(Java Script Object Notation)を扱うためのライブラリ
benchmark ベンチマークを取るためのライブラリ
logger ログを記録するためのライブラリ

Gem

世の中にはRubyのシステムに元々含まれているライブラリ以外にも、様々なライブラリが存在しています。
それらはGemという名前で公開されています(参考)

GemはRubyにおけるライブラリ配布用の標準フォーマットです。
Gemは簡単にインストールでき、requireすることによってその機能を使用することができます。

Gemでライブラリ等をインストールする

1
$ gem install gemの名前

標準ライブラリ・Gemの読み込み

標準ライブラリ・Gemを読み込む場合requireを使用しましょう。

1
require "標準ライブラリ名 / Gem名"

自作ファイルの利用

一方、自作したファイルを他のファイルから読み込む場合require_relativeを使用すると便利です。
これにより、現在のファイルからの相対パスでファイルが読み込まれます。

1
require_relative "自作ファイルの場所(相対パス)"

例えば、以下のディレクトリ構成で作業するとき、

1
2
3
4
5
6
7
8
/ruby-lecture
 ├─ /lib
 │   ├─ /dir
 │   │   └─ my_library2.rb
 │   │
 │   └─ my_library1.rb
 │
 └─ require_test.rb

ruby-lecture/require_test.rbというファイルから、

  • ruby-lecture/lib/my_library1.rb
  • ruby-lecture/lib/dir/my_library2.rb

の二つのファイルを読み込みたい場合、以下のようにします。

1
2
require_relative './lib/my_library1'
require_relative './lib/dir/my_library2'

いろんなライブラリを使ってみよう!

日時を扱うライブラリ

dateライブラリは時間を扱うための標準ライブラリです。
日付を表現するDateクラスと、そのサブクラスであり日付と時刻を表現するDateTimeクラスを扱うことが出来ます。

Dateクラス

date.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
require 'date'

# 日付を指定して出力
day = Date.new(2007, 5, 30)
puts day
#=> 2007-05-30

# 本日の日付を取得する
today = Date.today
puts today.strftime("%Y年%m月%d日") # フォーマットを指定して出力
#=> 2017年5月30日  ←ここは本日の日付になる

# 一部を抜き出して出力
puts today.year      #=> 2017   ←ここは本日の日付の「年」になる
puts today.month     #=> 5      ←ここは本日の日付の「月」になる
puts today.day       #=> 30     ←ここは本日の日付の「日」になる

Step Up! : 指定した日付が正しいかを調べる

指定した日付が正しいかどうかを確認するメソッドとして、valid_date?メソッドが用意されています。

valid_date.rb
1
2
3
4
5
6
7
require 'date'

puts Date.valid_date?(2016, 6, 8)
# => true

puts Date.valid_date?(2016, 2, 31)
# => false

自分で確認するロジックを作る必要がないので非常に楽ですね。

DateTimeクラス

DateTimeクラスは日付と時間を取得することができます。
フォーマットを指定して出力するstrftime (Str_ing _F_ormat _Time) メソッドもDateクラスと同様に使用できます。
ただし、月(month) のmと分(minute)のMを間違えないよう注意しましょう。

datetime.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
require 'date'

# 日付を指定して出力
datetime = DateTime.new(2007, 5, 30, 12, 30, 45, "+0900")
puts datetime
#=> 2007-05-30T12:30:45+0900

# 本日の日付を取得する
datetime = DateTime.now
puts datetime.strftime("%Y年%m月%d日 %H時%M分%S秒") # フォーマットを指定して出力
#=> 2017年5月30日 15時00分35秒

# 一部を抜き出して出力
puts datetime.hour    #=> 15
puts datetime.minute  #=> 00
puts datetime.second  #=> 35

Step Up! : 時間の加減算

DateTimeDateのサブクラス(Dateを更に拡張したもの)として定義されているので数値に対する操作は基本的に1日単位となります。(1[時間] = 1/24[日])

calculate_time.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
require 'date'

datetime = DateTime.now
puts datetime
#=> 2017-05-30T15:00:00+09:00

# 1月加算
puts datetime >> 1
#=> 2017-06-30T15:00:00+09:00

# 1日加算
puts datetime + 1
#=> 2017-05-31T15:00:00+09:00

# 5時間(5/24[日])加算
puts datetime + 5/24r
#=> 2017-05-30T20:00:00+09:00

# 30分(30/1440[日])加算
puts datetime + 30/1440r   # 30/ (24 * 60)
#=> 2017-05-30T15:30:00+09:00

# 20秒(20/86400[日])減算
puts datetime - 20/86400r   # 20/ (24 * 60 * 60)
#=> 2017-05-30T14:59:40+09:00

自分で確認するロジックを作る必要がないので非常に楽ですね。

Note : 日本標準時(JST)

DateTime型を出力したときに表示される+9:00はその時間が日本標準時(JST)であることを表しています。
JSTは協定世界時(UTC)から9時間進んでいるため+9:00となっています。
今回の研修では特に意識する必要はありませんが、作業環境によってはデフォルトがUTCになっている場合があるので注意しましょう。

日時のフォーマット

先のサンプルコードで%Y%Hのような書き方がありましたね。
これらは、「年・月・日・時・分・秒」をどのようなフォーマットで出力するかを指定するための書式です。
以下に、代表的なものを書いておきます。

書式 出力内容 具体例
%Y 年(西暦) "2018"
%m 月(0詰め2桁) "05"
%d 日(0詰め2桁) "01"
%H 時(0詰め2桁) "09"
%M 分(0詰め2桁) "00"
%s 秒(0詰め2桁) "00"

その他、いろいろな書式があります。
Rubyのドキュメントにまとまっていますので、必要に応じて調べてみましょう。

ファイルの読み書き

Fileクラス

テキストファイルや、データを保存しているファイルなどを読み込んだり、書き込んだりするときに使える組み込みライブラリです。

  • ファイルの読み込み
hello_world.txt
1
hello world
read_file.rb
1
2
3
4
5
File.open('./hello_world.txt', 'r') do |file|
  file_body = file.read
  puts file_body
  #=> “hello world”
end
  • ファイルの書き込み
write_file.rb
1
2
3
4
5
output_str = "こんにちは世界"

File.open('./output.txt', 'w') do |file|
  file.puts output_str
end
output.txt
1
こんにちは世界

File.open()メソッドの第2引数は、以下のようなパターンがあります。

モード  
“r” 読み込みモード
“w” 書き込みモード(オープン時にファイルがすでに存在していれば、その内容を空にします。)
“a” 読み書きが常にファイルの末尾で行われます。

他にも指定できるモードがいくつかあります。
必要に応じて調べてみましょう。

JSON形式のファイルを扱うライブラリ

JSONとは

JSON(JavaScript Object Notation)はプログラミングの世界で使用されるポピュラーなデータフォーマットです。
JavaScriptという言語の専用データフォーマットではなく、様々なソフトウェアやプログラミング言語で使用されています。
フォーマットとしてはHashに似ています。

1
{"キー0":"要素0", "キー1": "要素1", "キー2":"要素2", ...}

JSONでは以下のフォーマットを指定できます。

  • 数値
  • 文字列
  • 真偽値(true, false)
  • 配列([ ]に要素を入れる)
  • ハッシュ
  • null

ファイルとして保存するときは拡張子.jsonとして保存しましょう。

users.json
1
2
3
4
5
[
  {"name": "鈴木", "age": "20"},
  {"name": "佐藤", "age": "30"},
  {"name": "田中", "age": "25"}
]

JSONクラス

RubyではJSONを読み込んでハッシュ形式に変換してくれる標準ライブラリがあります。
先ほど作成したuser.jsonファイルを読み込んで、新しいファイルに書き込んでみましょう。

read_write_json.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
require "json"

# JSONファイル読み込み
user_data = open("users.json", "r") do |file|
  JSON.load(file)
end

puts user_data
#=>
# {"name"=>"鈴木", "age"=>"20"}
# {"name"=>"佐藤", "age"=>"30"}
# {"name"=>"田中", "age"=>"25"}

puts user_data[0]["name"]
#=> 鈴木

user_data[0]["name"] = "佐藤"

# JSONファイル書き込み
open("users_after.json", 'w') do |file|
  JSON.dump(user_data, file)
end

新しいファイルには以下のように書き込まれているでしょうか。

users_after.json
1
[{"name":"佐藤","age":"20"},{"name":"佐藤","age":"30"},{"name":"田中","age":"25"}]

Bundler

Bundlerとは、アプリケーションで利用するGemを定義し、依存関係を解決するためのツールです。
簡単に言うと、「これ動かすときはこのGemいるよ」というのを、記録しておくものです。
標準外のライブラリですが、Rubyでアプリ開発する際は必須と言っていい程重要です。

Bundlerの仕組み

業務では、複数のRubyプロジェクトを管理しなければならない場面があります。
そんな時、例えばライブラリがシステムの一箇所にまとまって、以下のように管理されている場合を考えてみましょう。

  • ライブラリa(バージョン2)
  • ライブラリb(バージョン2)
  • ライブラリc(バージョン1)

このとき、二つのプロジェクトで、それぞれ次のようなライブラリを使いたいとします。

Rubyプロジェクト 利用するライブラリ
プロジェクトA ライブラリa (バージョン1)
ライブラリb (バージョン2)
プロジェクトB ライブラリa (バージョン2)
ライブラリc (バージョン1)

すると、プロジェクトAではライブラリaのバージョン1を使いたいですが、システムではバージョンが2になってしまっています。

それでは、ライブラリがシステム全体で一元管理されているのではなく、プロジェクトごとに管理されていたらどうでしょうか。

このようになっていれば、プロジェクト間で干渉することなく使いたいライブラリを使うことができますね。
これを実現するのがBundlerです。
Bundlerでは、「使いたいライブラリのリストのファイル(Gemfile)」をプロジェクト内に置くと、そのリストにしたがってプロジェクト内に必要なライブラリをインストールします。
さらに、「Bundlerでインストールしたライブラリ情報のファイル(Gemfile.lock)」が生成されるため、これを開発チームで共有すればどの環境からでも同じバージョンのライブラリをインストールすることができます。

BundlerのインストールとGemfileの作成

gemをインストールしたい場合、これまではgem install {gem名}と一つずつインストールしていました。
Bundlerでは、使用したいgemを Gemfile というファイルに記述すると一括でインストールできます。
まず任意のディレクトリを作成し、 Gemfile を作成しましょう。

1
2
3
4
5
6
$ gem install bundler      # Gemでbundlerをインストール

$ mkdir bundler_sample     # bundler_sampleフォルダを作成
$ cd bundler_sample        # bundler_sampleフォルダに移動
$ bundle init              # Gemfileという雛形ファイルが作成される
$ ls                       # Gemfile があるか確認

Gemfileを開くと、以下のように記述されているかと思います。

Gemfile(初期状態)
1
2
3
4
5
6
7
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

# gem "rails"

1行目に# frozen_string_literal: true、5行目にgit_source(:github)...という記述があると思いますが、研修中はこちらは削除してかまいません。

それでは実際にbundlerを使用してみましょう。
今回は例として、RubyでHTTPリクエストを発行できるライブラリであるfaradayをbundler経由でインストールします。
HTTPに関してはTipsで説明します。

まずは、 Gemfile を以下のように編集しましょう。

Gemfile(編集後)
1
2
3
source "https://rubygems.org"

gem "faraday"

次にコマンドラインでbundle installを実行しましょう。

1
2
3
4
5
6
7
8
# Gemインストール先を指定
$ bundle install --path vendor/bundle
(略)
Bundle complete! 1 Gemfile dependency, 5 gems now installed.
Bundled gems are installed into ./vendor/bundle.

$ ls
Gemfile  Gemfile.lock  vendor

--path vendor/bundleは、Gemのインストール先のパスを指定しています。
これが先ほどの図のプロジェクトごとにライブラリを管理する場合ですね。
この記述がないとシステムに直接Gemが入りますが、研修中は常にパスを指定して入れるようにしましょう。
なお、2回目以降のbundle install時にはpathの指定は不要です。

上記によって Gemfile.lockvendor/ が新たにできました。
Gemfile.lock はGemの依存関係などが記述されています。
Gemfileで指定したGemは、Gemfile.lockに記述されたバージョンでインストールされ、vendor/bundle 以下に入ります。

それでは実際にfaradayを使用してみましょう。

faraday.rb
1
2
3
4
5
6
7
8
9
10
11
require "faraday"

response = Faraday.get 'https://www.val.co.jp'

puts "------------------status------------------"
puts response.status



puts "-------------------body-------------------"
puts response.body

bundler経由で入れたGemを使用して実行するときはbundle execを使用します。

1
$ bundle exec ruby faraday.rb

さて、どんな結果が返ってきたでしょうか?


演習問題

問1

本日から、10年3ヶ月5日前の日付を求めてください。
例:本日が2018年05月29日の場合

1
#=> 本日から10年3ヶ月5日前の日付は2008年02月24日になります

問2

まず、以下のファイルを作成してください。

  • points.json : 「高円寺駅と各避難所の緯度経度情報」を表現したJSONデータ
points.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
  "Station": {
    "latitude": "35.705333",
    "longitude": "139.649826",
    "Name": "高円寺"
  },
  "Shelters": [
    {
        "latitude": "35.70662317",
        "longitude": "139.65348066",
        "name": "杉並区立杉並第四小学校"
    },
    {
        "latitude": "35.70872544",
        "longitude": "139.64307035",
        "name": "杉並区立馬橋小学校"
    },
    {
        "latitude": "35.70113485",
        "longitude": "139.65068392",
        "name": "杉並区立杉並第八小学校"
    }
  ]
}
  • geo_calcurator.rb : 2点の緯度経度情報から2点間の距離[m]を計算するモジュールGeoCalcurator
geo_calcurator.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
module GeoCalcurator
  #地球の半径(m)
  EARTH_RADIUS = 6378100

  module_function

  # 球面三角法による2点間の距離[m]
  # 引数:2点の緯度経度(Hash)
  #   { lat: 緯度, lng: 経度 }
  def calculate_distance(point1, point2)

    # 緯度、経度をラジアンに変換
    radian1 = {
      lat: point1[:lat] * Math::PI / 180,
      lng: point1[:lng] * Math::PI / 180
    }

    radian2 = {
      lat: point2[:lat] * Math::PI / 180,
      lng: point2[:lng] * Math::PI / 180
    }

    # ラジアン距離を計算
    radian_distance = Math::acos(
                        Math::sin(radian1[:lat]) * Math::sin(radian2[:lat]) +
                        Math::cos(radian1[:lat]) * Math::cos(radian2[:lat]) * Math::cos(radian1[:lng] - radian2[:lng])
                      )

    # メートル距離に変換
    EARTH_RADIUS * radian_distance
  end
end

問2-1

これらのファイルを利用して、高円寺から各避難所までの距離[m]を求めてください。

1
2
3
#=> 「高円寺駅」から「杉並区立杉並第四小学校」までの距離: 360m
#=> 「高円寺駅」から「杉並区立馬橋小学校」までの距離: 718m
#=> 「高円寺駅」から「杉並区立杉並第八小学校」までの距離: 474m

問2-2

問2-1で求めた値を、以下のフォーマットでJSONファイルに出力してください。

distances.json
1
{"from_staiton":"高円寺","distances":[{"distance":360,"name":"杉並区立杉並第四小学校"},{"distance":718,"name":"杉並区立馬橋小学校"},{"distance":474,"name":"杉並区立杉並第八小学校"}]}

(余力があれば、以下のように綺麗に整形して出力する方法を調べてみましょう)

distances_pretty.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
  "from_staiton": "高円寺",
  "distances": [
    {
      "distance": 360,
      "name": "杉並区立杉並第四小学校"
    },
    {
      "distance": 718,
      "name": "杉並区立馬橋小学校"
    },
    {
      "distance": 474,
      "name": "杉並区立杉並第八小学校"
    }
  ]
}