【Rails】FlipperでFeature Flagを導入


railsで作ったapiの利用可否を切り替えられるように、feature flag(機能フラグ)を導入したいということで、[flipper](https://github.com/jnunemaker/flipper)というgemを使いました。
環境は、rails6.0.3 apiモードです。

# feature flag(機能フラグ)とは
アプリケーションの各種機能の有効無効を、**コードはそのままに、再デプロイもせずに**、実行中のプログラムの外部から切り替えるための仕組みです。

少し細かく書くと、

* ある機能の使用可否を判定するフラグを用意でき、
* フラグのON/OFFは、ユーザ全体だけでなく、特定のユーザや、ユーザのグループ単位でも行える
* フラグのON/OFFは、コードの変更なしに、**railsコンソール上や、外部からweb UIやapiで行える**

といったことを実現するものです。

# gemの導入手順
* まず、Gemfileに下記を設定します。

```ruby:Gemfile
gem 'flipper'                 # flipper本体
gem 'flipper-active_record'   # フラグを永続化するため
```
※フラグの永続化については「[アダプタについて](#アダプタについて)」のところで書きます

* インストールします。

```bash
$ bundle install
$ rails g flipper:active_record
$ rails db:migrate
```

* 次に、`config/initializers` ディレクトリに`flipper.rb`を追加

```ruby:config/initializers/flipper.rb
require 'flipper'
require 'flipper/adapters/active_record'

Flipper.configure do |config|
  config.default do
    adapter = Flipper::Adapters::ActiveRecord.new # アダプタをActiveRecordアダプタとする

    Flipper.new(adapter)
  end
end
```

# feature flagの使い方
たとえば、フラグ `:hoge` があって、このtrue/falseに応じて挙動を切り替えたい箇所がある場合、そこに`Flipper.enabled?(:hoge)`を埋め込みます。
（ユーザー単位で有効/無効が設定されているフラグの場合は、`Flipper.enabled?(:hoge, user)`)

### 例1
ログイン中ユーザに対して、フラグ `:hoge`が有効な場合だけ、特定のコードを実行する

```ruby
if Flipper.enabled?(:hoge, current_user)
  # 実行したいコード
end
```

### 例2
ログイン中ユーザに対して、フラグ`:hoge`が有効な場合だけ、特定のactionを実行可能とする

```ruby
class FugaController < ApplicationController
  before_action -> {
    require_feature(:hoge, current_user)
  }, only: :show

  def show
    # 省略
    head :ok
  end
end

class ApplicationController < ActionController::API
  
  private

  def require_feature(flag_name, user)
    return if Flipper.enabled?(flag_name, user)

    raise Forbidden, '実行権限がありません'
  end
end
```


# フラグの有効化
フラグ`:hoge`を有効化するには下記のようにします。
※rails consoleで直接叩いても、rake taskでも、controllerでも何処に書いてもいいです。

### 全てのユーザで有効化する
```ruby:console
user = User.find(...)

# 有効化前は無効
Flipper.enabled?(:hoge)
=> false
Flipper.enabled?(:hoge, user)
=> false

# 有効化
Flipper.enable(:hoge)

# (ユーザに関係なく)有効になる
Flipper.enabled?(:hoge)
=> true
Flipper.enabled?(:hoge, user)
=> true
```

### ユーザ単位で有効化する
特定のユーザのみでフラグを有効化したい場合

```ruby:console
user = User.find(...)
other_user = User.find(...)

# 有効化前は無効
Flipper.enabled?(:hoge)
=> false
Flipper.enabled?(:hoge, user)
=> false

# userに対して有効化
Flipper.enable_actor(:hoge, user)

# 有効化したuserのみ有効になっている
Flipper.enabled?(:hoge)
=> false
Flipper.enabled?(:hoge, user)
=> true
Flipper.enabled?(:hoge, other_user)
=> false
```

### group単位で有効化する
特定の条件に該当するユーザたち（group）のフラグを一括で有効かしたい場合

まず、groupを予め定義しておく必要があります。`config/initializers/flipper.rb` に以下のような定義を書きました。（他に良い場所が思いつかなかったのですが、もし別案あったら追記します）

```ruby:config/initializers/flipper.rb
# groupを定義します
Flipper.register(:admins) do |actor, context|
  actor.respond_to?(:admin?) && actor.admin?
end
```


次に、groupに対してフラグを有効化

```ruby:console
user  = User.find(...)
admin = User.find(...)

# 有効化前は無効
Flipper.enabled?(:hoge, user)
=> false
Flipper.enabled?(:hoge, admin)
=> false

# groupに対してフラグを有効化
Flipper.enable_group(:hoge, :admins)

# groupに属するuserのみ有効になる
Flipper.enabled?(:hoge, user)
=> false
Flipper.enabled?(:hoge, admin)
=> true
```


### console上ではなく、initializersで、フラグを有効化してしまいたい場合の書き方

`config/initializers/flipper.rb`でフラグの有効化まで行いたい場合は次のように書きます。

```ruby:config/initializers/flipper.rb
require 'flipper'
require 'flipper/adapters/active_record'

Flipper.configure do |config|
  config.default do
    adapter = Flipper::Adapters::ActiveRecord.new # アダプタをActiveRecordアダプタとする

    Flipper.new(adapter)
  end
end

# groupを定義します
Flipper.register(:admins) do |actor, context|
  actor.respond_to?(:admin?) && actor.admin?
end

# フラグの有効化
if ActiveRecord::Base.connection.data_source_exists? 'flipper_features'
  Flipper.enable_group(:hoge, :admins)
end
```

最後3行がフラグの有効化をしているところです。
ifブロックはハックなのですが、initializerでフラグを有効化しようとすると、アダプタ用のテーブルがセットアップされていない場合、「そんなテーブルはない」と言われてしまうため、このようなifブロックで囲んでおく必要があります。


# アダプタについて
initializerでは、flipperで設定したフラグの値を保持するために使うアダプタを設定します。
アダプタには、`Redis`や、`Active Record` などが指定できます。

flipper本体には、メモリアダプタというアダプタが内蔵されているので、
gem `flipper-active_record`などを入れなくても、下記のような設定が可能です。

```ruby:config/initializers/flipper.rb
require 'flipper'

Flipper.configure do |config|
  config.default do
    adapter = Flipper::Adapters::Memory.new # アダプタをメモリアダプタとする

    Flipper.new(adapter)
  end
end
```

ただし、これだと**フラグが永続化されません**

つまり、consoleなどでフラグを有効化をしても、有効なのはコンソール上だけで、apiなどからフラグを参照した場合は、無効のままになってしまいます。

公式でも、アダプタには「**アクティブレコードアダプタのような永続的な物を強く推奨します**」と書かれていました。

### どんなアダプタがあるか
[こちらのページ](https://github.com/jnunemaker/flipper/blob/master/docs/Adapters.md)にある通り、以下のようなadapterがサポートされているようです

* ActiveRecord adapter
* ActiveSupportCacheStore adapter
* Cassanity adapter
* Http adapter
* memory adapter
* Moneta adapter
* Mongo adapter
* PStore adapter
* read-only adapter
* Redis adapter
* Sequel adapter
* Community Sup

公式ドキュメントには、[あるアダプタを使っていて途中から、別のアダプタに乗り換えたい時の手順](https://github.com/jnunemaker/flipper/blob/master/docs/Adapters.md#swapping-adapters)についても書かれています。

# さいごに
flipperをかんたんに紹介しました。

他に、ユーザのうち指定のパーセントの任意のユーザのフラグを有効化するという機能（A/Bテストやカナリアリリースを想定したような機能）もあるようです。

また、フラグの確認や設定のための[webインターフェース](https://github.com/jnunemaker/flipper/blob/master/docs/ui/README.md)や、[api](https://github.com/jnunemaker/flipper/blob/master/docs/api/README.md)も用意されています。

まだ、そういった機能は使っていませんが、必要に応じて、他の機能も調べたり試したりしてみようと思います。