deviseのコードを少し読んだ
current_xxxってなぜ参照できるのか?(≒定義元がよくわからん)的なことを聞かれて、自分もどこに定義されているかよくわかってないなと思いdeviseのコードを読んでみた
current_xxx を定義しているのはここ
コメントを見ると、どうやら define_helpers メソッドに渡したmappingの mapping.name がモデル名になるということはわかったのですが、ここを読んだだけだとなぜそうなるのかまではわかりませんでした
code: Ruby
# Define authentication filters and accessor helpers based on mappings.
# These filters should be used inside the controllers as before_actions,
# so you can control the scope of the user who should be signed in to
# access that specific controller/action.
# Example:
#
# Roles:
# User
# Admin
#
# Generated methods:
# authenticate_user! # Signs user in or redirect
# authenticate_admin! # Signs admin in or redirect
# user_signed_in? # Checks whether there is a user signed in or not
# admin_signed_in? # Checks whether there is an admin signed in or not
# current_user # Current signed in user
# current_admin # Current signed in admin
# user_session # Session data available only to the user scope
# admin_session # Session data available only to the admin scope
#
# Use:
# before_action :authenticate_user! # Tell devise to use :user map
# before_action :authenticate_admin! # Tell devise to use :admin map
#
def self.define_helpers(mapping) #:nodoc: mapping = mapping.name
class_eval <<-METHODS, __FILE__, __LINE__ + 1
def authenticate_#{mapping}!(opts = {})
warden.authenticate!(opts) if !devise_controller? || opts.delete(:force)
end
!!current_#{mapping}
end
def current_#{mapping}
@current_#{mapping} ||= warden.authenticate(scope: :#{mapping})
end
current_#{mapping} && warden.session(:#{mapping})
end
METHODS
ActiveSupport.on_load(:action_controller) do
if respond_to?(:helper_method)
helper_method "current_#{mapping}", "#{mapping}_signed_in?", "#{mapping}_session"
end
end
end
そこで define_helpers が呼び出されている箇所を遡ると、以下の self.add_mappingメソッドから呼ばれていることがわかった
ここで define_helpers の引数に渡しているmappingは Devise::Mapping.new(resource, options)のインスタンス
code: Ruby
# Small method that adds a mapping to Devise.
def self.add_mapping(resource, options)
mapping = Devise::Mapping.new(resource, options)
@@default_scope ||= mapping.name
@@helpers.each { |h| h.define_helpers(mapping) }
mapping
end
Devise::Mappingのソースはこちら
class Mappingの説明コメントを読むと、mapping.nameが devise_forの引数で渡したモデル名(シンボル)であることがわかる
code: Ruby
# Responsible for handling devise mappings and routes configuration. Each
# resource configured by devise_for in routes is actually creating a mapping
# object. You can refer to devise_for in routes for usage options.
#
# The required value in devise_for is actually not used internally, but it's
# inflected to find all other values.
#
# map.devise_for :users
# mapping = Devise.mappings:user #
# # is the scope used in controllers and warden, given in the route as :singular.
#
# # how the mapping should be search in the path, given in the route as :as.
#
# # is the class to be loaded from routes, given in the route as :class_name.
#
# # is the modules included in the class
#
上記の add_mappingメソッドは devise_forメソッドから呼び出されている
mapping = Devise.add_mapping(resource, options) で渡されている resourceはdevise_forの引数で渡されているシンボルのモデル名になる
code: Ruby
def devise_for(*resources)
@devise_finalized = false
raise_no_secret_key unless Devise.secret_key
options = resources.extract_options!
options:as ||= @scope:as if @scope:as.present? resources.map!(&:to_sym)
resources.each do |resource|
mapping = Devise.add_mapping(resource, options)
begin
raise_no_devise_method_error!(mapping.class_name) unless mapping.to.respond_to?(:devise)
rescue NameError => e
raise unless mapping.class_name == resource.to_s.classify
next
rescue NoMethodError => e
raise unless e.message.include?("undefined method `devise'")
raise_no_devise_method_error!(mapping.class_name)
end
unless mapping.omniauthable?
raise ArgumentError, "Mapping omniauth_callbacks on a resource that is not omniauthable\n" \
"Please add devise :omniauthable to the #{mapping.class_name} model"
end
end
routes = mapping.used_routes
devise_scope mapping.name do
with_devise_exclusive_scope mapping.fullpath, mapping.name, options do
routes.each { |mod| send("devise_#{mod}", mapping, mapping.controllers) }
end
end
end
end
devise_forを起点にシンボルのモデル名が self.define_helpers メソッドのmapping = mapping.nameに渡されることによって、current_モデル名というヘルパーメソッドが動的に定義される
この記事にもっと詳しく書かれていた
では、なぜ動的に定義されたメソッドがすべてのコントローラーで使えるようになるかというと、ApplicationControllerにincludeされる、Devise::Controllers::Helpersにメソッドが作成されるからっぽい
たしかにDevise::Controllers::Helpersを見ると Those helpers are convenience methods added to ApplicationController.って書かれている
ただ、これコード上どうApplicationControllerにモジュールがincludeされることになるかっていうのがよくわかってない...