ログインをmodalで実装してるんだけど、validationにひっかかったときに
registrations#newに遷移してしまう。
→modal上で完結させたい。
devise触りたてでソース読んでないので、流れとか整理しておく。
def create
build_resource(sign_up_params) #01
resource.save
yield resource if block_given?
if resource.persisted?
if resource.active_for_authentication? #02
set_flash_message! :notice, :signed_up #03
sign_up(resource_name, resource) #04
respond_with resource, location: after_sign_up_path_for(resource) #05
else
set_flash_message! :notice, : "signed_up_but_#{resource.inactive_message}"
expire_data_after_sign_in! #[06]
respond_with resource, location: after_inactive_sign_up_path_for(resource) #07
end
else
clean_up_passwords resource #08
set_minimum_password_length #09
respond_with resource
end
end
#01 build_resource(sign_up_params)
# Build a devise resource passing in the session. Useful to move
# temporary session data to the newly created user.
def build_resource(hash = {})
self.resource = resource_class.new_with_session(hash, session)
end
●recource_classに関して
# Proxy to devise map class
def resource_class
devise_mapping.to
end
# Attempt to find the mapped route for devise based on request path
def devise_mapping
@devise_mapping ||= request.env["devise.mapping"]
end
# Gives the class the mapping points to.
def to
@klass.get
end
resource_class = Devise.mappingsの@klassってことね。
request.env:
https://qiita.com/kentosasa/items/a0500f2f21b33b29beb6
●new_with_sessionに関して
module ExtendMethods
def new_with_session(params, session)
super.tap do |user|
if data = session["devise.facebook_data"]
user.email = data["email"]
user.confirmed_at = Time.now
end
end
end
end
.tap(Object)
obj.tap {|myself| block }
tapメソッドは、ブロック変数にレシーバ自身を入れてブロックを実行します。
戻り値はレシーバ自身です。メソッドチェーンの中にtapメソッドをはさみ込み、
ソースコードを簡潔にする目的で使われます。
https://ref.xaio.jp/ruby/classes/object/tap
Facebook認証の場合、
resource_class(つまりは@klass)に対して.tapをするのかな?
Facebook認証じゃない場合は、そのまんま。
というわけで、
build_resource(sign_up_params)
→受け取ったパラメータの中にsession["devise.facebook_data"]があれば、
→user.emailをそのパラメータの中に入っている:emailで上書きする
→confirmed_atは現在の(上書きした)時間
facebook認証用のもの(?)
#02 active_for_authentication?
●active_for_authentication?に関して
def active_for_authentication?
true
end
えっ。
と思ったらこんな注意書きが
# == active_for_authentication?
#
# After authenticating a user and in each request, Devise checks if your model is active by
# calling model.active_for_authentication?. This method is overwritten by other devise modules. For instance,
# :confirmable overwrites .active_for_authentication? to only return true if your model was confirmed.
#
# You can overwrite this method yourself, but if you do, don't forget to call super:
#
# def active_for_authentication?
# super && special_condition_is_valid?
# end
#
# Whenever active_for_authentication? returns false, Devise asks the reason why your model is inactive using
# the inactive_message method. You can overwrite it as well:
#
# def inactive_message
# special_condition_is_valid? ? super : :special_condition_is_not_valid
# end
#
ユーザを認証した後、deviseは`model.active_for_authentication?`を実行してモデルが本当にアクティブかを調べる。
このメソッドは他のdevice modulesによってオーバライトされる。
例えば、:confirmableモデルでは、モデルが確認できたらtrueを返すようにオーバライトしている。
オーバライトしてもいいけど、superを呼ぶのを忘れるなよ。
このメソッドがfalseを返すと、deviseはなんでモデルがアクティブじゃねーんだよ、と
inactive_messageメソッドを通じて聞いてくる。これもオーバライトしてよい
オーバライト前提だからtrueしか返してないんですかね。。とりあえず次
#03 set_flash_message! :notice, :signed_up
def set_flash_message(key, kind, options = {})
message = find_message(kind, options) #refer below
if options[:now]
flash.now[key] = message if message.present?
else
flash[key] = message if message.present?
end
end
keyが:notice、:kindが:signed_upに対応してますね。
messageってのがあれば、messageをflashの中に入れるんですね。
それがoptions[:now]があるかどうかで入れ方が変わると。
んでそのmessageってのは、find_message(:signed_up)だと。
# Get message for given
def find_message(kind, options = {})
options[:scope] ||= translation_scope #refer below
options[:default] = Array(options[:default]).unshift(kind.to_sym)
options[:resource_name] = resource_name
options = devise_i18n_options(options) #refer below
I18n.t("#{options[:resource_name]}.#{kind}", options)
end
I18n.t
→I18n.translateの略。
翻訳したのをfind_messageの戻り値として返してくれるんですかね。
https://railsguides.jp/i18n.html
https://qiita.com/Kta-M/items/bd4ba36a58ad602a9d8b
# Controllers inheriting DeviseController are advised to override this
# method so that other controllers inheriting from them would use
# existing translations.
def translation_scope
"devise.#{controller_name}"
end
def devise_i18n_options(options)
options
end
というわけで大まかにいうと、
set_flash_message! :notice, :signed_up
↓
flash[:notice] = :signed_up
flashを規定するもの(まあメソッド名からもわかるが
#04 sign_up(resource_name, resource)
# Signs in a user on sign up. You can overwrite this method in your own
# RegistrationsController.
def sign_up(resource_name, resource)
sign_in(resource_name, resource)
end
# Sign in a user that already was authenticated. This helper is useful for logging
# users in after sign up. All options given to sign_in is passed forward
# to the set_user method in warden.
# If you are using a custom warden strategy and the timeoutable module, you have to
# set `env["devise.skip_timeout"] = true` in the request to use this method, like we do
# in the sessions controller: https://github.com/plataformatec/devise/blob/master/app/controllers/devise/sessions_controller.rb#L7
#
# Examples:
#
# sign_in :user, @user # sign_in(scope, resource)
# sign_in @user # sign_in(resource)
# sign_in @user, event: :authentication # sign_in(resource, options)
# sign_in @user, store: false # sign_in(resource, options)
#
def sign_in(resource_or_scope, *args)
options = args.extract_options!
scope = Devise::Mapping.find_scope!(resource_or_scope)
resource = args.last || resource_or_scope
expire_data_after_sign_in! #refer below
if options[:bypass]
ActiveSupport::Deprecation.warn(<<-DEPRECATION.strip_heredoc, caller)
[Devise] bypass option is deprecated and it will be removed in future version of Devise.
Please use bypass_sign_in method instead.
Example:
bypass_sign_in(user)
DEPRECATION
warden.session_serializer.store(resource, scope) #refer below
elsif warden.user(scope) == resource && !options.delete(:force)
# Do nothing. User already signed in and we are not forcing it.
true
else
warden.set_user(resource, options.merge!(scope: scope))
end
end
Array#extract_options! で Rails API のように柔軟な引数を取るメソッドを定義する
http://www.techscore.com/blog/2012/12/25/arrayextract_options-%E3%81%A7-rails-api-%E3%81%AE%E3%82%88%E3%81%86%E3%81%AB%E6%9F%94%E8%BB%9F%E3%81%AA%E5%BC%95%E6%95%B0%E3%82%92%E5%8F%96%E3%82%8B%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%82%92/
def expire_data_after_sign_in!
# session.keys will return an empty array if the session is not yet loaded.
# This is a bug in both Rack and Rails.
# A call to #empty? forces the session to be loaded.
session.empty?
session.keys.grep(/^devise\./).each { |k| session.delete(k) }
end
alias :expire_data_after_sign_out! :expire_data_after_sign_in!
sessionが読み込まれないとsession.keysは空の配列を返してくる。
これはRackとRailsのバグだから、一回読み込んでやる必要がある。
そのためのsession.empty?だと。。
ただ単にsession消してるだけかな。
●warden.session_serializer.store(resource, scope)
# Points to a SessionSerializer instance responsible for handling
# everything related with storing, fetching and removing the user
# session.
# :api: public
def session_serializer
@session_serializer ||= Warden::SessionSerializer.new(@env)
end
# encoding: utf-8
# frozen_string_literal: true
module Warden
class SessionSerializer
attr_reader :env
def initialize(env)
@env = env
end
def key_for(scope)
"warden.user.#{scope}.key"
end
def serialize(user)
user
end
def deserialize(key)
key
end
def store(user, scope)
return unless user
method_name = "#{scope}_serialize"
specialized = respond_to?(method_name)
session[key_for(scope)] = specialized ? send(method_name, user) : serialize(user)
end
# 中略
end # SessionSerializer
end # Warden
親玉みたいのが出てきた笑
storeもここに載ってる。
なんかsession機能の根幹になってそう。。
storeは、大まかに言うと
session[:scope] = user
を担ってるのね。実際にscope、userを入れているわけではないけど。
●warden.userについて
def user(argument = {})
opts = argument.is_a?(Hash) ? argument : { :scope => argument }
scope = (opts[:scope] ||= @config.default_scope)
if @users.has_key?(scope)
@users[scope]
else
unless user = session_serializer.fetch(scope)
run_callbacks = opts.fetch(:run_callbacks, true)
manager._run_callbacks(:after_failed_fetch, user, self, :scope => scope) if run_callbacks
end
@users[scope] = user ? set_user(user, opts.merge(:event => :fetch)) : nil
end
end
● warden.set_userについて
# Manually set the user into the session and auth proxy
#
# Parameters:
# user - An object that has been setup to serialize into and out of the session.
# opts - An options hash. Use the :scope option to set the scope of the user, set the :store option to false to skip serializing into the session, set the :run_callbacks to false to skip running the callbacks (the default is true).
#
# :api: public
def set_user(user, opts = {})
scope = (opts[:scope] ||= @config.default_scope)
# Get the default options from the master configuration for the given scope
opts = (@config[:scope_defaults][scope] || {}).merge(opts)
opts[:event] ||= :set_user
@users[scope] = user
if opts[:store] != false && opts[:event] != :fetch
options = env[ENV_SESSION_OPTIONS]
if options
if options.frozen?
env[ENV_SESSION_OPTIONS] = options.merge(:renew => true).freeze
else
options[:renew] = true
end
end
session_serializer.store(user, scope)
end
run_callbacks = opts.fetch(:run_callbacks, true)
manager._run_callbacks(:after_set_user, user, self, opts) if run_callbacks
@users[scope]
end
あとでつづきかく