Welcome to Giant Robots Smashing Into Other Giant Robots — a weblog about development, business, design and technology — written by thoughtbot.
Widgetfinger Is Go.

A long time ago, I posted here, about our upcoming product, Widgetfinger. To refresh your memory, Widgetfinger is Simple Content Management for Simple Websites. Its a content management and hosting service specifically built for design firms that includes normal hosting as well as email, and statistics and a few other things that we think are pretty helpful. If you’re a small design firm that builds what we call Brochure websites (Home, About, Services, Contact, and not much else) then widgetfinger is built for you – and you can signup and start using it for free today.
At the time, it seemed like the release of Widgetfinger was imminent – we had successfully started hosting quite a few websites in it, we successfully moved our own website and email hosting to Widgetfinger, and everything was going well. Well, this has certainly been a lesson for us that client work can easily derail the best intentions of our own product development.
All that said, we’ve been incrementally improving things since that time, expanding its use to more websites, and the time has come to really get this show on the road.
So, I’m happy to officially launch Widgetfinger – the website for Widgetfinger is online, and you can signup and start using it today!
I’ll keep this post short, and plan on posting some more info on Widgetfinger in the near future. Suffice to say, we look forward to hearing your feedback as you start to use it – we will continue to improve it based on your feedback. And if you’re just a “normal rails developer” and are thinking Widgetfinger doesn’t apply to you – you’re probably right – but we’ll definitely be posting here about some of the tech used in it, or the things we’ve learned along the way.
Skinny Controllers, Skinny Models
I hear a lot of people recommending the “skinny controller, fat model” approach
to Rails development. I’m all for keeping controllers simple, but who wants a
fat model? If your editor slows down while loading up your model files, I have
some advice: put your models on a diet.
Let’s say you have an application that needs to handle PDF documents. You have a very simple Document model to keep track of them:
1 2 3 4 5 |
class Document < ActiveRecord::Base validates_presence_of :title has_attached_file :pdf validates_attachment_presence :pdf end |
It’s just you and a skinny, attractive model. It’s going to be a good day.
But after your application has been live for a few days, it becomes clear that you need to provide a way to view these documents online, and your client’s weapon of choice is HTML. So, you add a method to convert your PDFs to HTML documents:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Document < ActiveRecord::Base # ... def convert_to_html # ...some fancy magic... end def converted_to_html? File.exist?(html_file_path) end def html_file_path File.join(HTML_STORAGE_DIR, pdf.original_filename + '.html') end # probably a few more methods... end |
Everything is working great, but now you have to look through all this HTML junk whenever you’re working on Document. Worse, the tests for HTML conversion and documents are all mixed up. Even worse, Document is getting pretty fat, and its model friends won’t stop making fun of it. If you don’t do something soon, this could mark the end of your good days with skinny models. A very common and simple technique can save us from this message: composition.
For some reason, many Rails developers seem to avoid using model classes that are not stored in the database. This leads to shoving too much key functionality into one of your key models, which of course leads to fat, incomprehensible model files and tests. I see no reason for an HTML file to have its own, separate entry in the database, but it certainly has enough behavior to warrant its own class. Let’s pull that functionality into an HtmlFile class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class HtmlFile attr_reader :source_path def initialize (source_path) @source_path = source_path end def name @name ||= File.basename(source_path) + '.html' end def generate # ...some magic with file.path here... self end def path @path ||= File.join(HTML_STORAGE_DIR, name) end def exists? File.exist?(path) end # ... some other useful methods ... end |
Now we have another beautiful model. Let’s get these two acquainted:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Document # ... def html_file @html_file ||= HtmlFile.new(pdf.original_filename) end def convert_to_html @html_file = HtmlFile.new(pdf.original_filename).generate end end |
Result: two beautiful, skinny models. Analysis: it’s going to be a very good day.
Pitfalls in RESTful "wizards"
In an application we’re currently building, users go through a wizard to order teams. We implemented this as RESTful controllers – teams, purchases, and orders.
The relevant wizard steps are:
- teams/new
- teams/:id/purchases/new
- orders/new
When each step is submitted, the related create action is called and the user is redirected to the next new action.
Pitfall: the user hits the back button.
On step 3, the user hits the back button. If they re-submit step 2, they will get a validation error because they cannot create two purchases for a team.
There’s a temptation here to stray from RESTful design. Don’t!
When the user hits the back button from step 3, we want to send them to purchases/edit for their newly-created object instead of purchases/new.
Solution: Force the browser to not cache
To ensure that the page isn’t cached by the browser and will always be re-fetched, we add a before_filter to the purchases controller which calls a private method on ApplicationController:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
before_filter :no_cache, :only => [:new] private def no_cache response.headers["Last-Modified"] = Time.now.httpdate response.headers["Expires"] = 0 # HTTP 1.0 response.headers["Pragma"] = "no-cache" # HTTP 1.1 'pre-check=0, post-check=0' (IE specific) response.headers["Cache-Control"] = 'no-store, no-cache, must-revalidate, max-age=0, pre-check=0, post-check=0' end |
Next, if the team already has a purchase, we redirect to the edit action using another before filter on the purchases controller:
1 |
before_filter :redirect_to_edit, :only => [:new], :if => :team_has_registration_purchase? |
#team_has_registration_purchase? is a method on the purchases controller. If the :if syntax is unfamiliar to you, its provided to us by our plugin, when.
Pitfall: ‘not caching’ only works in Firefox.
To the best of our knowledge, setting the HTTP headers should have worked in all browsers, but it did not work in Safari or in IE 6 and 7. A little more research proved that any page with an iframe on it will never be cached, and will always be refetched.
Solution: iframe
So, we add this to views/purchases/new:
<iframe style="height:0px;width:0px;visibility:hidden" src="about:blank">
this frame prevents back forward cache
</iframe>
This works. We now have cross-browser no-caching in a RESTful wizard.
The iframe hack feels a little dirty, though. Does anyone know a better way? Or, do we live with it, similar to using a hidden frame for “Ajax” file uploads with responds_to_parent ?
This concept might be useful outside of these more complex, wizard type controller actions, and might come in handy if you had just a normal restful controller where you wanted the user to get the edit action instead of the new action if they use the back button. Have you done something like this before, or can you think of a different way to accomplish that?
photo courtesy of Michael Porter via Flickr
not yet
Ok here’s an app as of right now.
All the app consists of are some forms for creating users, some forms for users to create comments, plus 1 additional page that displays each user and the total # of comments they have created. There is NO page that displays a comment and its user who created it.
The correct domain model based on the requirements right now is:
1 2 3 4 5 6 7 8 |
class User < ActiveRecord::Base has_many :comments end class Comment < ActiveRecord::Base end |
This association is unidirectional, it can only be traversed in one direction. There is no point in making this association bidirectional by adding a corresponding belongs_to :user in Comment. Adding that would be premature and a case of massive over-engineering. Since the association from Comment to User currently is not used in the app, adding it would just cause unnecessary complexity.
There is nothing wrong with unidirectional associations. Don’t make all associations bidirectional when its just not necessary; someday it might be but until then don’t do it.
mas o menos
Everyone seems to trip up when it comes to #< and #> and dates and times.
1 2 3 4 5 6 7 8 9 10 11 |
def recent if published_on > 1.week.ago # blah end end def old if published_on < 1.week.ago # blah end end |
Nobody thinks of dates and times as numbers, so its hard to do comparisons on them quickly like you would with numbers. So lets add 2 methods to our dates and times.
1 2 3 4 5 6 7 |
def after?(other) self > other end def before?(other) self < other end |
1 2 3 4 5 6 7 8 9 10 11 |
def recent if published_on.after?(1.week.ago) # blah end end def old if published_on.before?(1.week.ago) # blah end end |
Luckily our “good friends” over at dzone have already a “real solid” impl of these 2 queries. LEFT_SIDE_LATER!
..in other news…

thoughtbot is hiring
We are hiring web developers and web designers in both Boston and New York, NY.
What are we up to?
We built Shoulda, an eclectic set of additions to Test::Unit; Paperclip to manage uploaded files without hassle; Jester, a REST/ActiveResource client library written in Javascript, and Squirrel, an enhancement for ActiveRecord's find syntax; — amongst some other projects.

Chad (President) and Jon (CTO) co-authored a technical book titled Pro Active Record: Databases with Ruby and Rails, which explores the ins and outs of the ActiveRecord ruby library. You can buy it today at Amazon.com.
About thoughtbot, inc.
We are a small web application development consulting business, with offices in Boston, MA and New York, NY. If you're looking to find a team for your next web development project or your new web application — get in touch.