ruby/sinatra: will code outside the routes run only once or each time site is pinged? - ruby

I have a slim Sinatra site.
If I include code outside the get routes, will it run in the background only once, or will it trigger each time the IP address is pinged.
For example will the function 'start' only run once on server creation / gitpush or will it run anew each site visit.
--
other-code.rb
$variable
$count = 0
def start
$variable = "hello world + #{$count}"
$count += 1
end
start
--
index.rb
require 'sinatra'
require 'json'
require 'other-code'
get '/' do
content_type :json
puts $variable
end

Require only loads the ruby code from the required file once.
Here is how you can tell:
#index.rb
require 'sinatra'
require 'json'
require_relative 'other_code'
get '/' do
content_type :json
puts $variable
end
# other_code.rb
$variable
def start
$variable = 'hello world'
end
puts 'other code called'
start
Now start your sinatra server
ruby index.rb
You will see this in the console:
other code called
== Sinatra (v2.0.5) has taken the stage on 4567 for development with backup from Puma
Puma starting in single mode...
Then hit your browser a few times and look at your console, you will only see other code called output 1 time. However each time you hit your get route, you should see output hello world!

Related

Ruby: Display SNMP output in Sinatra

i am trying to make a little website with Sinatra, where i want to display SNMP data.
require 'sinatra'
#require 'sinatra/reloader'
require 'snmp'
get'/' do
'Hello World'
SNMP::Manager.open(:host => 'localhost') do |manager|
response = manager.get(["sysDescr.0","sysName.0"])
response.each_varbind do |vb|
puts "#{vb.name.to_s} #{vb.value.to_s} #{vb.value.asn1_type}"
end
end
end
Unfortunately this code outputs the result on the console and not on the Web Page.
I hope you can help me.
It looks like your calling puts as you iterate through your data, this will print the results to the console as ruby cannot input items directly onto the web page, and because puts is only able to print into your console/ terminal. if you want to display the results on your web page you will need to pass them as params in to your :erb file, then display them within the erb file like so:
get'/' do
'Hello World'
SNMP::Manager.open(:host => 'localhost') do |manager|
#response = manager.get(["sysDescr.0","sysName.0"]) # add the # symbol to then pass as params into the erb file
end
erb(:index) # load up your erb file
end
then simply load your values in the erb file like so
<%=#response.each_varbind do |vb|%>
<p>
<%={vb.name.to_s} + {vb.value.to_s} + {vb.value.asn1_type}%>
</p>
<%end%>
Now the controller will load the index.html.erb file whenever the route get('/') is called and you should see your values displayed within the paragraph tag on screen
Hope that helps!

How do you open StringIO in Ruby?

I have a Sinatra application with the following main.rb:
require 'bundler'
Bundler.require
get '/' do
##p = Pry.new
haml :index
end
post '/' do
code = params[:code]
$stdout = StringIO.new
##p.eval(code)
output = $stdout.string
$stdout = STDOUT
output_arr = []
output.each_line('\n') { |line| output_arr << line }
output_arr[1]
binding.pry
end
When I hit the binding.pry at the bottom to see if output contains any output, it seems like the IO stream is not closed, as I can't get anything to show up in the console.
However if I try to call open on StringIO.new, I receive an NoMethodError - private method 'open' called.
I am requiring 'stringio' in a config.ru file, and I've also tried requiring it in the main.rb file:
config.ru:
require 'stringio'
require './main'
run Sinatra::Application
I'm not sure if this is related but something interesting that I've noticed is that, in irb, if I require 'pry' before requiring stringio, then it returns false, otherwise it returns true.
This makes me wonder if Sinatra is including Pry from my Gemfile before loading the config.ru. Could that be the problem? Not sure how to solve this.

Sinatra App JSON and Routes

