using send_file in deferred sinatra request - ruby

I'm trying to return a file during an async sinatra request, something like this:
aget "/test" do
if(File.exists?("test.tar"))
send_file("test.tar", :filename => "test.tar", :type => "application/octet-stream")
return
end
EM.defer(proc{
# create test.tar
},
proc{ |r|
send_file("test.tar", :filename => "test.tar", :type => "application/octet-stream")
})
However it seems that when I do that, I get an error:
wrong number of arguments (0 for 1)
file: file.rb
location: call
line: 29
BACKTRACE:
/var/lib/gems/1.9.1/gems/rack-1.4.1/lib/rack/file.rb in call
def call(env)
/var/lib/gems/1.9.1/gems/async_sinatra-1.0.0/lib/sinatra/async.rb in block in body
response.body = Array(async_handle_exception {response.body.call})
/var/lib/gems/1.9.1/gems/async_sinatra-1.0.0/lib/sinatra/async.rb in async_handle_exception
yield
/var/lib/gems/1.9.1/gems/async_sinatra-1.0.0/lib/sinatra/async.rb in body
response.body = Array(async_handle_exception {response.body.call})
/var/lib/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb in invoke
body(res.pop)
/var/lib/gems/1.9.1/gems/async_sinatra-1.0.0/lib/sinatra/async.rb in block in async_catch_execute
invoke { halt h }
/var/lib/gems/1.9.1/gems/async_sinatra-1.0.0/lib/sinatra/async.rb in async_handle_exception
yield
/var/lib/gems/1.9.1/gems/async_sinatra-1.0.0/lib/sinatra/async.rb in async_catch_execute
async_handle_exception do
/var/lib/gems/1.9.1/gems/async_sinatra-1.0.0/lib/sinatra/async.rb in block in async_schedule
native_async_schedule { async_catch_execute(&b) }
/var/lib/gems/1.9.1/gems/eventmachine-1.0.0.beta.4/lib/eventmachine.rb in call
end.each { |j| j.call }
/var/lib/gems/1.9.1/gems/eventmachine-1.0.0.beta.4/lib/eventmachine.rb in block in run_deferred_callbacks
end.each { |j| j.call }
/var/lib/gems/1.9.1/gems/eventmachine-1.0.0.beta.4/lib/eventmachine.rb in each
#next_tick_mutex.synchronize do
/var/lib/gems/1.9.1/gems/eventmachine-1.0.0.beta.4/lib/eventmachine.rb in run_deferred_callbacks
#next_tick_mutex.synchronize do
/var/lib/gems/1.9.1/gems/eventmachine-1.0.0.beta.4/lib/eventmachine.rb in run_machine
run_machine
/var/lib/gems/1.9.1/gems/eventmachine-1.0.0.beta.4/lib/eventmachine.rb in run
run_machine
/var/lib/gems/1.9.1/gems/thin-1.3.1/lib/thin/backends/base.rb in start
EventMachine.run(&starter)
/var/lib/gems/1.9.1/gems/thin-1.3.1/lib/thin/server.rb in start
#backend.start
/var/lib/gems/1.9.1/gems/rack-1.4.1/lib/rack/handler/thin.rb in run
server.start
/var/lib/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/base.rb in run!
handler.run self, :Host => bind, :Port => port do |server|
/var/lib/gems/1.9.1/gems/sinatra-1.3.2/lib/sinatra/main.rb in block in <module:Sinatra>
at_exit { Application.run! if $!.nil? && Application.run? }
end

The error you are receiving is cause you are using a function that requires 1 argument and you haven't supplied any. If you can show us the code around line 29 I or somebody can point it out for you.

Related

How to use lotus router with Rack::Builder::map

Is there a way to use map and the (lotus)router namespacing together? Below is a sample config.ru I'm trying to get running as a demo.
require 'bundler'
Bundler.require
module Demo
class Application
def initialize
#app = Rack::Builder.new do
map '/this_works' do
run Proc.new {|env| [200, {"Content-Type" => "text/html"}, ["this_works"]]}
end
map '/api' do
run Lotus::Router.new do
get '/api/', to: ->(env) { [200, {}, ['Welcome to Lotus::Router!']] }
get '/*', to: ->(env) { [200, {}, ["This is catch all: #{ env['router.params'].inspect }!"]] }
end
end
end
end
def call(env)
#app.call(env)
end
end
end
run Demo::Application.new
Your problem is due to the precedence of do..end in method calls. In your code the section
run Lotus::Router.new do
get '/api/', to: ->(env) { [200, {}, ['Welcome to Lotus::Router!']] }
get '/*', to: ->(env) { [200, {}, ["This is catch all: #{ env['router.params'].inspect }!"]] }
end
is parsed by Ruby as
run(Lotus::Router.new) do
get '/api/', to: ->(env) { [200, {}, ['Welcome to Lotus::Router!']] }
get '/*', to: ->(env) { [200, {}, ["This is catch all: #{ env['router.params'].inspect }!"]] }
end
In other words the block is passed to run, not to Lotus::Router.new as you intended, and run simply ignores the block.
To fix it you need to ensure the block is associated with the constructor of the router rather than the call to run. There are a couple of ways to do this. You could use {...} rather than do...end, as that has a higher precedence:
run Lotus::Router.new {
#...
}
An alternative would be to assign the router to a local variable, and use that as the argument to run:
router = Lotus::Router.new do
#...
end
run router

