Hashのエントリの順序を操作するhash_order_helperを作った

Hashのエントリの順序を破壊的に更新したいということが希によくあるので、エントリの順序をArrayのように操作できるメソッドを追加するhash_order_helperを作った。

github.com

以下のような感じで、Hashのエントリの順序を操作できる。

require 'hash_order_helper'

hash = {b: 200, a: 100, c: 150}

hash.sort_pair
#=> {:a=>100, :b=>200, :c=>150}

hash.unshift(d: 300)
#=> {:d=>300, :b=>200, :a=>100, :c=>150}

追加されるメソッドは以下の通り。

  • sort_pair -> Hash
  • sort_pair! -> Hash
  • sort_pair_by {|key, value| ... } -> Hash
  • sort_pair_by! {|key, value| ... } -> Hash
  • at(nth) -> Array
  • insert(nth, hash) -> Hash
  • last -> Array
  • last(n) -> Array
  • pop -> Array
  • pop(n) -> Array
  • push(key, hash) -> Hash
  • unshift(key, hash) -> Hash

シンプルなERBのヘルパー ERBh を作った

シンプルなERBのヘルパー ERBh を作った…というか、先日作ったrspec-match_fuzzyから分離・独立させた。

github.com

以下のように、1メソッドで変数の定義とEBBの評価が行える。

require 'erbh'
include ERBh

erbh('<%= @foo %>, <%= @bar %>', foo: 100, bar: 'zoo')
#=> "100, zoo"

trim modeも使える。

erbh(<<-EOS, {foo: 1..3}, trim_mode: '-')
<%- @foo.each do |i| -%>
<%= i %>
<%- end -%>
EOS
#=> "1\n2\n3\n"

モダンな書式のHash#inspectを作った

なんだかんだで必要になるわりにそれっぽいgemが見つからないので、モダンな書式のHash#inspect — hash_modern_inspectを作った。

github.com

使い方は以下の通り。

hash =  {
  foo: ['FOO', {baz: 100}],
  bar: {zoo: 200},
  "hoge"=>"piyo"
}

hash.modern_inspect
#=> '{foo: ["FOO", {baz: 100}], bar: {zoo: 200}, "hoge"=>"piyo"}'

modern_inspect_without_braceを使うと前後のブレースを削除した文字列を出力する。

hash.modern_inspect_without_brace
#=> 'foo: ["FOO", {baz: 100}], bar: {zoo: 200}, "hoge"=>"piyo"'

空白空行を無視するrspec-match_fuzzyを作った

なんだかんだで必要になるので、空白空行を無視するrspecのmatcher、rspec-match_fuzzyを作った。

github.com

以下の例は空白空行を無視してマッチする。

it '...' do
  str1 = <<-EOS
    London Bridge
    Is Broken down,
    Dance over my Lady Lee.
    London Bridge
    Is Broken down
    With a gay Lady.
  EOS

  str2 = <<-EOS
    London  Bridge
    Is  Broken  down,

    Dance over my Lady Lee.


    London\tBridge
    Is\tBroken\tdown
    With a gay Lady.
  EOS

  expect(str1).to match_fuzzy str2
end

差異がある場合はdiffが出る。

f:id:winebarrel:20160409220635p:plain

また、ERBのヘルパーメソッドも追加した。

include RSpec::MatchFuzzy

it '...' do
  result = erb("<%= @foo %>, <%= @bar %>", foo: 100, bar: 'zoo')
  expect(result).to eq '100, zoo'
end

Ridgepole v0.6.4

業務でも使ってみて問題なさそうな感じなので v0.6.4 をリリースしました。

github.com

主な変更点は以下の通りです。

  • Add --use-external-script option
  • Add --mysql-use-alter option
  • Add --alter-extra option
  • Add --dump-with-default-fk-name option
  • Support t.index
  • Remove migration_comments support
  • Fix fk apply order

--use-external-script / --mysql-use-alter

以前の記事にも書きましたが、--use-external-scriptは外部スクリプトで変更を実施するためのオプションです。

https://github.com/winebarrel/ridgepole#execute-sql-using-external-script

$ cat test.sh
#!/bin/sh
SQL="$1"
CONFIG_JSON="$2"
echo "$SQL" | mysql -u root my_db

$ ridgepole -c config.yml --apply --external-script ./test.sh

変更を適用するときに、SQLを外部のスクリプトに渡すことができるようになります。 --mysql-use-alterはそれに関連して、CREATE/DROP INDEXの代わりにALTER TABLEを使うようになるオプションです。 この方が外部スクリプトでのパースが楽なので…

業務では、CIで「実行時間長すぎ」と言われたクエリについては、自動的に「pt-oscでクエリを実行する」スクリプトを出力して、それを手動実行みたいなことをやっています。がんばればpt-oscで勝手に適用、とかまでできると思いますが、そこまでやったことはないです。

--alter-extra

--alter-extraMySQLのALRER文にLOCK=NONEとかつけるためのオプションです。

