Simulate slow connection with Sinatra - ruby

I'm a frontend developer and when i code i use Sinatra as a static file server backend:
require 'sinatra'
configure do
set :public_folder, File.dirname(__FILE__)
end
get '/' do
send_file File.join(settings.public_folder, 'index.html')
end
get '/:name' do
file = File.join(settings.public_folder, params[:name])
if File.exist?(file)
send_file file
else
halt 404
end
end
I was happy with that, but this time i was given a task to create a JS intro that does some complex behavior only while the page is being loaded.
I'm unable to test such JS behavior because in my development sandbox Sinatra serves files immediately.
How do i make Sinatra serve files slowly, at given max rate, e. g. 10 Kbps? Alternative approach suggestons are also appreciated.

It is possible if you split up a file into chunks and expose them gradually, here is an example:
require 'sinatra'
require "sinatra/streaming"
def file_chunks
[].tap do |chunks|
File.open("index.html", "rb") do |io|
while not io.eof?
chunks << io.read(10)
end
end
end
end
get '/send_file_slowly' do
stream do |out|
file_chunks.each do |chunk|
out.print chunk
out.flush
sleep 0.2
end
end
end

Related

Ruby Selenium Capybara not timing out

I'm having issues with my specs not timing out. Some of my specs are getting to a certain point and just hanging. I'm sure there is something wrong with one of the spec resulting in it being broken, what I can't figure out is why they are just hanging indefinitely when I have a timeout defined...
# frozen-string-literal: true
require 'rspec'
require 'capybara/rspec'
require 'capybara/dsl'
require 'selenium-webdriver'
require 'site_prism'
Dir[File.dirname(__FILE__) + '/page_objects/*/*.rb'].each do |page_object|
require page_object
end
def wait_for_ajax
Timeout.timeout(Capybara.default_max_wait_time) do
loop until page.evaluate_script('jQuery.active').zero? && page.has_no_css?(".k-loading-color")
end
end
def whole_page
Capybara.current_session
end
Capybara.register_driver :selenium do |app|
Capybara::Selenium::Driver.new(app, browser: :chrome)
end
Capybara.default_driver = :selenium
Capybara.app_host = #REDACTED
Capybara.default_max_wait_time = 20
RSpec.configure do |config|
config.before(:each) do
config.include Capybara::DSL
end
config.after(:each) do
Capybara.reset_sessions!
end
end
You don't mention what commands it's hanging on, but I'm going to guess it's in your wait_for_ajax method. If that's the case it's because you're using Timeout.timeout which is the most dangerous to use method Ruby provides. The way it works is by starting a second thread which will then raise an exception in the original thread when the timeout occurs. The problem with that is the exception can occur anywhere in the original thread which means if the block inside the timeout call is doing anything non-trivial it can end up in a completely unrecoverable state (network comms, etc). Basically Timeout.timeout can only ever be safely used with a VERY detailed knowledge of every little thing occurring it its block, which means it effectively should never be used around any calls to a third party library. Instead you should just use a timer and sleep if you need timeout. Something like
def wait_for_ajax
start = Time.now
until page.evaluate_script('jQuery.active').zero? && page.has_no_css?(".k-loading-color", wait: false) do
sleep 0.1
raise <Some Error> if (Time.now - start) > Capybara.default_max_wait_time
end
end
That being said you really shouldn't need wait_for_ajax with a useable UI and properly written tests.
Additionally, by including capybara/rspec you've already set up for reset_sessions to be called after every test and for Capybara::DSL to be included into the types of tests it should be included into - https://github.com/teamcapybara/capybara/blob/master/lib/capybara/rspec.rb#L9 - so by adding your own after block you're just ending up calling reset_sessions twice after every test which is just a waste of time.

How to run external Ruby scripts from a Sinatra application

