I have a modular Sinatra app. I'm setting some custom variables in my configure block and want to access these settings in my model.
The problem is, I get a NoMethodError when I try and access my custom settings from MyModel. Standard settings still seem to work fine though. How can I make this work?
# app.rb
require_relative 'models/document'
class App < Sinatra::Base
configure do
set :resource_path, '/xfiles/i_want_to_believe'
end
get '/' do
#model = MyModel.new
haml :index
end
end
# models/my_model.rb
class MyModel
def initialize
do_it
end
def do_it
...
settings.resource_path # no method error
...
settings.root # works fine
end
end
i think that you should be able to access it via
Sinatra::Application.settings.documents_path
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.
Related
Probably something really basic, but I want to be able to separate my Sinatra routes from controllers. I have this code in my routes.rb:
require 'sinatra/base'
class Server < Sinatra::Base
get '/' do
Action.index
end
end
This is my controller/server.rb
class Action
def sef.index
#user = User.new("Abiodun Shuaib")
haml: index
end
end
It gives the error undefined method 'haml' in Action:Class.
How can I fix this?
You are trying to access method haml in class Action. It simply doesn't contain it.
For example, you can do:
class Server
def index
#user = User.new("Abiodun Shuaib")
haml :index
end
end
By doing this, you will add to Server method index.
Or you can do in such way(it's called Mixin):
module ActionNew
def index
#user = User.new("Abiodun Shuaib")
haml :index
end
end
class Server < Sinatra::Base
include ActionNew
get '/' do
index
end
end
Here's the contents of my app/controllers/application_controller.rb:
require 'sinatra/base'
require 'slim'
require 'colorize'
class ApplicationController < Sinatra::Base
# Global helpers
helpers ApplicationHelper
# Set folders for template to
set :root, File.expand_path(File.join(File.dirname(__FILE__), '../'))
puts root.green
set :sessions,
:httponly => true,
:secure => production?,
:expire_after => 31557600, # 1 year
:secret => ENV['SESSION_SECRET'] || 'keyboardcat',
:views => File.expand_path(File.expand_path('../../views/', __FILE__)),
:layout_engine => :slim
enable :method_override
# No logging in testing
configure :production, :development do
enable :logging
end
# Global not found??
not_found do
title 'Not Found!'
slim :not_found
end
end
As you can see I'm setting the views directory as:
File.expand_path(File.expand_path('../../views/', __FILE__))
which works out to be /Users/vladdy/Desktop/sinatra/app/views
In configure.ru, I then map('/') { RootController }, and in said controller I render a view with slim :whatever
Problem is, all the views from all the controllers are all in the same spot! How do I add a folder structure to Sinatra views?
If I understand your question correctly, you want to override #find_template.
I stick this function in a helper called view_directory_helper.rb.
helpers do
def find_template(views, name, engine, &block)
views.each { |v| super(v, name, engine, &block) }
end
end
and when setting your view directory, pass in an array instead, like so:
set :views, ['views/layouts', 'views/pages', 'views/partials']
Which would let you have a folder structure like
app
-views
-layouts
-pages
-partials
-controllers
I was faced with same task. I have little experience of programming in Ruby, but for a long time been working with PHP. I think it would be easier to do on it, where you can easily get the child from the parent class. There are some difficulties. As I understand, the language provides callback functions like self.innereted for solving of this problem. But it did not help, because I was not able to determine the particular router in a given time. Maybe the environment variables can help with this. But I was able to find a workaround way to solve this problem, by parsing call stack for geting caller class and wrapping output function. I do not think this is the most elegant way to solve the problem. But I was able to realize it.
class Base < Sinatra::Application
configure do
set :views, 'app/views/'
set :root, File.expand_path('../../../', __FILE__)
end
def display(template, *args)
erb File.join(current_dir, template.to_s).to_sym, *args
end
def current_dir
caller_class.downcase!.split('::').last
end
private
def caller_class(depth = 1)
/<class:([\w]*)>/.match(parse_caller(caller(depth + 1)[1]))[1]
end
def parse_caller(at)
Regexp.last_match[3] if /^(.+?):(\d+)(?::in `(.*)')?/ =~ at
end
end
The last function is taken from here. It can be used as well as default erb function:
class Posts < Base
get '/posts' do
display :index , locals: { variables: {} }
end
end
I hope it will be useful to someone.
Simple question: I want to be able to pass options into my sinatra app in config.ru. How is that possible? My config.ru looks like this:
run MyApp
But I want to have this in my MyApp class to take arguments:
class MyApp < Sinatra::Base
def initialize(config)
#config = config
end
end
But I can't figure out a way to do this. Ideas?
Use set/settings
require 'sinatra/base'
class MyApp < Sinatra::Base
get '/' do
settings.time_at_startup.to_s
end
end
# Just arbitrarily picking time as it'll be static but, diff for each run.
MyApp.set :time_at_startup, Time.now
run MyApp
Use a config file. See Sinatra::ConfigFile in contrib (which also uses set and settings, but loads params from a YAML file)
If you want to configure with params, I figured out that you could do this:
require 'sinatra/base'
class AwesomeApp < Sinatra::Base
def initialize(app = nil, params = {})
super(app)
#bootstrap = params.fetch(:bootstrap, false)
end
end
rnicholson's response will be the best answer in most cases but if what you want is to have access to an instance variable in your routes, you can set these up using the before filter as explained in the Sinatra README:
Before filters are evaluated before each request within the same context as the routes will be and can modify the request and response. Instance variables set in filters are accessible by routes and templates:
before do
#note = 'Hi!'
request.path_info = '/foo/bar/baz'
end
get '/foo/*' do
#note #=> 'Hi!'
params['splat'] #=> 'bar/baz'
end
In Sinatra, I'm unable to create global variables which are assigned values only once in the application lifetime. Am I missing something? My simplified code looks like this:
require 'rubygems' if RUBY_VERSION < "1.9"
require 'sinatra/base'
class WebApp < Sinatra::Base
#a = 1
before do
#b = 2
end
get '/' do
puts #a, #b
"#{#a}, #{#b}"
end
end
WebApp.run!
This results in
nil
2
in the terminal and ,2 in the browser.
If I try to put #a = 1 in the initialize method, I'm getting an error in the WebApp.run! line.
I feel I'm missing something because if I can't have global variables, then how can I load large data during application instantiation?
before do seems to get called every time there is a request from the client side.
class WebApp < Sinatra::Base
configure do
set :my_config_property, 'hello world'
end
get '/' do
"#{settings.my_config_property}"
end
end
Beware that if you use Shotgun, or some other Rack runner tool that reloads the code on each request the value will be recreated each time and it will look as if it's not assigned only once. Run in production mode to disable reloading and you will see that it's only assigned on the first request (you can do this with for example rackup --env production config.ru).
I ran into a similar issue, I was trying to initialize an instance variable #a using the initialize method but kept receiving an exception every time:
class MyApp < Sinatra::Application
def initialize
#a = 1
end
get '/' do
puts #a
'inside get'
end
end
I finally decided to look into the Sinatra code for initialize:
# File 'lib/sinatra/base.rb', line 877
def initialize(app = nil)
super()
#app = app
#template_cache = Tilt::Cache.new
yield self if block_given?
end
Looks like it does some necessary bootstrapping and I needed to call super().
def initialize
super()
#a = 1
end
This seemed to fix my issue and everything worked as expected.
Another option:
helpers do
def a
a ||= 1
end
end
Building on Theo's accepted solution, it is also possible to do:
class App < Sinatra::Application
set :blabla, ''
namespace '/b' do
get '/baby' do
# do something where bouh is assigned a value
settings.blabla = 'bouh'
end
end
namespace '/z'
get '/human' do
# settings.blabla is available here with newly assigned value
end
end
end
You could use OpenStruct.
require 'rubygems'
require 'sinatra'
require 'ostruct'
configure do
Struct = OpenStruct.new(
:foo => 'bar'
)
end
get '/' do
"#{Struct.foo}" # => bar
end
You can even use the Struct class in views and other loaded files.
I need to render a Sinatra erb template inside a class in my controller. I'm having issues calling this though. I've looked in the Sinatra rdocs and have come up with this:
Sinatra::Templates.erb :template_to_render
When I do this, I get the following error:
undefined method `erb' for Sinatra::Templates:Module
Is there a way to call this from another class?
To imitate rendering behavior of Sinatra controller in some other class (not controller) you can create module like this:
module ErbRender
include Sinatra::Templates
include Sinatra::Helpers
include Sinatra::ContentFor
def settings
#settings ||= begin
settings = Sinatra::Application.settings
settings.root = "#{ROOT}/app"
settings
end
end
def template_cache
#template_cache ||= Tilt::Cache.new
end
end
Here you may need to tune settings.root
Usage example:
class ArticleIndexingPostBody
include ErbRender
def get_body
erb :'amp/articles/show', layout: :'amp/layout'
end
end
This will properly render templates with layouts including content_for
why you don't require 'erb' and after use only erb
## You'll need to require erb in your app
require 'erb'
get '/' do
erb :index
end
You could have your class return the template name and render it in the main app.
Of course that's not exactly an answer (I don't have enough rep to add a comment with this account) and you're probably doing just that by now anyway...