woshidan's loose leaf

ぼんやり勉強しています

会社のrubyのバージョンが2.4.0以降にあげられないので `Array#-` と同等のスニペットをおいておく

irb(main):010:0>   class Array
irb(main):011:1>     def - other
irb(main):012:2>       copy = self.dup
irb(main):013:2>       other.each do |other_array_item|
irb(main):014:3*         copy.delete other_array_item
irb(main):015:3>       end
irb(main):016:2>       copy
irb(main):017:2>     end
irb(main):018:1>   end
=> :-
irb(main):019:0> a = [1, 2, 3]
=> [1, 2, 3]
irb(main):020:0> b = [1, 2, 4]
=> [1, 2, 4]
irb(main):021:0> a - b
=> [3]

参考

自作クラスのテスト用にComparableモジュールと宇宙船演算子を使って同じオブジェクトか比較しやすくする

rubyでちょっとした処理を担当する自作クラスのテストをするとき、同じオブジェクトがどうかの比較する際、

assert_equal expect_obj.hash_value, actual_obj.hash_value # hash_valueはオブジェクトのidではないオブジェクトに特有の値

というような式をよく書いていました。ですが、そんなに広く使われないクラスであることがわかっているなら == 演算子の挙動をいじってしまって、

assert_equal expect_obj, actual_obj

とかけるようにしちゃっていいかも、ということでその方法をメモ。

class MyClass
  include Comparable # 宇宙船演算子の内容を元に `<=`, `>=` を使えるようにしてくれる

  def <=> other # 宇宙船演算子を使って、`<`, `==`, `>` 用の動作を定義
    return nil unless other.is_a? MyClass
    hash_value <=> other.hash_value # MyClassのオブジェクトが同一かどうか判断するための比較に、hash_valueの比較を使う
  end

  def hash_value
    # テストでも作りやすい値を元にハッシュ値を計算する
  end
end

参考

Effective Ruby

Effective Ruby

rubyでライブラリなどを書くとき、Rakefileと相対パスのrequireを使ってクラスの取得やテストをやりやすくする

+---+ lib
|   +-+ some_module
|     +-+-- a_processor
|       |   +-- order.rb
|       |   +-- item.rb
|       +-- b_processor
|       |   +-- order.rb
|       |   +-- item.rb
|       +-- composer.rb # composerは a_processor, b_processor 以下のクラスを必要とする
+---+ test # ここから一括で回せるようにしたい
    +-+ some_module
      +-+-- a_processor
        |   +-- order_test.rb
        |   +-- item_test.rb
        +-- b_processor
        |   +-- order_test.rb
        |   +-- item_test.rb
        +-- composer_test.rb

といった構成のライブラリを作るとして、composer 書いたり、 地味にテストを自動実行できるようにどうしたら良いか調べるのがめんどくさかったのでメモ。

追加分

+---+ lib
|   +-+ some_module
|     +-+-- a_processor
|       |   +-- order.rb
|       |   +-- item.rb
|       +-- b_processor
|       |   +-- order.rb
|       |   +-- item.rb
|       +-- composer.rb
|       +-- a_processor.rb ** 追加
|       +-- b_processor.rb ** 追加
+---+ test
    + Rakefile ** 追加
    +-+ some_module
      +-+-- a_processor
        |   +-- order_test.rb
        |   +-- item_test.rb
        +-- b_processor
        |   +-- order_test.rb
        |   +-- item_test.rb
        +-- composer_test.rb

それっぽく他パッケージにあるクラスを全て読み込む

比較的苦しくない抑えどころに近いんですが、そのパッケージのクラスを丸ごと読み込んでくれるファイル用意して、それを require すると楽そう。

# a_processor.rb
require "#{File.dirname(__FILE__)}/a_processor/order.rb"
require "#{File.dirname(__FILE__)}/a_processor/item.rb"

ただ、明示的にクラスの指定どうこうする必要がない場合は、 Dir クラスなどを使って each で回すと良さそう。

# a_processor.rb をなくしてbuilder.rb 冒頭で
Dir.glob("#{File.dirname(__FILE__)}/a_processor/*").each do |file|
  require file
end

test以下のディレクトリのファイル全てをコマンド一つで実行する

今回は test ディレクトリ以下で rake test で全部実行できればよかったので、下記のような Rakefiletest 直下に置く。 Rakefile はおいたところで Rakefile に書いたタスクが rake xxx (xxxなしだと task :default に定義したものが使われる)実行されるようになるので、書いてから、普通はトップディレクトリにおいて、 ./**/*_test.rb -> ./test/**/*_test.rb にしたほうがいいかとも思った。

# Rakefile

# coding: UTF-8

require 'rake/testtask'

task :default => [:test]

Rake::TestTask.new do |test|
  test.test_files = Dir['./**/*_test.rb']
  test.verbose = false
end

なお、テストファイルで本番用のファイルを要求するときは、そのライブラリ用のベースクラス的なのを用意して、そこで require しておくと楽。

# base_test.rb
require 'test/unit'