https://github.com/winebarrel/ridgepole#add-extra-statement-to-alter

$ ridgepole -a -c database.yml --alter-extra="LOCK=NONE" --debug
Apply `Schemafile`
...
-- add_column("dept_manager", "to_date2", :date, {:null=>false, :after=>"from_date"})
   (42.2ms)  ALTER TABLE `dept_manager` ADD `to_date2` date NOT NULL AFTER `from_date`,LOCK=NONE
   -> 0.0428s
-- remove_column("dept_manager", "to_date")
   (46.9ms)  ALTER TABLE `dept_manager` DROP `to_date`,LOCK=NONE
   -> 0.0471s

--dump-with-default-fk-name / fk apply order

外部キーに関して二点ほど。

Railsでadd_foreign_keyを実行したとき、名前をつけないと自動的にfk_rails_e74ce85cbcみたいな名前がつけられるんですよね。 ただ、この名前だと、スキーマをダンプしたときにname: が出力されない…

ridgepole的には外部キーに名前ついていないと困るので、--dump-with-default-fk-nameオプションで強制的にダンプできるようにしました。

あと、外部キーに関してはテーブルの作成順を考慮しないとエラーになる問題があったのですが、どういう定義順であっても、まずテーブルの作成を行ってから、外部キーを張るように修正しました。

Support t.index / Remove alias_method_chain

プルリクをいただきまして、

t.indexがサポートされて、alias_method_chainが削除されました。 Rails5対応への足がかりができて、ありがたいことです。

また、それに付随して、migration_commentsのサポートを切りました。 migration_commentsについては対応状況が微妙だったのと、prependとalias_method_chainが同居する状況は避けたかったので、外した感じです。 今後も追加はないかなぁ…

次バージョンついて

とりあえず、以下を進めていきたいなぁ…と考えています。

  • Rails5サポート
  • MySQL 5.7サポート
    • 特にJSONカラムと生成カラム
  • 可能ならViewのサポート
    • MySQLでうまい手が見つからないのですが…

miam 0.2.3.beta: カスタムManagedPolicyサポート

某カンファレンスの裏側で、ひっそりとIAM管理ツールmiamv0.2.3.betaをリリースしました。

github.com

「対応しないと」と思いつつ対応できていなかったカスタムManagedPolicyをサポートしました。

managed_policy "my-policy", :path=>"/" do
  {"Version"=>"2012-10-17",
   "Statement"=>
    [{"Effect"=>"Allow", "Action"=>"directconnect:Describe*", "Resource"=>"*"}]}
end

user "bob", :path => "/developer/" do
  login_profile :password_reset_required=>true

  groups(
    "Admin"
  )

  policy "bob-policy" do
    # ...
  end

  attached_managed_policies(
    "arn:aws:iam::123456789012:policy/my-policy"
  )
end

AWS側バージョン管理との整合性について

「gitで管理してください」というスタンスです。

基本的にAWS側のバージョンはデフォルトを見て更新を行います。 更新する場合には、新しいpolicy versionを作ってそれをデフォルトにします。 また、バージョン数が5を超える場合は古くてデフォルトになっていないバージョンを削除してから新しいpolicy versionを作成します。

gitの管理とAWS側のバージョン管理は相性悪いので、miamで「バージョンを指定する」などの機能をサポートするつもりは、今のところないです。

Hexにライブラリを登録してみた

Elixirの勉強の一環としてHexにライブラリを登録してみた。

banner | Hex

github.com

Ubuntusysvbannerをポートした小さいライブラリ。

iex(2)> IO.puts Banner.banner("hello")

 #    #  ######  #       #        ####
 #    #  #       #       #       #    #
 ######  #####   #       #       #    #
 #    #  #       #       #       #    #
 #    #  #       #       #       #    #
 #    #  ######  ######  ######   ####
:ok

ここ2〜3ヶ月、Elixirを触ってみた所感。

  • 悪くない…と思う。Goよりは性に合っている
  • 英語ドキュメントが大変だけど、かなり充実していると思う
  • たまにRubyの便利メソッドがほしくなる…程度の標準ライブラリの充実具合
  • 破壊的変更をしないスタイルにはまだなれない部分がある。さしあたってStringIOの代替を捜索中
  • OTPの世界観にもなかなかなれない。理念はわかるけど、MySQLのドライバがGenServerだったりするのを見ると「うーん、これはいい設計なのだろうか…」と思ったりする
  • エラーの投げ方、受け取り方についてのスタンダードがまだ疑問。「無視してリスタート」がやり方らしいが、すべてにそれが通用するか不明。あと、例外はあんまり使わない、とチュートリアルに書いてある割に、よく見かけるような
  • mixやhex—Rubyでいうところのbundler・gemあたりが整っているのはすばらしい。やっぱセントラルリポジトリからライブラリ落としたり、登録したりするのは便利だし楽しい

ちまたで騒がれているような熱狂的な感動はまだないけど、素性は良さそうなのでしばらく続けてみる。