メモ的な何か

技術的な私のメモになる予定です。

RubyでConnpassのAPIを叩く

はじめに

RubyCLIアプリを作成し始めました。
ConnpassのイベントをGoogleカレンダーに登録する感じのを作ろうと思います。
便利なものになるかはわかりません。(そもそもCLIアプリな時点で)
完成形は置いといてまずはHttpsでConnpassのAPIを叩くところを作りました。

API呼び出し

APIの仕様に沿って作成しました。
すごいAPIがシンプルなので難しいところはないのですが、APIの呼び出しがHTTPSになると以外に情報がなかったので呼び出し部分を抜粋で載せておきます。
特に外部ライブラリ使わなくても標準のnet/httpでも十分って感じでした。

def self.get(params)
  begin
    validation params # パラメータのバリデーションチェック呼び出し
    params = URI.encode_www_form params
    uri = URI.parse("https://connpass.com/api/v1/event/?#{params}")
    response = Net::HTTP.start(uri.host, uri.port, :use_ssl => true) { |http|
      request = Net::HTTP::Get.new uri
      http.request request
    }
    JSON.parse response.body
  rescue ArgumentError => e
    e.message
  rescue IOError => e
    e.message
  rescue Timeout::Error => e
    e.message
  rescue JSON::ParserError => e
    e.message
  rescue => e
    e.message
  end
end

パラメータのバリデーションチェックも簡単にですが実装しています。
引っかかった場合はArgumentErrorを返す感じです。
エラー処理部分は今はエラーメッセージを返す感じにしてますが、そのままエラーを呼び出しもとに投げる形に変えるかも。

RubyのHashとSymbolでハマったこと

事象

Thorのオプション機能を使ったときにハマりました。
コマンド入力のオプションで設定した内容はoptionsというHashクラスに入るのですが、以下がうまくいきませんでした。

p options[:a_key] # =>"a_value"

hash.each do |key, value|
  case key
  when :a_key
   puts value # =>表示されない
  end
end

p optins # =>{"a_key"=>"a_value"}

結果

最後の1行でお気づきの方もいると思いますが、
結果から言うとoptionsはHashクラスではありませんでした。
私が勝手にHashクラスだと思いこんでいただけです。

p options.class # =>Thor::CoreExt::HashWithIndifferentAccess

SymbolとStringを区別せずに同一のキーとして扱うクラスらしい。(Railsとかでも提供がある)
eachメソッドのkeyにはSymbolではなくStringが入るためcase分岐しませんでした。

以下とかとすごく似て見えるけど違う。
というか引数に型(クラス)を意識しないRubyの特徴だから類似も含めて気をつけねば。

options = {"a_key" => "a_value"}
p options # =>{"a_key"=>"a_value"}
p options["a_key"] # =>"a_value"
p options[:a_key] # =>nil

options_symbol = {:a_key => "a_value"}
p options_symbol # =>{:a_key=>"a_value"}
p options_symbol["a_key"] # =>nil
p options_symbol[:a_key] # =>"a_value"

Rubyでテストコードの作成

はじめに

Rubyでテストコードのお試しをしました。
Rubyでのテスティングフレームワークは何種類かあります。
以下あたりが人気っぽいです。

今回はRSpecを選びました。
選ぶ基準はここを参考にさせていただきました。(情報量の多さとか使われている多さ等々)
今読んでいるパーフェクトRubyではRubyに同梱されているtest-unitの説明のみだったため、 RSpecで書いてみた内容をメモして置きます。

RSpec導入

Rubyでの導入は簡単です。
というか前回のCLIアプリケーションにも導入しています。
前回の記事の通り以下の初期実行にてテスティングフレームをRSpecを選択していれば -t オプションをつけたときに勝手に入ります。

bundle gem sample -b -t

もしここでRSpecを選んでなかったよって人は以下で導入できます。

bundle gem sample -b -t rspec

