Sinatra commands do not work in modules - ruby

I have a Sinatra app which requires a module in a different file. When I use Sinatra commands in that module (e.g. redirect "http://facebook.com"), I get a NoMethodError. To illustrate the problem, I have made a simplified version:
--- mainapp.rb ---
#config
require './redirector.rb'
get '/' do
Redirector::redirect_to_stackoverflow
end
--- redirector.rb ---
module Redirector
require 'sinatra'
def self.redirect_to_stackoverflow
redirect "http://stackoverflow.com"
end
end
--- config.ru ---
require 'rubygems'
require 'sinatra'
require File.dirname(__FILE__) + "/ptt.rb"
run Sinatra::Application
What is wrong? Is there a place where I haven't required something properly?

The call to redirect inside the Redirector module is sent to the Redirector Module object, where the method does not exist. require 'sinatra' inside module Redirector is not necessary, and does not do any kind of method composition.
You probably could compose Sinatra methods into your Redirector module, but that is not normal practice. Usually it's the other way around - you write "helper" modules that are composed in to your Sinatra application in various ways.
This is a similar example application, with a more usual approach to composition:
app.rb
require 'sinatra'
require_relative 'redirect.rb'
class MyApp < Sinatra::Application
include Redirector
get '/' do
redirect_to_stackoverflow
end
end
redirect.rb
module Redirector
def redirect_to_stackoverflow
redirect "http://stackoverflow.com"
end
end
config.ru
require File.dirname(__FILE__) + "/app.rb"
run MyApp

#Neil Slater's explanation is correct, but I'd suggest you also make it an Sinatra extension, e.g.
require 'sinatra/base'
module Sinatra
module Redirector
def redirect_to_stackoverflow
redirect "http://stackoverflow.com"
end
end
helpers Redirector
end
Then (for a classic app) all you need to do is require it.
require 'sinatra/redirector'
get "/" do
redirect_to_stackoverflow
end

Related

Define sinatra request routes in included files

I am using Sinatra and I would like to have my project structed in a way that keeps all requests for a specific action in separated files.
The problem I'm running into is that the routes aren't registered with sinatra, and it always 404s and runs my not_found handler, even though I've included a file with the route.
Here's an example of what I'm trying to achieve; Rackup would start the Info app which requires user and post. Info only contains a error and not found handler, and the related routes go in the corresponding required file.
config.ru:
require 'rubygems'
require 'bundler'
Bundler.require
require 'rack'
require './info.rb'
run Info
info.rb:
require 'rubygems'
require 'bundler'
require 'sinatra'
class Info < Sinatra::Base
require './user.rb'
require './post.rb'
# 500 handler
error StandardError do
status 500
content_type :json
return '{"error": "Internal server error", "code": 500}'
end
not_found do
status 404
content_type :json
return '{"error": "Page not found", "code": 404}'
end
end
And user.rb (post.rb would look the same):
require 'rubygems'
require 'bundler'
require 'sinatra'
get '/1/user/:userid' do
# request stuff
end
require doesn’t work the way you seem to think it does. When you call require './user.rb', even though you do it inside the body of class Info < Sinatra::Base, its contents are not loaded as if they were inside that class. Instead they are parsed at the top level, and the routes are added to the default Sinatra::Application and not your application class.
You will have to have your user and post routes defined inside the same class body:
#info.rb
require 'sinatra/base' # Require 'sinatra/base' if you are using modular style.
class Info < Sinatra::Base
# It's a bit unusual to have require inside a class, but not
# wrong as such, and you might want to have the class defined
# before loading the other files.
require_relative 'user.rb' # require_relative is probably safer here.
require_relative 'post.rb'
# ... error handlers etc.
end
#user.rb
require 'sinatra/base'
# The routes need to be in the same class.
class Info < Sinatra::Base
get '/1/user/:userid' do
# request stuff
end
end

Sinatra I18n Fallbacks using Rack::Locale

I'm trying to set up a simple Sinatra app with I18n, following the recommended Sinatra recipe, and using Rack:Locale to determine the language.
My app.rb:
require 'rubygems'
require 'sinatra'
require 'rack/contrib'
require 'i18n'
require 'i18n/backend/fallbacks'
require 'tilt/haml'
use Rack::Locale
configure do
I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
I18n.load_path = Dir[File.join(settings.root, 'locales', '*.yml')]
I18n.backend.load_translations
end
helpers do
def t(*args)
I18n.t(*args)
end
end
get '/' do
haml :index
end
My locales/en.yml:
en:
welcome: "Welcome!"
When I run rackup and visit the root path of my Sinatra app, I get the following:
I18n::InvalidLocale at /
"en-US" is not a valid locale
file: i18n.rb location: enforce_available_locales! line: 284
I thought that the I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks) would handle this, by not finding en-US and falling back to en (which I have), but apparently not. What am I missing?
Add:
I18n.enforce_available_locales = false

Sinatra Routing - Separate Files

