Синатра: класс модульных маршрутов не распознает вспомогательные методы

Я строю API в Синатре и перешел на модульный стиль. Однако у меня проблема с вызовами методов внутри файлов маршрутов, которые не распознаются.

Я упростил приложение, чтобы сообщение было короче, но основная проблема в том, что если я GET /test в WorkoutHandler - он не может распознать методы в WardenStrategies или же LoginHelper если только я не включу эти файлы в обработчик (они уже включены в app.rb). Однако, как только я это сделаю, методы, объявленные в гемах, которые они используют, не распознаются. Все они зарегистрированы в app.rb и необходимы в моем файле Rackup.

Вот мой файл app.rb

require 'sinatra/base'
require 'sinatra/activerecord'

class WorkoutApp < Sinatra::Base

  register Sinatra::ActiveRecordExtension
  register WardenStrategies

  use WorkoutHandler

  helpers LoginHelper
  helpers HashHelpers

  use Rack::Session::Cookie
  use Warden::Manager do |manager|
    manager.default_strategies :password
    manager.intercept_401 = false
    manager.failure_app = WorkoutApp
    manager.serialize_into_session(&:id)
    manager.serialize_from_session { |id| User.find(id) }
  end
  set :database_file, '../config/database.yml'

  Warden::Manager.before_failure do |env, _opts|
    env['REQUEST_METHOD'] = 'POST'
  end
end

Здесь находятся файлы config.ru, Handler и Helper. (Обработчики - это просто имя, которое я использую для файлов контроллера / маршрута.

module LoginHelper
  def warden_handler
    env['warden']
  end

  def current_user
    warden_handler.user
  end

  def check_authentication
    return if warden_handler.authenticated?
    body 'User not authenticated'
    halt 401
  end
end

Я предполагал, что helpers, use, а также register нужно только объявить в app.rb (так как мне требуются все файлы в моем файле Rackup, и я запускаю приложение оттуда). Тем не менее check_authentication метод не распознается, пока я не зарегистрирую здесь помощника.

class WorkoutHandler < Sinatra::Base
  helpers LoginHelper                   # Is there a way to not need this?
  register WardenStrategies             # Or this?

  get '/test' do
    check_authentication
  end
end

module WardenStrategies
  Warden::Strategies.add(:password) do
    def valid?
      params['email'] || params['password']
    end

    def authenticate!
      user = User.find_by(email: params['email'])
      if user && user.authenticate(params['password'])
        success!(user)
      else
        fail!('Could not log in')
      end
    end
  end
end

require 'rubygems'
require 'bundler'

Bundler.require     #Shouldn't this give my Handler access to gem methods?

ENV['APP_NAME'] = 'workout'

require_all 'app'

run WorkoutApp

На момент написания этой статьи я получаю следующую ошибку при запуске спецификации:

NoMethodError: undefined method `authenticated?' for nil:NilClass

Это означает, что env не имеет ключа / значения: warden (что я подтвердил в консоли). authenticated? Метод находится в каталоге гемов Warden, так что, думаю, я мог бы require 'warden' в каждом файле Handler - я не думаю, что именно так должны создаваться модульные приложения Sinatra.

Я прочитал все посты блога и главы книг, которые я могу найти в модульных приложениях Sinatra, и я не могу решить свою проблему. Из того, что я понимаю, необходимость перерегистрации (включения) помощников во все файлы, которые их используют, является посторонней. Я думал, что, расширив Sinatra::Base, я получу доступ ко всем объявленным классам в app.rb.

Любая помощь будет оценена. Спасибо!

1 ответ

Решение

Краткий ответ на ваш вопрос: каждый раз, когда вы создаете подкласс Sinatra::Base, он создает полностью независимое приложение Sinatra. Тот факт, что несколько классов являются подклассами Sinatra::Base, не означает, что они все наследуют атрибуты друг друга. Вот почему это называется "модульный" стиль!

Если у вас есть некоторые общие функции, которые можно использовать в разных приложениях Sinatra, вы можете либо создать миксины, либо расширить / включить их в каждое приложение (что, в сущности, вы и делаете сейчас helpers а также register), или вы можете создать промежуточный класс, который подкласс вашего приложения. Например:

my_base_app.rb:

require 'sinatra/base'
require 'warden'

require_relative 'login_helper'

class MyBaseApp < Sinatra::Base
  register WardenStrategies

  helpers LoginHelper
end

workout_app.rb:

require 'sinatra/activerecord'
require 'warden'

require_relative 'my_base_app'
require_relative 'hash_helpers'
require_relative 'workout_handler'

class WorkoutApp < MyBaseApp
  register Sinatra::ActiveRecordExtension

  use WorkoutHandler

  helpers HashHelpers

  use Rack::Session::Cookie
  use Warden::Manager do |manager|
    # yadda yadda...
  end
end

workout_handler.rb:

require_relative 'my_base_app'

class WorkoutHandler < MyBaseApp
  get '/test' do
    check_authentication
  end
end

Насколько ваш вопрос "Разве это не должно дать моему Обработчику доступ к методам гемов?" это зависит от того, как выложен ваш Gemfile. Если у вас есть что-то вроде этого:

gem 'sinatra', :require => 'sinatra/base'
gem 'sinatra-activerecord', :require => 'sinatra/activerecord'
gem 'warden'

Тогда да, делаю Bundle.require в вашем сценарии рэкапа избавит от необходимости делать отдельные require заявления в ваших файлах классов.

Другие вопросы по тегам