This document is a humble attempt to explain the internal workings of Ramaze and how the different parts fit into the big picture. It does not try to describe every little detail, as you will be better off reading the actual source instead. But you will at least get an idea of where to look. ### Ramaze Ramaze is a web framework and therefor tries to make it simple to deploy your own applications on top of it. Let's outline the realms that Ramaze works in as to show what the current limitations and features are. #### Life of a request/response cycle This is a small summary of how a request creates a response in the examples/hello.rb and webrick (to keep it simple and short). All files referenced here are in lib/ramaze/ unless indicated otherwise. We don't explain the startup/shutdown process, only what happens from request to the eventual response. Browser sends request, webrick receives it and hands the ENV over to our rack handler (adapter/webrick.rb and adapter/base.rb). There it gets converted into a Ramaze::Request (trinity/request.rb) and a new blank Ramaze::Response (trinity/response.rb) is created. These two objects are then sent to Dispatcher::handle (dispatcher.rb). This all happens inside a new Thread, which is what Ramaze takes advantage of by assigning Thread-variables. You can see this now in Dispatcher::setup_environment which sets :request/:session/:response in Thread.current. After that, Dispatcher::dispatch is called with the path from request.path_info which in turn takes every class/module set in Dispatcher::FILTER and calls ::process with the path. ##### First, `/favicon.ico` (for most browsers) The first in FILTER is Dispatcher::File (dispatcher/file.rb) which searches Global.public_root and afterwards Global.public_proto for a matching file on this path. In our example the file found is a favicon.ico from Global.public_proto. The contents of FILTER have to return a Ramaze::Response, either the (possibly modified) original (Dispatcher::build_response helps with that) or a totally new one. In our case, Dispatcher::File uses build_response with an opened filehandler as body, '200 OK' as status and the Content-Type set to what Tool::MIME.type_for tells it is the correct mimetype for this file. ##### Second, `/hello` We start in the processing of FILTER, since the previous steps are the same for every request. Here we now get only a nil value back from Dispatcher::File since no file in our two public directories exists for the given path. So, this time, Dispatcher::Action is called via ::process and the first thing this dispatcher does is trying to set the body of the request to the answer of Controller.handle (controller.rb) for the given path. Controller::handle doesn't do much either, but calls Controller::resolve (controller/resolve.rb) for the path. Here we get now into the guts of Ramaze, despite our efforts it's no easy reading since the underlying theory is a bit complex and many edge-cases have to be solved. Let me try to quickly sketch what happens here, maybe we can add a chapter about this aspect of Ramaze later. First we generate a pattern of how the requested path could fit into our controller and template structure. `/hello` only has 3 possible outcomes: Controller on '/', template or method `hello` Controller on '/', method `index` with parameter `hello` Controller on '/hello', method or template `index` After matching (in the order we wrote here) these possibilities against your application we get only one possibility, MainController on '/' with method `index`. The result is stored into an instance of Action and cached for repeated lookups, then passed back into Dispatcher::handle which subsequently calls Action#render (action/render.rb) on it. From here, Thread.current[:action] gets set and #uncached_render is called. This calls #before_process which only has effects if you have the AspectHelper activated and next on comes engine.transform with self as parameter. #engine is a method that figures out which engine this Action has to be rendered with, according to things like trait[:engine] in your controller and the extension of a template. In this case we have neither and the default engine Template::Ezamar (template/ezamar.rb) is used. So off we go, to Template::Ezamar::transform with the current instance of Action as parameter. ::transform first calls ::wrap_compile which is inherited from Template (template.rb) and manages caching of compiled templates, calling ::compile with the action and template. The template again is retrieved through two attempts, firstly, we always call the method (if specified from Controller::resolve) on the controller, we temporarily store its result and replace it if a path for a template is set with the contents of the file on this path. The result of this is the final template that is ready for compilation. In the case of Ezamar, we first walk the TRANSFORM_PIPELINE (only Ezamar::Element (template/ezamar/element.rb) with ::transform and pass each the template for manipulation. In the example we don't have any elements, so we get back what we gave and generate a new instance of Ezamar::Template (template/ezamar/engine.rb) with the template and a path that indicates eval where we currently are supposed to be (templates path or the transformer). This instance is passed back to Ezamar::transform and we call #result with the actions binding (retrieved from earlier instantiation of the controller and subsequently eval `binding` inside of it). Now it's all done, we have got our body for the response which will be sent back to our handler as body. #### The Ramaze module Ramaze is also the main module or so-called namespace that the framework lives in. It has Tasks on require * Inform * LogHub.new(Informer) * Global * GlobalStruct.new Tasks on startup as defined in Ramaze.trait[:internals] in lib/ramaze.rb * Global::startup * passed options to Ramaze.start * CLI options from bin/ramaze (treat as passed.merge) * Global options set before startup (fake Global?) * Cache::startup * adds the following caches via Cache.add: * compiled If Global.compile is set true this cache is used to store the compiled templates. * actions Caching compiled actions. * patterns This is used in Controller::resolve to cache the generated patterns for a path. * resolved Caching the resolved but not yet compiled actions by their path. * shield Caching the generated errors for the path, so on repeated erronous requests no new error-page has to be generated. * Controller::startup * mapping of all subclassed Controller * validation of mapping * validation of template_root * Session::startup * adds Cache.sessions if Global.sessions is true This cache is used to store all sessions with their session-id as key. * SourceReload::startup * start with Global.reload_interval * assign Global.sourcereload * Adapter::startup * interpret Global.adapter * add every created adapter to Global.adapters Tasks on shutdown * Adapter::shutdown (iterates Global.adapters) * Inform::shutdown (iterates all in LogHub) #### Global configuration Any serious application or framework needs to be configured. Yes, I wished there was a silver bullet to serve all your needs as well, but at the current stage of programming development there is no such thing. So, since we need to configure, we should make it as simple and painless as possible, and, thanks to Ruby, it is actually quite enjoyable to do that. You can find a very detailed description about Global in the section about Configuration, for now just the basics. The basis of Ramaze::Global, the instance that is holding most part of your configuration or at least links to the actual places, is the Ramaze::GlobalStruct (ramaze/global.rb), which is a subclass of OpenStruct. If you are not yet familiar with OpenStruct, I very much recommend to read its documentation and play around a bit, it is basically just a wrapper around a normal Hash where you can access the keys instead of ostruct[:foo] with ostruct.foo. It simply defines a new accessors on assignment by catching things in method_missing. I won't go into more details here, I hope you got the principle. Now, based on this technique, GlobalStruct adds things like defaults and a couple of convenience methods like they are common in Ruby, giving you more power by tapping to the internal Hash of the GlobalStruct and adding update/setup methods so you can assign many key/value pairs at once. The RDocs will give you a very good overview of what is available and how one is supposed to work with it. Now back to the big picture. Ramaze accesses Global all over the place, there have been made several choices as to why using something like Global is considered beneficial against choosing for example global variables, which would be considered as a fatal choice by any respected Rubyist anyway. Now to something more subtle, which has to do with Global. I speak about traits, which is a very different concept in most of its implementations, but it is something that basically fits this name. It is configuration of single Objects and whole ancestries. You can give an object a trait, and most likely will use it along the lines of something like MyController.trait(:map => '/'), which would be picked up on startup and used to create Global.mapping - where we are at configuration again. Choosing this style of configuration complementary to a central place to put all your configuration was made very early in the development of Ramaze, and the basic code of how traits are implemented and used has proven very efficient both in understanding and using them. There is a basic distinction when to use Global and when to use traits and when to generate or assume one based on the other. If something affects your whole application and is either used directly in the Ramaze module or throughout the framework it is considered to be put into Global to gain benefits of better documentation and accessibility. On the other hand - if something is used in a configuring manner by a class that is instantiated often (like Controller is for example), or a module that cannot hold instance variables in a nice manner and is also not a constant - then it is configured using traits. Ramaze Global Adapter Dispatcher Controller Action Session Helper Tool #### The Web The so called web consists of a plethora of data, most of it is browsable through a web browser that just about every toaster has installed these days. The browser is the target of a web author, trying to utilize common standards like HTML and CSS which are means for data and layout to be combined, giving the browser a way to display the data. Ramaze works on the URI-scale web, meaning that addresses like http://someserver.com/blog/article/1 have a unique representation on your server. If we stay with this example, we see already everything Ramaze needs to see to serve a page based on your instructions. We assume that blog/article is a Controller named ArticleController in the blog application.