Skip to content

Instantly share code, notes, and snippets.

@josevalim
Created September 13, 2012 21:52
Show Gist options
  • Save josevalim/3717973 to your computer and use it in GitHub Desktop.
Save josevalim/3717973 to your computer and use it in GitHub Desktop.
Sinatra like routes in Rails controllers

Sinatra like routes in Rails controllers

A proof of concept of having Sinatra like routes inside your controllers.

How to use

Since the router is gone, feel free to remove config/routes.rb. Then add the file below to lib/action_controller/inline_routes.rb inside your app.

Then, all you need to do is to tell your Rails application what is the endpoint it should use. It is as easy as:

class Application < Rails::Application
  endpoint lambda { |env| ApplicationController.call(env) }
end

We use a lambda so the controller is reloaded in development. You could pass simply endpoint ApplicationController if you don't care about reloading.

Now, Rails will skip the router and go direct to ApplicationController. In there, simply do:

require "action_controller/inline_routes"

class ApplicationController < ActionController::Base
  include ActionController::InlineRoutes

  protect_from_forgery

  get "/hello" do
    render text: "Hello Gorgeous!"
  end
end

And that is it! You can use all Rails helpers, filters, views, etc.

How it works

Internally, ActionController::InlineRoutes is going to define an index action for you. So callbacks, middleware and everything works just fine and as if you were inside the index action.

In cases no route matches, the method not_found can be called, which you can then further customize or simply do nothing and let Rails default behaviour kick in.

You could even mix Rails conventional actions with this Sinatra routes style, but please don't!

TODO

  • Sinatra routes pattern matching does not work. You can't do "/posts/:id" yet, if you need this, fork it!

  • Add support to mount (like in Rails routers, also called forward in Sinatra(. This will allow you to organize your code in more than one controller. Again, fork it if you need it!

  • Make it a gem with proper README, release and docs!

License

Copyright (c) 2012 José Valim

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

module ActionController::InlineRoutes
extend ActiveSupport::Concern
included do
class_attribute :inline_routes
self.inline_routes = Hash.new { |h,k| h[k] = [] }
end
module ClassMethods
def call(env)
action(:index).call(env)
end
def get(path, &block)
define_route("GET", path, block)
end
def post(path, &block)
define_route("POST", path, block)
end
def put(path, &block)
define_route("PUT", path, block)
end
def delete(path, &block)
define_route("DELETE", path, block)
end
def patch(path, &block)
define_route("PATCH", path, block)
end
private
def define_route(verb, path, block)
current = inline_routes[verb] || []
current += [[path, block]]
self.inline_routes = inline_routes.merge(verb => current)
end
end
def index
unless run_inline_route
not_found
end
raise "inline route did not redirected nor rendered" unless performed?
end
private
def not_found
headers["X-Cascade"] = "pass"
self.response_body = ""
end
def run_inline_route
inline_routes[request.request_method].each do |path, block|
if request.path_info == path
instance_exec(&block)
return true
end
end
false
end
end
@lwoodson
Copy link

I like this. Routes are one area that I would prefer less metaprogramming auto-configured magic and more explicit declaration that is easily understood at a glance.

@shime
Copy link

shime commented Nov 8, 2012

@josevalim I've pushed a simple gem for that yesterday. Supports pattern matching!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment