How to inject a Rack middleware into an existing Rack app? - ruby

Given a Rack app that is not Rails,
builder.rb:
def app
Rack::Builder.new{
use Rack::Static, urls:static_paths, root:'public'
run ThaApp
}.to_app
end
How to inject a testing middleware using the spec_helper?

If you're using Builder (with use, run, etc.) it does not look like you can easily inject or remove middleware at runtime. Here's the code: https://github.com/rack/rack/blob/master/lib/rack/builder.rb
Notice that it builds the stack of middleware, and when you call run it instantiates the stack (called "#use") in a tree of middleware objects which each have a reference to the next one - see methods "use" and "to_app".
So: don't think Builder is designed to allow dynamically adding and subtracting middleware in the stack.
You could rebuild a new dynamic stack, or use multiple Rack apps with and without testing middleware, or do some backflips like Rails does to dynamically reconfigure the stack.
You could also add a testing middleware only in test mode, or one that can be deactivated easily so it becomes a pass-through middleware. Then your spec_helper would just set and clear the variable telling it to pass through.

Since I want to prepend to the middleware stack, solving for this particular use case was easy.
Given a app defined as above named "app", add the new middleware: use ...
def new_app
Rack::Builder.new do
use ...
use ...
run app
end.to_app
end

Related

Making a Rack CLI

I'm trying to make a framework similar to Rails, but purely focused on GraphQL. Once nice feature of Rails is that it provides a CLI interface and a config.ru for Rack. Therefore, you can call rackup or you can call bin/rails server and the Rails app will run. I managed to mimic this functionality by putting the Rack app into a separate file (config/application.rb), which I import in config.ru and in the CLI, then instantiate and run.
However, I have an issue with Rack middleware. Since Rack middleware appears to just magically work when you run use MyMiddleware with an instantiated Rack app, I'm not really sure how I can do this in both config.ru and in my CLI. Right now it looks like I need to instantiate the app in a separate location, add the middleware, then hand it over to config.ru or the CLI. Which, I could do, but it feels like there has to be a way to attach middleware in a cleaner way. For instance, can I require config.ru in some way and then run it? Or can I attach middleware before I instantiate the app?
config.ru is just a ruby file, it's loaded by Rails as part of running each command. You can require it yourself as normal if that's what you'd like to do.
If you want to really figure out how Rails does it, the config loading is buried in this part of the Rails CLI:
https://github.com/rails/rails/blob/3cac5fe94f0f81b4263cfa03d4822c05a55eb49c/railties/lib/rails/application.rb

Testing a rack app

I have a rack based gem, where the user defines routes, and they are then processed by the gem. I an trying to figure out how to test this setup. Testing methods directly impacted by the creation of routes doesn't work, because they are obviously not defined yet, because no app has been created. Is there a solution to this? I am currently using RSpec, and I would really like it if there is an RSpec solution to this.

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.

Espresso Enginery

i use framwork Espresso with Enginery generator (Ruby gems). I create new project, and not understand how work this application. I will work with Espresso. Explain me struct Expresso Application, please.
I can run this apllication: rackup config.ru
I can edit controllers, but i not understand depending between ruby scripts in this project.
i run projects, but why this work it?
config.ru
require File.expand_path('../base/boot', __FILE__)
puts App.urlmap
run App
in project not /base/boot directory.
requiring '../base/boot' will actually load dependencies, controllers, models etc. and build the application.
The application are stored under App constant, so you can access it from different files:
https://github.com/espresso/enginery/blob/master/app/base/base/boot.rb#L9
puts App.urlmap will display all the routes to be served by app.
And run App will start your app.
You can also start app by ruby app.rb, then you do not need to pass server/port at startup.
Instead you'll set them in config/config.yml, like this:
development:
server: Thin
port: 5252
The config.ru file looks quite normal for a rack start-up file. You would start the application from the project folder, with a command like:
rackup -p 8080
The following line:
require File.expand_path('../base/boot', __FILE__)
will load in the ../base/boot file (similar to require_relative, but also works with older Ruby e.g. 1.8.7), which I would guess requires the dependencies where App is defined. The class or module App will implement a call method. To start the server, the rack host calls App.new (which is called due to run App) and then on each request it will call .call( env ) on the resulting object (the object doesn't have to be an App object, but in simpler frameworks it will be).
The variable env contains all the details of the request and the rack environment that can be inspected to fetch details of the current path, cookies, query params etc. Typically access to this data is abstracted through Sinatra and Espresso helper methods that you will use.
The Sinatra and Espresso helper methods look like they are doing magic declarations, but they are just normal methods. Usually they do some calculation and then stash a code block/lambda for rack to call later. Sinatra's get is like this . . . it isn't true declarative code. Instead, when the controller is parsed, it just takes the code block and tells the application object to call it (later) when the path matches.

Stubbing a controller method with Sinatra and rspec

So I'm trying to figure out a way of stubbing a controller method in rspec for a Sinatra app. The main reason for this is to test the logical flow of the application and to make sure it calls the necessary functions when certain conditions are met. So, in essence, I want to be able to do something like
controller.should_receive(:fancy_method).and_return("This is a string")
What I'm having difficulty doing is accessing the controller instance within the sinatra app. I am able to override the current functions using a class_eval on the sinatra controller class, but I'd love to assert that these functions actually run.
Anyone have any advice?
Thanks.
Dan, I believe what you really want is to just test the controller actions. From a tester's perspective you shouldn't really care about what it actually called but rather for the output, given a specific input and maybe some other special conditions (that is mocking or stubbing other classes) (1).
You can check the official documentation for Sinatra + Rack::Test or this blog post from devver.net.
(1) : If your controller pages are calling some other classes (models, services, etc) you could mock these instead and put expectations on them. For example :
SomeClass.should_receive(:msg).with(:arg).and_return(:special_value)
Some more info for mocking (with RSpec in this exmaple) can be found on the RSpec documentation pages.

Resources