Is it possible to to rewrite the base URL in Sinatra? - ruby

Is it possible to to rewrite the base URL?
E.g. instead of www.host.com/ to use www.host.com/blah/ as
a base url and so:
get '/' do
...
end
would work for www.host.com/blah/
I could append to all my routes '/blah/..' but any gems etc.
would fail to work as well.
This can be done in Rails easily and I would like to have it in Sinatra as well.

I use a rack middleware for this rack-rewrite and I am quite happy with it :)
use Rack::Rewrite do
rewrite %r{^/\w{2}/utils}, '/utils'
rewrite %r{^/\w{2}/ctrl}, '/ctrl'
rewrite %r{^/\w{2}/}, '/'
end
EDIT:
Not sure if I understand your problem, but here are a config.ru file
# encoding: utf-8
require './config/trst_conf'
require 'rack-flash'
require 'rack/rewrite'
use Rack::Session::Cookie, :secret => 'zsdgryst34kkufklfSwsqwess'
use Rack::Flash
use Rack::Rewrite do
rewrite %r{^/\w{2}/auth}, '/auth'
rewrite %r{^/\w{2}/utils}, '/utils'
rewrite %r{^/\w{2}/srv}, '/srv'
rewrite %r{^/\w{2}/}, '/'
end
map '/auth' do
run TrstAuth.new
end
map '/utils' do
run TrstUtils.new
end
map '/srv' do
map '/tsk' do
run TrstSysTsk.new
end
map '/' do
run TrstSys.new
end
end
map '/' do
run TrstPub.new
end
and an example Sinatra::Base subclass
# encoding: utf-8
class TrstAuth < Sinatra::Base
# Render stylesheets
get '/stylesheets/:name.css' do
content_type 'text/css', :charset => 'utf-8'
sass :"stylesheets/#{params[:name]}", Compass.sass_engine_options
end
# Render login screen
get '/login' do
haml :"/trst_auth/login", :layout => request.xhr? ? false : :'layouts/trst_pub'
end
# Authentication
post '/login' do
if user = TrstUser.authenticate(params[:login_name], params[:password])
session[:user] = user.id
session[:tasks] = user.daily_tasks
flash[:msg] = {:msg => {:txt => I18n.t('trst_auth.login_msg'), :class => "info"}}.to_json
redirect "#{lang_path}/srv"
else
flash[:msg] = {:msg => {:txt => I18n.t('trst_auth.login_err'), :class => "error"}}.to_json
redirect "#{lang_path}/"
end
end
# Logout
get '/logout' do
session[:user] = nil
session[:daily_tasks] = nil
flash[:msg] = {:msg => {:txt => I18n.t('trst_auth.logout_msg'), :class => "info"}}.to_json
redirect "#{lang_path}/"
end
end
maybe this helps :) full source on github.