このコマンド後RSpecの設定ファイル(.rspecとspec/spec_helper.rb)も作成されてます。
途中からって人は多分.gemspecに書くとかすればいける気しますが、試してません。

またテストコードも一部自動で作成済みです。
spec/sample_spec.rbはlib/sample.rbに対するテストコードで、バージョンがnilでないこととfalseがtrueであることを確認しています。

RSpec.describe Sample do
  it "has a version number" do
    expect(Sample::VERSION).not_to be nil
  end

  it "does something useful" do
    expect(false).to eq(true)
  end
end

テストコードの実行

試しにこの状態ままで実行してみます。
実行のコマンドと実行結果は以下です。

$ bundle exec rspec

Sample
  has a version number
  does something useful (FAILED - 1)

Failures:

  1) Sample does something useful
     Failure/Error: expect(false).to eq(true)

       expected: true
            got: false

       (compared using ==)
     # ./spec/sample_spec.rb:7:in `block (2 levels) in <top (required)>'

Finished in 0.04696 seconds (files took 0.40854 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./spec/sample_spec.rb:6 # Sample does something useful

もちろん後者は失敗しました。

テストの作成

続いてはテストを自分で作成してみます。
前回CLIアプリケーションのお試しで作ったSampleプロジェクトを使います。
Thor部分はテストし辛いので少しソースをいじります。
lib/sample/cli.rbを以下のように修正します。

  require "sample"
  require "thor"
 +require "sample/greeting"

  module Sample
    class Cli < Thor
        desc "hello {name}", "Hello {name}!"
        def hello(name)
 -          puts "Hello #{name}!"
 +          puts_str = Sample::Greeting.hello_create(name)
 +          puts puts_str
        end
    end
  end

lib/sample/greeting.rbを作成します。

require "sample"

module Sample
  class Greeting
    def self.hello_create(name)
      "Hello #{name}!"
    end
  end
end

見てわかるとおり挙動は変えてません。
テストするためだけにGreetingクラスを作成しました。

このGreetingクラスに対するテストコードを書きます。
rspec/sample/greeting_spec.rbを作成します。
このコードはhello_createメソッドの挙動を確認しています。最後だけわざと失敗するように作成しました。

require "sample/greeting"

RSpec.describe Sample::Greeting do
  it "return Hello Tanaka!" do
    expect(Sample::Greeting.hello_create("Tanaka")).to eq("Hello Tanaka!")
  end

  it "return Hello 田中!" do
    expect(Sample::Greeting.hello_create("田中")).to eq("Hello 田中!")
  end

  it "test failed" do
    expect(Sample::Greeting.hello_create("山田")).to eq("Hello 田中!")
  end
end

テストを実行してみます。
新たに作成したテストが追加で実行できていることが確認できると思います。

$ bundle exec rspec

Sample::Greeting
  return Hello Tanaka!
  return Hello 田中!
  test failed (FAILED - 1)

Sample
  has a version number
  does something useful (FAILED - 2)

Failures:

  1) Sample::Greeting test failed
     Failure/Error: expect(Sample::Greeting.hello_create("山田")).to eq("Hello 田中!")

       expected: "Hello 田中!"
            got: "Hello 山田!"

       (compared using ==)
     # ./spec/sample/greeting_spec.rb:12:in `block (2 levels) in <top (required)>'

  2) Sample does something useful
     Failure/Error: expect(false).to eq(true)

       expected: true
            got: false

       (compared using ==)
     # ./spec/sample_spec.rb:7:in `block (2 levels) in <top (required)>'

Finished in 0.05989 seconds (files took 0.43274 seconds to load)
5 examples, 2 failures

Failed examples:

rspec ./spec/sample/greeting_spec.rb:11 # Sample::Greeting test failed
rspec ./spec/sample_spec.rb:6 # Sample does something useful

今回のテストコードはものすごい基本的な文字列確認ですが、RSpecで提供されているメソッドをいろいろ使えば柔軟にテストを書くことができると思います。

RubyでのCLIアプリケーション開発