Dir.glob("#{File.dirname(__FILE__)}/a_processor/*").each do |file|
  require file
end

Dir.glob("#{File.dirname(__FILE__)}/b_processor/*").each do |file|
  require file
end

Dir.glob("#{File.dirname(__FILE__)}/**.rb").each do |file|
  require file
end

class SomeModule::BaseTest < Test::Unit::TestCase
  include SomeModule

  ...

# 他のテスト用クラスは

require "#{File.dirname(__FILE__)}/path/to/base_test.rb"

class SomeModule::SomeTest < SomeModule::BaseTest

参考

gitのローカルリポジトリからお目当のタグを抽出する

今日は時間がないので渾身かも、と思ったコードに数行コメントつけてPOST!

  • シェルは便利なんだけどシェルの結果の文字列を気軽に処理したい時、ruby書いたことある人にとってはrubyがとても楽
  • rubyだとシェルのコマンドを %x( command ) などで実行できる
  • ruby xx.rb --params などのコマンドライン引数は ARGV に配列で入ってる
  • 一時的にディレクトリを移動したいだけなら Dir.chdir にブロック渡すのが便利
  • 他の人とディレクトリ構造が違う(ホームディレクトリなどは?) => ENV['HOME'] など ENV から取れるよ
  • git fetch remote-repo --tags で現在のステージの内容などを気にせずに git tag の出力だけ更新できるよ
    • git tag の出力は git FETCH_HEAD にある tag と一致していて*1、要するに git の管理対象のファイルの内容を読み書きしているわけではないので
    • 作業途中で同期のための雑な git stash しなくていいの嬉しい!
latest_tag = Dir.chdir("#{ENV['HOME']}/woshidan-test/sample-repo") do
  if fetch_remote
    puts "fetch tags from remote repository ..."
    %x( git fetch remote-repo --tags ) # ローカルリポジトリを最新にするコマンドを挟むことで自分を信じないことができる
    puts "done"
  end
  %x( git tag ).split("\n").select { |tag| tag.match(/お目当のタグの正規表現/) }.last
end

参考

*1:このファイルから出力されているわけではないだろうが

AWS CLIの設定ファイルにて名前付きプロファイルを利用して手軽にAWS CLIのIAMユーザを切り替える

普段業務でAWS CLIを使っているため、業務本番用のアカウントとちょっと個人の調査用のアカウントを分けて使いたいわけですが*1aws configure コマンドで設定できるアカウントは一つだけです。

これではちょっと調査をするたびにクレデンシャル情報を引っ張ってきて入力するのがめんどくさくなるのが目に見えているので他の方法を考えます。

aws configure コマンドで何をしているか

aws configure コマンドで何をしているかというか、 ~/.aws/config~/.aws/credentials にコマンドで与えた引数の入力を行なっています。

入力結果のファイルは下記のような形です。

~/.aws/config

[default]
region = ap-northeast-1

~/.aws/credentials

[default]
aws_access_key_id = ABCDEEGHEXAMPLEKEYID
aws_secret_access_key = ABCdefgHIjkLM/OPQrstUVwxyzAbcsExampleKey

このファイルに何か見出しつけてもう1組書いてやったらできそうですね。

awsの設定ファイルで名前付きプロファイルを使う

[XXX] のように見出しをつけて、認証情報などをその見出しごとに管理でき、これを 名前付きプロファイル と言うそうです。

~/.aws/config

[default]
region = ap-northeast-1

[profile user2] // credentialsと見出しが違うので注意
region=us-east-1
output=text // 出力形式も設定できる

~/.aws/credentials

[default]
aws_access_key_id = ABCDEEGHEXAMPLEKEYID
aws_secret_access_key = ABCdefgHIjkLM/OPQrstUVwxyzAbcsExampleKey

[user2]
aws_access_key_id=AKIAI44QH8DHBEXAMPLE
aws_secret_access_key=je7MtGbClwBF/2Zp9Utk/h3yCo8nvbEXAMPLEKEY

このように見出しをつけて、

$ aws ec2 describe-instances --profile user2

のように --profile オプションをつけて実行するか、

$ export AWS_PROFILE=user2

のように環境変数を設定することでaws cliで利用するユーザーアカウントを切り替えることができます。

現場からは以上です。

参考

docs.aws.amazon.com

*1:以前はできたらプライベートAWSの費用も持って欲しい気持ちがあったのですが、いまのところ月50円以下、いっても500円以内ということが判明したので請求する方がめんどくさい、ということでやめた

AWSのCLIで各種リソースやサービスにアクセスするためにIAMでアクセスキーを発行する

AWSのAthenaクライアント aws-sdk-athena を使ってみようとしたら、Athenaクライアントは aws cli を利用していて、これを利用するために IAMAWSアカウントにアクセスするユーザを作る必要があったので手順メモ。

ちなみに IAMIdentity and Access Management だそうです。

事前準備

まず、事前準備としてルートアカウントの多要素認証(MFA)を有効化します。

MFAって多要素認証の略なんですね。なるほどです。