Ruby stubbing with faraday, can't get it to work

Sorry for the title, I'm too frustrated to come up with anything better right now.
I have a class, Judge, which has a method #stats. This stats method is supposed to send a GET request to an api and get some data as response. I'm trying to test this and stub the stats method so that I don't perform an actual request. This is what my test looks like:
describe Judge do
describe '.stats' do
context 'when success' do
subject { Judge.stats }
it 'returns stats' do
allow(Faraday).to receive(:get).and_return('some data')
expect(subject.status).to eq 200
expect(subject).to be_success
end
end
end
end
This is the class I'm testing:
class Judge
def self.stats
Faraday.get "some-domain-dot-com/stats"
end
end
This currently gives me the error: Faraday does not implement: get
So How do you stub this with faraday? I have seen methods like:
stubs = Faraday::Adapter::Test::Stubs.new do |stub|
stub.get('http://stats-api.com') { [200, {}, 'Lorem ipsum'] }
end
But I can't seem to apply it the right way. What am I missing here?
Note that Faraday.new returns an instance of Faraday::Connection, not Faraday. So you can try using
allow_any_instance_of(Faraday::Connection).to receive(:get).and_return("some data")
Note that I don't know if returning "some data" as shown in your question is correct, because Faraday::Connection.get should return a response object, which would include the body and status code instead of a string. You might try something like this:
allow_any_instance_of(Faraday::Connection).to receive(:get).and_return(
double("response", status: 200, body: "some data")
)
Here's a rails console that shows the class you get back from Faraday.new
$ rails c
Loading development environment (Rails 4.1.5)
2.1.2 :001 > fara = Faraday.new
=> #<Faraday::Connection:0x0000010abcdd28 #parallel_manager=nil, #headers={"User-Agent"=>"Faraday v0.9.1"}, #params={}, #options=#<Faraday::RequestOptions (empty)>, #ssl=#<Faraday::SSLOptions (empty)>, #default_parallel_manager=nil, #builder=#<Faraday::RackBuilder:0x0000010abcd990 #handlers=[Faraday::Request::UrlEncoded, Faraday::Adapter::NetHttp]>, #url_prefix=#<URI::HTTP:0x0000010abcd378 URL:http:/>, #proxy=nil>
2.1.2 :002 > fara.class
=> Faraday::Connection
Coming to this late, but incase anyone else is too, this is what worked for me - a combination of the approaches above:
let(:json_data) { File.read Rails.root.join("..", "fixtures", "ror", "501100000267.json") }
before do
allow_any_instance_of(Faraday::Connection).to receive(:get).and_return(
double(Faraday::Response, status: 200, body: json_data, success?: true)
)
end
Faraday the class has no get method, only the instance does. Since you are using this in a class method what you can do is something like this:
class Judge
def self.stats
connection.get "some-domain-dot-com/stats"
end
def self.connection=(val)
#connection = val
end
def self.connection
#connection ||= Faraday.new(some stuff to build up connection)
end
end
Then in your test you can just set up a double:
let(:connection) { double :connection, get: nil }
before do
allow(connection).to receive(:get).with("some-domain-dot-com/stats").and_return('some data')
Judge.connection = connection
end
I ran into the same problem with Faraday::Adapter::Test::Stubs erroring with Faraday does not implement: get. It seems you need to set stubs to a Faraday adapter, like so:
stubs = Faraday::Adapter::Test::Stubs.new do |stub|
stub.get("some-domain-dot-com/stats") { |env| [200, {}, 'egg'] }
end
test = Faraday.new do |builder|
builder.adapter :test, stubs
end
allow(Faraday).to receive(:new).and_return(test)
expect(Judge.stats.body).to eq "egg"
expect(Judge.stats.status).to eq 200
A better way to do this, rather than using allow_any_instance_of, is to set the default connection for Faraday, so that Faraday.get will use the connection you setup in your tests.
For example:
let(:stubs) { Faraday::Adapter::Test::Stubs.new }
let(:conn) { Faraday.new { |b| b.adapter(:test, stubs) } }
before do
stubs.get('/maps/api/place/details/json') do |_env|
[
200,
{ 'Content-Type': 'application/json' },
{ 'result' => { 'photos' => [] } }.to_json
]
end
Faraday.default_connection = conn
end
after do
Faraday.default_connection = nil
end

