Change Sinatra views directory location - ruby

I'd like to structure a Sinatra application more like a Rails application with the following structure:
.
├── app
│ ├── models
│ │ └── a_model.rb
│ └── views
│  └── a_view.erb
└── app.rb
According to the documentation, it can be done by overwriting the :views setting:
:views - view template directory
A string specifying the directory where view templates are located. By default, this is assumed to be a directory named “views” within the application’s root directory (see the :root setting). The best way to specify an alternative directory name within the root of the application is to use a deferred value that references the :root setting:
set :views, Proc.new { File.join(root, "templates") }
I've setup :root and :views:
set :root, File.dirname(__FILE__)
set :views, Proc.new { File.join(root, 'app', 'views') }
# Also tried some variations like:
# set :views, 'app/views/'
# set :views, Proc.new { File.join(setting.root, 'app', 'views' }
# set :public_folder, Proc.new { File.join(root, 'app', 'views' }
# ...
class MyApp < Sinatra::Base
get '/' do
erb :a_view
end
end
But I'm always facing the same error message:
No such file or directory # rb_sysopen - /path/to/my/app/views/a_view.erb
Indeed, settings.views is evaluated to /path/to/my/app/views (instead of /path/to/my/app/app/views)
It looks like I cannot control the value of the :views (settings.views) variable. I know I could simply move the views folder to the root location.
Can anyone explain why I'm not able to control theses settings ?

Move settings inside class definition has all the settings are inherited from inside Sinatra::Base
class MyApp < Sinatra::Base
set :root, File.dirname(__FILE__)
set :views, Proc.new { File.join(root, 'app', 'views') }
get '/' do
erb :a_view
end
end
Or
class MyApp < Sinatra::Base
configure do
set :root, File.dirname(__FILE__)
set :views, Proc.new { File.join(root, 'app', 'views') }
end
get '/' do
erb :a_view
end
end

Related

my Rack::Static middleware works with root url in urls parameter and without

I have this in my config.ru:
require 'rack'
require './app.rb'
use(Rack::Static,
:urls => %w(/css /fonts /images /js),
:root => '../static',
:index => 'index.html')
end
run MyApp
it correctly serves static files for all four of the above URLs, but in addition, it also serves index.html from the root URL as if I had :urls => %w(/ /css /fonts /images /js) and I do not understand why this is happening. I could not find any explanation in the documentation.

How to get Liquid template partials (includes) working with Sinatra?

