Reputation: 238707
I have a config.ru file that is starting to have duplicate code:
map '/route1' do
run SampleApp.new
end
map '/route2' do
run SampleApp.new
end
I would like to turn this config.ru file into its own Rack application so that all I have to do is:
map '/' do
run MyApp.new
end
What is the correct way to create your own Rack application? Specifically, how can I create a class so that I can use the map
method within my class to define a bunch of routes?
Solution:
Here is a working solution:
class MyApp
def initialize
@app = Rack::Builder.new do
# copy contents of your config.ru into this block
map '/route1' do
run SampleApp.new
end
map '/route2' do
run SampleApp.new
end
end
end
def call(env)
@app.call(env)
end
end
I tried this before, but couldn't get it to work because I was trying to pass instance variables to the map
blocks. For example:
def initialize
@sample_app = SampleApp.new
@app = Rack::Builder.new do
map '/route1' do
run @sample_app # will not work
end
end
end
The reason this will not work is because the block being passed to map
is being evaluated in the context of a Rack::Builder
instance.
However, it will work if I pass a local variable:
def initialize
sample_app = SampleApp.new
@app = Rack::Builder.new do
map '/route1' do
run sample_app # will work
end
end
end
Upvotes: 10
Views: 10582
Reputation: 2479
How about using URLMap?
app = Rack::URLMap.new(
"/path1" => Path1App.new,
"/path2" => Path2App.new
)
run app
Upvotes: 3
Reputation: 16241
Here is a really basic example. You should probably look into Rack::Response
for handling the response rather than building it yourself, but it gives you a good idea of how basic Rack middleware works:
class MyApp
def call(env)
request = Rack::Request.new(env)
headers = { 'Content-Type' => 'text/html' }
case request.path
when '/'
[200, headers, ["You're at the root url!"]]
when '/foo'
[200, headers, ["You're at /foo!"]]
else
[404, headers, ["Uh oh, path not found!"]]
end
end
end
EDIT:
Mapping multiple Rack apps into one:
class RootApp
def call(env)
[200, {'Content-Type' => 'text/html' }, ['Main root url']]
end
end
class FooApp
def call(env)
[200, {'Content-Type' => 'text/html' }, ['Foo app url!']]
end
end
class MyApp
def initialize
@apps = {}
end
def map(route, app)
@apps[route] = app
end
def call(env)
request = Rack::Request.new(env)
if @apps[request.path]
@apps[request.path].call(env)
else
[404, {'Content-Type' => 'text/html' }, ['404 not found']]
end
end
end
app = MyApp.new
app.map '/', RootApp.new
app.map '/foo', FooApp.new
run app
Upvotes: 4
Reputation: 79733
The DSL used in config.ru
is defined in Rack::Builder
. When using a config.ru
, contents of the file are passed to an instance of Builder
to create the Rack app. You can do this directly yourself in code.
For example, you can take the contents of your existing config.ru
, and create a new class from it:
require 'rack'
class MyApp
def initialize
@app = Rack::Builder.new do
# copy contents of your config.ru into this block
map '/route1' do
run SampleApp.new
end
map '/route2' do
run SampleApp.new
end
end
end
def call(env)
@app.call(env)
end
end
You need the call
method so that your class is a Rack app, but you can just forward the request on to the app you create with Builder
. Then you can create your new config.ru
that uses your new app:
require './my_app'
run MyApp.new
Upvotes: 11
Reputation: 34031
I do this:
class MyApp
def call(env)
@env = env
# REQUEST_URI is still encoded; split before decoding to allow encoded slashes
@path = env['REQUEST_URI'].split('/')
# REQUEST_URI starts with a slash, so delete blank first element
@path.delete_at(0)
@path.each_index do |i|
@path[i]= CGI.unescape(@path[i])
end
route()
end
end
And then route()
can do whatever it wants to route the request, e.g.:
class MyApp
def route
m = @env['REQUEST_METHOD']
@section = @path.shift
if not @section
home()
elsif @section == 'route1' and m == 'GET'
route1()
# else ...
end
end
end
Upvotes: 0