はじめに

rubyもなんとなくわかって来たのと開発環境もそこそこ準備が整って来たので、お試しも含めて何か作っていこうと思います。
railsはまだ触ってないのでまずはCLIアプリケーションからと思ってその環境について調べました。
こんな感じを基本構成としようと思ってます。

rubyプロジェクトの作成

rubyプロジェクトフォルダの基本構成はgemの構成に従うのがスタンダードっぽい。
gemの雛形づくりはbundleでできるんだけどそのbundleのrubyバージョンはrbenvが握っているためまずはrbenvでbundleのコマンドを実施する前にバージョンを設定します。

$ rbenv global 2.5.1

もちろんこのバージョンのrubyにbundleが入っている必要があります。

このあとはgemの雛形をつくります。
このときgitの設定でnameとかemailとか設定しておく必要があります。このへんの情報を使うので。ないとどうなるかは試してません。

$ bundle gem sample -b -t
reating gem 'sample'...
Do you want to generate tests with your gem?
Type 'rspec' or 'minitest' to generate those test files now and in the future. rspec/minitest/(none): rspec
Do you want to license your code permissively under the MIT license?
This means that any other developer or company will be legally allowed to use your code for free as long as they admit you created it. You can read more about the MIT license at https://choosealicense.com/licenses/mit. y/(n): y
MIT License enabled in config
Do you want to include a code of conduct in gems you generate?
Codes of conduct can increase contributions to your project by contributors who prefer collaborative, safe spaces. You can read more about the code of conduct at contributor-covenant.org. Having a code of conduct means agreeing to the responsibility of enforcing it, so be sure that you are prepared to do that. Be sure that your email address is specified as a contact in the generated code of conduct so that people know who to contact in case of a violation. For suggestions about how to enforce codes of conduct, see https://bit.ly/coc-enforcement. y/(n): n

-bのオプションは実行ファイルの作成をする、-tのオプションはテストファイルを作成をするになります。
最初だけこんな感じでいろいろ聞かれます。
- テスティングフレームワークrspecとminitestどっち?→rspec(私の回答)
- MITライセンスでいいか?→y(私の回答)
- code of conduct に関するファイルを作成する?→n(私の回答)
Contributor Code of Conduct
code of conductについてはこちらがわかりやすいです。

プロジェクトの雛形として以下のファイルが作成されます。

create  sample/Gemfile
create  sample/lib/sample.rb
create  sample/lib/sample/version.rb
create  sample/sample.gemspec
create  sample/Rakefile
create  sample/README.md
create  sample/bin/console
create  sample/bin/setup
create  sample/.gitignore
create  sample/.travis.yml
create  sample/.rspec
create  sample/spec/spec_helper.rb
create  sample/spec/sample_spec.rb
create  sample/LICENSE.txt
create  sample/exe/sample

更にプロジェクトのrubyのバージョンを固定するために以下をしておきます。(あまり強制力はないです。)

$ cd sample/
$ rbenv local 2.5.1

最後にbundle installとかするときにエラーとならないようにsample.gemspecを修正しておきます。 TODOがあるとエラーになります。

- spec.summary       = %q{TODO: Write a short summary, because RubyGems requires one.}
- spec.description   = %q{TODO: Write a longer description or delete this line.}
- spec.homepage      = "TODO: Put your gem's website or public repo URL here."
+ spec.summary       = "sample application"
+ spec.description   = "sample application"
+ spec.homepage      = "https://github.com/ruihub/"

ちなみにhomepageに適当な文字列いれたらだめでした。どこまでかわからないですが、存在するURLをいれておくのが無難です。

CLIアプリケーションの作成

CLIアプリケーションにはThorというgemを使います。
gemの追加に関してはsample.gemspecにgemを追記します。

  spec.add_development_dependency "bundler", "~> 1.16"
  spec.add_development_dependency "rake", "~> 10.0"
  spec.add_development_dependency "rspec", "~> 3.0"

