Calling Sinatra from within Sinatra - ruby

I have a Sinatra based REST service app and I would like to call one of the resources from within one of the routes, effectively composing one resource from another. E.g.
get '/someresource' do
otherresource = get '/otherresource'
# do something with otherresource, return a new resource
end
get '/otherresource' do
# etc.
end
A redirect will not work since I need to do some processing on the second resource and create a new one from it. Obviously I could a) use RestClient or some other client framework or b) structure my code so all of the logic for otherresource is in a method and just call that, however, it feels like it would be much cleaner if I could just re-use my resources from within Sinatra using their DSL.

Another option (I know this isn't answering your actual question) is to put your common code (even the template render) within a helper method, for example:
helpers do
def common_code( layout = true )
#title = 'common'
erb :common, :layout => layout
end
end
get '/foo' do
#subtitle = 'foo'
common_code
end
get '/bar' do
#subtitle = 'bar'
common_code
end
get '/baz' do
#subtitle = 'baz'
#common_snippet = common_code( false )
erb :large_page_with_common_snippet_injected
end

Sinatra's documentation covers this - essentially you use the underlying rack interface's call method:
http://www.sinatrarb.com/intro.html#Triggering%20Another%20Route
Triggering Another Route
Sometimes pass is not what you want, instead
you would like to get the result of calling another route. Simply use
call to achieve this:
get '/foo' do
status, headers, body = call env.merge("PATH_INFO" => '/bar')
[status, headers, body.map(&:upcase)]
end
get '/bar' do
"bar"
end

I was able to hack something up by making a quick and dirty rack request and calling the Sinatra (a rack app) application directly. It's not pretty, but it works. Note that it would probably be better to extract the code that generates this resource into a helper method instead of doing something like this. But it is possible, and there might be better, cleaner ways of doing it than this.
#!/usr/bin/env ruby
require 'rubygems'
require 'stringio'
require 'sinatra'
get '/someresource' do
resource = self.call(
'REQUEST_METHOD' => 'GET',
'PATH_INFO' => '/otherresource',
'rack.input' => StringIO.new
)[2].join('')
resource.upcase
end
get '/otherresource' do
"test"
end
If you want to know more about what's going on behind the scenes, I've written a few articles on the basics of Rack you can read. There is What is Rack? and Using Rack.

This may or may not apply in your case, but when I’ve needed to create routes like this, I usually try something along these lines:
%w(main other).each do |uri|
get "/#{uri}" do
#res = "hello"
#res.upcase! if uri == "other"
#res
end
end

Building on AboutRuby's answer, I needed to support fetching static files in lib/public as well as query paramters and cookies (for maintaining authenticated sessions.) I also chose to raise exceptions on non-200 responses (and handle them in the calling functions).
If you trace Sinatra's self.call method in sinatra/base.rb, it takes an env parameter and builds a Rack::Request with it, so you can dig in there to see what parameters are supported.
I don't recall all the conditions of the return statements (I think there were some Ruby 2 changes), so feel free to tune to your requirements.
Here's the function I'm using:
def get_route url
fn = File.join(File.dirname(__FILE__), 'public'+url)
return File.read(fn) if (File.exist?fn)
base_url, query = url.split('?')
begin
result = self.call('REQUEST_METHOD' => 'GET',
'PATH_INFO' => base_url,
'QUERY_STRING' => query,
'rack.input' => StringIO.new,
'HTTP_COOKIE' => #env['HTTP_COOKIE'] # Pass auth credentials
)
rescue Exception=>e
puts "Exception when fetching self route: #{url}"
raise e
end
raise "Error when fetching self route: #{url}" unless result[0]==200 # status
return File.read(result[2].path) if result[2].is_a? Rack::File
return result[2].join('') rescue result[2].to_json
end

Related

return json from ruby using rack

i'm still fairly new to server side scripts and try myself a little bit on ruby to write me little helpers and to learn some new things.
I currently try to write a small ruby app which sends a json file of all images within a specific folder to my page where i can use those to handle them further in js.
I read quite a few introductions to ruby and rails and got a recommendation to look into rack as a lightweight communicator between server and app.
While the ruby part works fine, i have difficulties to understand how to send out the generated JSON as a reaction to a future ajax call (e.g.). Hope someone can give me a few hints or sources to look into for further understanding. Thanks!
require 'json'
class listImages
def call(env)
imageDir = Dir.chdir("./img");
files = Dir.glob("img*")
n = 0
tempHash = {}
files.each do |i|
tempHash["img#{n}"] = i
n += 1
end
File.open("temp.json","w") do |f|
f.write(tempHash.to_json)
end
[200,{"Content-Type" => "application/javascript"}, ["temp.json"]]
end
puts "All done!"
end
run listImages.new
if $0 == __FILE__
require 'rack'
Rack::Handler::WEBrick.run MyApp.new
end
You don't have to save the JSON to a file before you can send it. Just send it directly:
[200, {"Content-Type" => "application/json"}, [tempHash.to_json]]
With your current code, you are only sending the String "temp.json".
That said, the rest of your code looks a little bit messy/not conform Ruby coding standards:
Start your classnames with an uppercase: class ListImages, not class listImages.
Use underscores, not camelcase for variable names: image_dir, not imageDir.
The puts "All done!" statement is outside the method definition and will be called early, when the class is loaded.
You define a class ListImages but in the last line of your code you refer to MyApp.

AFMotion HTTP GET request syntax for setting variable

My goal is to set an instance variable using AFMotion's AFMotion::HTTP.get method.
I've set up a Post model. I would like to have something like:
class Post
...
def self.all
response = AFMotion::HTTP.get("localhost/posts.json")
objects = JSON.parse(response)
results = objects.map{|x| Post.new(x)}
end
end
But according to the docs, AFMotion requires some sort of block syntax that looks and seems to behave like an async javascript callback. I am unsure how to use that.
I would like to be able to call
#posts = Post.all in the ViewController. Is this just a Rails dream? Thanks!
yeah, the base syntax is async, so you don't have to block the UI while you're waiting for the network to respond. The syntax is simple, place all the code you want to load in your block.
class Post
...
def self.all
AFMotion::HTTP.get("localhost/posts.json") do |response|
if result.success?
p "You got JSON data"
# feel free to parse this data into an instance var
objects = JSON.parse(response)
#results = objects.map{|x| Post.new(x)}
elsif result.failure?
p result.error.localizedDescription
end
end
end
end
Since you mentioned Rails, yeah, this is a lil different logic. You'll need to place the code you want to run (on completion) inside the async block. If it's going to change often, or has nothing to do with your Model, then pass in a &block to yoru method and use that to call back when it's done.
I hope that helps!

Sinatra, DRY and scoping

I'm looking for ways to DRY my Sinatra app and have run into some scoping issues -- in particular, helpers and Sinatra functions are not available inside my handlers. Can someone please tell me if there's a way to fix this code and more importantly, what is going on?
Thank you.
require 'sinatra'
require 'pp'
helpers do
def h(txt)
"<h1>#{txt}</h1>"
end
end
before do
puts request.path
end
def r(url, get_handler, post_handler = nil)
get(url){ get_handler.call } if get_handler
post(url){ post_handler.call } if post_handler
end
routes_composite_hash = {
'/' => lambda{ h('index page'); pp params }, #can't access h nor params!!!
'/login' => [lambda{'login page'}, lambda{'login processing'}],
'/postonly' => [nil, lambda{'postonly processing'}],
}
routes_composite_hash.each_pair do |k,v|
r(k, *v)
end
Interesting!
Do this:
def r(url, get_handler, post_handler = nil)
get(url, &get_handler) if get_handler
post(url, &post_handler) if post_handler
end
routes_composite_hash = {
'/' => lambda{ h('index page'); pp params },
'/login' => [lambda{'login page'}, lambda{'login processing'}],
'/postonly' => [nil, lambda{'postonly processing'}],
}
routes_composite_hash.each_pair do |k,v|
r(k, *v)
end
As Kashyap explains, you were calling your get and post handlers inside the main context. This just converts sent lambda to a block and passes it to the desired method.
The methods you define inside helpers do .. end blocks are available only inside routes and filters and views contexts and thus, since you are not using them inside any of those, it won't work. Lambdas preserve the execution context which means that in the hash {'/' => lambda { h }..}, the context is main but inside the get method, the context changes and the helpers are available only in this context.
To achieve what you want to do though, (although I would suggest you avoid doing this), you can just define the helpers as lambdas inside your app file itself. In your case, it would be:
def h(txt)
"<h1>#{txt}</h1>"
end
# And then the rest of the methods and the routes hash
This way, the h method is in the context of the main object and thus will be visible all over.

Detect which method called the routing condition in Sinatra

I'm using Sinatra with namespace.
When I tried to use condition, I met a problem.
Here's the snippet of code
class MainApp < Sinatra::Base
register Sinatra::Namespace
set(:role) do |role|
condition{
### DETECT WHERE THIS IS CALLED
p role
true
}
end
namespace '/api', :role => :admin do
before do
p "before"
end
get '/hoo' do
p "hoo"
end
end
namespace '/api' do
get '/bar' do
p "bar"
end
end
end
The above code outputs following message to console when accessing /api/hoo
:admin
:admin
"before"
:admin
"hoo"
I could not understand why :admin is displayed three times. However, maybe one is from namespace, and other twos are from before and get '/hoo'.
On the other hand, accessing /api/bar shows :admin two times.
I just want to do the filtering only before get '/hoo'. Is there any idea?
NOTE: I don't wan't to change URL from /api/hoo to something like /api/baz/hoo
You can debug the steps using the caller:
http://ruby-doc.org/core-2.0/Kernel.html#method-i-caller
(Note: I wouldn't recommend to leave caller in production code unless you absolutely need it for introspection, because it's quite slow.)
Re the Sinatra filters in particular, note that you can at the very least qualify the route and conditions they apply to:
http://www.sinatrarb.com/intro#Filters
before '/protected/*' do
authenticate!
end
before :agent => /Songbird/ do
# ...
end
I can't recollect how to get the http method, but if you look at the sinatra source code you'll likely find it -- last I looked, I recollect each of get, post, etc. to forward their call to the same function, with a method parameter.

Using specific VCR cassette based on request

Situation: testing a rails application using Rspec, FactoryGirl and VCR.
Every time a User is created, an associated Stripe customer is created through Stripe's API. While testing, it doesn't really makes sense to add a VCR.use_cassette or describe "...", vcr: {cassette_name: 'stripe-customer'} do ... to every spec where User creation is involved. My actual solution is the following:
RSpec.configure do |config|
config.around do |example|
VCR.use_cassette('stripe-customer') do |cassette|
example.run
end
end
end
But this isn't sustainable because the same cassette will be used for every http request, which of course is very bad.
Question: How can I use specific fixtures (cassettes) based on individual request, without specifying the cassette for every spec?
I have something like this in mind, pseudo-code:
stub_request(:post, "api.stripe.com/customers").with(File.read("cassettes/stripe-customer"))
Relevant pieces of code (as a gist):
# user_observer.rb
class UserObserver < ActiveRecord::Observer
def after_create(user)
user.create_profile!
begin
customer = Stripe::Customer.create(
email: user.email,
plan: 'default'
)
user.stripe_customer_id = customer.id
user.save!
rescue Stripe::InvalidRequestError => e
raise e
end
end
end
# vcr.rb
require 'vcr'
VCR.configure do |config|
config.default_cassette_options = { record: :once, re_record_interval: 1.day }
config.cassette_library_dir = 'spec/fixtures/cassettes'
config.hook_into :webmock
config.configure_rspec_metadata!
end
# user_spec.rb
describe :InstanceMethods do
let(:user) { FactoryGirl.create(:user) }
describe "#flexible_name" do
it "returns the name when name is specified" do
user.profile.first_name = "Foo"
user.profile.last_name = "Bar"
user.flexible_name.should eq("Foo Bar")
end
end
end
Edit
I ended doing something like this:
VCR.configure do |vcr|
vcr.around_http_request do |request|
if request.uri =~ /api.stripe.com/
uri = URI(request.uri)
name = "#{[uri.host, uri.path, request.method].join('/')}"
VCR.use_cassette(name, &request)
elsif request.uri =~ /twitter.com/
VCR.use_cassette('twitter', &request)
else
end
end
end
VCR 2.x includes a feature specifically to support use cases like these:
https://relishapp.com/vcr/vcr/v/2-4-0/docs/hooks/before-http-request-hook!
https://relishapp.com/vcr/vcr/v/2-4-0/docs/hooks/after-http-request-hook!
https://relishapp.com/vcr/vcr/v/2-4-0/docs/hooks/around-http-request-hook!
VCR.configure do |vcr|
vcr.around_http_request(lambda { |req| req.uri =~ /api.stripe.com/ }) do |request|
VCR.use_cassette(request.uri, &request)
end
end
IMO, libraries like this should provided you with a mock class, but w/e.
You can do your pseudocode example already with Webmock, which is the default internet mocking library that VCR uses.
body = YAML.load(File.read 'cassettes/stripe-customer.yml')['http_interactions'][0]['response']['body']['string']
stub_request(:post, "api.stripe.com/customers").to_return(:body => body)
You could put that in a before block that only runs on a certain tag, then tag the requests that make API calls.
In their tests, they override the methods that delegate to RestClient (link). You could do this as well, take a look at their test suite to see how they use it, in particular their use of test_response. I think this is a terribly hacky way of doing things, and would feel really uncomfortable with it (note that I'm in the minority with this discomfort) but it should work for now (it has the potential to break without you knowing until runtime). If I were to do this, I'd want to build out real objects for the two mocks (the one mocking rest-client, and the other mocking the rest-client response).
The whole point (mostly anyway) of VCR to just to replay the response of a previous request. If you are in there picking and choosing what response goes back to what request, you are quote/unquote doing-it-wrong.
Like Joshua already said, you should use Webmock for something like this. That's what VCR is uing behind the scenes anyway.

Resources