Sinatra: three logs - ruby

I'm using a very simple Sinatra app that works well. However, every log message is repeated three times. I can bring that down to two by disabling the Sinatra logging with
disable :logging
but I still have two. The messages are slightly different, so I gather they are coming from Rack and somewhere else in the stack too.
How do I completely disable logging of successful web requests?

Rack is adding own logging as a middleware
try to run
rackup -E none
This removes one log entry. The second one is sinatra native which you've already disable. And the third one is Rack::Lint logging if I remember correctly.
General approach is to restructure your app like
app.rb
require 'sinatra/base'
class App < Sinatra::Base
get '/' do
"hello"
end
end
config.ru
require 'myapp'
run MyApp
Or you can run app outside rack
if __FILE__ == $0
App.run!
end

Related

What is this file config.ru, and what is it for?

What is this file config.ru, and what is it for in Sinatra projects? In my lanyard of the project, such code is written:
require './app'
run Sinatra::Application
config.ru is a Rack configuration file (ru stands for "rackup"). Rack provides a minimal interface between web servers that support Ruby and Ruby frameworks. It's like a Ruby implementation of a CGI which offers a standard protocol for web servers to execute programs.
Rack's run command here means for requests to the server, make Sinatra::Application the execution context from which Sinatra's DSL could be used. All DSL methods on the main are then delegated to this class.
So in this config.ru file, first you require your app code which uses Sinatra's DSL then run the Sinatra framework. In the context of Sinatra::Application if your app.rb contained this:
get '/' do
'Hello world!'
end
The get block would mean something to Rack, in this case when someone tries to access (GET) the home url, send back 'Hello world!'
Rack provides a minimal interface between webservers that support Ruby and Ruby frameworks.
The interface just assumes that you have an object that responds to a call method (like a proc) and returns a array with:
The HTTP response code
A Hash of headers
The response body, which must respond to each
You can run a basic Rack server with the rackup command which will search for a config.ru file in the current directory.
You can create a minimal hello world server with:
# config.ru
run Proc.new { |env| ['200', {'Content-Type' => 'text/html'}, ['Hello World']] }
# run this with the `rackup` command
Since Sinatra just like Rails builds on Rack it uses rackup internally to interface between the server and the framework. config.ru is thus the entry point to any Rack based program.
What it does is bootstrap the application and pass the Sinatra::Application class to rack which has a call class method.
Sinatra::Application is then responsible for taking the incoming request (the env) and passing it to the routes your application provides and then passing back the response code, headers, and response body.
config.ru is a default configuration file for a rackup command with a list of instructions for Rack.
Rack is an interface and architecture that provides a domain specific language (DSL) and connects an application with a world of web. In two words, it allows to build web applications and work with requests, responses (and many other web-related technologies) in a most convenient way.
Sinatra as well as Rails are web frameworks, so they both use Rack:
http://recipes.sinatrarb.com/p/middleware
https://guides.rubyonrails.org/rails_on_rack.html

Using routes from one sinatra app from another when racked up

In the real world, thanks to Rack::Builder, I've started up two sinatra apps and mapped one to "/api/v1" and the other to "/ui".
I'd like the app mapped to /ui to be able to take advantage of the routes in /api but,
becuase they're separate, the ui app can't perform calls on the api side of things.
Is there a way to call one app's route from another via rack, or should I just use Net::HTTP ?
Here's a simplified example of what I'm trying to do:
#/usr/bin/ruby
require 'sinatra'
class API < Sinatra::Base
get '/accounts/' do
'{"json":"account_data"}'
end
end
class UI < Sinatra::Base
get '/accounts/' do
# How do I get /api/accounts?
# call "/api/accounts" obviously does not work
# would use erb here to render accounts list in human readable form
end
end
rack = Rack::Builder.new
rack.map "/ui" do
run UI.new
end
rack.map "/api" do
run API.new
end
Rack::Server.start :app => rack
exit
Thanks very much!
If these apps are racked up in Rack together, they have no real connectivity. What Rack does call .call(env) on whatever app you have racked up and that responds with [status,env,body] You can have layers in the middle that act on and modify env. Your Rack is triggering #call on whatever app you mapped to respond to that path. The called app has no concept of what is running in Rack. Nor can it call another app unless there is a handle for it in env that was populated upstream.
But you have to consider why you decided to make these 2 different apps in the first place. I would imagine it is due to the fact that there could be multiple apps using the API. Or you enjoy the decoupled nature. So in that case you have to think if you would want some coupling through Rack at all. What if the API was on another server? Any internal link between the 2 through Rack would break the UI application. If you always know your API will be self contained on the same machine you could just as well make it a library and not do HTTP calls.
So even if it is possible to call routes from a co racked up app I would never do it. The whole reason for having API servers is to have a decoupled layer. And you should access that layer via HTTP so it would work if it were on same box or across the world.