I'm going through a recently released book on Sinatra that demonstrates this way of setting up routes in different files:
# app.rb
require "sinatra"
require "slim"
class Todo < Sinatra::Base
# ...
Dir[File.join(File.dirname(__FILE__), "lib", "*.rb")].each { |lib| require lib }
end
# lib/routes.rb
get "/test" do
"The application is running"
end
# config.ru
require "sinatra"
require "bundler/setup"
Bundler.require
ENV["RACK_ENV"] = "development"
require File.join(File.dirname(__FILE__), "app.rb")
Todo.start!
However, it fails to find the route at http://localhost:4567/test. It would make sense to me that this should work when I run ruby config.ru or bundle exec rackup -p 4567. But coming from Rails development where all this configuration is built-in, I don't have a complete understanding of how everything gets wired together. The server is running on that port and I get the Sinatra doesn't know this ditty 404 page. If I reopen the class as suggested by this SO answer, the /test route is found.
# lib/routes.rb
class Todo < Sinatra::Base
get "/test" do
"The application is running"
end
end
Is there something I'm missing about this suggested way to include routes without reopening the class?
Try ruby app.rb, it should work.
You'll need to restart the webserver to load routes that were added while it was running. Routes are loaded into memory when app.rb is invoked and Sinatra is launched. The route itself looks fine and it appears routes.rb is being imported successfully via Dir[File.join(File.dirname(__FILE__), "lib", "*.rb")].each { |lib| require lib }.
If you're running the server directly through terminal Ctrl+X, Ctrl+C should shut it down, then restart it via rackup config.ru* or ruby app.rb. You may confirm the route is recognized by making a get request through your browser to: http://127.0.0.1:4567/test.
For the rackup config.ru command to work, you can change config.ru to something like:
# config.ru
require './app'
run Sinatra::Application
This is just a deployment convenience.
Edit: #shaun, because Todo extends Sinatra::Base it's fine to use run Todo in your case.
The book suggested Todo.start! to run the application from the config.ru file, but the Sinatra documentation example uses run Sinatra::Application. So I just changed the line from Todo.start! to
run Todo
That seems to work, but I'll have to look into the consequences.

How to use RSpec to test a Sinatra application within a gem?

I am writing a gem which includes a Sinatra application that a developer can extend. For example:
# gem code:
require 'sinatra'
module Mygem
class Application < Sinatra::Base
get 'auth/login' {}
get 'auth/logout {}
end
end
# developer code:
require 'mygem'
class DeveloperApp < Mygem::Application
# ..
end
I am also getting started using RSpec. How should I configure RSpec for testing this functionality?
The references above are all informative and useful but mostly rails specific. I found it quite hard to find a simple recipe for a basic test of a modular Sinatra app, so I am hoping this will answer the question for others. Here is a completely bare-bones, small as possible test. This is probably not the only way to do it, but it works well for a modular app:
require 'sinatra'
class Foo < Sinatra::Base
get '/' do
"Hello"
end
end
require 'rack/test'
describe Foo do
include Rack::Test::Methods
def app
Foo.new
end
it "should be testable" do
get '/'
last_response.should be_ok
end
end
Note that there is no need to have the server running when you launch the test (some tutorials I saw implied that you do) - it's not an integration test.
It's actually pretty simple -- just add rspec to your gemfile (then bundle install), and make a directory in your gem called spec/. Once you've done that, add a file spec/spec_helper.rb that contains some configuration for rspec (mostly requiring various files from your library) as well as defining some helper methods for your specs. Then, for each model and controller, make a file called my_model_name_spec.rb or my_controller_name_spec.rb, and do the test there.
Here are some useful resources for getting started with rspec:
Railscasts:
http://railscasts.com/episodes/275-how-i-test
http://railscasts.com/episodes/71-testing-controllers-with-rspec
http://railscasts.com/episodes/157-rspec-matchers-macros/
And for some more advanced (but well-explained) stuff:
http://benscheirman.com/2011/05/dry-up-your-rspec-files-with-subject-let-blocks
Be sure to include the rack-test gem.
You spec helper should have:
require 'rack/test'
require 'foo' # or where ever your app is
# This can go in a helper somewhere
module AppHelper
def app
Foo.new
end
end
RSpec.configure do |config|
config.include Rack::Test::Methods
config.include AppHelper
end
Then, your spec can be as follows:
require 'spec_helper'
# Example app. Delete this example.
class Foo < Sinatra::Base
get '/' do
'Jesse Pinkman'
end
end
describe Foo do
it 'is testable' do
get '/' do
expect(last_response).to be_ok
end
end
end

Using Cucumber With Modular Sinatra Apps

I'm building out a medium-sized application using Sinatra and all was well when I had a single app.rb file and I followed Aslak's guidance up on Github:
https://github.com/cucumber/cucumber/wiki/Sinatra
As the app grew a bit larger and the app.rb file started to bulge, I refactored out a lot of of the bits into "middleware" style modules using Sinatra::Base, mapping things using a rack-up file (config.ru) etc.
The app works nicely - but my specs blew up as there was no more app.rb file for webrat to run against (as defined in the link above).
I've tried to find examples on how to work this - and I think I'm just not used to the internal guts of Cuke just yet as I can't find a single way to have it cover all the apps. I tried just pointing to "config.ru" instead of app.rb - but that doesn't work.
What I ended up doing - which is completely hackish - is to have a separate app.rb file in my support directory, which has all the requires stuff so I can at least test the model stuff. I can also specify routes in there - but that's not at all what I want to do.
So - the question is: how can I get Cucumber to properly work with the modular app approach?
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.
Thanks to Mr. BaroqueBobcat - the answer now, of course, seems so damn obvious :). Here's the env.rb (/features/support/env.rb):
require 'sinatra'
require 'test/unit'
require 'spec/expectations'
require 'rack/test'
require 'webrat'
require 'app1'
require 'app2'
require 'app3'
Webrat.configure do |config|
config.mode = :rack
end
class MyWorld
require 'test/unit'
set :environment, :test
include Rack::Test::Methods
include Webrat::Methods
include Webrat::Matchers
Webrat::Methods.delegate_to_session :response_code, :response_body, :response
def app
Rack::Builder.new do
map '/' do
run App1 #important - this is the class name
end
map '/app1' do
run App2
end
map '/app2' do
run App3
end
end
end
end
World do
MyWorld.new
end
https://gist.github.com/28d510d9fc25710192bc
def app
eval "Rack::Builder.new {( " + File.read(File.dirname(__FILE__) + '/../config.ru') + "\n )}"
end

Resources