August 20th, 2009 admin
A few coworkers at Citrusbyte (@soveran, @djanowski) have been working on Monk, a new open source web framework. At its core is Sinatra, with a set of bundled technologies to help new projects get moving as quick as possible. It helps you figure out the best structure for your Sinatra app, and gets you moving quickly with logging, settings, and testing.
Monk consists of several components:
Monk Binary
At the core of Monk is a command line tool for starting new projects. Think of the rails command, and what it does and you have a good idea of what the monk command does. It lists, manages and instantiates new skeletons for your project. The Monk binary can instatiate any skeleton you want, which means it is a great way to start projects in any language.
Monk Skeletons
A Monk skeleton is a set of files defining the structure of a new project. There are almost no rules here, the new project can be Sinatra, Django, an ircbot setup, or anything else you want, heck, it could even be Rails.
Monk Default Skeleton
The default Monk skeleton is where lots of work has gone in, and where you’ll find the meat of Monk. It’s a set of amazing tools forming a full-stack Sinatra based web development platform.
- Sinatra - super lightweight web DSL. You know what this is (and if you don’t, go read the rest of this blog, and check it out)
- Ohm - data access layer using Redis, a stupid fast key-value database. Super simple models for your app
- Monk Glue - a Reloader, Logger, and Settings handler for your new application. Don’t worry about it, Sinatra Logging finally made easy… Monk Glue is destined to keep growing too, including a handful of mix & match helpers for common tasks.
- Rack-Test, Contest, Stories, Webrat, Faker, Spawn - An amazing unit and integration test stack, allowing both low level unit tests of your code, and high level integration tests, similar in style to Cucumber, but without that nasty repeating-yourself part. You owe it to yourself to try the stories & webrat approach to testing your Sinatra application.
- Dependencies - trivial management of all the gems you rely on. Just define a dependencies file, and the dependencies gem will handle vendoring and unpacking your requirements.
Monk Glue
Logging - a perennial problem and hassle in setting up new projects is now simple with Monk, and Monk Glue.
get '/' do
logger.info("Entered homepage route")
"homepage text"
end
Reloader - an alternative to the classic Sinatra reloading (with weird require related issues), and Shotgun, which causes a fork() each request, which is fairly heavyweight. Glue’s Reloader is the happy midpoint, between the other two solutions. It reloads everything, but in the same Ruby process, making it much faster.
Settings - a minimalist implementation of the settings.yml file, with environment support. Easily switch up your database settings, your email address, url and more. Bonus Feature: the default skeleton creates an automatic settings.yml file when you first run monk:start.
get '/' do
@foobar = settings(:foobar)
"the value of the foobar setting is:" + @foobar
end
Ohm Persistence Layer
Ohm is a lightweight data management layer that uses Redis as it’s backend. Redis is a fast key-value database, and Ohm makes mappings that are simple to define, and automates the nasty parts of key-value databases like reverse lookups (indexes). Ohm isn’t technically an ORM, since there’s no relational database backing it, but it fills the same niche as ActiveRecord or Datamapper, storing your data for future use.
Awesome Reddit Clone Example
Check out http://news.monkrb.com/, the code is located at http://github.com/monkrb/reddit-clone
Pay attention for a future blog post digging further into this example code, and dissecting it.
Get Monk & Find out More
Install Monk
Links
Posted in Ruby, Sinatra | 2 Comments »
June 23rd, 2009 cschneid
I was using Active Record in a batch application to manipulate some data, and I needed the timezone handling built into the newer versions. But I had a hell of a time trying to figure out how to make it all work.
In plain Rails, it’s simple:
Rails::Initializer.run do |config|
config.time_zone = 'Pacific Time (US & Canada)'
end
But I wasn’t running Rails proper, and didn’t want to pull in the whole Rails boot sequence. So the Rails::Initializer call didn’t work. To do it manually, time zone config turns out it’s a 3 lines of setup calls:
require 'activerecord'
Time.zone = "Pacific Time (US & Canada)"
ActiveRecord::Base.time_zone_aware_attributes = true
ActiveRecord::Base.default_timezone = "Pacific Time (US & Canada)"
When you do that, time zone attributes work both going into the database, and coming back out. If you miss the last line, they’ll just not work coming out, which is a damn confusing thing to deal with.
Ohh, and one other thing I ran into when getting this setup is that AR pulls in Active Support, which in turn pulls in Builder (to hack xml support or something). Just watch out for that, you’ll get weird crashes if you don’t have builder installed. Honestly, I just went and commented out the areas of Active Support that did it, I didn’t want builder anyway.
Posted in Programming, Rails, Ruby, Sinatra | 1 Comment »
December 15th, 2008 cschneid
I’ve updated this post for Sinatra 0.10.1
Sinatra makes amazing web service endpoints. The lightweight “nothing in your way” approach is great. Rendering alternate media types isn’t immediately obvious though. Here’s a little snippet that demonstrates rendering JSON. XML would work in a very similar way.
Posted in Programming, Ruby, Sinatra | 5 Comments »
September 9th, 2008 cschneid
Ryan Tomayko provided a great changelist for the new gem release of Sinatra, which I hope to expand on a bit here, by pointing you to more details on the changes.
Here’s a quick summary of changes since the 0.2.2 release:
-
Add sinatra.gemspec w/ support for github gem builds. Forks can now
enable the build gem option in github to get free username-sinatra.gem
builds: gem install username-sinatra.gem — source=http://gems.github.com/
-
Require Rack 0.4; removes frozen rack dir.
-
Basic RSpec support; require ‘sinatra/test/rspec’ instead of
‘sinatra/test/spec’ to use. [avdi]
-
before filters can modify request environment vars used for
routing (e.g., PATH_INFO, REQUEST_METHOD, etc.) for URL rewriting
type functionality.
-
In-file templates now uses @@ instead of ## as template separator.
-
Top-level environment test predicates: development?, test?, production?
-
Top-level “set”, “enable”, and “disable” methods for tweaking
app options. [rtomayko] — This allows the syntax in my post about options
-
Top-level “use” method for building Rack middleware pipelines
leading to app. see readme for usage. [rtomayko] — this is the use syntax in my post about sessions
-
New “reload” option - set false to disable reloading in development.
-
New “host” option - host/ip to bind to [cschneid]
-
New “app_file” option - override the file to reload in development
mode [cschneid] — This makes embedding Sinatra inside other apps easier to do during development. More info in my app_file post.
-
Development error/not_found page cleanup [sr, adamwiggins]
-
Remove a bunch of core extensions (String#to_param, String#from_param,
Hash#from_params, Hash#to_params, Hash#symbolize_keys, Hash#pass)
-
Various grammar and formatting fixes to README; additions on
community and contributing [cypher]
-
Build RDoc using Hanna template: http://sinatra.rubyforge.org/api/
-
Specs, documentation and fixes for splat’n routes [vic] — See how to use splat routes in my post on it.
-
Fix whitespace errors across all source files. [rtomayko]
-
Fix streaming issues with Mongrel (body not closed). [bmizerany]
-
Fix various issues with environment not being set properly (configure
blocks not running, error pages not registering, etc.) [cypher]
-
Fix to allow locals to be passed to ERB templates [cschneid] — More Details
-
Fix locking issues causing random errors during reload in development.
-
Fix for escaped paths not resolving static files [Matthew Walker]
Posted in Programming, Ruby, Sinatra | No Comments »
July 17th, 2008 cschneid
Most web applications have some sort of admin area where users shouldn’t be. Passwording it is the obvious solution, but it isn’t obvious how to implement passwords in Sinatra. I stole this code from Ryan Tomayko’s Wink blog, and extracted it into a more generic module for reuse. Two parts are involved, the module, which I put into a separate file, and include. And then the code itself to pull it into the helpers and use it in the event handlers.
The module:
module Sinatra
module Authorization
def auth
@auth ||= Rack::Auth::Basic::Request.new(request.env)
end
def unauthorized!(realm="myApp.com")
header 'WWW-Authenticate' => %(Basic realm="#{realm}")
throw :halt, [ 401, 'Authorization Required' ]
end
def bad_request!
throw :halt, [ 400, 'Bad Request' ]
end
def authorized?
request.env['REMOTE_USER']
end
def authorize(username, password)
# Insert your logic here to determine if username/password is good
false
end
def require_administrative_privileges
return if authorized?
unauthorized! unless auth.provided?
bad_request! unless auth.basic?
unauthorized! unless authorize(*auth.credentials)
request.env['REMOTE_USER'] = auth.username
end
def admin?
authorized?
end
end
end
To use the module:
require 'authorization'
helpers do
include Sinatra::Authorization
end
get '/admin' do
require_administrative_privileges
# Do private stuff
end
EDIT: Thanks to foca on #sinatra for the nicer setup of authorize
License: Because this was extracted out of Wink, follow the Wink license (found here).
Posted in Programming, Ruby, Sinatra | 8 Comments »
July 17th, 2008 cschneid
Once of the common things needed in webapps is to escape html.
Most frameworks acknowledge that need, and include helpers to do it for you. Sinatra isn’t that kind of framework. If you want it, it’s easy to wire in, but you need to ask.
Luckily, Sinatra is easily convinced, and will point you to it’s close friend, Rack.
helpers do
include Rack::Utils
alias_method :h, :escape_html
end
These few lines will include all of Rack::Utils (a few methods), and then rename one of them to match what Rails and Merb (and others) provide.
Now in your views, you can easily call escape html like:
Posted in Programming, Sinatra | 3 Comments »
July 16th, 2008 cschneid
After some talk in the #sinatra IRC channel, I decided to start a proper book project to collect and collate all of the brain dumping I’ve been doing here.
Check out the git repository at http://github.com/cschneid/sinatra-book
So far the project has been progressing fairly slowly, but there is already some great content in there. I will continue to convert blog posts into chapters, and look forward to any other contributions from others.
I will be setting up a nightly build of the book, pointing at the currently unsetup sinatra-book.gittr.com.
To build it yourself in the mean time, you will need a copy of “thor”, a reasonably cool rake/sake replacement. You’ll also need maruku, the markdown library we are using to write everything.
Posted in Sinatra | 1 Comment »
June 25th, 2008 cschneid
A question came up on IRC today regarding Sinatra’s redirect helper. royfreeman was trying to redirect from inside a post action. Sinatra redirected the URL ok, but the new request continued to use the POST method to request the new URL. He was trying to do this from a mobile device, and was confused when the request used a GET with his laptop’s browser.
Sinatra sends a 302 response code as a redirect. According to the spec, 302 shouldn’t change the request method, but you can see a note saying that most clients do change it. Apparently the mobile browser that person was using did things correctly (instead of the mainstream misinterpretation).
The fix for this in the spec is 2 different response codes: 303 and 307. 303 resets to GET, 307 keeps the same method.
To force sinatra to send a different response code, it’s very simple:
redirect '/', 303 # forces the 303 return code
## OR:
redirect '/', 307 # forces the 307 return code
Many thanks to Sydd for working through this, he did most of the hunting to find the specification details and to figure out what was going on.
Posted in Programming, Ruby, Sinatra | 4 Comments »
June 17th, 2008 cschneid
I’ve previously written about deploying Sinatra to Dreamhost, both with FastCGI, and also Passenger (mod_rails). This howto will cover how to deploy Sinatra to a load-balanced reverse-proxy setup using Lighttpd and Thin.
1) Install Lighttpd and Thin
# Figure out lighttpd yourself, it should be handled by your
# linux distro's package manager
# For thin:
gem install thin
2) Create your rackup file - the “require ‘app’” line should require the actual sinatra app you have written.
require 'sinatra'
Sinatra::Application.default_options.merge!(
:run => false,
:env => :production
)
require 'app'
run Sinatra.application
3) Setup a config.yml - change the /path/to/my/app path to reflect reality.
---
environment: production
chdir: /path/to/my/app
address: 127.0.0.1
user: root
group: root
port: 4567
pid: /path/to/my/app/thin.pid
rackup: /path/to/my/app/config.ru
log: /path/to/my/app/thin.log
max_conns: 1024
timeout: 30
max_persistent_conns: 512
daemonize: true
4) Setup lighttpd.conf - change mydomain to reflect reality. Also make sure the first port here matches up with the port setting in config.yml.
$HTTP["host"] =~ "(www\.)?mydomain\.com" {
proxy.balance = "fair"
proxy.server = ("/" =>
(
( "host" => "127.0.0.1", "port" => 4567 ),
( "host" => "127.0.0.1", "port" => 4568 )
)
)
}
5) Start thin and your application . I have a rake script so I can just call “rake start” rather than typing this in. It’s just a few lines to do that.
thin -s 2 -C config.yml -R config.ru start
Go to mydomain.com/ and see the result! Everything should be setup now, check it out at the domain you setup in your lighttpd.conf file.
Variations - nginx - I haven’t looked into the config file syntax, but there isn’t any reason that this exact same approach wouldn’t work with nginx. Thin and the rackup file stay the same, while the nginx layer has to be configured to reverse proxy to the thin install.
Variations - More Thin instances - To add more thin instances, change the -s 2 parameter on the thin start command to be how ever many servers you want. Then be sure lighttpd proxies to all of them by adding more lines to the proxy statements. Then restart lighttpd and you’re done.
Posted in Linux, Programming, Ruby, Sinatra, System Admin | 4 Comments »
June 15th, 2008 cschneid
There are several ways of getting parameters and data into your Sinatra application.
1) URL Options
get '/post/:id' do
post_id = params[:id]
end
# Several options, period gets interpreted as a separator between the two.
get '/author/:id.:format' do
author_id = params[:id]
format = params[:format]
end
# Several options, separated by '/'
get '/author/:author_id/comments/:comment_id' do
author_id = params[:author_id]
comment_id = params[:comment_id]
end
2) URL Parameters - the classic ?foo=bar&baz=quux
# this will get the post id out of /author?id=2
get '/author' do
author_id = params[:id]
end
# Works the same way for the POST method (and DELETE and PUT of course)
post '/author' do
author_id = params[:id]
end
3) Raw Posted Data - if the POSTed content is in the “application/x-www-form-urlencoded” format, it will get parsed as above, with the params[:id] method of getting the data. But occasionally, you want the whole chunk of raw data. This is useful for images, or big chunks of xml.
post '/data' do
raw = request.env["rack.input"].read
end
Posted in Programming, Ruby, Sinatra | 11 Comments »