IAMユーザの作成

次にAWSの各種サービスにアクセスするためにIAMユーザを作成します。 IAMユーザを作成するにあたって、Webの画面からアクセス可能か、CLIからアクセスできる可能か、あるいは両方可能かを最初に設定します。

f:id:woshidan:20170929143416p:plain

f:id:woshidan:20170929143437p:plain

グループの作成・設定

IAMユーザを作成したあとは、そのユーザが所属するグループを設定します。 IAMではユーザーの権限をグループ単位で設定し、グループ単位で権限を設定する際には権限に関する設定が書かれたポリシーをいくつ採用するか、といった方式を用います。

f:id:woshidan:20170929143708p:plain

f:id:woshidan:20170929143724p:plain

f:id:woshidan:20170929143739p:plain

なお、AthenaのCLIで結果をS3のバケットに書き込む場合(必須)、上記以外に書き込み先のS3のバケットへの読み書きの権限もいります(一敗)。

完了

確認画面で内容をもう一度確認したら「ユーザー作成」ボタンを押して完了です。

f:id:woshidan:20170929143752p:plain

f:id:woshidan:20170929143821p:plain

なお、シークレットアクセスキーはユーザー作成時かアクセスキーID発行*1時しかできないので注意しましょう。

*1:一応これは後からいつでもすぐできます

整形されたJSON文字列を見たい時、minifyしたい時、jqコマンドがとても便利だったという話

昨日頑張って sed をかじったため

sed -e '1,10s/\([}\]]\),/\1,\
/g' -e '1,10s/:{/&\
  /g' -e '1,10s/e,/&,\
  /g' test.json 

みたいな涙ぐましい事してたんですが、検索してたら jq ってコマンドで簡単にできるらしいです。

まずは単に整形する

例えばこんなminifyされたJSONがあったとして

{"photo":{"taken_at":1506609841,"created_at":1504609841,"title":"さっちゃん"}}

これをこうじゃ。

$ echo '{"photo":{"taken_at":1506609841,"created_at":1504609841,"title":"さっちゃん"}}' | jq .
{
  "photo": {
    "taken_at": 1506609841,
    "created_at": 1504609841,
    "title": "さっちゃん"
  }
}

この標準出力をファイルへリダイレクトする事で、整形されたJSON文字列をファイルへ書き込むこともできます。

必要な要素だけを取り出してみる

jqコマンドは .キー名 で、そのキーの要素だけを取り出して表示させることが可能です*1

これを利用すると、

$ echo '{"photo":{"taken_at":1506609841,"created_at":1504609841,"title":"さっちゃん"}}' | jq .photo
{
  "taken_at": 1506609841,
  "created_at": 1504609841,
  "title": "さっちゃん"
}

$ echo '{"photo":{"taken_at":1506609841,"created_at":1504609841,"title":"さっちゃん"}}' | jq .photo.title
"さっちゃん"

といったことができます。

配列の中の指定した要素を列挙する

さらに、配列については以下のようなことも可能です。

こういうJSONがあったとして

// sample.json
{
  "order" : {
      "items" : [
          {
            "id": 1,
            "name": "野球ボール",
            "price": 1296,
            "count": 3
          },
          {
            "id": 2,
            "name": "野球バット",
            "price": 1600,
            "count": 1
          }
      ]
  }
}
$ cat sample.json | jq .order.items
[
  {
    "id": 1,
    "name": "野球ボール",
    "price": 1296,
    "count": 3
  },
  {
    "id": 2,
    "name": "野球バット",
    "price": 1600,
    "count": 1
  }
]

// []で出力から配列の[]をはぐ
$ cat sample.json | jq .order.items[]
{
  "id": 1,
  "name": "野球ボール",
  "price": 1296,
  "count": 3
}
{
  "id": 2,
  "name": "野球バット",
  "price": 1600,
  "count": 1
}

// [].キー名で配列の中の要素でそのキーだけを列挙する
$ cat sample.json | jq .order.items[].name
"野球ボール"
"野球バット"

出力から "(ダブルクオート) をはぐ

列挙した要素を他のコマンドの入力にしたい場合など " が邪魔な場合には -r オプションではげます

cat sample.json | jq .order.items[].name -r
野球ボール
野球バット

パイプが利用できる

なんと、 jq の中だけでもパイプを使うことができます。これによって複雑なJSONの整形、要素の抽出や簡単な集計処理が行えます。

$ cat sample.json | jq .order.items[].price | awk '{m+=$1} END{print m;}'
2896

minifyする

-c オプションを利用することで、整形されているJSON文字列をminifyすることも可能です。

$ jq -c . < sample.json
{"order":{"items":[{"id":1,"name":"野球ボール","price":1296,"count":3},{"id":2,"name":"野球バット","price":1600,"count":1}]}}

めっちゃ便利! 現場からは以上です。

なんか awk もやらねばいけないような気がするけど、明日は簡易自作コマンドの話をする。

参考

*1:先ほどの . は全体を表示する、という意味