In a before block you can edit env['PATH_INFO]`; Sinatra will then use the edited value for routing.
For your example, something like this might work...
before do
env['PATH_INFO'].sub!(/^\/blah/, '')
end
I agree with the other answers that using a middleware component is a more robust solution but if you want something concise and simple, that works inside the Sinatra app instead of via config.ru, then munging the Rack environment is not bad.

You could have a look at https://github.com/josh/rack-mount, maybe that one can help you out?

Related

How to custom layout directory in sinatra

I have a Sinatra app with multiple layouts, one for admin panel, and one for public. I want to isolate them into their own subdirectory in views:
views/
views/layout.erb (for public)
views/auth/
views/auth/layout.erb (for admin)
but I got error. config.ru :
require 'sinatra'
get "/" do
erb :layout
end
get "/auth" do
erb :layout => :'auth/layout'
end
First, the second "erb" call is wrong. Change it to:
erb :layout, :layout_options => { :views => 'views/auth' }
If you want use Sinatra's "Classic Style", the code you showed should not be in config.ru. Move the code from config.ru to another file, e.g. app.rb. Correcting the second "erb" call, app.rb will contain:
# app.rb
require 'sinatra'
get "/" do
erb :layout
end
get "/auth" do
erb :layout, :layout_options => { :views => 'views/auth' }
end
Run it like this: ruby app.rb. It will by default start a local server on port 4567.
Now the URLs http://localhost:4567 and http://localhost:4567/auth should work.
Remember, you should not render a layout.erb file directly. It is used to wrap your views. Check Sinatra: Getting Started for more examples.

Unable to use Warden in Sinatra App: env['warden'] returns nil

I'm writing a Sinatra Rack App and I want to use Warden for authentication. I'm using heroku's toolbelt so I use foreman to run my app. I've found some code that's presumably supposed to get this working. Unfortunately, when I attempt to actually access the Warden env object, it is nil.
I've attempted to use the sinatra_warden gem, but it also has its own bugs (might be related to this one).
config.ru:
require './web.rb'
use Rack::Static, :urls => ["/css", "/img", "/js"], :root => "public"
run MyApp
web.rb:
require 'sinatra'
require 'warden'
require 'data_mapper'
require './config/datamapper.rb'
require './config/warden.rb' # I've tried this inside of MyApp, still didn't work
class MyApp < Sinatra::Base
get '/test' do
env['warden'].authenticate! # env['warden'] is nil :(
end
end
config/warden.rb:
use Rack::Session::Cookie, :secret => ENV['SESSION_SECRET']
use Warden::Manager do |manager|
manager.default_strategies :password
manager.failure_app = MyApp.new
end
Warden::Manager.serialize_into_session { |user| user.id }
Warden::Manager.serialize_from_session { |id| User.get(id) }
Warden::Manager.before_failure do |env,opts|
# Sinatra is very sensitive to the request method
# since authentication could fail on any type of method, we need
# to set it for the failure app so it is routed to the correct block
env['REQUEST_METHOD'] = "POST"
end
Warden::Strategies.add(:password) do
def valid?
params["email"] || params["password"]
end
def authenticate!
u = User.authenticate(params["email"], params["password"])
u.nil? ? fail!("Could not log in") : success!(u)
end
end
Versions:
Sinatra: 1.1.0
Warden: 1.2.1
Rack: 1.4.1
Ruby: 1.9.3p194
Foreman: 0.60.0
Any ideas how to use Warden the set up I've described?
(P.S. Out of curiosity, what exactly is the env variable?)
Rack internally uses the class Rack::Builder to parse your config.ru file and wrap directives to build up the middleware components.
I believe your builder calls to use in config/warden.rb are getting ignored. It may work to remove the directives from that file and add them to the middleware stack in config.ru:
require './web.rb'
use Rack::Session::Cookie, :secret => ENV['SESSION_SECRET']
use Warden::Manager do |manager|
manager.default_strategies :password
manager.failure_app = MyApp.new
end
use Rack::Static, :urls => ["/css", "/img", "/js"], :root => "public"
run MyApp
Put a link to your config/warden in your config.ru
require File.dirname(__FILE__) + '/config/warden'
Read the warden readme. Or look right in the lib/warden.rb
I put
Warden.test_mode!
in place of the env call at the /test path and get a nice blank page at
http://localhost:9292/test
Some bloggers have stated that there isn't a lot of documentation for warden but I disagree. There is a whole wiki. see https://github.com/hassox/warden/wiki
Take it slow and find out how to use middleware in Rack. Here's a very good article https://blog.engineyard.com/2015/understanding-rack-apps-and-middleware
I think maybe you might want to start out with tests as I found a good example and you could use it with your app.
ENV['RACK_ENV'] = 'test'
require 'test/unit'
require 'rack/test'
require File.dirname(__FILE__) + '/web'
class AuthenticationTest < Test::Unit::TestCase
include Rack::Test::Methods
def app
WardenTest #MyApp
end
def test_without_authentication
get '/protected'
assert_equal 401, last_response.status
end
def test_with_bad_credentials
authorize 'bad', 'boy'
get '/protected'
assert_equal 401, last_response.status
end
def test_with_proper_credentials
authorize 'admin', 'admin'
get '/protected'
assert_equal 200, last_response.status
assert_equal "You're welcome, authenticated client", last_response.body
end
end
Then a few routes added to your app.
helpers do
def protected!
return if authorized?
headers['WWW-Authenticate'] = 'Basic realm="Restricted Area"'
halt 401, "Not authorized\n"
end
def authorized?
#auth ||= Rack::Auth::Basic::Request.new(request.env)
#auth.provided? and #auth.basic? and #auth.credentials and
#auth.credentials == ['admin', 'admin']
end
end
get '/' do
"Everybody can see this page"
end
get '/protected' do
protected!
"You're welcome, authenticated client"
end
In my experience working with Ruby, it's always a good idea to start out with tests for any new project. I often test little pieces first though just to gain an understanding of how they work.
Once you get a better understanding of Rack, especially Rack::Builder, you can use
map '/test' do
...all the middleware needed
run App
end
and try out different configurations to see which ones work best for your needs as I'm doing while I write this.
Enjoy! ;-)