I have a Ruby script in a folder outside of my Sinatra application that I would like to run with a button click.
This is app.rb file:
require 'sinatra'
get '/' do
erb :home
end
get '/launch_script' do
system("ruby path\\to\\file\\delete_rows_csv.rb")
end
This is home.erb file:
<a href='/launch_script'> Launch a Script </a>
Am I supposed to load a filepath or require the file to get this to work?
This is the solution that worked great:
require 'sinatra'
get '/' do
erb :home
end
get '/launch_script' do
load 'path\\to\\file\\delete_rows_csv.rb'
end
You can use require or require_relative (depending if your file is located in a static place or relative to this file). Adding .rb is not required at the end either.
require 'sinatra'
get '/' do
erb :home
end
get '/launch_script' do
require 'path\\to\\file\\delete_rows_csv'
end

Getting Rack mounted path in Sinatra application

Suppose I have the following config.ru file
require './status.rb'
map "/status" do
run Sinatra::Application
end
and the status.rb is a simple
require 'sinatra'
get '/' do
'Some status here...'
end
I'd like to know where the Sinatra application is mounted inside status.rb (for example to provide proper paths to resources). Is there a way of retrieving that information from Rack?
To get where the app is mounted you can use request.script_name.
get '/' do
p request.script_name # will print "/status"
'Some status here...'
end
If you’re generating urls for resources, you might want to look at the url method instead. That will take into account things like proxies as well as where the app is mounted:
get '/' do
p url('foo') # will print "http://localhost:9292/status/foo"
'Some status here...'
end

Why this sinatra code is not working

I'm having a hard time figuring out what I'm doing wrong here. The result is empty and I'm looking it to return hello (calling the method testing through the before helper).
require 'rubygems'
require 'sinatra'
get '/' do
end
before do
testing
end
def testing
return "hello"
end
There are several problems here. For one thing you have to actually call the output or variable you want in the view, most typically as an instance variable (otherwise every user gets the same output.) Take the modified code below for example:
require 'rubygems'
require 'sinatra'
get '/' do
#word
end
before do
testing
end
def testing
#word = "hello"
end
Check out the Sinatra Book, a free online resource, for information on getting started with Sinatra.
Because you are not calling the output on the Get request, you need to tell your Get Method to return an output. Like thekungfuman suggested. or try the Minimal Hello World Sinatra app as follows:
#imports
require 'rubygems'
require 'sinatra'
#Get Request on Root ("/")
get '/' do
"Hello Sinatra World!"
end
Also it's useful to put your program under a class, so you can also do :
#imports
require 'rubygems'
require 'sinatra/base'
#My Application Class
class AppName < Sinatra::base
get '/' do
'Hello Sinatra World!'
end
end
AppName.run!
This way you can also use this as a seperate app file and import it within other files like.
require 'app_name' #replace this with the name of the physical file
#Run Application "AppName"
AppName.run!

Using Sinatra for larger projects via multiple files

