Andrew
Andrew

Reputation: 238707

How to turn a config.ru file into a single Rack application?

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

Answers (4)

radiospiel
radiospiel

Reputation: 2479

How about using URLMap?

app = Rack::URLMap.new(
  "/path1" => Path1App.new,
  "/path2" => Path2App.new
)

run app

Upvotes: 3

Lee Jarvis
Lee Jarvis

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

matt
matt

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

Darshan Rivka Whittle
Darshan Rivka Whittle

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

Related Questions