💡

[Rails] serializerと個人情報

2021/01/15に公開

業務でserializerを使用しているのですが、含めたい情報/含めたくない情報を
しっかり管理していかないといけないな、と感じることがあったので書いておきます。

前提:ActiveModelSerialierとは

Rails APIにリクエストを送ると、そのアクションで取得したレコードを
render jsonを使用してレスポンスに含めることができます。

class Api::V1::UsersController < Api::V1::ApplicationController
  def show
    user = User.find(params[:id])
    
    render json: user
  end
end

上記の場合、userの情報をレスポンスに含めることができます。
ただ、これだとuserが保持しているすべてのカラムの情報を返してしまいます。

たとえば、Usersテーブルに下記カラムが存在する場合、

class User
  # id, email, first_name, last_name
end

render json: userだとid, email, first_name, last_nameすべての情報を返してしまいます。

ですがserializerを使うと、Usersテーブルに存在するカラムのうち任意のカラムだけレスポンスに含めることができます。

# first_name, last_nameのみレスポンスに含まれる
class UserSerializer < ActiveModelSerializer
  attributes :first_name, :last_name
end

UserSerializerに基づいたuserオブジェクトをレスポンスに含めたい場合、
コントローラでは下記のように記述します。

class Api::V1::UsersController < Api::V1::ApplicationController
  def show
    user = User.find(params[:id])
    
    # { user: { first_name: 'xxx', last_name: 'yyy' } }
    render json: user, serializer: UserSerializer
  end
end

条件によって含めるカラムを変える

また、条件によってレスポンスに含めるカラムを変えることができます。
attributesに記載したカラム(シンボル)は、どのような条件でも 必ずレスポンスに含まれます が、
attribute を使えばif - lambdaで条件を指定することができます。

class Api::V1::UsersController < Api::V1::ApplicationController
  def show
    user = User.find(params[:id])
    
    render json: user, serializer: UserSerializer, page: :profile
  end
end
class UserSerializer < ActiveModelSerializer
  attributes :first_name, :last_name

  # page: :profileを指定した場合のみgenderをレスポンスに含める
  attribute :gender, if: -> { instance_options[:page] == :profile }
end

instance_optionsはコントローラから与えられたパラメータを取得するときに使います。
上記コントローラでは page: :profileという引数を渡しているので、
これをinsntace_options[:page]という形で取得します。

instance_options[:page] == :profile、つまりpage: :profileという
関係のときにのみgenderの値をレスポンスに含めることができます。
引数がなかったり、page: :adminのような場合はfirst_name, last_nameの値だけが含まれます。


含めたい情報/含めたくない情報

ここからが本題ですが、 上記のようなカラムをもつUserが存在する場合、
このような書き方をする方もいらっしゃるかと思います。

class UserSerializer < ActiveModelSerializer
  attributes :id, :email, :first_name, :last_name
end

とりあえず後々参照しそうになるカラムは全部レスポンスに含めておこう、
という考え方です。

危ない感じがしませんか?

ですがこれ、よくよく考えると危なくないでしょうか?

てのもemailは個人情報であり、render json: userが呼び出されるたび必ずレスポンスに含まれることになります。
となると、userの名前(first_name, last_name)しか使用しない状況においても、
emailが必ず含まれることになります。

これは明らかに余計な情報ですし、個人情報ですから不用意に含めたくないですよね。

このserializerを使う局面がAdmin側のページにしか値を返さないAPIだけである、
ということなら問題はないと思います。
AdminページはAdmin側(運営側)の人間しかアクセスできないはずですから。

ただ 不特定多数の方がアクセス可能なAPIでも使用するのであれば、
個人情報を含める/含めないは選択可能にしたほうがよい
、と個人的に思っています。

firestoreでプライベートなデータを保存する場合にコレクションをわけるのと似ていると思います。

修正してみる

ということで、個人情報を含めないように上記serializerを修正してみたいと思います。
具体的にはattributesに個人情報を書かない対応をします。

class Api::V1::UsersController < Api::V1::ApplicationController
  def show
    user = User.find(params[:id])
    
    # emailが必要な場合のみemail: trueとする
    render json: user, serializer: UserSerializer, email: true
  end
end
class UserSerializer < ActiveModelSerializer
  attributes :id, :first_name, :last_name
  
  attribute :email, if: -> { instance_options[:email] } 
end

このようにすれば instance_options[:email]がtruthyな値の場合に、
id, email, first_name, last_nameが、そうでない場合に
id, first_name, last_nameの3つだけが含まれるようになります。

コントローラ側でemail: trueを付与する/しないによって、
emailを含む/含まないを切り替えられるようになりました。

慣れてる方からしたら当たり前かもしれませんが、
こういう点には気をつけて設計していきたいですね。

Discussion