lib/roda/plugins/class_level_routing.rb
# frozen-string-literal: true # class Roda module RodaPlugins # The class_level_routing plugin adds routing methods at the class level, which can # be used instead of or in addition to using the normal +route+ method to start the # routing tree. If a request is not matched by the normal routing tree, the class # level routes will be tried. This offers a more Sinatra-like API, while # still allowing you to use a routing tree inside individual actions. # # Here's the first example from the README, modified to use the class_level_routing # plugin: # # class App < Roda # plugin :class_level_routing # # # GET / request # root do # request.redirect "/hello" # end # # # GET /hello/world request # get "hello/world" do # "Hello world!" # end # # # /hello request # is "hello" do # # Set variable for both GET and POST requests # @greeting = 'Hello' # # # GET /hello request # request.get do # "#{@greeting}!" # end # # # POST /hello request # request.post do # puts "Someone said #{@greeting}!" # request.redirect # end # end # end # # When using the class_level_routing plugin with nested routes, you may also want to use the # delegate plugin to delegate certain instance methods to the request object, so you don't have # to continually use +request.+ in your routing blocks. # # Note that class level routing is implemented via a simple array of routes, so routing performance # will degrade linearly as the number of routes increases. For best performance, you should use # the normal +route+ class method to define your routing tree. This plugin does make it simpler to # add additional routes after the routing tree has already been defined, though. module ClassLevelRouting # Initialize the class_routes array when the plugin is loaded. Also, if the application doesn't # currently have a routing block, setup an empty routing block so that things will still work if # a routing block isn't added. def self.configure(app) app.opts[:class_level_routes] ||= [] end module ClassMethods # Define routing methods that will store class level routes. [:root, :on, :is, :get, :post, :delete, :head, :options, :link, :patch, :put, :trace, :unlink].each do |request_meth| define_method(request_meth) do |*args, &block| meth = define_roda_method("class_level_routing_#{request_meth}", :any, &block) opts[:class_level_routes] << [request_meth, args, meth].freeze end end # Freeze the class level routes so that there can be no thread safety issues at runtime. def freeze opts[:class_level_routes].freeze super end end module InstanceMethods def initialize(_) super @_original_remaining_path = @_request.remaining_path end private # If the normal routing tree doesn't handle an action, try each class level route # to see if it matches. def _roda_after_10__class_level_routing(result) if result && result[0] == 404 && (v = result[2]).is_a?(Array) && v.empty? # Reset the response so it doesn't inherit the status or any headers from # the original response. @_response.send(:initialize) @_response.status = nil result.replace(_roda_handle_route do r = @_request opts[:class_level_routes].each do |request_meth, args, meth| r.instance_variable_set(:@remaining_path, @_original_remaining_path) r.public_send(request_meth, *args) do |*a| send(meth, *a) end end nil end) end end end end register_plugin(:class_level_routing, ClassLevelRouting) end end