Standardizing api responses in a modular Sinatra application

I'm developing an api as a modular Sinatra web application and would like to standardize the responses that are returned without having to do so explicitly. I thought this could be achieved by using middleware but it fails in most scenarios. The below sample application is what I have so far.
config.ru
require 'sinatra/base'
require 'active_support'
require 'rack'
class Person
attr_reader :name, :surname
def initialize(name, surname)
#name, #surname = name, surname
end
end
class MyApp < Sinatra::Base
enable :dump_errors, :raise_errors
disable :show_exceptions
get('/string') do
"Hello World"
end
get('/hash') do
{"person" => { "name" => "john", "surname" => "smith" }}
end
get('/array') do
[1,2,3,4,5,6,7, "232323", '3245235']
end
get('/object') do
Person.new('simon', 'hernandez')
end
get('/error') do
raise 'Failure of some sort'
end
end
class ResponseMiddleware
def initialize(app)
#app = app
end
def call(env)
begin
status, headers, body = #app.call(env)
response = {'status' => 'success', 'data' => body}
format(status, headers, response)
rescue ::Exception => e
response = {'status' => 'error', 'message' => e.message}
format(500, {'Content-Type' => 'application/json'}, response)
end
end
def format(status, headers, response)
result = ActiveSupport::JSON.encode(response)
headers["Content-Length"] = result.length.to_s
[status, headers, result]
end
end
use ResponseMiddleware
run MyApp
Examples (in JSON):
/string
Expected: {"status":"success","data":"Hello World"}
Actual: {"status":"success","data":["Hello World"]}
/hash (works)
Expected: {"status":"success","data":{"person":{"name":"john","surname":"smith"}}}
Actual: {"status":"success","data":{"person":{"name":"john","surname":"smith"}}}
/array
Expected: {"status":"success","data": [1,2,3,4,5,6,7,"232323","3245235"]}
Actual: {"status":"error","message":"wrong number of arguments (7 for 1)"}
/object
Expected: {"status":"success","data":{"name":"simon","surname":"hernandez"}}
Actual: {"status":"success","data":[]}
/error (works)
Expected: {"status":"error","message":"Failure of some sort"}
Actual: {"status":"error","message":"Failure of some sort"}
If you execute the code, you will see that /hash and /error give back the required responses, but the rest do not. Ideally, I would not like to change anything in the MyApp class. It's currently being built on top of Sinatra 1.3.3, ActiveSupport 3.2.9 and Rack 1.4.1.
With some help from #sinatra on irc.freenode.org, I managed to get it down to what I want. I added the following to MyApp:
def route_eval
result = catch(:halt) { super }
throw :halt, {"result" => result}
end
I then changed the following line in ResponseMiddleware:
response = {'status' => 'success', 'data' => body}
to
response = {'status' => 'success', 'data' => body["result"]}
and all my test cases passed.

Accessing an EventMachine channel from a Sinatra route

I have a simple Sinatra App running on EventMachine, like this example.
The app is working, now I'd like to allow the routes I'm defining in Sinatra to access the websocket using the EventMachine channel that is created. I naively tried the following, but of course within the Sinatra App, the #channel variable isn't defined, so this doesn't work.
require 'em-websocket'
require 'sinatra'
EventMachine.run do
#channel = EM::Channel.new
class App < Sinatra::Base
get '/' do
erb :index
end
post '/test' do
#channel.push "Post request hit endpoint"
status 200
end
end
EventMachine::WebSocket.start :host => '0.0.0.0', :port => 8080 do |socket|
socket.onopen do
sid = #channel.subscribe { |msg| socket.send msg }
#channel.push "Subscriber ID #{sid} connected!"
socket.onmessage do |msg|
#channel.push "Subscriber <#{sid}> sent message: #{msg}"
end
socket.onclose do
#channel.unsubscribe(sid)
end
end
end
App.run! :port => 3000
end
How could I access the EventMachine channel I've got open within my Sinatra app?
In case others don't know what we're talking about in the comments, here's an example of using a class instance variable in the way I suggested. This runs, but I don't know if it does what's expected:
require 'em-websocket'
require 'sinatra'
require 'haml'
module Example
def self.em_channel
#em_channel ||= EM::Channel.new
end
EventMachine.run do
class App < Sinatra::Base
configure do
enable :inline_templates
end
get '/' do
haml :index
end
get '/test' do
Example.em_channel.push "Test request hit endpoint"
status 200
end
end
EventMachine::WebSocket.start :host => '0.0.0.0', :port => 8080 do |socket|
socket.onopen do
sid = Example.em_channel.subscribe { |msg| socket.send msg }
Example.em_channel.push "Subscriber ID #{sid} connected!"
socket.onmessage do |msg|
Example.em_channel.push "Subscriber <#{sid}> sent message: #{msg}"
end
socket.onclose do
Example.em_channel.unsubscribe(sid)
end
end
end
App.run!
end
end
__END__
## layout
%html
= yield
## index
%div.title Hello world.

