Using Cucumber With Modular Sinatra Apps
Update to include dealing with multiple Sinatra apps
Require the file where your app comes together and change
def app
Sinatra::Application
end
to def app
Rack::Builder.new do
map '/a' { run MyAppA }
map '/b' { run MyAppB }
end
end
and just test the app proper.eg, if you define middleware in your config.ru
that you want to test, maybe move loading those into your app's definition.
Setup Cucumber, Capybara and RSpec with Modular Sinatra application
set your env.rb
:
ENV['RACK_ENV'] = 'test'
require File.join(File.dirname(__FILE__), '..', '..', 'config/boot.rb')
require 'capybara'
require 'capybara/cucumber'
require 'rspec'
Capybara.app = eval("Rack::Builder.new {( " + File.read(File.dirname(__FILE__) + '/../../config.ru') + "\n )}")
class SomeWorld
include Capybara
include RSpec::Expectations
include RSpec::Matchers
end
World do
SomeWorld.new
end
And It'll work! Set up cucumber to test sinatra app using watir
env.rb executes an arbitrary Ruby code as a part of Cucumber setup, so you should be able to start your app or do whatever else you need. Try:
require 'YourApp'
YourApp.run!
Having said that, it might not be the most logical place to do. An automated build cycle for a web app usually consists of the following steps:- Build: compile, copy necessary files, package a binary, etc.
- Run unit tests
- Deploy: put the application on a target server
- Run integration/feature tests
Error handlers don't run in modular Sinatra app
The use
method is for adding middleware to an app, you can’t use it to compose an app like this.
In your example you actually have three different Sinatra applications, two of which are being run as middleware. When a Sinatra app is run as middleware then any request that matches one of its routes is handled by that app, otherwise the request is passed to the next component in the Rack stack. Error handlers will only apply if the request has been handled by the same app. The app that you have defined the error handlers in has no routes defined, so all requests will be passed on down the stack — the error handlers will never be used.
One way to organise a large app like this would be to simply use the same class and reopen it in the different files. This other question has an example that might be useful: Using Sinatra for larger projects via multiple files.
Architecture for a modular, component-based Sinatra Application
This is similar to include's proposal, but it doesn't require access to the rackup file.
Write your various Handlers like:
class FoodHandler < Sinatra::Base
get '/chunky/:food' do
"Chunky #{params[:food]}!"
end
end
Then in your main application file:require './lib/handlers/food_handler.rb'
class Main < Sinatra::Base
enable :sessions
... bla bla bla
use FoodHandler
end
I've used this kind of structure to build some fairly complex Sinatra apps. It scales just as well as Rails.EDIT
To have your config file define the routes, you could do something like this:
class PlacesHandler < Sinatra::Base
# Given your example, this would define 'places/paris' and 'places/losangeles'
CONFIG['components'].select { |c| c['compontent_type'] == 'Mapper' }.each do |c|
get c['route'] do
@latitude = c['component_settings']['latitude']
@longitude = c['component_settings']['longitude']
end
end
end
Converting to modular sinatra app breaks tests
I think you just need to change your app
method to return your modularised application class (MyApp) rather than the Sinatra::Base class. So replace:
def app() Sinatra::Base end
in web_spec.rb, with:def app
MyApp
end
Rack::Test::Methods depends on the app method to tell it which class to call to process requests. In a simple non-modular Sinatra application that class is Sinatra::Base because that is the class in which the routes are applied by default. In a modular application that is the class in which you define your routes (MyApp in your case). Access Sinatra settings from a model
I ended up doing:
#document.rb
class Document
def self.documents_path=(path)
@documents_path = path
end
def self.documents_path
@documents_path
end
...
end
#app.rb
configure do
set :documents_path, settings.root + "/../documents/"
Document.documents_path = settings.documents_path
end
then just using Document.documents_path inside my find method. Set Up for RSpec in a Sinatra modular app
I'm guessing you're following this recipe, correct?
The described_class
in this line:
def app() described_class end
is meant to be the class under test, in this case ZerifApp
. Try it like so:def app() ZerifApp end
EDITIt turns out the above answer is not correct about what described_class
does. I assumed it was a placeholder -- actually it is an RSpec method that returns the class of the implicit subject, that is to say, the thing being tested.
The recipe at the link is misleading because of the way it recommends writing the describe
block:
describe "My Sinatra Application" do
This is valid RSpec, but it does not define the subject class. Executing described_class
in an example for this block will return nil
. To make it work, replace the describe block:describe ZerifApp do
Now described_class
will return the expected value (ZerifApp
) How do I config.ru properly in modular Sinatra application.?
With URLMap
you specify a base url where the app should be mounted. The path specified in the map is not used when determining which route to use within the app itself. In other words the app acts as if it's root is after the path used in URLMap
.
For example, your code will respond to the following paths:
/
: will be routed to the/
route inMyApp
/another
: will go to the/
route inAnotherRoute
. SinceAnotherRoute
extendsMyApp
this will be the same as/
inMyApp
(but in a different instance).URLMap
sees/another
and uses it to map toAnotherRoute
, stripping this part of the request from the path.AnotherRoute
then only sees/
./another/another
: will be routed to the two/another
routes inAnotherRoute
. Again, the firstanother
is used by the URLMap to route the request toAnotherRoute
.AnotherRoute
then only sees the secondanother
as the path.Note that this path will respond to both
GET
andPOST
requests, each being handled by the appropriate block.
AnotherRoute
, with a config.ru
that is just:run AnotherRoute.new
Since AnotherRoute
extends MyApp
, the /
route will be defined for it.If you're looking for a way to add routes to an existing Sinatra application, you could create a module with an included
method that adds the routes, rather than use inheritance.
Related Topics
Ruby Readline Fails If Process Started with Arguments
Where Are Keywords Defined in Ruby
Phusion Passenger with Ruby 1.8 and 1.9
Browsing Ruby Code a La Smalltalk
Ruby/Rails 3.1: Given a Url String, Remove Path
Ruby: How to Escape Url with Square Brackets [ and ]
Hw Impossibility: "Create a Rock Paper Scissors Program in Ruby Without Using Conditionals"
How to Set in a Middleware a Variable Accessible in All My Application
Best Way to Create a Blog with Static Pages in Ruby
Why The Unit Test Frameworks in Fortran Rely on Ruby Instead of Fortran Itself
Gem Ransack Doesn't Return Any Results When Searched with Full Name
Sidekiq to Execute at Specific Time in Every Timezones
Timeout When Installing Ruby Gems
Strong Parameters with Nested Hash
How to Handle Single Table Inheritance in Simpleform So a Single Helper Handles All Models