Scenario
I have a Sinatra App
I have a route that fetches articles based on a certain named path
# Get Articles for a certain time period
get '/frontpage/:type' do
case params[:type]
when "today"
#news = Article.find(...)
when "yesterday"
#news = Article.find(...)
when "week-ago"
#news = Article.find(...)
when "month-ago"
#news = Article.find(...)
else
not_found
end
erb :frontpage
end
Question
Is it possible to keep this route "/frontpage/:type" and show a .json page if for example someone ask for "/frontpage/:today.json" instead of "/frontpage/:type" ?
OR
Is it better to create a separate route specifically for requests for JSON ?
You will have to create a new route.
Though, you can factor your code like that:
get '/frontpage/:type' do
#news = get_articles(params[:type])
erb :frontpage
end
get '/frontpage/:type.json' do
get_articles(params[:type]).json
end
def get_articles(type)
case
when "today"
Article.find(...)
when "yesterday"
Article.find(...)
when "week-ago"
Article.find(...)
when "month-ago"
Article.find(...)
else
raise "Unsupported type #{type}. Supported types are: today, yesterday, week-ago and month-ago."
end
end
This can actually be done with a single route:
require 'rubygems'
require 'sinatra'
get %r{/frontpage/([^\.]+)\.?(.+)?} do |type, ext|
'the type is: ' + type + ' and the extension is: ' + "#{ext}"
end
You can the use the ext var to return your json content if it's non-nill, and has the value 'json'.
The route order matters.
Compare this app, .json first
require "sinatra"
require "sinatra/contrib/all"
get "/greet/:name.json" do |name|
json ({"greeting" => greeting(name)})
end
get "/greet/:name" do |name|
greeting name
end
def greeting(name)
"Hello #{name}"
end
with this app, .json last
require "sinatra"
require "sinatra/contrib/all"
get "/greet/:name" do |name|
greeting name
end
get "/greet/:name.json" do |name|
json ({"greeting" => greeting(name)})
end
def greeting(name)
"Hello #{name}"
end
With the first:
$ curl localhost:4567/greet/frank
Hello frank
$ curl localhost:4567/greet/frank.json
{"greeting": "Hello frank"}
But with the second,
$ curl localhost:4567/greet/frank
Hello frank
$ curl localhost:4567/greet/frank.json
Hello frank.json

Why doesn't the sinatra-redirect-with-flash gem work with shotgun?

I want to show flash messages using sinatra-redirect-with-flash gem.
Here's my ruby code:
require 'sinatra'
require 'sinatra/base'
require 'sinatra/flash'
require 'sinatra/redirect_with_flash'
require 'data_mapper'
require 'haml'
require 'builder'
# ...
class App < Sinatra::Base
enable :sessions
register Sinatra::Flash
helpers Sinatra::RedirectWithFlash
use Rack::MethodOverride
get '/' do
#notes = Note.all :order => :id.desc
#title = 'All TODOs'
if #notes.empty?
flash.now[:error] = 'No TODOs found. Add your first below.'
end
haml :home
end
post '/' do
n = Note.new
n.content = params[:content]
n.created_at = Time.now
n.updated_at = Time.now
if n.save
redirect '/', :notice => 'TODO saved successfully.'
else
redirect '/', :error => 'Failed to save TODO.'
end
end
# ...
end
And views/layout.haml is:
!!! 5
%html{:lang => "en"}
%head
%meta{:charset => "utf8"}
%body
%header
%hgroup
%h1
%a{:href => "/"}= SITE_TITLE
%h2= SITE_DESCRIPTION
#main
=styled_flash
=yield
After adding a TODO successfully, I expected to see the flash message 'TODO saved successfully.' on the home page. But no flash messages are shown after redirection when I run my app using shotgun. Flash messages are shown well when I run ruby app.rb or rackup.
How can I solve this problem?
Another problem is also happening when I run the app using shotgun. In get '/' method, if I use flash[:error] instead of flash.now[:error], the flash message doesn't show up on the page.
I am shadowning this tutorial, but I made some differences:
erb -> haml
Classic Sinatra app -> Subclassing Sinatra::Base
rack-flash -> sinatra-flash
You can browse whole codes here.
Thanks for any answers/comments.
The shotgun gem reloads Sinatra after every request. The README says:
Each time a request is received, it forks, loads the application in
the child process, processes the request, and exits the child process. The
result is clean, application-wide reloading of all source files and templates on
each request.
As a result, you will need some sort of mechanism to preserve state between requests that doesn't rely on data stored in each child process.

Is there a way to flush html to the wire in Sinatra

I have a Sinatra app with a long running process (a web scraper). I'd like the app flush the results of the crawler's progress as the crawler is running instead of at the end.
I've considered forking the request and doing something fancy with ajax but this is a really basic one-pager app that really just needs to output a log to a browser as it's happening. Any suggestions?
Update (2012-03-21)
As of Sinatra 1.3.0, you can use the new streaming API:
get '/' do
stream do |out|
out << "foo\n"
sleep 10
out << "bar\n"
end
end
Old Answer
Unfortunately you don't have a stream you can simply flush to (that would not work with Rack middleware). The result returned from a route block can simply respond to each. The Rack handler will then call each with a block and in that block flush the given part of the body to the client.
All rack responses have to always respond to each and always hand strings to the given block. Sinatra takes care of this for you, if you just return a string.
A simple streaming example would be:
require 'sinatra'
get '/' do
result = ["this", " takes", " some", " time"]
class << result
def each
super do |str|
yield str
sleep 0.3
end
end
end
result
end
Now you could simply place all your crawling in the each method:
require 'sinatra'
class Crawler
def initialize(url)
#url = url
end
def each
yield "opening url\n"
result = open #url
yield "seaching for foo\n"
if result.include? "foo"
yield "found it\n"
else
yield "not there, sorry\n"
end
end
end
get '/' do
Crawler.new 'http://mysite'
end

Resources