How do I config.ru properly in modular Sinatra application.? - ruby

I'm trying to use subclassing style in Sinatra application. So, I have a main app like this.
class MyApp < Sinatra::Base
get '/'
end
...
end
class AnotherRoute < MyApp
get '/another'
end
post '/another'
end
end
run Rack::URLMap.new \
"/" => MyApp.new,
"/another" => AnotherRoute.new
In config.ru I understand that it's only for "GET" how about other resources (e.g. "PUT", "POST")? I'm not sure if I'm missing someting obvious. And also if I have ten path (/path1, /path2, ...) do I have to config them all in config.ru even though they are in the same class?

app.rb
class MyApp < Sinatra::Base
get '/'
end
end
app2.rb If you want two separate files. Note this inherits from Sinatra::Base not MyApp.
class AnotherRoute < Sinatra::Base
get '/'
end
post '/'
end
end
The config.ru
require 'bundler/setup'
Bundler.require(:default)
require File.dirname(__FILE__) + "/lib/app.rb"
require File.dirname(__FILE__) + "/lib/app2.rb"
map "/" do
run MyApp
end
map "/another" do
run AnotherRoute
end

You could write this as
class MyApp < Sinatra::Base
get '/'
end
get '/another'
end
post '/another'
end
end
in config.ru
require './my_app'
run MyApp
Run:
rackup -p 1234
Refer to documentation at http://www.sinatrarb.com/intro#Serving%20a%20Modular%20Application

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 in MyApp
/another: will go to the / route in AnotherRoute. Since AnotherRoute extends MyApp this will be the same as / in MyApp (but in a different instance).
URLMap sees /another and uses it to map to AnotherRoute, stripping this part of the request from the path. AnotherRoute then only sees /.
/another/another: will be routed to the two /another routes in AnotherRoute. Again, the first another is used by the URLMap to route the request to AnotherRoute. AnotherRoute then only sees the second another as the path.
Note that this path will respond to both GET and POST requests, each being handled by the appropriate block.
It's not clear what you're trying to do, but I think you can achieve what you want by running an instance of 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

Getting Rack mounted path in Sinatra application

Suppose I have the following config.ru file
require './status.rb'
map "/status" do
run Sinatra::Application
end
and the status.rb is a simple
require 'sinatra'
get '/' do
'Some status here...'
end
I'd like to know where the Sinatra application is mounted inside status.rb (for example to provide proper paths to resources). Is there a way of retrieving that information from Rack?
To get where the app is mounted you can use request.script_name.
get '/' do
p request.script_name # will print "/status"
'Some status here...'
end
If you’re generating urls for resources, you might want to look at the url method instead. That will take into account things like proxies as well as where the app is mounted:
get '/' do
p url('foo') # will print "http://localhost:9292/status/foo"
'Some status here...'
end

How do you set the WEBRick options parameter in the run method with Padrino

I don't want to monkey patch Padrino.
I still want to be able to use the command padrino start -d from the command line.
I want to get SSL up and running within padrino. Within Sinatra I just do:
Rack::Handler::WEBrick.run MyServer, MyServerOptionsWithAppropriateSSLStuffEtc
I found the file deep inside the Padrino core that handles setting these options, but I really don't want to monkey patch the application.
Ideally I'd like there to be be some way I could set the options within my Padrino::Application subclass.
So far I haven't found any documentation on how to do this, or if this is even possible.
mmm, you should be able to do the same.
In your project folder you should see config.ru
Try to edit it removing last line with:
Rack::Handler::WEBrick.run Padrino.application, MyServerOptionsWithAppropriateSSLStuff
Then from command line:
$ rackup
I know this is old, but in case anybody is trying to do this cleanly, here is what I use:
class MyApplication < ::Sinatra::Base
# ...
def self.server_settings
{ key: value, ... }
end
# ...
end
You can also inject settings prior to runtime:
MyApplication.class_exec(server_settings) do |server_params|
def self.server_settings
server_params
end
end
I frequently use the second example for injecting a custom logger into my application for specs.
For example:
module CustomLogger
def logger
settings.try(:server_settings)[:Logger] || request.logger
end
end
MyApplication.class_exec(CustomLogger) do |logger_module|
helpers logger_module
def self.server_settings
# global specified in guard/spec helper
{ Logger: $LOGGER }
end
end
class MyApplication < ::Sinatra::Base
enable :logging
get '/' do
logger.info "FOO"
end
end
MyApplication.run!
See this source link for more info about server_settings usage in Application::self.run!.

