This article is part 1 in a series of articles about an “open source project” I’ve been working on. I’ve put it in quotes because it’s not a real open source project in the sense that many people has worked together - it’s a hobby project with me as the only developer, and now I’m choosing to make its source public because I feel like contributing something back to the community. I call this project Clouds, and you can try it right now, right here.
The idea behind Clouds
Basically, Clouds is some kind of generic notice board. I wrote Clouds because I wanted to use it make a wish list application with a different approach, but I also wanted to make it generic enough to be able to reuse it in other applications. Perhaps even transform it into an engine to make it easier for other people to use it in their applications.
Upon entering the Clouds for the first time, a blank cloud is created automatically. The user is presented to an entirely white page (except for discreet menu bar at the top) and are able to add topics anywhere on the page by double-clicking and entering a text in the appearing box. The user can also add notes to topics, move topics around and even delete them again.
To further enhance the users ability to express themselves freely in their clouds, one could add features such as choosing the color, size and font of topics, but that is not essential to the real function of the cloud: To let users build lists in two dimensions instead of just one (typically vertical). In its bare form, Clouds can be used in many ways and taken in many directions, which is why I wanted people to be able to extend the application to suit specific needs, like a wish list, a mind map or …
As I had this basic functionality, I felt Clouds needed a bit more functionality to be usable to anyone, and yet I still wanted it to be basic and generic. So added the ability to protect editing and/or viewing with a password (not a registration and login - too much work for the user), and the ability to extend any list into a new list. The extension feature would be vital in, say, the wish list application, because it would allow people to extend a wish list and then coordinate who is buying which presents through their new list without the gift receiver knowing, while still maintaining the link to the original wish list.
I still have several ideas for things I want to add or improve, i.e. something as basic as getting the whole thing to work properly in Internet Explorer (no, Prototype and Scriptacolous cannot save you from all the headaches of IE), but also the fancy notion of synchronizing lists live between users, so multiple users working on the same cloud will see each others changes immediately.
The rest of this article and the one(s) to follow will dip into the technical aspects of Clouds, especially highlighting the parts where I’ve run into trouble. You can review and download the source code right here, but remember to check the license if you want to reuse any substantial parts of the code.
Creating a RESTful Rails application
What does it mean for a Rails application to be RESTful? To me it means to use the scaffold_resource as a basis for most entities in the application, and to strive to keep the application normalized by having only the CRUD actions generated by scaffold_resource in the controllers. Of course, these actions will also give the application a RESTful web service API for free, but that’s just an added bonus to me.
This article is a not a tutorial, but I do want to walk you through creating a RESTful Rails application as it will help you to build cleaner applications, also when it comes to database structure. To build Clouds, my first task was to generate the two central entities: The Cloud and the Topic.
./script/generate scaffold_resource Cloud title:string, created_on:datetime, updated_on:datetime, password:string, settings:integer, parent_id:integer ./script/generate scaffold_resource Topic cloud_id:integer, text:string, notes:text, posx:integer, posy:integer
The scaffold_resource generates everything you need to get started with your new entity: Model, controller, views, migration, tests and route. No need to worry about when to use singular or plural names, lowercase or uppercase, underscores or camel case. All files are created and named according to current Rails conventions. All you need to do is run rake db:migrate and restart the server.
Take a look at config/routes.rb. The generator has added map.resources :topics and map.resources :clouds. This enables the user to browse your Rails application using the nice RESTful urls, meaning that the same urls are reused for different actions, depending on the HTTP method used. For instance, if you do a GET (opening the page in your browser) on the url /clouds/1 the controllers show action is invoked, while a PUT on the same url will invoke the update action.
As you can probably read out of the scaffold_resource statements, a Cloud has a title, a password, some settings and a parent (plus two timestamps automatically updated by Rails), but for the basic functionality of Clouds, none of these attributes are required. A Cloud is nothing but a collection of Topics, and a topic has a text (short “headline” visible at all time), notes (longer text only visible on mouse over) and a position on the screen.
Take a look at the index action in the clouds_controller. After determining if the user already has a cloud to edit (by reading a cookie), the action utilizes either the edit_cloud_path(cloud) or the new_cloud_path methods to redirect the user. There is nothing new about named routes in Rails, but as a part of using RESTful Rails we get these named routes for free. Another interesting point in the clouds_controller is the calls to an authorize method located in application_controller, which is used to check if the user is required to enter a password to either view or edit the cloud. This method is invoked with a symbol as its third parameter, which is used to ask the cloud if either the show or edit action are protected (cloud.send(method)). I think that’s quite clever use of Ruby’s fantastic reflection abilities.
Using bitflags
A last thing I want to highlight in this first part in my article series about Clouds, is my use of bitflags. I believe the term bitflags is used and understood differently by different people, but in this context I talk about storing multiple true/false values in a single integer. In Clouds I needed to store some settings per cloud, specifically if the cloud’s show or edit actions where password protected. I could have added two fields to the clouds table called protect_edit and protect_show with the data type boolean (bit), but what if I needed further settings later on? Should I just continue adding more fields to my clouds table?
No. I believe bitflags is a cleaner solution for this, and to my enjoyment I was quickly able to find a small plugin providing me with this exact functionality called has_flags. In a nutshell, bitflags uses the power of 2 to combine multiple numbers into a single integer, while still being able to tell exactly which power of 2’s are in the integer. So the bitflag 22 are the numbers 2, 4 and 16 combined, while 8 is not present in that bitflag.
Take a look at the cloud model:
# Utilizes the has_flags plugin for easy bitflag usage has_flags [ :protect_edit, :protect_show ], [ :column => 'settings' ]
This single line of code automagically adds protect_edit and protect_show as boolean attributes on the cloud model, while [ :column => 'settings' ] tells the plugin to store the bitflags in a field called settings. This is an easy, elegant and first and foremost “Railsy” way of using bitflags in your models.
In part 2 of this article series on Clouds we jump into the core part of the application: The javascript.
Jaikus