Draftsman gem: Add a draft state to your Ruby on Rails and Sinatra relational models
Draftsman is a Ruby gem that lets you create draft versions of your database records. If you’re developing a system in need of simple drafts or a publishing approval queue, then Draftsman just might be what you need.
I built Draftsman to handle the draft logic in Live Editor. Because draft logic gets complicated pretty quickly, I felt that this warranted its own gem with its own set of tests. Plus this was a great opportunity to share a piece of Live Editor with the open source community!
Live Editor uses the excellent PaperTrail gem for publication revision tracking, so I built the API behind Draftsman to work much like PaperTrail.
A taste of how it works
Have a read of the README to see more examples of how to work with the Draftsman gem.
To get up and running with Draftsman, you generally have to do a few things:
- Add the
draftstable to your database with the
rails g draftsman:installcommand (or copy the migration file into your application if you’re using Sinatra).
trashed_atcolumns to the database tables you want to have drafts on.
has_draftsto the models you want to have drafts on.
More specific installation instructions are available at the GitHub project.
Persistence of draft data
The Draftsman API requires you to be explicit about when you want to save data as a draft. It provides these persistence methods on your models containing
draft_update work like ActiveRecord’s
update methods: they validate data in the model, run callbacks, and return
false if everything saved successfully.
Publishing and reverting drafts
draft_destroy, you’re effectively storing a copy of the record with drafted changes in the
You can access these drafts through a query directly on the
Draftsman::Draft class or through an instance method called
draft on your model objects.
Once you have a draft record, you can call
revert! on it to undo the draft or
publish! to publish the changes to the main record.
Once you’re living in the brave new world of drafts, you’ll want to query your model data differently depending on where you are in the application.
Here is a quick listing of scopes that Draftsman adds to your model when you add
has_drafts to it:
live scope is used in the "admin" section of your application because it limits the query to records that have not been put in the trash (or
draft_destroyed, to use Draftsman terminology).
When you want to show only published data (usually in the public or non-admin area of your application), you should use the
published scope to query your data.
Loading drafted data
In the admin area of your application, you’ll most likely want to load your data in a mixed state:
- If a record has a draft, show that data
- If a record doesn’t have a draft, show that data
- Hide records that have been drafted for destruction (AKA put in the trash)
The way to accomplish this is through the
reify method on each record’s
draft. Take a look at these sample
show actions for a
posts admin area:
reify returns a
Post object with the drafted data loaded in. If any
Post record is not a draft, then nothing is done.
All drafts in one spot
Live Editor, using Draftsman under the hood, allows admins to browse to a Drafts section that lists all drafts polymorphically, no matter if the drafts are of content, files, or design elements.
That way, you can review everything that’s in progress and publish or revert multiple drafts at once, no matter the type of content.
Try doing that in WordPress!
Other features of Draftsman
There is more to this gem that I won’t cover in this introduction. Some features that I didn’t mention are as follows:
- Does not store drafts for updates that don’t change anything.
- Allows you to specify attributes (by inclusion or exclusion) that must change for a draft to be stored.
revert!methods handle any dependent drafts so you don’t end up with orphaned records.
- Allows you to get at every draft, even if the schema has since changed.
- Automatically records who was responsible via your controller. Draftsman calls
current_userby default if it exists, but you can have it call any method you like.
- Allows you to store arbitrary model-level metadata with each draft (useful for filtering).
- Allows you to store arbitrary controller-level information with each draft (e.g., remote IP, current account ID).
- Stores everything in a single database table by default (generates migration for you), or you can use separate tables for separate models.
- Supports custom draft classes so different models’ drafts can have different behavior.
- Supports custom name for
For more detailed information, examples, bug reports, etc., visit the Draftsman GitHub repo. Give it a try!