[Rails] serializerと個人情報
業務で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