首页 > 解决方案 > Ruby gem 方法仅在控制器中可用?如果是这样,如何使其在其他文件中可用?

问题描述

我的印象是,一旦使用 bundler 安装 gem 的方法,就可以在 Rails 应用程序的任何地方访问它。但事实并非如此吗?我很困惑,因为controller当移出文件时无法识别其中可访问的方法。有什么方法可以要求 gem 在command文件中运行下面的代码而不是controller?

我正在使用一个名为sorcery来实现 OAuth 登录系统的身份验证 gem。有一些类似gem 提供的方法login_from(provider),可以从我的控制器中访问。

# app/controllers/oauths_controller.rb
class OauthsController < ApplicationController
  skip_before_action :require_login

  def callback
    provider = params[:provider]
    if @user = login_from(provider)
    # method continues
  end

但是,我们试图在我们的应用程序中采用命令-查询分离的方法,所以我尝试将这个包含login_from(provider)方法的进程移动到另一个名为app/commands/authentication/login_command.rb. 这导致无法识别该方法:

NoMethodError (undefined method 'login_from' for #<Authentication::LoginCommand:>)

提前致谢。

标签: ruby-on-railsrubyrubygems

解决方案


我的印象是,一旦使用 bundler 安装 gem 的方法,就可以在 Rails 应用程序的任何地方访问它。

那将是一个错误的印象。

如您所见,Sorcery::Engine原因ActionController::Base包括在Sorcery::Controller此处定义的方法:

require 'sorcery'
require 'rails'

module Sorcery
  # The Sorcery engine takes care of extending ActiveRecord (if used) and ActionController,
  # With the plugin logic.
  class Engine < Rails::Engine
    config.sorcery = ::Sorcery::Controller::Config

    initializer 'extend Controller with sorcery' do
      # TODO: Should this include a modified version of the helper methods?
      if defined?(ActionController::API)
        ActionController::API.send(:include, Sorcery::Controller)
      end

      if defined?(ActionController::Base)
        ActionController::Base.send(:include, Sorcery::Controller)
        ActionController::Base.helper_method :current_user
        ActionController::Base.helper_method :logged_in?
      end
    end
  end
end

Sorcer::Controller反过来,包括一系列子模块:

module Sorcery
  module Controller
    def self.included(klass)
      klass.class_eval do
        include InstanceMethods
        Config.submodules.each do |mod|
          # FIXME: Is there a cleaner way to handle missing submodules?
          # rubocop:disable Lint/HandleExceptions
          begin
            include Submodules.const_get(mod.to_s.split('_').map(&:capitalize).join)
          rescue NameError
            # don't stop on a missing submodule.
          end
          # rubocop:enable Lint/HandleExceptions
        end
      end
      Config.update!
      Config.configure!
    end

    ...
  end
end

这些子模块之一是Sourcery::Controller::Submodules::External,当包含时,还包括Sourcery::Controller::Submodules::External::InstanceMethods,这里:

module Sorcery
  module Controller
    module Submodules
      # This submodule helps you login users from external auth providers such as Twitter.
      # This is the controller part which handles the http requests and tokens passed between the app and the @provider.
      module External
        def self.included(base)
          base.send(:include, InstanceMethods)
          ...
        end
      end
    end
  end
end

并且,InstanceMethods包含方法login_from,这里:

module Sorcery
  module Controller
    module Submodules
      # This submodule helps you login users from external auth providers such as Twitter.
      # This is the controller part which handles the http requests and tokens passed between the app and the @provider.
      module External
        def self.included(base)
          base.send(:include, InstanceMethods)

        module InstanceMethods
          protected

          ...

          # tries to login the user from provider's callback
          def login_from(provider_name, should_remember = false)
            sorcery_fetch_user_hash provider_name

            return unless (user = user_class.load_from_provider(provider_name, @user_hash[:uid].to_s))

            # we found the user.
            # clear the session
            return_to_url = session[:return_to_url]
            reset_sorcery_session
            session[:return_to_url] = return_to_url

            # sign in the user
            auto_login(user, should_remember)
            after_login!(user)

            # return the user
            user
          end

          ...
        end
      end
    end
  end
end

Soooo,这是一个很长的说法,它login_from被定义为继承自的类上的受保护实例方法,ActionController::Base而不是不继承自的类上的实例方法,ActionController::Base这就是你得到NoMethodError.

有什么方法可以要求 gem 在命令文件而不是控制器中运行下面的代码?

也许。也许不吧。如您所见,login_from是使用其他sorcery方法和继承自ActionController::Base. 因此,您需要确保所有这些方法都可用Authentication::LoginCommand才能login_from正常运行。


推荐阅读