Rails routes with optional scope ":locale"

I'm working on a Rails 3.1 app and I'd like to set specific routes for the different languages the app is going to support.
/es/countries
/de/countries
…
For the default language ('en'), I don't want the locale to be displayed in the url.
/countries
Here is the route definition I've set.
scope "(:locale)", :locale => /es|de/ do
resources :countries
end
It works great, until I try to use a path helper with 'en' as the locale.
In the console :
app.countries_path(:locale => 'fr')
=> "/fr/countries"
app.countries_path(:locale => 'en')
=> "/countries?locale=en"
I don't want the "?locale=en".
Is there a way to tell rails that with an 'en' locale, the locale param should not be added to the url?
Thanks
This SHOULD be a better solution:
In your routes.rb,
scope "(:locale)", locale: /#{I18n.available_locales.join("|")}/, defaults: {locale: "en"} do
As MegaTux said, set defaults: {locale: "en"} in the scope.
The advantage:
The jlfenaux solution works in most contexts, but not all. In certain contexts (like basically anything outside of your main controllers and views), the path helpers will get confused and put the object or object.id in the locale parameter, which will cause errors. You'll find yourself putting locale: nil in lots of path helpers to avoid those errors.
The possible problem:
It seems that defaults: {locale: "en"} always overrides any other value you pass in for locale. The option is named default, so I'd expect it to assign locale to 'en' only when there's no value already, but that's not what happens. Anyone else experiencing this?
I finally figured out how to do it easily. You just have to set the default_url_options in the app controller as below.
def default_url_options(options={})
{ :locale => I18n.locale == I18n.default_locale ? nil : I18n.locale }
end
This way, you are sure the locale isn't sent to the path helpers.
If you don't want the query string you don't have to pass it to the helper:
1.9.2 (main):0 > app.countries_path(:locale=>:de)
=> "/de/countries"
1.9.2 (main):0 > app.countries_path
=> "/countries"
1.9.2 (main):0 > app.countries_path(:locale=>:en)
=> "/countries?locale=en"
1.9.2 (main):0 > app.countries_path
=> "/countries"
1.9.2 (main):0 > app.countries_path(:locale=>nil)
=> "/countries"
I'm doing a combination of what #Arcolye and #jifenaux are doing, plus something extra to keep the code as DRY as possible. It might not be suitable for everybody, but in my case, whenever I want to support a new locale I also have to create a new .yml file in config/locales/ anyways, so this is how it works best for me.
config/application.rb:
locale_files = Dir["#{Rails.root}/config/locales/??.yml"]
config.i18n.available_locales = locale_files.map do |d|
d.split('/').last.split('.').first
end
config.i18n.default_locale = :en
config/routes.rb
root_path = 'pages#welcome'
scope '(:locale)', locale: /#{I18n.available_locales.join('|')}/ do
# ...
end
root to: root_path
get '/:locale', to: root_path
app/controllers/application_controller.rb:
private
def default_url_options(options = {})
if I18n.default_locale != I18n.locale
{locale: I18n.locale}.merge options
else
{locale: nil}.merge options
end
end
If you decide to put default_url_options in the application_controller to fix your path helpers, keep in mind you might want to put it in your admin's application_contoller as well
In my case I solved this problem using this technique:
class ApplicationController < ActionController::Base
layout -> {
if devise_controller?
'devise'
end
}
before_action :set_locale
def set_locale
I18n.locale = params[:locale] || I18n.default_locale
end
def url_options
{ :locale => I18n.locale }.merge(super)
end
end

Is there a unified way to get content at a file:// or http:// URI scheme in Ruby?

