Sinatra: A Real Site
The Site
I have a young relative who draws a lot of comic strips, and thinking it would be nice if he could publish them, I looked around for an Open Source system.
I wanted something that was as near to XKCD as possible - latest comic, next/previous and random.
Not finding anything that was both pretty and ultra-simple, I decided to write my own.
The version for my relative is here -
the system will be up on GitHub as soon as I finish making it configurable.
Technologies
Web Framework
If you've read the title of this post, you won't be surprised to read that I chose Sinatra.
Database
I decided to use DataMapper as the ORM.
I'm also using SQLite for both development and production.
HTML and CSS
As will be clear from the example site, I didn't work over hard on making a fresh graphic look -
all I did was strip out a lot of HTML that was there to make rounded corners,
and replaced it with Mozilla and WebKit specific rounding.
I then translated the HTML into HAML and
the CSS into SASS (SCSS).
Development
Documentation for Sinatra, DataMapper, HAML and SASS is quite good, so things went quite smoothly.
What follows is a list of the phases I went through while developing (on Ubuntu), and especially the bits I had trouble with.
DataMapper
Installation
$ sudo gem install dm-core $ sudo gem install data_objects $ sudo gem install data_mapper $ sudo gem install do_sqlite3
Using DataMapper in Code
Here is minimal code for one DataMapper model:
require 'dm-core' require 'data_mapper' DataMapper.setup({ :adapter => 'sqlite3', :host => 'localhost', :username => '', :password => '', :database => 'db/sidcom_development' }) class Comic include DataMapper::Resource property :title, :text property :permalink, :text property :created_at, :datetime property :updated_at, :datetime end Comic.auto_upgrade!
The 'Comic.auto_upgrade!' bit handles migrations -
at startup, table columns are adjusted to match the declaration in the equivalent class.
Rack
Authentication
This application has a 'public' and an 'admin' area, so I actually created two Sinatra Apps,
wrapping one in Rack::Auth::Digest::MD5.
A local settings file holds user names and hashed (kinda salted) passwords.
Sinatra
Sinatra apps have access to a number of globals, I used:
- request,
-
params
Helpers
This app being a little more complex than my first, I decided to implement some Rails-like helpers.
The most important helper is url_for.
Sinatra - by design - includes routing in the structure of an app, so it cannot provide pre-cooked methods of the sort.
HAML
I found HAML very easy to start using, what I didn't immediately understand was:
- how to interpolate variables,
- how to call functions,
- and how to include partials.
These are actually all the same problem, but it took me a while to realize that!
In HAML, you can interpolate the Ruby way (with '#{...}') or the HAML way (with '=').
Variable Interpolation
Just use Ruby String interpolation:
%h1 Hello #{ @name }!
Calling Functions
Use HAML evaluation syntax:
%h1
= @name
!
HAML partials
Again, these can be evaluated as above:
= haml(:_my_partial, :layout => false)
SASS
Generating CSS
I chose to keep my SCSS files in a subdirectory under my views directory, and generate CSS from them under the static path.
During development, you need to keep the static versions up to date, and to do so, you just need to keep a process running as follows:
$ sass --watch views/stylesheets:static/stylesheets
Conclusion
Sinatra handles small sites very well.
The code can be kept DRY, and if you decide to, Sinatra projects can be quite easily transformed into Rails projects.
Actually, I have doubts whether it was worth doing this project in Sinatra -
a stripped-down Rails 3 project would have been just as lightweight and easy to develop.
But, then again, it's useful to run a sytem through its paces, to have an idea what areas it can usefully be applied to.
googletest Hello World
This is a quick run down of how to get started with using googletest on Ubuntu.
Preparation
Assuming you have a working GCC build environment, all you have to do is install the googletest packages:
$ sudo apt-get install libgtest0 libgtest-dev
Makefile
The only think of note about the Makefile is that it includes 'libgtest_main' - which implements main() and calls RUN_ALL_TESTS()
NAME = hello-world
LIBS = -lgtest_main
debug: all
run-debug:
./${NAME}
all: $(NAME).o
c++ -lstdc++ $(LIBS) -o $(NAME) $(NAME).o
compile: $(NAME).o
clean:
find . -name '*.o' -exec rm -f {} ';'
find . -name $(NAME) -exec rm -f {} ';'
$(NAME).o: $(NAME).c++
gcc -c -I. -o $(NAME).o $(NAME).c++
.c++.o:
gcc -c -I. -o $@ $<
Source
I've put everything into a single source file 'hello-world.c++' to keep things minimal:
/////////////////////////////
// In the header file
#include <sstream>
using namespace std;
class Salutation
{
public:
static string greet(const string& name);
};
///////////////////////////////////////
// In the class implementation file
string Salutation::greet(const string& name) {
ostringstream s;
s << "Hello " << name << "!";
return s.str();
}
///////////////////////////////////////////
// In the test file
#include <gtest/gtest.h>
TEST(SalutationTest, Static) {
EXPECT_EQ(string("Hello World!"), Salutation::greet("World"));
}
Compilation
Just run:
$ make
Output
This test produces the following:
$ ./hello-world Running main() from gtest_main.cc [==========] Running 1 test from 1 test case. [----------] Global test environment set-up. [----------] 1 test from SalutationTest [ RUN ] SalutationTest.Static [ OK ] SalutationTest.Static [----------] Global test environment tear-down [==========] 1 test from 1 test case ran. [ PASSED ] 1 test.
Conclusion
It couldn't really be much simpler!
The warning was not heeded.
A spokesman said that traffic is now at record levels (for the tunnel, that is).