こんにちは!kossyです!
さて、今回はOSSソースコードリーディング回ということで、devise_token_authのregistrations_controllerのcreateアクションのソースコードを読んでみたので、ブログに残してみたいと思います。
コードリーディング
registrations_controllerのcreateアクションは、ユーザー登録を担うアクションになっています。
devise_token_auth/registrations_controller.rb at master · lynndylanhurley/devise_token_auth · GitHub
def create build_resource unless @resource.present? raise DeviseTokenAuth::Errors::NoResourceDefinedError, "#{self.class.name} #build_resource does not define @resource,"\ ' execution stopped.' end # give redirect value from params priority @redirect_url = params.fetch( :confirm_success_url, DeviseTokenAuth.default_confirm_success_url ) # success redirect url is required if confirmable_enabled? && !@redirect_url return render_create_error_missing_confirm_success_url end # if whitelist is set, validate redirect_url against whitelist return render_create_error_redirect_url_not_allowed if blacklisted_redirect_url?(@redirect_url) # override email confirmation, must be sent manually from ctrl callback_name = defined?(ActiveRecord) && resource_class < ActiveRecord::Base ? :commit : :create resource_class.set_callback(callback_name, :after, :send_on_create_confirmation_instructions) resource_class.skip_callback(callback_name, :after, :send_on_create_confirmation_instructions) if @resource.respond_to? :skip_confirmation_notification! # Fix duplicate e-mails by disabling Devise confirmation e-mail @resource.skip_confirmation_notification! end if @resource.save yield @resource if block_given? unless @resource.confirmed? # user will require email authentication @resource.send_confirmation_instructions({ client_config: params[:config_name], redirect_url: @redirect_url }) end if active_for_authentication? # email auth has been bypassed, authenticate user @token = @resource.create_token @resource.save! update_auth_header end render_create_success else clean_up_passwords @resource render_create_error end end
before_actionが呼ばれるのでまずはそちらから読んでみます。
# https://github.com/lynndylanhurley/devise_token_auth/blob/4c5245b88b39c1bb305e0cbdbfc2513eebdeda93/app/controllers/devise_token_auth/registrations_controller.rb#L189 def validate_sign_up_params validate_post_data sign_up_params, I18n.t('errors.messages.validate_sign_up_params') end # https://github.com/lynndylanhurley/devise_token_auth/blob/4c5245b88b39c1bb305e0cbdbfc2513eebdeda93/app/controllers/devise_token_auth/registrations_controller.rb#L189 def validate_post_data which, message render_error(:unprocessable_entity, message, status: 'error') if which.empty? end # https://github.com/lynndylanhurley/devise_token_auth/blob/4c5245b88b39c1bb305e0cbdbfc2513eebdeda93/app/controllers/devise_token_auth/registrations_controller.rb#L91 def sign_up_params params.permit(*params_for_resource(:sign_up)) end
sign_up_paramsが空の時かどうかをチェックして、空の場合はエラーを返すバリデーションでした。
こういった、実際にcontroller内で値の処理を行う前にパラメータに対して何かしらのバリデーションを実行するのは定石ですね。
次にbuild_resourceメソッドを読んでみます。
devise_token_auth/registrations_controller.rb at master · lynndylanhurley/devise_token_auth · GitHub
def build_resource @resource = resource_class.new(sign_up_params) @resource.provider = provider # honor devise configuration for case_insensitive_keys if resource_class.case_insensitive_keys.include?(:email) @resource.email = sign_up_params[:email].try(:downcase) else @resource.email = sign_up_params[:email] end end
resource_classはdeviseをincludeしているモデルのことですね。例えばUserモデルが定義されていた場合は、
@resource = User.new(sign_up_params)
となります。次の処理の「provider」はdefaultでemailになっています。
resource_class.case_insensitive_keys.include?(:email)はdeviseの定義が存在していればそちらを優先して処理を行います。
deviseの定義はconfig/initializers/devise.rbの、
# Configure which authentication keys should be case-insensitive. # These keys will be downcased upon creating or modifying a user and when used # to authenticate or find a user. Default is :email. config.case_insensitive_keys = [:email]
の部分です。
case_insensitive_keysにemailが含まれている場合は、emailをdowncaseして、そうでない場合はパラメータで送信されたemailをそのままresourceのattrとして代入しています。
createアクションに戻ります。
unless @resource.present? raise DeviseTokenAuth::Errors::NoResourceDefinedError, "#{self.class.name} #build_resource does not define @resource,"\ ' execution stopped.' end
もしbuild_resourceを実行して、@resourceが存在しない場合は、例外を発生させています。
次の処理は、リダイレクト先のURLを定義しています。
その次のif文は、deviseをincludeしているモデルがconfirmableモジュールをincludeしていて、redirect_urlがnilの場合、「リダイレクト先のURLがない」というエラーを返却していました。
blacklisted_redirect_url?は内部の処理を見ましょう。
def blacklisted_redirect_url?(redirect_url) DeviseTokenAuth.redirect_whitelist && !DeviseTokenAuth::Url.whitelisted?(redirect_url) end
devise_token_authのconfigファイルでホワイトリストを設定していて、かつ引数のredirect_urlがホワイトリストで設定されたURLでない場合はtrueを返すメソッドでした。
次の行を見てみます。
callback_name = defined?(ActiveRecord) && resource_class < ActiveRecord::Base ? :commit : :create resource_class.set_callback(callback_name, :after, :send_on_create_confirmation_instructions) resource_class.skip_callback(callback_name, :after, :send_on_create_confirmation_instructions)
ActiveRecordが定義されていて、かつresource_classがActiveRecordを継承していた場合、:commitが、そうでなければ :create がcallback_nameとして定義されていました。
その次の行はresource_classに対してcallback_nameのcallbackとskip_callbackをセットしていました。
次の行は、実質的にresource_classがconfirmableモジュールをincludeしているかどうかを判定しています。
devise/confirmable.rb at master · heartcombo/devise · GitHub
疲れてきたので最後はソースコードに適宜コメントを入れて書いていきます、、、
# saveに成功した場合 if @resource.save # ブロックが渡されていた場合は渡されたブロック内で@resourceを使って処理 yield @resource if block_given? # 確認済みでない場合(confired_atに値がない場合) unless @resource.confirmed? # user will require email authentication @resource.send_confirmation_instructions({ client_config: params[:config_name], redirect_url: @redirect_url }) end # resource_classがdatabase_authenticatableをincludeしている場合 if active_for_authentication? # email auth has been bypassed, authenticate user @token = @resource.create_token @resource.save! update_auth_header end render_create_success else clean_up_passwords @resource render_create_error end
これでひと通り目を通せました。
おわりに
confirmableモジュールを使っている場合の条件分岐がそこそこあったので、ユーザー登録後の確認メール機能を実装する場合はこのブログが役に立ってくれること願っています。