Why this sinatra code is not working

I'm having a hard time figuring out what I'm doing wrong here. The result is empty and I'm looking it to return hello (calling the method testing through the before helper).
require 'rubygems'
require 'sinatra'
get '/' do
end
before do
testing
end
def testing
return "hello"
end
There are several problems here. For one thing you have to actually call the output or variable you want in the view, most typically as an instance variable (otherwise every user gets the same output.) Take the modified code below for example:
require 'rubygems'
require 'sinatra'
get '/' do
#word
end
before do
testing
end
def testing
#word = "hello"
end
Check out the Sinatra Book, a free online resource, for information on getting started with Sinatra.
Because you are not calling the output on the Get request, you need to tell your Get Method to return an output. Like thekungfuman suggested. or try the Minimal Hello World Sinatra app as follows:
#imports
require 'rubygems'
require 'sinatra'
#Get Request on Root ("/")
get '/' do
"Hello Sinatra World!"
end
Also it's useful to put your program under a class, so you can also do :
#imports
require 'rubygems'
require 'sinatra/base'
#My Application Class
class AppName < Sinatra::base
get '/' do
'Hello Sinatra World!'
end
end
AppName.run!
This way you can also use this as a seperate app file and import it within other files like.
require 'app_name' #replace this with the name of the physical file
#Run Application "AppName"
AppName.run!

Sinatra - Setting Cache-Control Headers via config.ru