It seems that in Sinatra all route handlers are being written into a single file, if I understand right it acts as a one large/small controller. Is there any way to split it into separate independent files, so when let's say somebody calls "/" - one action is executed, and if smth like "/posts/2" is received then another action - similar logic that is applied in PHP?
Here is a basic template for Sinatra apps that I use. (My larger apps have 200+ files broken out like this, not counting vendor'd gems, covering 75-100 explicit routes. Some of these routes are Regexp routes covering an additional 50+ route patterns.) When using Thin, you run an app like this using:
thin -R config.ru start
Edit: I'm now maintaining my own Monk skeleton based on the below called Riblits. To use it to copy my template as the basis for your own projects:
# Before creating your project
monk add riblits git://github.com/Phrogz/riblits.git
# Inside your empty project directory
monk init -s riblits
File Layout:
config.ru
app.rb
helpers/
init.rb
partials.rb
models/
init.rb
user.rb
routes/
init.rb
login.rb
main.rb
views/
layout.haml
login.haml
main.haml
config.ru
root = ::File.dirname(__FILE__)
require ::File.join( root, 'app' )
run MyApp.new
app.rb
# encoding: utf-8
require 'sinatra'
require 'haml'
class MyApp < Sinatra::Application
enable :sessions
configure :production do
set :haml, { :ugly=>true }
set :clean_trace, true
end
configure :development do
# ...
end
helpers do
include Rack::Utils
alias_method :h, :escape_html
end
end
require_relative 'models/init'
require_relative 'helpers/init'
require_relative 'routes/init'
helpers/init.rb
# encoding: utf-8
require_relative 'partials'
MyApp.helpers PartialPartials
require_relative 'nicebytes'
MyApp.helpers NiceBytes
helpers/partials.rb
# encoding: utf-8
module PartialPartials
def spoof_request(uri,env_modifications={})
call(env.merge("PATH_INFO" => uri).merge(env_modifications)).last.join
end
def partial( page, variables={} )
haml page, {layout:false}, variables
end
end
helpers/nicebytes.rb
# encoding: utf-8
module NiceBytes
K = 2.0**10
M = 2.0**20
G = 2.0**30
T = 2.0**40
def nice_bytes( bytes, max_digits=3 )
value, suffix, precision = case bytes
when 0...K
[ bytes, 'B', 0 ]
else
value, suffix = case bytes
when K...M then [ bytes / K, 'kiB' ]
when M...G then [ bytes / M, 'MiB' ]
when G...T then [ bytes / G, 'GiB' ]
else [ bytes / T, 'TiB' ]
end
used_digits = case value
when 0...10 then 1
when 10...100 then 2
when 100...1000 then 3
else 4
end
leftover_digits = max_digits - used_digits
[ value, suffix, leftover_digits > 0 ? leftover_digits : 0 ]
end
"%.#{precision}f#{suffix}" % value
end
module_function :nice_bytes # Allow NiceBytes.nice_bytes outside of Sinatra
end
models/init.rb
# encoding: utf-8
require 'sequel'
DB = Sequel.postgres 'dbname', user:'bduser', password:'dbpass', host:'localhost'
DB << "SET CLIENT_ENCODING TO 'UTF8';"
require_relative 'users'
models/user.rb
# encoding: utf-8
class User < Sequel::Model
# ...
end
routes/init.rb
# encoding: utf-8
require_relative 'login'
require_relative 'main'
routes/login.rb
# encoding: utf-8
class MyApp < Sinatra::Application
get "/login" do
#title = "Login"
haml :login
end
post "/login" do
# Define your own check_login
if user = check_login
session[ :user ] = user.pk
redirect '/'
else
redirect '/login'
end
end
get "/logout" do
session[:user] = session[:pass] = nil
redirect '/'
end
end
routes/main.rb
# encoding: utf-8
class MyApp < Sinatra::Application
get "/" do
#title = "Welcome to MyApp"
haml :main
end
end
views/layout.haml
!!! XML
!!! 1.1
%html(xmlns="http://www.w3.org/1999/xhtml")
%head
%title= #title
%link(rel="icon" type="image/png" href="/favicon.png")
%meta(http-equiv="X-UA-Compatible" content="IE=8")
%meta(http-equiv="Content-Script-Type" content="text/javascript" )
%meta(http-equiv="Content-Style-Type" content="text/css" )
%meta(http-equiv="Content-Type" content="text/html; charset=utf-8" )
%meta(http-equiv="expires" content="0" )
%meta(name="author" content="MeWho")
%body{id:#action}
%h1= #title
#content= yield
Absolutely. To see an example of this I recommend downloading the Monk gem, described here:
https://github.com/monkrb/monk
You can 'gem install' it via rubygems.org. Once you have the gem, generate a sample app using the instructions linked above.
Note that you don't have to use Monk for your actual development unless you want to (in fact I think it may not be current). The point is to see how you can easily structure your app in the MVC style (with separate controller-like route files) if you want to.
It's pretty simple if you look at how Monk handles it, mostly a matter of requiring files in separate directories, something like (you'll have to define root_path):
Dir[root_path("app/**/*.rb")].each do |file|
require file
end
Do a Google search for "Sinatra boilerplate" to get some ideas for how others are laying out their Sinatra applications. From that you can probably find one that suits your needs or simply make your own. It's not too hard to do. As you develop more Sinatra apps, you can add to your boilerplate.
Here's what I made and use for all of my projects:
https://github.com/rziehl/sinatra-boilerplate
I know this is an old query but I still can't believe no one mentioned Padrino You can use it as a framework on top of Sinatra, or piecemeal adding only the gems that interest you. It kicks ten buttloads of ass!
The key for modularity on Sinatra for larger projects is learning to use the underlying tools.
SitePoint has a very good tutorial from where you can see modular Sinatra apps and helpers. However you should pay special attention to one important detail. You keep multiple Sinatra apps and mount them with Rackup. Once you know how to write a basic app look at the config.ru file of that tutorial and observe how they mount independent Sinatra apps.
Once you learn to run Sinatra with Rack a whole new world of modularity strategies will open up. This obviously invites to try something really useful: now you can rely on having individual Gems for each sub application, what might enable you to easily version your modules.
Do not underestimate the power of using gem-modules for your app. You can easily test experimental changes in a well delimited environment and easily deploy them. Equally easy to revert back if something goes wrong.
There are a thousand ways to organize your code, so it would not hurt trying to get a layout similar to Rails. However there are also some great posts about how to customize your own structure. That post covers other frequent needs of most web developers.
If you have the time, I encourage you to learn more about Rack, the common ground for any Ruby based web application. It might have a far lesser impact in how you do your work, but there are always certain tasks that most people do on their apps that fits better as a Rack middleware.
My approach to host different projects on the same site is to use sinatra/namespace in such way:
server.rb
require "sinatra"
require "sinatra/namespace"
if [ENV["LOGNAME"], ENV["USER"]] == [nil, "naki"]
require "sinatra/reloader"
register Sinatra::Reloader
set :port, 8719
else
set :environment, :production
end
for server in Dir.glob "server_*.rb"
require_relative server
end
get "/" do
"this route is useless"
end
server_someproject.rb
module SomeProject
def self.foo bar
...
end
...
end
namespace "/someproject" do
set :views, settings.root
get "" do
redirect request.env["REQUEST_PATH"] + "/"
end
get "/" do
haml :view_someproject
end
post "/foo" do
...
SomeProject.foo ...
end
end
view_someproject.haml
!!!
%html
...
Another detail about subprojects I used was to add their names, description and routes to some kind of global variable, that is used by "/" to make a guide homepage, but I don't have a snippet right now.
Reading the docs here:
Sinatra Extensions
It appears that Sinatra allows you to decompose your application into Ruby Modules, which can be pulled in through the Sinatra "register" method or "helpers" methods, like so:
helpers.rb
require 'sinatra/base'
module Sinatra
module Sample
module Helpers
def require_logged_in()
redirect('/login') unless session[:authenticated]
end
end
end
end
routing/foos.rb
require 'sinatra/base'
module Sinatra
module Sample
module Routing
module Foos
def self.registered(app)
app.get '/foos/:id' do
# invoke a helper
require_logged_in
# load a foo, or whatever
erb :foos_view, :locals => { :foo => some_loaded_foo }
end
end
end
end
end
end
app.rb
#!/usr/bin/env ruby
require 'sinatra'
require_relative 'routing/foos'
class SampleApp < Sinatra::Base
helpers Sinatra::Sample::Helpers
register Sinatra::Sample::Routing::Foos
end
When Monk didn't work for me, I started working on templates myself.
If you think about it, there is nothing special about tying up a set of files. The monk philosophy was explained to me early in 2011 during RedDotRubyConf and they have specifically told me that it's really optional to use it especially now that it's hardly maintained.
This is a good start for those who want to use ActiveRecord:
Simple Sinatra MVC
https://github.com/katgironpe/simple-sinatra-mvc

Resources