LoginSignup
4
3

More than 3 years have passed since last update.

【Rails】FlipperでFeature Flagを導入

Last updated at Posted at 2020-08-09

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

feature flag(機能フラグ)とは

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

少し細かく書くと、

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

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

gemの導入手順

  • まず、Gemfileに下記を設定します。
Gemfile
gem 'flipper'                 # flipper本体
gem 'flipper-active_record'   # フラグを永続化するため

※フラグの永続化については「アダプタについて」のところで書きます

  • インストールします。
$ bundle install
$ rails g flipper:active_record
$ rails db:migrate
  • 次に、config/initializers ディレクトリにflipper.rbを追加
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が有効な場合だけ、特定のコードを実行する

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

例2

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

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でも何処に書いてもいいです。

全てのユーザで有効化する

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

ユーザ単位で有効化する

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

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 に以下のような定義を書きました。(他に良い場所が思いつかなかったのですが、もし別案あったら追記します)

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

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

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でフラグの有効化まで行いたい場合は次のように書きます。

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などを入れなくても、下記のような設定が可能です。

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などからフラグを参照した場合は、無効のままになってしまいます。

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

どんなアダプタがあるか

こちらのページにある通り、以下のような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

公式ドキュメントには、あるアダプタを使っていて途中から、別のアダプタに乗り換えたい時の手順についても書かれています。

さいごに

flipperをかんたんに紹介しました。

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

また、フラグの確認や設定のためのwebインターフェースや、apiも用意されています。

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

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3