Pancake
Pancake is primarily a tool for making rack applictions. Rack has come up in the Ruby web world as the framework that matters when developing web applications. All the major frameworks use it, although so many application frameworks and middlewares are not really re-usable yet away from their specific implementations.
Pancake addresses this by making Rack the fundamental building block of an applicaiton. It provides very useful helpers on top of rack that assist in constructing rack stacks as mixins. Almost all aspects of web frameworks are covered as Pancake mixins to help you create your own re-usable Rack Stacks without worrying about the really low level plumbing.
Stacks
While Rack provides the framework for building web applications on. Pancake provides a stack as a place to start your application. A stack provides a whole bunch of behavior for you including a router and middleware stack. The stack will accept any valid rack application as the endpoint.
The general form of a stack is as follows:
-----------------------------------
| |
| Router |
| |
-----------------------------------
| Middleware |
-----------------------------------
| Middleware |
-----------------------------------
| Middleware |
-----------------------------------
| |
| Application |
| Endpoint |
| |
-----------------------------------
Stacks are designed to work together. It’s trivially easy to mount one stack inside another, or just mount any valid rack application inside your stack. They’re also designed to be gemed and used as libraries as full applications.
MyStack.router do |r|
r.mount(OtherStack, "/stack/mount/point")
end
A stack doesn’t have to mount other stacks. Pancake stacks are full applications in their own right and can be used standalone as Rack endpoints, or as middleware.
Rails 3 is shipping with a pluggable router which can be the awesome Usher router. If you use Usher in your Rails app, you’ll be able to mount Pancake stacks directly. For now, you can mount them as Metals.
All stacks are namespaced. Pancake makes heavy use of namespacing to help construct applications concisely.
Middleware Stacks
When you use Pancake middleware management, you can name middleware, set middleware to come before or after other middleware, or be included or not based on a stack type label. Lets have a look at that.
class MyStack < Pancake::Stack
stack(:session).use(Rack::Sessions::Cookie)
stack(:auth, :after => :session).use(Warden::Manager, #...)
stack(:bugz, :labels => [:development]).use(Rack::Bug)
use(AlwaysUse)
end
What can a stack do
Pancake provides a whole bunch of functionality as mixins.
- BootLoaders
- Router (Usher Based. Please read.)
- Sophisticated Middlware Configuration
- Stack/Custom Configuration
- Path Management with multiple roots (makes working with gem stacks transparent)
- Content Type negotiation
- Inheritable Templating System
- Action Publish Control
Stacks bundle these behaviors together into a cohesive unit.
The true power of a Pancake stack is that it is fully inheritable.
Stack Inheritance
When you inherit a stack, you’re really inheriting a full applictation. All the features above are inherited along with the stack class.
The easiest way to explain stack inheritance is perhaps with an example.
Lets take the example of a basic application skeleton. Many times I’ve seen github repos with pre-generated applications with some basic functionality. This is then cloned as a start point for further app development. It’s basically the same as a generator but a little more flexible. What if rather than generate an application skeleton with the basic functionality, you could construct a stack complete with router, default configuration, base templates, css and javascript, even mounted applications, and gem it up. Then when you want to start an application you just require the gem and inherit the stack class. This is what Pancake stacks provide.
class MyStack < SomeOtherStack; end # A full application
Namespacing
Pancake makes heavy use of namespacing. Namespacing is a good idea to begin with, but inside mounted applications it’s a must. By embracing the constraint of namespacing, there are many benefits. Unexpected name clashes of course being the big one.
Inheritable Inner Classes
There a fairly unique issue with inheriting a full application. Model data clashes.
Normally when you inherit a class in Ruby, when you reference an inner class in the child, it’s actually a reference to the inner class in the parent. Lets see why this is a problem.
class AStack < Pancake::Stack
class Post < ActiveRecord::Base
# post code
end
end
class AnotherStack < AStack; end
class ADifferentOne < AStack; end
AnotherStack::Post == ADifferentOne::Post == AStack::Post
When we create posts in all 3 stacks, and then select the posts in AStack::Post.all we’ll actually get the posts from all of the stacks. This referencing of inner classes is great for ruby efficiency, but not so good when we want to keep data seperate.
Awesomely some of the ORM’s include STI (Single Table Inheritance) that we can make use of. When using Pancake, we can set an inner class to inherit along with the parent. Lets have another look at that example:
class AStack < Pancake::Stack
inheritable_inner_classes :Post
class Post < ActiveRecord::Base
# post code
end
end
class AnotherStack < AStack; end
class ADifferentOne < AStack; end
class FurtherDown < AnotherStack; end
# Results in
class AnotherStack::Post < AStack::Post; end
class ADifferentOne::Post < AStack::Post; end
class FurtherDown::Post < AnotherStack::Post; end
In this case STI will be activated and each stack will use it’s own sti version of the Post model, segregating the data between the stacks.
If your Data layer doesn’t support STI there are on_inherit hooks for all classes provided by Pancake to make any changes you need to.
Mounting Applications
You can mount any valid rack application inside pancake.
Stacks look inside their root(s) for a “mounts” directory. Where they in-turn look for a sub-directory containing the file “pancake.init”. When you want to mount an application you just include that file (and any support files) and the pancake stack will load it.
my_stack
-- mounts
-- another_app
-- pancake.init # Called to initialize the mounted app
Inheritable Templates
Templates in Pancake are similar to Django templates. They define named content_blocks that can supply a default, but that can also be inherited from, and have their content either combined with some other content, or replaced. Unlike Django, templates are built on known templating languages like ERB and Haml. The templating system in Pancake is built on top of the great Tilt project.
Really this is best demonstrated with an example:
# base.html.erb
<html>
<head><title>Hey There</title></head>
<body>
<h1>My Page</h1>
<ul class='nav'>
<% content_block :navigation do -%>
<li><a href="/">Home</a></li>
<% end -%>
</ul>
<hr/>
<% content_block :content do- %>
<div class='emtpy'>Nothing Found</div>
<% end -%>
<% content_block :footer do -%>
Footer Text
<% end -%>
</html>
# index.html.erb
<% inherits_from :base %>
<% content_block :navigation do -%>
<%= content_block.super %>
<li><a href="/index">Home</a></li>
<% end -%>
<% content_block :content do -%>
# Stuff for the content
<% end -%>
When you render :index
in your stack, the index template will be activated, rendering the :base template but replacing the content_block, :content, with new data. And appending to the :navigation block.
Templates can be inherited as many times as required, so you could inherit from the :index template to build on it’s content.
When specifying which template to inherit from, you can dynamically specify which template you’d like to use if it makes sense to do so.
At the moment only ERB, Erubis and Haml are supported with inheritable templates. Other tilt templates are able to be rendered, but not inherited.
Short Stack
As part of the development of Pancake, the initial milestone was to develop a stack based on the awesome Sinatra framework. This was done as a way to develop Pancake to a simple level, not as a way to replace Sinatra. Pancake is very happy to mount Sinatra applications inside and make use of them.
A short stack is a resource focused. It uses the HTTP verbs and routes to access the code that defines a “resource”
Short stacks make use of content negotiation and views in addition to the normal stack behavior.
Get Started
Install
$ gem install pancake
Generate your stack
To start with a short stack use pancakes built in generator:
$ pancake-gen short my_stack
This will generate the basic stack for you. It’s generated as a gem and based on Jeweler so you can gem it up super easy.
class MyStack < Pancake::Stacks::Short
add_root(__FILE__)
router.mount(UserStack, "/users")
provides :html, :xml, :json
get "(/)", :_name => :home do
v[:some_models] = SomeModel.all
render :welcome
end
get "/new", :_name => :new do
v[:some_model] = SomeModel.new(params[:some_model])
render :new
end
post "(/)" do
v[:some_model] = SomeModel.new(params[:some_model])
if v[:some_model].save
redirect url(:home)
else
render :new
end
end
end
The Vault
In the example above you can see the “v” method. This method accesses the vault. The vault is where per-request data may be stored for later use. In this case, the data is stored so that we can access it in the views.
This is a way to seperate the concerns of Controller and View. These contexts are supposed to be seperate. Breaking the encapsulation of instance variables does not need to be done in a short stack.
Templates
Templates are searched for in (stack root)/views/
URL generation
When you’re in a stack, you can call @url(named_url) to generate a url. This will generate the url, taking into account the mount path for the stack.
You can generate urls for other stacks also by using the global Pancake.url helper.
Pancake.url(SomeStack, :named_url)
There’s a whole bunch more to Pancake than what I can go over here. But hopefully I’ve been able to provide a taste of what it is and why I think it’s pretty awesome in the up-coming Rack landscape.
Note on Patches/Pull Requests
- Fork the project.
- Make your feature addition or bug fix.
- Add tests for it. This is important so I don’t break it in a future version unintentionally.
- Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
- h4. me a pull request. Bonus points for topic branches.
Copyright
Copyright © 2009 Daniel Neighman. See LICENSE for details.