It appears the Net::HTTP library doesn't support loading of local file via file:// . I'd like to configure loading of content from a file or remotely, depending on environment.
Is there a standard Ruby way to access either type the same way, or barring that some succinct code that branches?
Do you know about open-uri?
require 'open-uri'
open("/home/me/file.txt") { |f| ... }
open("http://www.google.com") { |f| ... }
So to support either "http://" or "file://" in one statement, simply remove the "file://" from the beginning of the uri if it is present (and no need to do any processing for "http://"), like so:
uri = ...
open(uri.sub(%r{^file://}, ''))
Here's some experimental code that teaches "open-uri" to handle "file:" URIs:
require 'open-uri'
require 'uri'
module URI
class File < Generic
def open(*args, &block)
::File.open(self.path, &block)
end
end
##schemes['FILE'] = File
end
As Ben Lee pointed out, open-uri is the way to go here. I've also used it in combination with paperclip for storing resources associated with models, which makes everything brilliantly simple.
require 'open-uri'
class SomeModel < ActiveRecord::Base
attr_accessor :remote_url
has_attached_file :resource # etc, etc.
before_validation :get_remote_resource, :if => :remote_url_provided?
validates_presence_of :remote_url, :if => :remote_url_provided?,
:message => 'is invalid or missing'
def get_remote_resource
self.resource = SomeModel.download_remote_resource(self.remote_url)
end
def self.download_remote_resource (uri)
io = open(URI.parse(uri))
def io.original_filename; base_uri.path.split('/').last; end
io.original_filename.blank? ? nil : io
rescue
end
end
# SomeModel.new(:remote_url => 'http://www.google.com/').save

Ruby Rack - mounting a simple web server that reads index.html as default

I'm trying to get some information from this tutorial: http://m.onkey.org/2008/11/18/ruby-on-rack-2-rack-builder
basically I want to have a file config.ru that tell rack to read the current directory so I can access all the files just like a simple apache server and also read the default root with the index.html file...is there any way to do it?
my current config.ru looks like this:
run Rack::Directory.new('')
#this would read the directory but it doesn't set the root to index.html
map '/' do
file = File.read('index.html')
run Proc.new {|env| [200, {'Content-Type' => 'text/html'}, file] }
end
#using this reads the index.html mapped as the root but ignores the other files in the directory
So I don't know how to proceed from here...
I've also tried this following the tutorials example but thin doesn't starts properly.
builder = Rack::Builder.new do
run Rack::Directory.new('')
map '/' do
file = File.read('index.html')
run Proc.new {|env| [200, {'Content-Type' => 'text/html'}, file] }
end
end
Rack::Handler::Thin.run builder, :port => 3000
Thanks in advance
I think that you are missing the the rackup command. Here is how it is used:
rackup config.ru
This is going to run your rack app on port 9292 using webrick. You can read "rackup --help" for more info how you can change these defaults.
About the app that you want to create. Here is how I think it should look like:
# This is the root of our app
#root = File.expand_path(File.dirname(__FILE__))
run Proc.new { |env|
# Extract the requested path from the request
path = Rack::Utils.unescape(env['PATH_INFO'])
index_file = #root + "#{path}/index.html"
if File.exists?(index_file)
# Return the index
[200, {'Content-Type' => 'text/html'}, File.read(index_file)]
# NOTE: using Ruby >= 1.9, third argument needs to respond to :each
# [200, {'Content-Type' => 'text/html'}, [File.read(index_file)]]
else
# Pass the request to the directory app
Rack::Directory.new(#root).call(env)
end
}
I ended up on this page looking for a one liner...
If all you want is to serve the current directory for a few one-off tasks, this is all you need:
ruby -run -e httpd . -p 5000
Details on how it works: http://www.benjaminoakes.com/2013/09/13/ruby-simple-http-server-minimalist-rake/
You can do this using Rack::Static
map "/foo" do
use Rack::Static,
:urls => [""], :root => File.expand_path('bar'), :index => 'index.html'
run lambda {|*|}
end
For me, using Ruby 2.0 and Rack 1.5.2, sinm solution worked for serving the index page (both as default page for root and loaded explicitly), but for other files I obtained errors similar to the following:
Rack::Lint::LintError: Status must be >=100 seen as integer
I combined sinm solution with this SO answer and the snippet found on Heroku documentation to obtain the desired behavior (assuming that the entire site is contained in a folder called public):
use Rack::Static,
:urls => ["/images", "/js", "/css"],
:root => "public",
:index => 'index.html'
run Rack::File.new("public")
My example for doing the exact same below:
module Rack
class DirectoryIndex
def initialize(app)
#app = app
end
def call(env)
index_path = ::File.join($documentRoot, Rack::Request.new(env).path.split('/'), 'index.html')
if ::File.exists?(index_path)
return [200, {"Content-Type" => "text/html"}, [::File.read(index_path)]]
else
#app.call(env)
end
end
end
end
require 'rack_directory_index.rb'
$documentRoot = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'build'))
Capybara.app = Rack::Builder.new do |builder|
puts "Creating static rack server serving #{$documentRoot}"
use Rack::DirectoryIndex
run Rack::Directory.new($documentRoot)
end
Capybara.configure do |config|
config.run_server = true
end
The solution is mostly a copy and paste from different answers but it works fine. You can find it as a gist here aswell, good luck

Resources