I recently moved CVX Forum, a support forum for users of my convex optimization software, from Askbot to Discourse. You can read about my motivations for making the change here. We can have some of that discussion here if you want, but in this thread I wanted to offer up my experience and code.
The code I used can be found in this GitHub repo. There are only two files there:
base.rb is actually a very slightly modified version of
script/import_scripts/base.rb in the Discourse tree. Indeed there is exactly one change: in line 302, I changed
if bio_raw.present? || website.present?
if bio_raw.present? || website.present? || location.present?
so that I could port user-supplied location information over from Askbot profiles.
Let’s talk about the meat of the code in
askbot.rb. Some general notes:
- Askbot uses Markdown, making porting of the raw text easy.
- Askbot uses PostgreSQL for its database. Alas, it seems none of the other import scripts rely on PostgreSQL, so I had to figure out how to use the PG Ruby interface all by my lonesome. (Poor poor pitiful me.) The toughest part was getting the timestamps right; I might have still gotten the time zone issues wrong, but I am fine with the result now.
- I decided to thread comments and answers together into a single linear time stream. In my forum, the distinction between the two types of responses was unnecessary and forced, so I’m frankly happy not to have that distinction now.
- I did not bother to link comments to their matching questions or answers; our threads were not that long. In theory, you could do so.
- I chose to select a subset of Askbot tags to convert to categories. Any post that had one of those selected tags was moved into the corresponding category; all others were left uncategorized. I considered creating a default category to catch all of those other posts, but ultimately it seems like there is no real disadvantage to having uncategorized posts in my case.
execute procedure proceeds as follows:
create_cats: create Discourse categories for the list given in the
CATEGORIESglobal. These category names are assumed to match the Askbot tag name in a case-insensitive comparison.
import_users: suck in the Askbot users. I mapped the
locationfields quite directly to corresponding Discourse fields. I have no idea if password hashes can map over; I didn’t bother to try.
read_tags: for simplicity, I read in the entire tag database from Askbot. I decided to attach the full tag list for each post as a
custom_fieldentry, even as I sifted through them to make category determinations.
import_posts: read in the questions, determine their categories, and store their
thread_idvalues for later matching with comments and answers.
import_replies: read in comments and answers. Questions, answers, and comments are actually stored in the same Askbot table, but I decided to make two passes since comments and answers were to be processed differently.
post_process_posts: try to convert Askbot internal links to Discourse internal links. Thankfully, I didn’t have too many of these, but I did need to distinguish between
A HREF-style HTML links, Markdown-style
()links, and bare text links.
Needless to say, this is a hack. In theory, I could wipe my database clean and run this code, and be done. In practice, I ran it several times, commenting out some of the steps so I could verify the intermediate results before proceeding. This is not even close to being ready to dump into
import_scripts, and I don’t intend to make it so Besides, this is the longest piece of Ruby code I’ve written (the record before that was a Homebrew formula).
I’m quite pleased with how things turned out, and of course that’s no small part due to the quality of the Discourse code, in particular the
script/import_scripts directory. The
base.rb code is essential, of course, but the other templates there were extremely helpful. Thank you to all contributors.
If you have questions or comments, by all means, let me know and I will do my best to respond.