+ spec.add_dependency "thor"

これで後ほどのgemコマンドでthorが追加されます。うしろに「, "-> バージョン"」をつければバージョンも固定できます。
developmentがついているの開発時のみにしか使わないgemです。

次にCLIクラスを作成します。 参考先に従ってlib/パッケージ名/cli.rbとしました。 ThorではThorを継承したクラスに宣言したpubulicメソッドがコマンドとなるようです。
メソッド上部にdescで"コマンドの使い方","コマンド説明"を記載するこでhelpコマンドでその内容が確認できます。

  require "sample"
  require "thor"
  
  module Sample 
    class Cli < Thor
      desc "hello {name}", "Hello {name}!"
      def hello(name)
        puts "Hello #{name}!"
      end
    end
  end

続いてlib/パッケージ名.rbにcli.rbをrequireするよう修正します。
実行ファイルから呼び出されるファイルがこのファイルになります。

  require "sample/version"
+ require "sample/cli"
  
  module Sample
    # Your code goes here...
  end

実行ファイルはこれまた参考の更に参考先の意見をもとにexe/以下に配置することにしました。

  #!/usr/bin/env ruby

  require "sample"

+ Sample::Cli.start

アプリケーションの実行

まずはbunde installを実行してThor含め必要なgemをとってきます。
ここで忘れてはいけないのが今回に限った話ではないけどこのままbunde installを実行してしまうとグローバルにgemをインストールしてしまうので、プロジェクトフォルダ以下に配置するようにオプション --path ディレクトリ をつけてプロジェクトディレクトリ配下の指定のディレクトリにgemをインストールします。 ディレクトリはvendor/bundleとするのが一般的なようです。

$ bundle install --path vendor/bundle
$ ls -l
total 48
-rw-r--r--  1 rui  staff   161  7 10 21:33 Gemfile
-rw-r--r--  1 rui  staff   651  7 11 20:11 Gemfile.lock
-rw-r--r--  1 rui  staff  1073  7 10 21:33 LICENSE.txt
-rw-r--r--  1 rui  staff  1324  7 10 21:33 README.md
-rw-r--r--  1 rui  staff   117  7 10 21:33 Rakefile
drwxr-xr-x  4 rui  staff   128  7 10 21:33 bin
drwxr-xr-x  3 rui  staff    96  7 11 20:10 exe
drwxr-xr-x  4 rui  staff   128  7 11 19:59 lib
-rw-r--r--  1 rui  staff  1545  7 10 22:22 sample.gemspec
drwxr-xr-x  4 rui  staff   128  7 10 21:33 spec
drwxr-xr-x  3 rui  staff    96  7 11 20:11 vendor

その後は以下でコマンド一覧の確認

$ bundle exec exe/sample
Commands:
  sample hello {name}    # Hello {name}!
  sample help [COMMAND]  # Describe available commands or one specific command

ヘルプの参照

$ bundle exec exe/sample help hello
Usage:
  sample hello {name}

Hello {name}!

実装したhelloコマンドの実行

$bundle exec exe/sample hello Ruby
Hello Ruby!

という感じでThorを使うことによってCLIアプリケーションが簡単に作成できました!

参考

qiita.com

bashからzshに乗り換えたら気をつけること

ブログの名の通りメモです。
rubyのバージョンが気がついたら古くなっていて再びあれ?ってなりました。
シェルをzshからbashに変更したことによって発生した模様。

.bash_profileに以前以下を記載しました。

eval "$(rbenv init -)"

でもzshだと.bash_profileが読み込まれないようです。
代わりに.zshrcに記載します。

Rubyの開発環境構築

どうでもいい話

前回の記事でDockerを使用して開発環境を構築をしようと言っていたが、あれはなしとします。
一旦Mac内に環境を構築しようと思います。
方向転換した理由としては事前にいろいろ調べて先人様たちを参考にさしていただこうと思ったのですが、 実例として多かったのは作成済みのアプリケーションの開発環境として扱うパターンで一からアプリケーションを立ち上げるパターンがなかったのと、 Rubyの知識が乏しいので利点等が理解できませんでした。
DockerはもっとRubyを理解してから実施します。

