-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How does the route parsing work and what is the recommended way to split routes out into multiple files? #10
Comments
Note I found this in Stackoverflow. http://stackoverflow.com/questions/5015471/using-sinatra-for-larger-projects-via-multiple-files |
One approach that I find works quite well is to use For instance, a typical setup: require_relative 'blog_app'
require_relative 'user_app'
require_relative 'admin_app'
require_relative 'api'
map '/blog'
run BlogApp # Sinatra app whose routes are mounted under /blog
end
map '/user'
run UserApp
end
map '/admin'
run AdminApp
end
map '/api'
run API
end This is an ancient post, but describes the basics of the technique. To which I would add, get in the habit of using the According to rkh, splitting up into multiple apps this way improves performance too (particularly if you have a lot of routes):
(from an epic thread titled Should Sinatra deprecate classic? Dec 2010, sinatrarb google group) |
Note however that routing overhead is basically irrelevant for your app performance. Unless all your routes do is returning strings without any kind of computation, IO, etc. |
In your master app, could you do something like:
Where fake_app_1 and fake_app_2 contain things like:
I realize doing it in config.ru is probably cleaner, but this would be another way to keep the logic in your actual application, rather than at the Rack layer. Note: This is untested (and probably very un-ruby-like) but valid? !!!Edit!!! Nevermind :) That certainly won't work with the way load and require process through the interpreter. |
|
Going back to the question of how Sinatra compiles routes... anyone want to take a stab at explaining this? def compile!(verb, path, block, options = {})
options.each_pair { |option, args| send(option, *args) }
method_name = "#{verb} #{path}"
unbound_method = generate_method(method_name, &block)
pattern, keys = compile path
conditions, @conditions = @conditions, []
[ pattern, keys, conditions, block.arity != 0 ?
proc { |a,p| unbound_method.bind(a).call(*p) } :
proc { |a,p| unbound_method.bind(a).call } ]
end
def compile(path)
keys = []
if path.respond_to? :to_str
pattern = path.to_str.gsub(/[^\?\%\\\/\:\*\w]/) { |c| encoded(c) }
pattern.gsub!(/((:\w+)|\*)/) do |match|
if match == "*"
keys << 'splat'
"(.*?)"
else
keys << $2[1..-1]
"([^/?#]+)"
end
end
[/^#{pattern}$/, keys]
elsif path.respond_to?(:keys) && path.respond_to?(:match)
[path, path.keys]
elsif path.respond_to?(:names) && path.respond_to?(:match)
[path, path.names]
elsif path.respond_to? :match
[path, keys]
else
raise TypeError, path
end
end
def generate_method(method_name, &block)
define_method(method_name, &block)
method = instance_method method_name
remove_method method_name
method
end |
compile! is conceptually simple, though I'm futzing around in a detail or two. generate_method is a piece of metaprogramming which returns an unboundmethod object and ensures that a dynamically generated method is cleaned up from the enclosing class. compile() is kind of cute. It takes a path, which I thought was a string but then has to_str called on it. It santises the path once it's definitely a string. There's some delegation to this:
which I don't recognise at all. If the method.to_s does not match (beginning with __||to_str) and STRING (??) .respond_to? tmethod, unless the super class has returned false, then return true. |
Oh, anyway, if either path responds_to :keys, or path responds_to :names, or path responds_to :match, then [path, path.{whatever}] is created as an anonymous array and returned to the pattern, keys variables in compile!. compile then creates a copy of @conditions and blanks @conditions to []. If "pattern, keys, conditions, block.arity " does not equal 0 (I'm not sure why the comparison with 0), then the unbound method is bound and called with either with or without the arguments *p. Presumably, *p is some class data. So, how compile! works is to create a locally scoped method that corresponds to "GET /" or "HEAD /" or whatever and then called them with appropriate arguments. |
Once a
Sinatra
project gets beyond a certain level of complexity themy_sinatra_app.rb
file gets rather cluttered with route handling code. While the separation ofhelper
code out into multiple files is well documented, I am yet to find a best practice approach to separating out route handlers into multiple files.Having a clearer understanding how exactly how
Sinatra
parses routes in the first place would of course seem to be the way forward here, and then, based on this understanding, some example code and documentation could be written to cover this off canonically.The text was updated successfully, but these errors were encountered: