How mix in routes in Sinatra for a better structure - ruby

I found nothing about how I can mix-in routes from another module, like this:
module otherRoutes
get "/route1" do
end
end
class Server < Sinatra::Base
include otherRoutes
get "/" do
#do something
end
end
Is that possible?

You don't do include with Sinatra. You use extensions together with register.
I.e. build your module in a separate file:
require 'sinatra/base'
module Sinatra
module OtherRoutes
def self.registered(app)
app.get "/route1" do
...
end
end
end
register OtherRoutes # for non modular apps, just include this file and it will register
end
And then register:
class Server < Sinatra::Base
register Sinatra::OtherRoutes
...
end
It's not really clear from the docs that this is the way to go for non-basic Sinatra apps. Hope it helps others.

You could do this:
module OtherRoutes
def self.included( app )
app.get "/route1" do
...
end
end
end
class Server < Sinatra::Base
include OtherRoutes
...
end
Unlike Ramaze, Sinatra's routes are not methods, and so cannot use Ruby's method lookup chaining directly. Note that with this you can't later monkey-patch OtherRoutes and have the changes reflected in Server; this is just a one-time convenience for defining the routes.

I prefer the use of sinatra-contrib gem to extend sinatra for cleaner syntax and shared namespace
# Gemfile
gem 'sinatra', '~> 1.4.7'
gem 'sinatra-contrib', '~> 1.4.6', require: 'sinatra/extension'
# other_routes.rb
module Foo
module OtherRoutes
extend Sinatra::Extension
get '/some-other-route' do
'some other route'
end
end
end
# app.rb
module Foo
class BaseRoutes < Sinatra::Base
get '/' do
'base route'
end
register OtherRoutes
end
end
sinata-contrib is maintained alongside the sinatra project

Well you can also use the map method to map routes to your sinatra apps
map "/" do
run Rack::Directory.new("./public")
end
map '/posts' do
run PostsApp.new
end
map '/comments' do
run CommentsApp.new
end
map '/users' do
run UserssApp.new
end

Just my two cents:
my_app.rb:
require 'sinatra/base'
class MyApp < Sinatra::Base
set :root, File.expand_path('../', __FILE__)
set :app_file, __FILE__
disable :run
files_to_require = [
"#{root}/app/helpers/**/*.{rb}",
"#{root}/app/routes/**/*.{rb}"
]
files_to_require.each {|path| Dir.glob(path, &method(:require))}
helpers App::Helpers
end
app/routes/health.rb:
MyApp.configure do |c|
c.before do
content_type "application/json"
end
c.get "/health" do
{ Ruby: "#{RUBY_VERSION}",
Rack: "#{Rack::VERSION}",
Sinatra: "#{Sinatra::VERSION}"
}.to_json
end
end
app/helpers/application.rb:
module App
module Helpers
def t(*args)
::I18n::t(*args)
end
def h(text)
Rack::Utils.escape_html(text)
end
end
end
config.ru:
require './my_app.rb'

Related

How to map routes to modules without the use of multiple Sinatra apps?

I have this structure:
module Analytics
def self.registered(app)
module DepartmentLevel
departmentParticipation = lambda do
end
departmentStatistics = lambda do
end
app.get '/participation', &departmentParticipation
end
module CourseLevel
courseParticipation = lambda do
end
end
end
And at the end of the module Analytics I would like to route each piece of the request to his specific subModule. If it is requested
'analytics/department'
it should redirect to the module DepartmentLevel which has its own routes as
app.get 'participation', &departmentParticipation
I first thought on using map. But how to use it without having to run a new or inherit Sinatra::Base object?
Not sure if this is what you need, but here's how I build my modular Sinatra apps: By using use
First, I have my ApplicationController. It's the base class for all other Controllers. It lives in controllers/application_controller.rb
class ApplicationController < Sinatra::Base
# some settings that are valid for all controllers
set :views, File.expand_path('../../views', __FILE__)
set :public_folder, File.expand_path('../../public', __FILE__)
enable :sessions
# Helpers
helpers BootstrapHelpers
helpers ApplicationHelpers
helpers DatabaseHelpers
configure :production do
enable :logging
end
end
Now, all other Controllers/Modules inherit from ApplicationController. Example controllers/website_controller.rb:
require 'controllers/application_controller'
class WebsiteController < ApplicationController
helpers WebsiteHelpers
get('/') { slim :home }
get('/updates') { slim :website_updates }
get('/test') { binding.pry; 'foo' } if settings.development?
end
At last, in app.rb is where it all comes together:
# Require some stuff
require 'yaml'
require 'bundler'
Bundler.require
require 'logger'
# Require own stuff
APP_ROOT = File.expand_path('..', __FILE__)
$LOAD_PATH.unshift APP_ROOT
require 'lib/core_ext/string'
require 'controllers/application_controller.rb'
# Some Run-Time configuration...
ApplicationController.configure do
# DB Connections, Logging and Stuff like that
end
# Require ALL models, controllers and helpers
Dir.glob("#{APP_ROOT}/{helpers,models,controllers}/*.rb").each { |file| require file }
# here I glue everything together
class MyApp < Sinatra::Base
use WebsiteController
use OtherController
use ThingController
not_found do
slim :'404'
end
end
With this Setup, all I need to do in config.ru is
require './app.rb'
run MyApp
Hope this helps!

