I am building an API in Sinatra, and have converted to a modular style. However, I am having an issue with method calls inside the routes files not being recognized.
I have simplified the app so the post is shorter, but the basic problem is that if I GET /test
in WorkoutHandler
- it cannot recognize methods in WardenStrategies
or LoginHelper
unless I also include those files in the Handler (they are already included in app.rb). However once I do that, the methods declared in the gems they use are unrecognized. All of these are registered in app.rb, and are required in my Rackup file.
Here is my app.rb file
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
Here are the config.ru, Handler, and Helper files. (Handlers are just the name I use for Controller/Route files.
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
I was under the assumption that helpers
, use
, and register
only need to be declared in app.rb (since I require all files in my rackup file and run the app from there). However the check_authentication
method isn't recognized unless I register the helper here as well.
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
As of the time of this writing, I am getting the following error when running specs:
NoMethodError: undefined method `authenticated?' for nil:NilClass
This means that env doesn't have a :warden key/value (which I confirmed in console). The authenticated?
method is in the Warden gem directory, so I guess I could require 'warden'
in each Handler file - I don't think that's the way modular Sinatra apps are supposed to be constructed.
I've read all the blog posts and book chapters I can find on modular Sinatra apps, and I can't seem to debug my issue. From what I understand, needing to re-register (include) helpers in all files that use them is extraneous. I thought that by extending Sinatra::Base, I would have access to all declared classes in app.rb.
Any help would be appreciated. Thanks!
The short answer to your question is that every time you subclass Sinatra::Base, it creates a completely independent Sinatra application. Just because multiple classes subclass Sinatra::Base doesn't mean they all inherit each others' attributes. This is why it's called a "modular" style!
If you have some common functionality to be shared between different Sinatra applications, you can either create a mixin and extend/include it in every application (which is basically what you're doing now with helpers
and register
), or you can create an intermediate class that your applications subclass. For example:
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
As far as your question "Shouldn't this give my Handler access to gem methods?" that depends on how your Gemfile is laid out. If you have something like this:
gem 'sinatra', :require => 'sinatra/base'
gem 'sinatra-activerecord', :require => 'sinatra/activerecord'
gem 'warden'
Then yes, doing Bundle.require
in your rackup script would eliminate the need to do individual require
statements in your class files.