Macでの開発環境構成

とりあえず基本的なところをいれます。

  • rbenv
  • ruby
  • gem
  • bundler

開発環境構築手順

Homebrew(Rubyとは関係ないけど)
いきなり記載のないものから。
Macのパッケージマネージャー。
今回はこれでRuby向けのアプリケーションの管理を行うことにするから記載。
特にRuby用ってことではないので、説明は省略します。この辺みてやるだけです。

brew.sh

rbenv
Rubyのバージョン管理アプリケーション。 あと一緒にruby-buildもいれる。これはruby installコマンドを提供するプラグイン

インストール

$ brew install rbenv ruby-build

ruby
Ruby本体はrbenv経由でいれます。

いまのRubyのバージョン確認。

$ rbenv versions
* system (set by ~/.rbenv/version)

Macに最初から入っているRubyを使用しています。

入れられるRubyの確認。いろいろ出てくるからそこから選びます。

$ rbenv install -l

今回はバージョン2.5.1をインストール。

$ rbenv install 2.5.1
$ rbenv versions
* system (set by ~/.rbenv/version)
  2.5.1

使用するrubyを2.5.1に変更。

$ rbenv global 2.5.1
$ rbenv versions
  system
* 2.5.1 (set by /Users/rui/.rbenv/version)

バージョン確認。

$ ruby -v
ruby 2.3.3p222 (2016-11-21 revision 56859) [universal.x86_64-darwin17]

あれ?ってなったのでいろいろ調べるとこれで解決。

$ rbenv init
# Load rbenv automatically by appending
# the following to ~/.bash_profile:

eval "$(rbenv init -)"

.bash_profileにeval "$(rbenv init -)"を追記しろとのことです。

.bash_profileがなければ作って記載し、読み込み。

$touch .bash_profile
$vi .bash_profile
eval "$(rbenv init -)"
$source ~/.bash_profile
$ ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin17]

バージョンOK。

gem
Rubyのライブラリやアプリケーションはgemとよばれており、 そのgemをダウンロードしたり、インストールしたりできます。
rubyに内包されているのですでに使えます。

bundler
アプリケーションに必要なgemの種類やそのバージョンを管理してくれます。 bundler自身もgemなのでgemのインストールコマンドを使ってインストール。

$ gem install bundler

まとめ

一旦は基本的なところは抑えたので、これをベースにいろいろ実験と、何か作ってみようと思います。
使い方等で詰まったり、なにか追加でいれたほうが良さそうなものがあったら記事書いてきます。

Docker入れてみました

どうでもいい話

開発環境をリニューアルしたくてMac買いました。
iPhoneアプリとか開発したいなーという感じです。

この機会にDocker入れて開発環境の構築を検討してみようかと思い、とりあえずまだ触りだけだけどメモ。

インストール

Docker for Macをインストールします。
公式サイトからダウンロードだけど、アドレス、アカウント、パスが必要なアカウント登録が必要でした。

docs.docker.com

ちなみに複数のコンテナを管理するDocker ComposeもすでにDocker for Mac入ってます。

docs.docker.com

Install Compose on macOS
Docker for Mac and Docker Toolbox already include Compose along with other Docker apps, so Mac users do not need to install Compose separately.

起動

バージョン確認のコマンドでちゃんと起動していることが確認できました。

$ docker version
Client:
 Version:      18.03.1-ce
 API version:  1.37
 Go version:   go1.9.5
 Git commit:   9ee9f40
 Built:        Thu Apr 26 07:13:02 2018
 OS/Arch:      darwin/amd64
 Experimental: false
 Orchestrator: swarm

Server:
 Engine:
  Version:      18.03.1-ce
  API version:  1.37 (minimum version 1.12)
  Go version:   go1.9.5
  Git commit:   9ee9f40
  Built:        Thu Apr 26 07:22:38 2018
  OS/Arch:      linux/amd64
  Experimental: true