I'm currently running an Octopress (based on Jekyll) site on Heroku's Cedar stack—the code lives here: https://github.com/elithrar/octopress
I want to selectively apply a Cache-Control header based on the file type:
.html files get a value of public, max-age=3600
.css|.js|.png|.ico (etc) get a value of public, max-age=604800 - alternatively, I'd like to apply this rule to anything served from the /stylesheets', '/javascripts', '/imgs' directories.
Have used both set :static_cache_control , [:public, :max_age => 3600] and just the vanilla cache_control :public, :max_age => 3600 statements with no luck.
I have managed to set public, max-age=3600 on the articles themselves (e.g. /2012/lazy-sundays/), but have not been able to get the headers to apply to the CSS/JS (e.g. /stylesheets/screen.css)
My config.ru currently looks like this (updated):
require 'bundler/setup'
require 'sinatra/base'
# The project root directory
$root = ::File.dirname(__FILE__)
class SinatraStaticServer < Sinatra::Base
get(/.+/) do
cache_control :public, :max_age => 7200
send_sinatra_file(request.path) {404}
end
not_found do
send_sinatra_file('404.html') {"Sorry, I cannot find #{request.path}"}
cache_control :no_cache, :max_age => 0
end
def send_sinatra_file(path, &missing_file_block)
file_path = File.join(File.dirname(__FILE__), 'public', path)
file_path = File.join(file_path, 'index.html') unless file_path =~ /\.[a-z]+$/i
File.exist?(file_path) ? send_file(file_path) : missing_file_block.call
end
end
use Rack::Deflater
run SinatraStaticServer
Here's how to set long expiry headers for static assets, and an arbitrary expiry header for you main content on Heroku:
gemfile:
gem 'rack-contrib'
config.ru:
require 'rack/contrib'
get '*.html' do |page|
# whatever code you need to serve up your main pages
# goes here... use Rack::File I guess.
page
end
# Set content headers for that content...
before do
expires 5001, :public, :must_revalidate
end
# Assets in /static/stylesheets (domain.com/stylesheets)
# are served by Rack StaticCache, with a default 2 year expiry.
use Rack::StaticCache, :urls => ["/stylesheets"], :root => Dir.pwd + '/static'
run Sinatra::Application
By default that will give you a 2 year expiry for content listed in the array of urls (static/stylesheets, static/images etc.).
You have to move from /public to /static because otherwise you are unnecessarily fighting with Heroku's nginx config (the right place to apply these sorts of settings really...).
I know you said you're trying to not use Rack Contrib but that makes no sense. There's no harm in using a tiny 90 line library to do this https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/static_cache.rb.
The "right" way would be to host static content on an environment where you can configure nginx, and the second best way is renaming your static file path so heroku ignores it, and use rack static to serve static files with the headers you want.
--
Also to be clear, simply renaming your public folder to something else will allow you to do this via routes, and the normal Sinatra expires function. But I'd use StaticCache because it's less verbose. (The real issue is Heroku doesn't let nginx talk to your app for requests to public/, I believe.)
I have very little familiarity with Sinatra, but I think something like this would do the trick:
class SinatraStaticServer < Sinatra::Base
before '*.html' do
response.headers['Cache-Control'] = 'public, max-age=3600'
end
before %r{\.(css)|(js)|(png)|(ico)} do
response.headers['Cache-Control'] = 'public, max-age=604800'
end
# ...
end
Update: I looked into it further when you said that the above was not successfully getting the headers added. I determined that the issue was that Sinatra was automatically serving the files out of public/ rather than going through the app, and thus the headers weren't being added. My solution was to move the static files from public/ to public/public/ and adjust send_sinatra_file accordingly:
class SinatraStaticServer < Sinatra::Base
# ...
def send_sinatra_file(path, &missing_file_block)
file_path = File.join(File.dirname(__FILE__), 'public/public', path)
file_path = File.join(file_path, 'index.html') unless file_path =~ /\.[a-z]+$/i
File.exist?(file_path) ? send_file(file_path) : missing_file_block.call
end
# ...
end
I confirmed that this works on my machine. Note that I used response.headers['Cache-Control'] as in the first part of my answer, not set :static_cache_control which you tried, but I think is meant to only be run once, in a configure do block.
Also note that with this current set-up, a 404 that matches the above, e.g. nonexistant.png will serve a 404 status with the Cache-Control header still there. I can see several ways around that, but I figure you do to, so I'm just pointing it out and figure you'll deal with it however you like.

Using Sinatra for larger projects via multiple files

It seems that in Sinatra all route handlers are being written into a single file, if I understand right it acts as a one large/small controller. Is there any way to split it into separate independent files, so when let's say somebody calls "/" - one action is executed, and if smth like "/posts/2" is received then another action - similar logic that is applied in PHP?
Here is a basic template for Sinatra apps that I use. (My larger apps have 200+ files broken out like this, not counting vendor'd gems, covering 75-100 explicit routes. Some of these routes are Regexp routes covering an additional 50+ route patterns.) When using Thin, you run an app like this using:
thin -R config.ru start
Edit: I'm now maintaining my own Monk skeleton based on the below called Riblits. To use it to copy my template as the basis for your own projects:
# Before creating your project
monk add riblits git://github.com/Phrogz/riblits.git
# Inside your empty project directory
monk init -s riblits
File Layout:
config.ru
app.rb
helpers/
init.rb
partials.rb
models/
init.rb
user.rb
routes/
init.rb
login.rb
main.rb
views/
layout.haml
login.haml
main.haml
config.ru
root = ::File.dirname(__FILE__)
require ::File.join( root, 'app' )
run MyApp.new
app.rb
# encoding: utf-8
require 'sinatra'
require 'haml'
class MyApp < Sinatra::Application
enable :sessions
configure :production do
set :haml, { :ugly=>true }
set :clean_trace, true
end
configure :development do
# ...
end
helpers do
include Rack::Utils
alias_method :h, :escape_html
end
end
require_relative 'models/init'
require_relative 'helpers/init'
require_relative 'routes/init'
helpers/init.rb
# encoding: utf-8
require_relative 'partials'
MyApp.helpers PartialPartials
require_relative 'nicebytes'
MyApp.helpers NiceBytes
helpers/partials.rb
# encoding: utf-8
module PartialPartials
def spoof_request(uri,env_modifications={})
call(env.merge("PATH_INFO" => uri).merge(env_modifications)).last.join
end
def partial( page, variables={} )
haml page, {layout:false}, variables
end
end
helpers/nicebytes.rb
# encoding: utf-8
module NiceBytes
K = 2.0**10
M = 2.0**20
G = 2.0**30
T = 2.0**40
def nice_bytes( bytes, max_digits=3 )
value, suffix, precision = case bytes
when 0...K
[ bytes, 'B', 0 ]
else
value, suffix = case bytes
when K...M then [ bytes / K, 'kiB' ]
when M...G then [ bytes / M, 'MiB' ]
when G...T then [ bytes / G, 'GiB' ]
else [ bytes / T, 'TiB' ]
end
used_digits = case value
when 0...10 then 1
when 10...100 then 2
when 100...1000 then 3
else 4
end
leftover_digits = max_digits - used_digits
[ value, suffix, leftover_digits > 0 ? leftover_digits : 0 ]
end
"%.#{precision}f#{suffix}" % value
end
module_function :nice_bytes # Allow NiceBytes.nice_bytes outside of Sinatra
end
models/init.rb
# encoding: utf-8
require 'sequel'
DB = Sequel.postgres 'dbname', user:'bduser', password:'dbpass', host:'localhost'
DB << "SET CLIENT_ENCODING TO 'UTF8';"
require_relative 'users'
models/user.rb
# encoding: utf-8
class User < Sequel::Model
# ...
end
routes/init.rb
# encoding: utf-8
require_relative 'login'
require_relative 'main'
routes/login.rb
# encoding: utf-8
class MyApp < Sinatra::Application
get "/login" do
#title = "Login"
haml :login
end
post "/login" do
# Define your own check_login
if user = check_login
session[ :user ] = user.pk
redirect '/'
else
redirect '/login'
end
end
get "/logout" do
session[:user] = session[:pass] = nil
redirect '/'
end
end
routes/main.rb
# encoding: utf-8
class MyApp < Sinatra::Application
get "/" do
#title = "Welcome to MyApp"
haml :main
end
end
views/layout.haml
!!! XML
!!! 1.1
%html(xmlns="http://www.w3.org/1999/xhtml")
%head
%title= #title
%link(rel="icon" type="image/png" href="/favicon.png")
%meta(http-equiv="X-UA-Compatible" content="IE=8")
%meta(http-equiv="Content-Script-Type" content="text/javascript" )
%meta(http-equiv="Content-Style-Type" content="text/css" )
%meta(http-equiv="Content-Type" content="text/html; charset=utf-8" )
%meta(http-equiv="expires" content="0" )
%meta(name="author" content="MeWho")
%body{id:#action}
%h1= #title
#content= yield
Absolutely. To see an example of this I recommend downloading the Monk gem, described here:
https://github.com/monkrb/monk
You can 'gem install' it via rubygems.org. Once you have the gem, generate a sample app using the instructions linked above.
Note that you don't have to use Monk for your actual development unless you want to (in fact I think it may not be current). The point is to see how you can easily structure your app in the MVC style (with separate controller-like route files) if you want to.
It's pretty simple if you look at how Monk handles it, mostly a matter of requiring files in separate directories, something like (you'll have to define root_path):
Dir[root_path("app/**/*.rb")].each do |file|
require file
end
Do a Google search for "Sinatra boilerplate" to get some ideas for how others are laying out their Sinatra applications. From that you can probably find one that suits your needs or simply make your own. It's not too hard to do. As you develop more Sinatra apps, you can add to your boilerplate.
Here's what I made and use for all of my projects:
https://github.com/rziehl/sinatra-boilerplate
I know this is an old query but I still can't believe no one mentioned Padrino You can use it as a framework on top of Sinatra, or piecemeal adding only the gems that interest you. It kicks ten buttloads of ass!
The key for modularity on Sinatra for larger projects is learning to use the underlying tools.
SitePoint has a very good tutorial from where you can see modular Sinatra apps and helpers. However you should pay special attention to one important detail. You keep multiple Sinatra apps and mount them with Rackup. Once you know how to write a basic app look at the config.ru file of that tutorial and observe how they mount independent Sinatra apps.
Once you learn to run Sinatra with Rack a whole new world of modularity strategies will open up. This obviously invites to try something really useful: now you can rely on having individual Gems for each sub application, what might enable you to easily version your modules.
Do not underestimate the power of using gem-modules for your app. You can easily test experimental changes in a well delimited environment and easily deploy them. Equally easy to revert back if something goes wrong.
There are a thousand ways to organize your code, so it would not hurt trying to get a layout similar to Rails. However there are also some great posts about how to customize your own structure. That post covers other frequent needs of most web developers.
If you have the time, I encourage you to learn more about Rack, the common ground for any Ruby based web application. It might have a far lesser impact in how you do your work, but there are always certain tasks that most people do on their apps that fits better as a Rack middleware.
My approach to host different projects on the same site is to use sinatra/namespace in such way:
server.rb
require "sinatra"
require "sinatra/namespace"
if [ENV["LOGNAME"], ENV["USER"]] == [nil, "naki"]
require "sinatra/reloader"
register Sinatra::Reloader
set :port, 8719
else
set :environment, :production
end
for server in Dir.glob "server_*.rb"
require_relative server
end
get "/" do
"this route is useless"
end
server_someproject.rb
module SomeProject
def self.foo bar
...
end
...
end
namespace "/someproject" do
set :views, settings.root
get "" do
redirect request.env["REQUEST_PATH"] + "/"
end
get "/" do
haml :view_someproject
end
post "/foo" do
...
SomeProject.foo ...
end
end
view_someproject.haml
!!!
%html
...
Another detail about subprojects I used was to add their names, description and routes to some kind of global variable, that is used by "/" to make a guide homepage, but I don't have a snippet right now.
Reading the docs here:
Sinatra Extensions
It appears that Sinatra allows you to decompose your application into Ruby Modules, which can be pulled in through the Sinatra "register" method or "helpers" methods, like so:
helpers.rb
require 'sinatra/base'
module Sinatra
module Sample
module Helpers
def require_logged_in()
redirect('/login') unless session[:authenticated]
end
end
end
end
routing/foos.rb
require 'sinatra/base'
module Sinatra
module Sample
module Routing
module Foos
def self.registered(app)
app.get '/foos/:id' do
# invoke a helper
require_logged_in
# load a foo, or whatever
erb :foos_view, :locals => { :foo => some_loaded_foo }
end
end
end
end
end
end
app.rb
#!/usr/bin/env ruby
require 'sinatra'
require_relative 'routing/foos'
class SampleApp < Sinatra::Base
helpers Sinatra::Sample::Helpers
register Sinatra::Sample::Routing::Foos
end
When Monk didn't work for me, I started working on templates myself.
If you think about it, there is nothing special about tying up a set of files. The monk philosophy was explained to me early in 2011 during RedDotRubyConf and they have specifically told me that it's really optional to use it especially now that it's hardly maintained.
This is a good start for those who want to use ActiveRecord:
Simple Sinatra MVC
https://github.com/katgironpe/simple-sinatra-mvc

Resources