How to test if some specific rack middleware is being used?

To be more particular, I'm talking about sentry-raven and sinatra here. I saw examples testing sinatra applications, or middlewares. But I didn't see ones testing if some particular middleware is present. Or should I be testing behavior, not configuration (or how should I call it)?
The important thing (I'd say) is the behaviour, but if you wish to check for middleware there are 2 ways I'd suggest after taking a delve into the Sinatra source (there are possibly much easier/better ways):
The env
In the Sinatra source there's a method that uses the env to check if a middleware is already present:
# Behaves exactly like Rack::CommonLogger with the notable exception that it does nothing,
# if another CommonLogger is already in the middleware chain.
class CommonLogger < Rack::CommonLogger
def call(env)
env['sinatra.commonlogger'] ? #app.call(env) : super
end
You could do the same thing in a route, e.g.
get "/env-keys" do
env.keys.inspect
end
It'll only show you the middleware if it's inserted something in env hash, e.g.
class MyBad
def initialize app, options={}
#app = app
#options = options
end
def call env
#app.call env.merge("mybad" => "I'm sorry!")
end
end
output:
["SERVER_SOFTWARE", "SERVER_NAME", "rack.input", "rack.version", "rack.errors", "rack.multithread", "rack.multiprocess", "rack.run_once", "REQUEST_METHOD", "REQUEST_PATH", "PATH_INFO", "REQUEST_URI", "HTTP_VERSION", "HTTP_HOST", "HTTP_CONNECTION", "HTTP_CACHE_CONTROL", "HTTP_ACCEPT", "HTTP_USER_AGENT", "HTTP_DNT", "HTTP_ACCEPT_ENCODING", "HTTP_ACCEPT_LANGUAGE", "GATEWAY_INTERFACE", "SERVER_PORT", "QUERY_STRING", "SERVER_PROTOCOL", "rack.url_scheme", "SCRIPT_NAME", "REMOTE_ADDR", "async.callback", "async.close", "rack.logger", "mybad", "rack.request.query_string", "rack.request.query_hash", "sinatra.route"]
It's near the end of that list.
The middleware method
There's also a method called middleware in Sinatra::Base:
# Middleware used in this class and all superclasses.
def middleware
if superclass.respond_to?(:middleware)
superclass.middleware + #middleware
else
#middleware
end
end
Call it in the class definition of a modular app and you can get the middlewares in an array:
require 'sinatra/base'
class AnExample < Sinatra::Base
use MyBad
warn "self.middleware = #{self.middleware}"
output:
self.middleware = [[MyBad, [], nil]]
There may be a way to get it from Sinatra::Application, but I haven't looked.
With help from ruby-raven guys, we've got this:
ENV['RACK_ENV'] = 'test'
# the app: start
require 'sinatra'
require 'sentry-raven'
Raven.configure(true) do |config|
config.dsn = '...'
end
use Raven::Rack
get '/' do
'Hello, world!'
end
# the app: end
require 'rspec'
require 'rack/test'
Raven.configure do |config|
logger = ::Logger.new(STDOUT)
logger.level = ::Logger::WARN
config.logger = logger
end
describe 'app' do
include Rack::Test::Methods
def app
#app || Sinatra::Application
end
class TestRavenError < StandardError; end
it 'sends errors to sentry' do
#app = Class.new Sinatra::Application do
get '/' do
raise TestRavenError
end
end
allow(Raven.client).to receive(:send).and_return(true)
begin
get '/'
rescue TestRavenError
end
expect(Raven.client).to have_received(:send)
end
end
Or if raven sending requests is in the way (when tests fail because of raven sending requests, and not because of the underlying error), one can disable them:
Raven.configure(true) do |config|
config.should_send = Proc.new { false }
end
And mock Raven.send_or_skip instead:
...
allow(Raven).to receive(:send_or_skip).and_return(true)
begin
get '/'
rescue TestRavenError
end
expect(Raven).to have_received(:send_or_skip)
...

How can I use my Sinatra helpers in included modules?

I have my routes and helpers defined in external files and included by Sinatra, however I'm new to Ruby and I can't now figure out how I can use my helper methods in my routes. When I run the code in RubyMine and access a profile URL I get the error "NoMethodError - undefined method `protected!'"
## Main class
require 'sinatra/base'
class MyApp < Sinatra::Base
register Sinatra::MyHelpers
register ProfileRoutes
...
end
## Helpers include
require 'sinatra/base'
module Sinatra
module LocutusHelpers
def self.registered( app )
app.before do
...
end
def protected!
...
end
end
end
end
## Routes include
require 'sinatra/base'
module ProfileRoutes
def self.registered( app )
app.get '/profile/:userid' do
protected!
...
end
end
end
I've tried def self.protected! for the helper but then it cant access the request object.
I've also tried Sinatra::MyHelpers.protected!, Sinatra.protected! and app.protected!, errors are thrown for all of these too
Do you know how I can access the helpers from my routes? Or have I set something up incorrectly?
It looks like you are mixing up adding helpers from extensions and configuring your app from extensions.
You need to move protected! into a module, then when registering your extension add that module as a helpers module.
module LocutusHelpers
# new module, move protected! into here
module HelperMethods
def protected!
...
end
end
def self.registered( app )
# add new hlpers module
app.helpers HelperMethods
# other extension setup as before...
app.before do
...
end
end
end

Modular Sinatra App using Sinatra Reloader?

Hi if I had a 'main' sinatra file with the following code,
require 'sinatra'
require "sinatra/reloader"
class MyApp < Sinatra::Base
configure do
require "./rest/auth.rb"
register Sinatra::Reloader
also_reload '/rest/auth'
end
get '/' do
erb :home
end
end
And I wanted to put my authentication logic inside of /rest/auth.rb, how can I get /rest/auth.rb to reload in development mode? Must I put the configure block and require sinatra/reloader in every one of my controller files? I'd like for my controllers to inherit the settings of my main class. The code inside of auth.rb is as follows:
class MyApp < Sinatra::Base
set(:auth) do |*roles| # <- notice the splat here
condition do
unless logged_in?
session[:success_url] = request.path_info
redirect '/'
end
end
end
def logged_in?
current_user
end
def current_user
if session[:user_id]
u = User.find(:id=>"#{session[:user_id]}")
else
false
end
end
end
I need to restart the server for my changes to take place but I can throw that reload code in auth.rb's configure block though I wouldn't like to. Any ideas?
Try to rewrite like this
require 'sinatra/base'
require "sinatra/reloader"
class MyApp < Sinatra::Base
configure :development do
register Sinatra::Reloader
also_reload './rest/auth'
end
require "./rest/auth.rb"
get '/' do
erb :home
end
end

Sinatra : how to use helpers in Mustache views

I want to use my Sinatra helpers methods in Mustache views.
I do this:
# in app.rb:
...
helpers do
def helloworld
"helloworld!"
end
end
get '/'
mustache :home
end
...
# in views/home
class App < Sinatra::Base
module Views
class Home < Mustache
def hello
helloworld
end
end
end
end
# in home.mustache
<p>{{hello}}</p>
It does not work, I have the error message :
«undefined local variable or method `helloworld' for App::Views::Home:0x000000023ebd48»
How can I use my method helper in Mustache view ?
Or, how can I use my method helper directly from home.mustache ? like this :
# in home.mustache
<p>{{helloworld}}</p>
Many thanks for your help!
You should be able to do something with a module:
# app_helpers.rb
module AppHelpers
def helloworld
"helloworld!"
end
end
# app.rb
helpers AppHelpers
get '/'
mustache :home
end
# views/home.rb
class App < Sinatra::Base
module Views
class Home < Mustache
include AppHelpers
def hello
helloworld
end
end
end

Resources