- Product Manager
- Web Engineer
- Customer Support
- Other occupations (62)
-
Development
- Web Engineer
- バックエンドエンジニア
- バックエンドエンジニア/MFK
- フロントエンドエンジニア
- テックリード候補
- アーキテクト(バックエンド)
- フロントエンドマネージャー
- Webフロントエンド
- サーバーサイドエンジニア
- エンジニア@京都
- エンジニア@大阪
- Rails@京都
- Androidエンジニア
- アナリティクスエンジニア
- Data Scientist
- マーケティングエンジニア
- Webアナリティクスエンジニア
- QAエンジニア
- UIデザイナー
- プロダクトデザイナー
- Webデザイナー
- コミュニケーションデザイナー
- デザイナーオープンポジション
- Graphic Designer
- デザインプログラムマネージャー
-
Business
- Product Manager
- プロダクトマネージャー
- 広報
- 経理
- カルチャー推進・浸透
- 知財戦略立案・推進・発明発掘
- リスクマネジメント統括本部
- 内部監査
- AML/CFTコンプライアンス
- AML・金融犯罪対策Ops
- 金融コンプライアンス
- システム監査
- ビジネス採用担当
- 経営企画(予実・IR)
- HRBP
- Legal
- 債権管理/MFK
- インサイドセールス
- ToB Sales
- フィールドセールス
- インサイドセールス SDR
- インサイドセールス企画
- オンラインセールス
- SaaS営業、MFBC
- インサイドセールス MFBC
- セールス MFBC
- マーケティングリサーチャー
- マーケター
- データマーケター
- BtoBマーケティングリーダー
- CRMスペシャリスト
- イベントマーケター
- Other
Railsエンジニアの越川です。
弊社では、テストを書く際にdatabase_rewinderを使うことが多いのですが、先日困ったことがありましたのでエンジニアブログで共有します。
database_nameを明示したモデルで困った
今回、複数のデータベースのテーブルを結合する際に明示的にデータベース名を指定したいケースがありました。
例えば、railsでいうとhas_many through: 'hogehoge'というシーンで、データベース名を明示したいケースがありました。
対象としたいモデルには以下の様な記述をします。
class Post < ActiveRecord::Base
self.table_name = 'my_database.posts'
end
このようなモデルのデータがdatabase_rewinderで削除されないという事象がありました。
database_rewinderの仕組み
ところで、database_rewinderの仕組みはどうなっているかというと、
テストが実行中に、実行されるINSERT文を分析して、対象となるtableを記憶するテストが終わった際に、対象となったtablesにDELETE文を発行する
こうすることで、データの削除対象を最小化し高速化を実現しています。
原因はなにか探る
さて、お話を戻すと、先のPostモデルを操作した際、railsがpostsテーブルに対して発行するSQLを調査すると以下のようになっていました。
INSERT INTO `my_database`.`posts` (`created_at`, `updated_at`)
VALUES ('2015-01-26 05:57:42', '2015-01-26 05:57:42')
そもそも、database_rewinderがこのSQLをどう解釈し、削除テーブルを記憶しているのか調べました。
すると、それっぽい正規表現を発見。
lib/database_rewinder.rb#L50
match = sql.match(/\AINSERT INTO [`"]?([^\s`"]+)[`"]?/i)
table = match[1] if match
if table
cleaner.inserted_tables << table unless cleaner.inserted_tables.include? table
cleaner.pool ||= connection.pool
end
なるほど、この正規表現だと、
INSERT INTO `my_database`.`posts` (`created_at`, `updated_at`)
VALUES ('2015-01-26 05:57:42', '2015-01-26 05:57:42')
というSQLは、my_databaseにマッチしてしまいますね。原因がわかりました。
pull requestを出そう
というわけで、さっそくpull requestを出しました。
database_rewinderの作者は日本の松田明さんですが、OSSであれば英語でpull requestです。
そんなこともあり、勇気を出して英語で出しました。
社内でこの英語で良いかな?みたいな話を相談したら「you! その想いが大事、そのまま出しちゃいなよ」と言った意見を頂いたために割とそのまま出しました。
Support table_name with database name by ppworks · Pull Request #23 · amatsuda/database_rewinder
具体的にはこんな差分です
- match = sql.match(/\AINSERT INTO [`"]?([^\s`"]+)[`"]?/i)
- table = match[1] if match
+ match = sql.match(/\AINSERT INTO [`"]?([^\s`"]+)[`"]?(?:\.[`"]?([^\s`"]+)[`"]?)?/i)
+ return unless match
+
+ table = match[2] || match[1]
そして、すぐに取り込んで頂きました:)
さらに pull requestを出そう
最初のpull requestの中でこのようなやりとりがありました。
amatsuda commented 4 days ago
Wait, I merged this because it’s not a bad change, but asked this question because I’m still not sure if this regexp is perfect.
One actual example that I’m aware now is MS SQLServer. SQLServer accepts more than one dots in an object notation https://msdn.microsoft.com/en-us/library/ms177563.aspx
Please give me some more time to think.
なるほど、server.scheme.database.table みたいな記述もありうるのですね。
というわけで別のpull requestを出しました。
Support more than one dots in an object notation
- match = sql.match(/\AINSERT INTO [`"]?([^\s`"]+)[`"]?(?:\.[`"]?([^\s`"]+)[`"]?)?/i)
+ match = sql.match(/\AINSERT INTO (?:\.*[`"]?([^.\s`"]+)[`"]?)*/i)
return unless match
- table = match[2] || match[1]
+ table = match[1]
その後、このpull reqが取り込まれ、v0.4.2がリリースされ、弊社のプロジェクト内でもrubygemsから最新バージョンのdatabase_rewinderを取り込み、今回の問題を解決出来ました。
最後に
マネーフォワードでは、仕事の中でも自然とOSSに貢献することを推奨しています。
仕事中でもOSSに貢献したいエンジニアを募集しています!みなさまのご応募お待ちしております!