Dockerの用語の解釈

いろいろ調べた結果のあくまで個人的な解釈です。ご意見あればください。
イメージ
オブジェクト指向のクラスのようなものという解釈。アプリケーションが動くための情報が提供される感じ。

コンテナ
オブジェクト指向インスタンスのようなものという解釈。イメージをもとに作成されてアプリケーションが動く実際の環境。

Dockerfile
イメージの作り(動作?)を記載するもの。ソースコード的な位置づけ?

触ってみる

イメージをとってくる
試しにrubyのイメージをとってくる。 とってくる先はデフォルトではDockerHub。

$docker pull ruby

とってきているイメージの確認。

$docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ruby                latest              857bc7ff918f        2 weeks ago         869MB

特に指定がない場合は最新(latest)がとって来れるもよう。バージョンを指定したい場合はruby:{バージョン}でいける。

コンテナを作る
とってきたrubyのイメージからコンテナを作成してみる。

$docker run ruby ruby --version
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux]

これはrubyのイメージからコンテナを作成し、ruby --versionコマンドを実行したというコマンド。 ちなみにDockerHubにある最新(2018/6/27時点)のrubyのバージョンは2.5.1p57ということがわかった。
存在するコンテナの確認は以下のコマンド。
停止している(今回の場合はruby --versionが完了したら停止する)ものも含めての表示の場合は-aオプションが必要。

$docker container ls -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
6f5fbb0441bc        ruby                "ruby --version"    6 minutes ago       Exited (0) 6 minutes ago                       loving_noyce

さらにもう一度同じコマンドでコンテナを作成した場合は別のコンテナが作成、実行される。

$ docker run ruby ruby --version
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux]
$ docker container ls -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
9ed7ce97548e        ruby                "ruby --version"    2 seconds ago       Exited (0) 1 second ago                        upbeat_yalow
6f5fbb0441bc        ruby                "ruby --version"    9 minutes ago       Exited (0) 9 minutes ago                       loving_noyce

コンテナの削除
コンテナの削除は以下のコマンドで可能

$ docker container rm {CONTAINER ID}

イメージの削除
イメージの削除は以下のコマンドで可能

$ docker image rm {IMAGE ID}

Dockerfile
Dockerfileを作成してrubyにてHello World!を出力してみます。 Dockerfileを作成する用の空のディレクトリを切り(推奨 ※公式も)、以下のファイルを配置します。 今回はそのディレクトリをカレントディレクトリにします。

hello.rb

puts "Hello World!"

Dockerfile

FROM ruby
ADD hello.rb /test/
WORKDIR /test
ENTRYPOINT ["ruby", "hello.rb"]

ちなみにDockerfileの意味は以下の通りです。

命令 解説
FROM ベースのイメージを記載します。
ADD ホストPCのファイルをイメージにコピーします。
WORKDIR 以下ENTRYPOINT等のコマンドを実行するディレクト
ENTRYPOINT docker run時の実行コマンド

Dockerfileをもとにイメージを作成します。
コマンドの最後のドットはDockerdileの場所を指定(今回はカレントディレクトリ)。

$ docker build .

作成されればこんな感じで確認できます。
今回はオプションをつけてないので、REPOSITORYもTAGもnoneです。

$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
<none>              <none>              b8c5ba4a7800        13 minutes ago      869MB
ruby                latest              857bc7ff918f        2 weeks ago         869MB

ちなみにDockerfileの記載ミスでbuildが途中で失敗した場合もイメージが作成されていたので、その場合は要注意。

最後にrunコマンドでコンテナを作成し、実行します。(IMAGE IDで実行)

$ docker run b8c5ba4a7800
Hello World!

終わりに

触りとしてはそれなりにやれたかな思ってます。
感覚はつかめたのでこれをベースにrubyの開発環境構築を検討しようかな。