Beauty is in the eye of the beholder
In my experience, there are a few different attitudes when it comes to beautiful design. Many consider Rails to be a beautiful framework to work with. In it’s own right, I certainly would agree with these folks. If you’re willing to accept the 80/20 divide and the tool fits the job, it could be dreamy to work with Rails. But in my limited experience, I sort of feel like a decision to work with Rails is more or less an all-or-nothing decision.
Like a train that wants to go east but only has tracks running North to South, a change in course is going to create some major problems. I don’t think this is a bad design, that’s what a train is meant to do. But maybe for some jobs, you need a dune buggy. That’s where the little wheels come in.
The camping framework is designed in light of another definition of beauty, and that definition is closer to the austere. When I started a job in it, I asked the usual questions one might run into when dealing with a small web app. “Does camping support sessions|file uploads|static files”
The answers were, ’sort of’,'nope’,'nope’. But all three were also appended with a “but it’s easy to roll your own”. The rest of this article will show you one way to do all three, and hopefully show some appreciation for the simplicity of the task.
Sessions with Camping
There is optional support for sessions. To me, it totally rocks how easy these are to set up. Camping lets you use SQLite3 with zero config, so all you need to do is use the session library to create a schema, and you’re right off the ground in no time.
Straight from an actual job of mine, the relevant parts look like this.
require "camping/session
module Importer
include Camping::Session
end
def Importer.create
Camping::Models::Session.create_schema
end
Now in the rest of my app, the @state variable will hold a unique session that I can just stuff things in for the user, no further thoughts needed.
for example:
@state.my_attribute = "Chunky Bacon"
could be accessed throughout my app, maintaining state. Very cool.
The thing is, to a Rubyist with minimal rails experience, this kind of thing might make more sense. I *see* where the schema is being created, I see the library that’s in charge of my sessions, etc. And if I really wanted to, I can hack on those things without digging much deeper. This to me is a valuable tradeoff from ‘just works’ to ‘works easily and you can see how’. Now yeah, I’d be annoyed if I was looking for more high level support, but for what I was looking for, I saw this as ’super cool’
File Uploads
Note that the way I do this is a hack, but the joy of Camping is that’s okay! This particular app is meant to run locally, and all users to be considered equal, so I had no need for ACLs or any other constraints, which others may.
Here is the controller:
class Upload < R '/upload'
def get
render :upload
end
def post
file = @input.File.tempfile.read
@state.mi.load(file) #does something with the file, using sessions
redirect Import
end
end
And the relevant bits of the view:
def upload
form :action => "?upload_id=#{Time.now.to_f}", :method => 'post',
:enctype => 'multipart/form-data' do
p do
input({:name => "File", :id => "File", :type => 'file'})
input.newfile! :type => "submit", :value => "Upload"
end
end
end
Nothing particularly magical here, and that’s the way I like it. This just creates the appropriate form, lets me select a file from my machine, whack the ‘Upload’ button, and then handle that in my controller. There are a ton of other ways to deal with this, and the more complicated they get, the more likely you’ll be to say something like “Maybe I should be using Rails”. But for simple needs, I like having this kind of “roll your own” power.
Static Files
I copied and pasted this code from the camping wiki, but again, I was awestruck by the coolness of the ‘if I need to tweak that, it should be easy’ effect.
require 'pathname'
module Camping::Controllers
class Static < R '/static/(.+)'
MIME_TYPES = {'.css' => 'text/css', '.js' => 'text/javascript',
'.jpg' => 'image/jpeg'}
PATH = File.expand_path(File.dirname(__FILE__))
def get(path)
@headers['Content-Type'] = MIME_TYPES[path[/.w+$/, 0]] || "text/plain"
unless path.include? ".." # prevent directory traversal attacks
@headers['X-Sendfile'] = "#{PATH}/static/#{path}"
else
@status = "403"
"403 - Invalid path"
end
end
end
You can happily ride the little wheels too!
I realized that this was the kind of tool I was looking for. Something simple, basic, and super extendable. I’m not afraid of rolling my sleeves up, and sparse documentation isn’t enough to keep me away from code that seems cool. If you’re in the same boat, be sure to check Camping out. Most of my code here is either taken directly from the wiki page or via suggestions from the kind folks in #camping on Freenode.
A slight warning is that if you plan on working with the framework, give yourself some extra exploration time. The little wheels are *awesome*, but I can promise you you’ll spend more time on IRC and sifting blogs and wikis at first then you might for the equivalent Rails job. I personally think it’s well worth it for the minimal web needs I have, and even if you don’t have a pressing need for a minimal tool like this, it’s great eye opener to the benefits of a simple design.


Hi, Greg. The static files example had gone a bit out of date; I updated it on the wiki. You may want to reflect the new changes here to avoid confusion.
Thanks for this! How to handle file uploads is a very common question. Note that unlike in Rails, the tempfile method works for any size file, no matter how small.
evan,
Done! Thanks for the feedback!
I think Merb (http://merb.rubyforge.org/) deserves a mention in this context. There.
Chritstoffer,
I actually thought about mentioning Merb, because I think it's super cool. But I wasn't sure how to mention it in this article, and also haven't really dug into it yet. :)
Christoffer,
Sorry for butchering your name above.
Thanks for writing this! You make it sound so adventurous.
You can use redirect Import rather than redirect "import", if your controller is constant Import.
_why,
Ah, but you sir are the one who made it an adventure!
Thanks for the tip, I'll update the article.
I was having trouble with Pathname on win2k, so i've updated the example..
path[/.w+$/, 0] should be path[/\.\w+$/, 0]
good catch, will update!
Annoying! the blog is eating my formatting. i'll need to look it later.. sorry
These days it's:
file = @input.File[:tempfile].read