How do you set up Rack::URLMap to work with RSpec in Sinatra?

I'm relatively new to Sinatra, and I want to figure out a way to integrate RSpec with my Sinatra setup.
config.ru
require 'sinatra'
require 'mongoid'
require 'uri'
require './lib/twilio_wrapper'
Mongoid.load!("./config/mongoid.yml")
Dir["./controllers/*.rb"].each { |file| require file }
run Rack::URLMap.new(
'/' => HomeController.new,
'/users' => UsersController.new(TwilioWrapper.new)
)
With this setup, I can modularize my controllers and create single instances of helper classes (such as TwilioWrapper). However, if I want to set up RSpec, I need to point it to my application's class. However, in the situation above, because I'm using Rack::URLMap, I don't have a specific application class to point RSpec to.
How can I keep my code modular in the fashion outlined above while including RSpec for tests?
Rack does not care about controllers, it cares about apps. So HomeController and UsersController are 2 Sinatra applications "racked up" in Rack. These are not controllers, they are separate Rack apps. I do not think you want 2 applications but rather to put these 2 controllers in 2 files so you can spec them out separately and keep the code readable.
The naming convention for Sinatra is to name it something like MyApp to reflect this. Sinatra is a flat framework, but you can name your "controller" files what you want.
So in folder routes you can have 'users.rb' and 'home.rb' but both files have at the top
MyApp < Sinatra::Application
Then you can test using Rack::Test with Rspec.
If you do indeed want to test 2 apps and want the prefix using Rack::Test w Rspec you simply need to define app in your spec_helper or spec file as:
def app
run Rack::URLMap.new(
'/' => HomeController.new,
'/users' => UsersController.new(TwilioWrapper.new)
)
end
All Rack::Test does is rackup your Sinatra app into a test container.
Also please see Phrogz's excellent answer on how to lay out a Sinatra application

API polling bot on heroku

I want to create a bot which makes API request per minute to some API url. This then needs to ping a particular user if data entry has changed against his name in the API feed. I want to go for a free solution on Heroku. Can this be achieved?
Yes, heroku supports thin as a web server, which is EventMachine enabled, so an easy way to do this is to write a quick sinatra app and use EM.add_periodic_timer for your API calls. When you deploy this sinatra app to heroku, it'll use thin by default, so there's no extra configuration needed. You can test via thin start -p 4567 assuming your config.ru is correct. Here's a pretty standard one, assuming your app is in app.rb:
require 'bundler/setup'
Bundler.require :default
require File.expand_path('app', File.dirname(__FILE__))
run Sinatra::Application
I currently check the status of some sites for free on heroku. The secret? Rufus-Scheduer.
Install the gem
gem install rufus-scheduler
Make sure you include the gem in bundler or however you are including it.
Then you need to create a file called task_scheduler.rb and stick this in you initializers directory.
require 'rufus/scheduler'
scheduler = Rufus::Scheduler.start_new
scheduler.every '1m' do
url = "http://codeglot.com"
response = Net::HTTP.get_response(URI.parse(url))
#do stuff with response.body
end
If you have any trouble you can see this blog post:
http://intridea.com/2009/2/13/dead-simple-task-scheduling-in-rails?blog=company

Is there a Rack or Sinatra based environment configuration utility?

Is there anything in the Sinatra / Rack world similar to Rails configuration loading scheme that loads one of the config\enviroments\*.rb files depending on Rails.env
I know I could develop one pretty easily, i was just wondering if there was something already in place.
If you're following the Rails convention of putting a file for each environment in config/environments/environment_name.rb, you can put something like this in your Sinatra app, or for Rack in your config.ru file:
Dir.glob(File.dirname(__FILE__) + "/config/environments/#{settings.environment}.rb", &method(:require))
With some minor modifications you could make it load other file locations/combinations. Sinatra's configure blocks work just as well, too.
It turns out that there is something from Sinatra, that provides a similar, though limited, functionality.
See the code:
https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1120
So that you can do this:
class MyApp < Sinatra::Base
configure :development, :test do
#only executes this code when environment is equal to one of the passed arguments
# I'm pretty sure Sinatra sets this based on ENV['RACK_ENV']
end
end
There is one called Sinatra::ConfigFile, which now lives in Sinatra::Contrib http://www.sinatrarb.com/contrib/config_file.html
There's lots of useful stuff in there.
I adapted mine from monkrb.com (it's also yaml in RoR anyways)
YAML.load_file(path_of "config/settings.yml")[RACK_ENV]
e.g.
http://github.com/codepants/yasumi/blob/master/config/settings.yml

Resources