運用での定期的なプログラム改修を自動化する

Happy Elements株式会社 カカリアスタジオ Advent Calendar 2016の22日目の記事です。
担当は [@ryooo](https://github.com/ryooo) です。よろしくお願いします。

# はじめに
ゲームサービスを運営していると、定期的なイベント・フェスなどの運用タスクがあります。
すべてのパラメーターがマスター化されていればデータ投入だけで実施できるのですが、様々な事情からそうなっておらず、ソースファイル上で必要な対応もいくつかあります。

そういった属人的になりがちな実装業務を自動化し、**エンジニアは新機能開発などのより価値を生み出す作業に少しでもコミットできれば**というツールの紹介です。

※ ソースの完全な公開まではできておらず、本稿でも色々と割愛していますがご容赦ください。

# どういうことができるか
## input(設定ファイルを用意して)
ソースファイル修正用の変数情報を記載した設定ファイル（yml）
![1.png](https://qiita-image-store.s3.amazonaws.com/0/7847/0869d578-4d33-bed5-e4f7-cd2bcf190c0a.png)


## process（rakeタスクに流すと）
![2.png](https://qiita-image-store.s3.amazonaws.com/0/7847/44564d56-8f0f-ba15-fa7b-7c04a42ad003.png)


## output（プログラムの修正を行います）
修正済みプログラム
![3.png](https://qiita-image-store.s3.amazonaws.com/0/7847/921d4f65-245f-8081-5cf7-59d9c32b2157.png)


※ 実際には設定ファイルのpushをトリガーにCIで動かすのがよいと思います。

# 前提
自分のチームでは、基本的に以下のワークフローでイベントの開発を行っています。
① イベントに対するブランチを作成
② イベントに必要なプログラムの修正を行う
③ プランナーさんの設定したパラメータや、デザイナーさんが作成した画像をコミット
その後レビュー・テスト・リリースとなります。

今回紹介しているのは、上記の②を行うためのツールになります。

# 実装紹介
## 概要
**設定ファイル(yml)とテンプレートファイル(erb)から、改修後のソースコードを生成**
**改修後のソースコードを以下の3つのいずれかで出力**
1. ファイルに出力
2. ソースファイル内の所定の位置のソースコードを置換
3. ソースファイル内の所定の位置に追記

# ファイルに出力
### テンプレートファイル
![02.png](https://qiita-image-store.s3.amazonaws.com/0/7847/dff4b5aa-bfd5-8cb9-9c42-f99c61c4f107.png)


### 実装例
```rb
yml = YAML.load("冒頭で紹介したymlの設定ファイル")
yml[:configs].each do |config|
  erbs = YAML.load("前述したテンプレートファイル")
  # ymlの設定ファイル内容をhashで読み込み、OpenStructのインスタンスを通して、bindingとしてerbに流し込む
  result_code = ERB.new(erbs['migrate']).result(OpenStruct.new(config).instance_eval {binding})
  # result_codeをファイルに出力(割愛)
end
```

### 出力されるソースコード
```rb
class EventHoge20161231 < ActiveRecord::Migration
def change

stage_count = 2
monster_ids = [1, 2, 3]
name = 'イベント○○'

stage_count.times do |i|
  Stage.create({
    :name => "#{name} ステージ#{i}",
  })
end

monster_ids.each do |monster_id|
  Monster.create({
    :id => monster_id,
  })
end

end
end
```
※ seeds.rbでもmigrateでも良いのですがmigrateだとこんな感じになります。

# ソースファイル内の所定の位置のソースコードを置換
### テンプレートファイル
![5.png](https://qiita-image-store.s3.amazonaws.com/0/7847/3ec245b5-0eb6-e12f-f64a-d82c0dcd5109.png)

### ソースファイル上の指定
あらかじめ、ソースには置換範囲をコメントで記載しておきます。

```rb
# dev.event_hoge.consts start
EVENT_RELEASED_AT = Time.parse("2016/11/30 15:00:00")
# dev.event_hoge.consts end
```

### 実装例
```rb
new_code = ERB.new(erbs['program_gsubs'][k]).result(OpenStruct.new(config).instance_eval {binding})

new_code = new_code.gsub(%Q{'}, %Q{"}).gsub(%Q{\/}, %Q{\\/})
from = "# dev\.#{type}\.#{k} start"
to   = "# dev\.#{type}\.#{k} end"

script = <<-EOS
  find #{Rails.root}/app -type f -name '*.rb' -print0 |
  xargs -0 perl -0pi -e 's/#{from}(.*?)#{to}/#{from}\n#{new_code}\n#{to}/s'
EOS
`#{script}`
```

### 出力されるソースコード
コメントのstart〜endの範囲内を、テンプレートファイルの実装に沿って置換します。

```diff
 # dev.event_hoge.consts start
-EVENT_RELEASED_AT = Time.parse("2016/11/30 15:00:00")
+EVENT_RELEASED_AT = Time.parse("2016/12/31 15:00:00")
 # dev.event_hoge.consts end
```

# ソースファイル内の所定の位置に追記
### テンプレートファイル
![6.png](https://qiita-image-store.s3.amazonaws.com/0/7847/be7d0626-e00e-2bdd-642b-f83f6d30283e.png)

### ソースファイル上の指定
```rb
monster_ids = []
# dev.event_hoge.monster_ids append
```

### 実装例
```rb
new_code = ERB.new(erbs['program_appends'][k]).result(OpenStruct.new(config).instance_eval {binding})

new_code = new_code.gsub(%Q{'}, %Q{"}).gsub(%Q{\/}, %Q{\\/})
append = "# dev\.#{type}\.#{k} append"

script = <<-EOS
  find #{Rails.root}/app -type f -name '*.rb' -print0 |
  xargs -0 perl -0pi -e 's/#{append}/#{new_code}\n#{append}/s'
EOS
`#{script}`
```

### 出力されるソースコード
コメントのappendの前行に、テンプレートファイルの実装に沿ったプログラムを追記します。

```diff
 monster_ids = []
+monster_ids << [1, 2, 3]
 # dev.event_hoge.monster_ids append
```

# 最後に
**Happy Elements株式会社 カカリアスタジオでは、一緒にものづくりをする仲間を募集しています！**

ものづくりが大好きな方は是非お気軽にご応募下さい。

[カカリアスタジオ 採用情報](http://cacalia.jp/recruit/)
> ひとりひとりのチカラを最大限に引き出せるこのうえない環境と体制、文化があればこそ、最高のものづくりをつながると私たちは信じています。カカリアスタジオのメンバーとして、私たちと一緒に働きたいと思う仲間を募集しています。