How to spec a file download

I am working on a library that needs to be able to download plugin files from a remote API using RestClient. The library first grabs a list of plugins, and then downloads each plugin as a raw file, saving each inside a plugins directory.
Here is what I have thus far but it is failing me:
require 'yaml'
module Monitaur
class Client
attr_accessor :logger, :client_key, :server_url, :config, :raw_config,
:plugin_manifest
def initialize
load_config
#plugin_manifest ||= []
end
def run
get_plugin_manifest
sync_plugins
end
def get_plugin_manifest
res = RestClient.get("#{server_url}/nodes/#{client_key}/plugins")
#plugin_manifest = JSON.parse(res)
end
def sync_plugins
#plugin_manifest.each do |plugin|
res = RestClient.get("#{server_url}/plugins/#{plugin['name']}")
File.open(File.join(Monitaur.plugin_dir, "#{plugin['name']}.rb"), "w+") do |file|
file.write res.body
end
end
end
def load_config
if File.exist?(Monitaur.config_file_path) && File.readable?(Monitaur.config_file_path)
#raw_config = YAML.load_file(Monitaur.config_file_path)
else
raise IOError, "Cannot open or read #{Monitaur.config_file_path}"
end
#server_url = raw_config['server_url']
#client_key = raw_config['client_key']
end
end
end
And the client_spec.rb
require 'spec_helper'
module Monitaur
describe Client do
let(:server_url) { "http://api.monitaurapp.com" }
let(:client_key) { "asdf1234" }
describe "#load_config" do
let(:client) { Monitaur::Client.new }
before do
File.open(Monitaur.config_file_path, "w") do |file|
file.puts "server_url: http://api.monitaurapp.com"
file.puts "client_key: asdf1234"
end
end
it "loads up the configuration file" do
client.load_config
client.server_url.should == "http://api.monitaurapp.com"
client.client_key.should == "asdf1234"
end
end
describe "#get_plugin_manifest" do
let(:client) { Monitaur::Client.new }
before do
stub_get_plugin_manifest
end
it "retrieves a plugins manifest from the server" do
client.get_plugin_manifest
client.plugin_manifest.should == plugin_manifest_response
end
end
describe "#sync_plugins" do
let(:client) { Monitaur::Client.new }
let(:foo_plugin) { mock('foo_plugin') }
let(:bar_plugin) { mock('bar_plugin') }
before do
FileUtils.mkdir("/tmp")
File.open("/tmp/foo_plugin.rb", "w+") do |file|
file.write %|
class FooPlugin < Monitaur::Plugin
name "foo_plugin"
desc "A test plugin to determine whether plugin sync works"
def run
{ :foo => 'foo' }
end
end
|
end
File.open("/tmp/bar_plugin.rb", "w+") do |file|
file.write %|
class BarPlugin < Monitaur::Plugin
name "bar_plugin"
desc "A test plugin to determine whether plugin sync works"
def run
{ :bar => 'bar' }
end
end
|
end
Monitaur.install
stub_get_plugin_manifest
stub_sync_plugins
client.get_plugin_manifest
end
it "downloads plugins to the cache directory" do
File.should_receive(:open).
with(File.join(Monitaur.plugin_dir, "foo_plugin.rb"), "w+")
and_yield(foo_plugin)
client.sync_plugins
File.exist?("/home/user/.monitaur/cache/plugins/foo_plugin.rb").should be_true
File.exist?("/home/user/.monitaur/cache/plugins/bar_plugin.rb").should be_true
end
end
end
end
def stub_get_plugin_manifest
stub_request(:get, "#{server_url}/nodes/#{client_key}/plugins").
to_return(
:status => 200,
:body => %Q{
[
{
"name": "foo_plugin",
"checksum": "qwer5678"
},
{
"name": "bar_plugin",
"checksum": "hjkl4321"
}
]
}
)
end
def plugin_manifest_response
[
{
"name" => "foo_plugin",
"checksum" => "qwer5678"
},
{
"name" => "bar_plugin",
"checksum" => "hjkl4321"
}
]
end
def stub_sync_plugins
stub_request(:get, "#{server_url}/plugins/foo_plugin").
to_return(:body => File.open('/tmp/foo_plugin.rb').read)
stub_request(:get, "#{server_url}/plugins/bar_plugin").
to_return(:body => File.open('/tmp/bar_plugin.rb').read)
end
How can I test the download process?
I use FakeWeb for this purpose, as there's really no need for your spec to fail if the other site is down or something. See "Replaying a recorded response" in the docs. What we do is curl the page, save it somewhere as a fixture and replay that in the specs.

Resources