SinatraBoard is a project mainly written in Ruby, it's free.
A spring break project to reimplement http://zyber17.com/statboard/ in Sinatra, for science!
So I've been charged with training Zyber17 in the ways of Ruby web development, so I thought it'd be neat to reimplement his pet project Statboard in Ruby (specifically, Sinatra this time).
The idea as it stands now is a small web app that runs on the user's computer, regularly checking up on social networks and posting that data in a clean, easy-to-read format. So, a widget for Twitter followers, Reddit karma, StackOverflow reputation, etc., regularly reloading with the latest data.
This version supports links in the pane headers, transfers data as JSON instead of the rendered HTML (though I think the original project is changing its direction in that sense) and I happen to think that the pane API is much easier to use :) (Wrote up that Github followers pane, remote API call and all, in under 3 minutes. Boo ya.)
Well, if all you want to know how it looks, I have a version configured for me running on Heroku. If you'd like to run it yourself, pull the repository, then make sure you have all of the necessary gems.
cd /path/to/app
bundle install
Then run it as you'd run any other Rack-based app. For instance, you could use
the thin
gem.
cd /path/to/app
thin start
There should be no further setup necessary to get the app running, especially since the automatic deploy to Heroku does almost exactly this.
Once things are up and running, it's easy-peasy to go into /config/panes
and
change around the relevant usernames, and it's also simple to change
/config/statboard.yml
to customize pane order and update interval.
Well, I think the real beauty of this project is that, even if it's ugly in parts due to all that metaprogramming, it allows for very pretty pane code, which is kinda the point: SinatraBoard is supposed to allow the panes to shine and, for the most part, stay out of the way. By making the panes super-easy to create, it does its job well.
One thing to take note of is the separation of concepts. Even though, in this
case, we're only ever going to have one set of config variables, the Pane class
and all the code in /panes
is totally prepared to have multiple users, since
the config is only passed in when we actually initialize the pane objects on
each request. It'd still be a big job, but the existing infrastructure would
not get in the way of expanding SinatraBoard into a multi-user web app.
Anyway. I happen to think that /lib/pane.rb
and /lib/resource.rb
are pretty
good reads, since they build powerful APIs while still keeping things
relatively simple. Tell me what you think.
Glad you asked! The API is simple, and the examples in /panes
really should be
enough. But here goes:
A pane class lives in /panes
, and inherits from Pane
. You may also create a
folder of the same name as your .rb
file, the contents of which will be
loaded after your main pane file.
A pane instance has the instance method config
, which, in this case, holds
the unserialized contents of /config/[pane_name].yml
.
Here are the class methods that Pane
offers:
title
: The title for the pane, which appears in big letters at the top.
You can pass either a string or a block. In this method, as in all others
that support both strings and blocks, the block will be evaluated on page
load within the context of that Pane
instance. (So, it has access to
config
and any instance methods you may create.)link
: The URL to which the title should link. Accepts a string or block,
and is optional.stat
: The first parameter is the label for what this statistic represents
(e.g. "followers"), and the block should return the value (e.g. 42). You may
provide more that one statistic. The label may be omitted; however, at this
time, you cannot provide more than one unlabeled statistic.The Lucky Number pane is about as simple as it gets:
class LuckyNumber < Pane
title "Lucky number"
stat { rand(100) }
end
You'll also notice that some of the included examples use a custom User
class
that inherits from Resource
. Resource
is designed to make it easier to
represent a remote resource to be accessed over HTTP. It has HTTParty
mixed
in, so supports format
, base_uri
, etc., and also offers some shortcuts:
source
: Where the resource lives. base_uri
+ source
= full URL. Takes
either a string or a block.query
: The query to append to the URL. So, if you would type
/users.json?id=1
in your browser, the query would be {:id => 1}
.
Similarly to the string/block methods, you may pass either a hash or a block
that returns a hash.scope
: If the data you want actually lives a few keys deep in the JSON
response, provide the wrapping keys here. For example, if the API returns
{"user" => {"id" => 1, "name" => "Matchu"}}
, running scope "user"
would
make the resource's data
method return {"id" => 1, "name" => "Matchu"}
.attr_resource
: Creates methods with the given names to refer to the given
keys in the data
method.
attr_resource :name
# is equivalent to
def name
data['name']
end
source
, query
, and scope
all help define what goes on in the automagic
data
method, which takes care of a lot of the heavy lifting and error
handling for you. If those three methods (and HTTParty's format
, base_uri
,
etc.) are set correctly, data
should return exactly the data you're
interested in, without you having to muck around with sending the HTTP
requests, handling 404s, etc.
Of course, it's best to learn by example. /panes/today.rb
is a very simple
example, and /panes/reddit.rb
is only slightly more complicated, but
significantly more powerful.
Anyway. Have fun browsing, and enjoy :) —Matchu