I am new to ruby and I am trying to get template partials working in my Sinatra + Liquid project.
I have several template partials in my /includes directory.
How can I get all of these templates working as liquid partials so I can use them with liquid include tag?
What I actually have done:
# Sinatra First App
require 'sinatra'
require 'sinatra/config_file'
require 'liquid'
config_file 'config.yml'
# WebRick
set :run, true
set :server, %w[webrick]
# App Paths
set :root, File.dirname(__FILE__)
set :views, File.dirname(__FILE__) + '/views'
set :controlers, File.dirname(__FILE__) + '/controlers'
set :public_folder, Proc.new { File.join(root, "static") }
# Includes Folder
includes = File.dirname(__FILE__) + '/includes'
get '/' do
Liquid::Template.file_system = Liquid::LocalFileSystem.new(includes)
Liquid::Template.parse(includes).render
liquid :index, :locals => { :title => "My Sinatra App"}
end
get '/test' do
"This is the test page."
end
But I am still getting: Liquid error: Illegal template name ''.
Here is my index template:
<html>
{% include 'header' %}
<body>
<h1>{{ title }}</h1>
{{content}}
</body>
</html>
and here is the header part template:
<head>
<title>{{ title }}</title>
</head>
How can I fix it? Thanks for every response.
Of the get '/' route, it seems to me that the first line should be in a configuration block, and the second line shouldn't be needed at all as Tilt/Sinatra should take care of calling render, e.g.
configure do
set :views, File.join(File.dirname(__FILE__),'/includes')
# or just put these in the views dir
end
get '/' do
liquid :index, :locals => { :title => "My Sinatra App"}
end
or, if you want a views directory with sub-directories for partials/includes etc, something like "./views/includes", you could pass the view folder as an option, e.g.
get '/' do
liquid :index, :locals => { :title => "My Sinatra App"}, :views => File.join(File.dirname(__FILE__),'views/includes')
end
or you could try Sinatra Partial (I'm the maintainer).
Since the above didn't work (see comments), I looked at the Sinatra tests for Liquid and played around with the OP's code. I found that the following worked for me:
# ./app.rb
# Sinatra First App
require 'sinatra'
require 'sinatra/config_file'
require 'liquid'
config_file 'config.yml'
configure do
# WebRick
set :run, true
set :server, %w[webrick]
# App Paths
set :root, File.dirname(__FILE__)
set :views, File.dirname(__FILE__) + '/views'
set :controlers, File.dirname(__FILE__) + '/controlers'
set :public_folder, Proc.new { File.join(root, "static") }
Liquid::Template.file_system = Liquid::LocalFileSystem.new(File.join(File.dirname(__FILE__),'views/includes'))
end
get '/' do
liquid :index, :locals => { :title => "My Sinatra App" }
end
get '/test' do
"This is the test page."
end
#./Gemfile
source "https://rubygems.org"
gem "sinatra"
gem "liquid"
gem "sinatra-contrib"
#./views/includes/_header.liquid
<head>
<title>{{ title }}</title>
</head>
#./views/index.liquid
<h1>{{ title }}</h1>
{{content}}
#./views/layout.liquid
<html>
{% include "header" %}
<body>
{{ yield }}
</body>
</html>

modular Sinatra App, setting error handling & configuration globally

I am using Sinatra to build a small Ruby API, and I would like to get some of the errors and configurations set to work at a global level so that i don't need to set them at the start of each of the classes.
My structure is this:
content_api.rb
require 'sinatra/base'
require 'sinatra/namespace'
require 'sinatra/json'
require 'service_dependencies'
require 'api_helpers'
require 'json'
module ApiApp
class ContentApi < Sinatra::Base
helpers Sinatra::JSON
helpers ApiApp::ApiHelpers
include ApiApp::ServiceDependencies
before do
content_type :json
end
get '/' do
content = content_service.get_all_content
content.to_json
end
get '/audio' do
package =content_service.get_type 'Audio'
package.to_json
end
get '/video' do
package =content_service.get_type 'Video'
package.to_json
end
get '/document' do
package =content_service.get_type 'Document'
package.to_json
end
end
end
config.ru:
$LOAD_PATH.unshift *Dir[File.join(File.dirname(__FILE__), '/src/**')]
$LOAD_PATH.unshift *Dir[File.join(File.dirname(__FILE__), '/src/api/**')]
require 'content_api'
require 'package_api'
require 'utility_api'
require 'sinatra/base'
configure do
set :show_exceptions => false
end
error { |err| Rack::Response.new([{'error' => err.message}.to_json], 500, {'Content-type' => 'application/json'}).finish }
Rack::Mount::RouteSet.new do |set|
set.add_route ApiApp::ContentApi, {:path_info => %r{^/catalogue*}}, {}, :catalogue
set.add_route ApiApp::PackageApi, {:path_info => %r{^/package*}}, {}, :package
set.add_route ApiApp::UtilityApi, {:path_info => %r{^/health_check*}}, {}, :health_check
end
When I run this, it will run okay, but when I force a 500 error (shut down MongoDb) I get a standard html type error that states:
<p id="explanation">You're seeing this error because you have
enabled the <code>show_exceptions</code> setting.</p>
If I add the
configure do
set :show_exceptions => false
end
error { |err| Rack::Response.new([{'error' => err.message}.to_json], 500, {'Content-type' => 'application/json'}).finish }
inside the content_api.rb file, then I get a JSON error returned as I would like to receive.
however, as I am building this modular, I don't want to be repeating myself at the top of each class.
Is there a simple way to make this work?
The simplest way to do this would be to reopen Sinatra::Base and add the code there:
class Sinatra::Base
set :show_exceptions => false
error { |err|
Rack::Response.new(
[{'error' => err.message}.to_json],
500,
{'Content-type' => 'application/json'}
).finish
}
end
This is probably not advisable though, and will cause problems if your app includes other non-json modules.
A better solution might be to create a Sinatra extension that you could use in your modules.
module JsonExceptions
def self.registered(app)
app.set :show_exceptions => false
app.error { |err|
Rack::Response.new(
[{'error' => err.message}.to_json],
500,
{'Content-type' => 'application/json'}
).finish
}
end
end
You would then use it by registering it in your modules:
# require the file where it is defined first
class ContentApi < Sinatra::Base
register JsonExceptions
# ... as before
end
As an alternative, inheritance:
class JsonErrorController < Sinatra::Base
configure do
set :show_exceptions => false
end
error { |err| Rack::Response.new([{'error' => err.message}.to_json], 500, {'Content-type' => 'application/json'}).finish }
end
class ContentApi < JsonErrorController
# rest of code follows…
end
From Sinatra Up and Running p73:
Not only settings, but every aspect of a Sinatra class will be
inherited by its subclasses. This includes defined routes, all the
error handlers, extensions, middleware, and so on.

Sinatra public assets not available when uploading to Heroku

I've just started working on a simple Sinatra app and when uploading it to Heroku. None of the files in the public folder seem to be available but it works fine locally.
Are there any obvious reasons this might be happening?
Right now the code is really simple:
require 'rubygems'
require 'sinatra'
require 'bundler/setup'
require 'haml'
require 'rdiscount'
set :static, true
set :public_folder, "#{File.dirname(__FILE__)}/public"
get '/' do
haml :landing
end
__END__
## layout
%html
%head
%meta{charset: "utf-8"}/
%meta{content: "width=device-width, initial-scale=1.0", name: "viewport"}/
%meta{content: "", name: "description"}/
%meta{content: "", name: "author"}/
%title TIL
%link{href: "http://yui.yahooapis.com/pure/0.3.0/pure-min.css", rel: "stylesheet"}
%link{rel: "stylesheet", href: "/styles.css"}
%body
= yield
##landing
%section.hero
.container
.pure-g-r
.pure-u-1
.logo
...
.container
%hr/
.pure-g-r
.pure-u-2-3
.padding-box
:markdown
...
.pure-u-1-3
.padding-box
..
%hr/
.pure-g-r
.pure-u-1
.padding-box
:markdown
...
%hr/
.pure-g-r
.pure-u-1
.padding-box
%h2 ...
.pure-u-1-3
.padding-box
%img.img-rounded{src: "GD-thumbnail.png"}/
:markdown
...
.pure-u-1-3
.padding-box
%img.img-rounded{src: "AL-thumbnail.png"}/
:markdown
...
.pure-u-1-3
.padding-box
%img.img-rounded{src: "BP-thumbnail.png"}/
:markdown
...
%hr/
%footer
.row
.col-lg-12
%p
Local file structure is:
TIL (folder)
- app.rb
- Gemfile
- Procfile
- public (folder)
- AL-thumbnail.png
- BP-thumbnail.png
- GD-thumbnail.png
- logo.png
- styles.css
Have a look in your Heroku log file:
heroku logs
If you can see something like
Rack::Flash::SessionUnavailable - Rack::Flash depends on session middleware.:
Then add
gem "rack-flash-session"
to you Gemfile.
Also add 'require 'rack/flash/test'' to you main file.
This will force heroku to load the desired middelware.

How to include configuration in multiple sinatra application

If my Sinatra Application structure is like this. Copied from Sinatra Help
require 'sinatra/base'
class LoginScreen Sinatra::Base
enable :sessions
get('/login') { haml :login }
post('/login') do
if params[:name] == 'admin' && params[:password] == 'admin'
session['user_name'] = params[:name]
else
redirect '/login'
end
end
end
class MyApp Sinatra::Base
# middleware will run before filters
use LoginScreen
before do
unless session['user_name']
halt "Access denied, please login."
end
end
get('/') { "Hello #{session['user_name']}." }
end
The question would be if the two applications require the same configuration such as, helpers, registers and asset-pack. How do I make it consistent between the two application without duplicating the code. Could I do something like this?
require 'sinatra/base'
class LoginScreen Sinatra::Base
include_relative("config_file.rb")
enable :sessions
get('/login') { haml :login }
post('/login') do
if params[:name] == 'admin' && params[:password] == 'admin'
session['user_name'] = params[:name]
else
redirect '/login'
end
end
end
class MyApp Sinatra::Base
# middleware will run before filters
include_relative("config_file.rb")
use LoginScreen
before do
unless session['user_name']
halt "Access denied, please login."
end
end
get('/') { "Hello #{session['user_name']}." }
end
config_file.rb
helpers Sinatra::Helper1
helpers Sinatra::Helper2
helpers Sinatra::Helper3
register Sinatra1
register Sinatra2
register Sinatra3
How about using inheritance?
class MyBase < Sinatra::Base
configure do
helpers Sinatra::Helper1
register Sinatra1
end
end
class LoginScreen < MyBase
end
class MyApp < MyBase
use LoginScreen
end
This way configuration gets shared among all applications that descend from MyBase.

Resources