I attended my first Dublin Ruby User Group meet last night and really enjoyed it. This months event was organized by Qamir Hussain, and I hope the format will be a template for future events. Eleven people attended, Ana Nelson and Olivier Ansaldi gave presentations on Radiant and REST respectively and then we relocated to a very nice bar in town for further discussions (and wine).
Ana's presentation was a great intro to Radiant and it's capabilities. She's currently using it in a very clever way to generate a static site for her company. She gave a tour of the various features, showed some small but useful examples of how to create your own tags and gave some tips on getting the best out of it.
Olivier's presentation covered REST both as a concept and as implemented by rails. It was like a cool remixed short version of DHHs keynote at RailsConf Chicago, involving lots of pictures of people sleeping on trains. It provoked a quite lively discussion on the concepts and ins and outs of REST that I think will continue over further meetings.
It was good to meet other rubyists in Ireland, and I'm looking forward to the next meeting. Though I think I may have agreed to do a presentation…
UPDATE: I created an IRC channel for Irish Rubyists, #ruby.ie on irc.freenode.net, come and hang out!
Some discussion on zsh PS1 settings yesterday lead to me posting a picture of mine:

It’s a very slightly modified version of a zsh prompt created by Phil Gregory available here.
The caboo.se blog is back in action after a lull. Check out Corey Donohue's article on getting rid of bloat in your tests .
I’ve been upgrading an app from 1.1.6 to edge recently and ended up with failing tests. To get to the bottom of why a particular test was failing, and find the revision of Rails that caused it I wrote the following little script:
UPDATED: Yes, this should have been a binary search from the start :)
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
#!/usr/bin/env ruby require 'optparse' KNOWN_BAD_REVISIONS = [4634, 4635] class Args < Hash def initialize(args) super() self[:quiet] = false opts = OptionParser.new do |opts| opts.banner = "Usage: #$0 [options]" opts.on('-s', '--start_revision NUMBER', Integer, 'start from revision NUMBER') do |revision| self[:start_revision] = revision end opts.on('-f', '--finish_revision [NUMBER]', Integer, 'finish on revision [NUMBER]', '(defaults to latest revision of', ' http://dev.rubyonrails.org/svn/rails/trunk)') do |revision| self[:end_revision] = revision end opts.on('-t', '--test_file STRING', 'path to test file to run') do |test_file| self[:test_file] = test_file end opts.on('-n', '--name STRING', 'name of test to run') do |test_name| self[:test_name] = test_name end opts.on('-b', '--know_bad_revisions LIST', 'Comma separated list if known bad revisions to skip', 'These are added to the list', "current list: #{KNOWN_BAD_REVISIONS.inspect}") do |extra_known_bad_revisions| self[:extra_known_bad_revisions] = extra_known_bad_revisions.split(",").map { |r| r.to_i } end opts.on('-q', '--quiet', "don't output each revision as it is tested") do self[:quiet] = true end opts.on_tail('-h', '--help', 'display this help and exit') do show_help(opts) end end opts.parse!(args) self[:extra_known_bad_revisions] ||= [] show_help(opts) if self[:start_revision].nil? || self[:test_file].nil? || self[:test_name].nil? end def show_help(opts) puts opts puts "\n***Assumes a checkout of rails trunk in vendor/rails***" exit end end class BadRevisionFinder def initialize(options) @options = options @end_revision = @options[:end_revision] ||= latest_rails_revision @start_revision = @options[:start_revision] @known_bad_revisions = KNOWN_BAD_REVISIONS + options[:extra_known_bad_revisions] puts "Known bad revision list: #{@known_bad_revisions.inspect}" end def find_bad_revision sanity_check while (@end_revision - @start_revision) > 1 check_revision = ((@start_revision + @end_revision) / 2.0).floor while @known_bad_revisions.include?(check_revision) check_revision += 1 end if revision_bad?(check_revision) puts "bad revision" @end_revision = check_revision else puts "good revision" @start_revision = check_revision end end bad_revision = revision_bad?(@start_revision) ? @start_revision : @end_revision puts "r#{bad_revision} caused #{@options[:test_name]} to fail! http://dev.rubyonrails.org/changeset/#{bad_revision}" end def sanity_check # make sure it's passing at the start revision if revision_bad?(@start_revision) puts "Hey, this test fails with the start revision #{@start_revision}!" exit end # make sure it's failing on end revision unless revision_bad?(@end_revision) puts "Hey, this test passed with the end revision #{@end_revision}!" exit end end def revision_bad?(revision) puts "checking r#{revision}" unless @options[:quiet] `svn up -r #{revision} vendor/rails` `ruby #{@options[:test_file]} -n #{@options[:test_name]}` $? != 0 end def latest_rails_revision puts "Finding latest edge revision number" `svn info http://dev.rubyonrails.org/svn/rails/trunk`.match(/Revision: (\d*)/m).captures[0].to_i end end BadRevisionFinder.new(Args.new(ARGV)).find_bad_revision |
Save the above as find_culprit.rb in root of your application, then run with something like:
1 2 3 |
find_culprit.rb -r 4191 -t test/unit/user.rb -n test_something_that_should_still_be_working
|
Start at whatever revision you know your app is good at. It requires a checkout of rails trunk to vendor/rails.
NB: This can be quite a slow process, as it’s doing an svn up each time. I’ve finished using it now, but next time I’m going to see if I can use svk to setup my own mirror of the rails repo locally to speed things up
I’m loving using Mephisto as my blog engine. Justin has done great work on the interface and Rick’s code is of his usual standard. There’s a lot more great stuff to come which I’m really looking forward to. In fact I’m loving Mephisto so much I’ve decided to use it as the blogging engine on a client project. This project doesn’t require very advanced blogging, but it does require that it’s integrated with the main site with seamless user registration and multiple sites. I’ll write more about that when I do it, but first I want to show how I setup my development environment so I can track changes to Mephisto trunk, make any local changes I require and deploy using Capistrano.
My requirements were:
- Have multiple local copies of Mephisto I can make versioned changes to
- Merge upstream changes from the Mephisto trunk into my local versions
- Be able to deploy with Capistrano
I initially tried this with darcs, but came to the conclusion that svk was a better choice as it works better with Subversion. I’ll go through the process of how I set this up here, I’m assuming there’s no local SVK repos already setup. If there is you may have to modify the steps below to fit.
First up is setting up an svk mirror of the Mephisto trunk. I ran the following to setup the mirror to pull from:
1 2 3 4 5 6 7 8 |
$ svk mkdir //pull_repositories
Repository /Users/chris/.svk/local does not exist, create? (y/n)y
$ svk mkdir //pull_repositories/mephisto
$ svk mirror http://svn.techno-weenie.net/projects/mephisto/trunk //pull_repositories/mephisto/trunk
$ svk sync //pull_repositories/mephisto/trunk
Syncing http://svn.techno-weenie.net/projects/mephisto/trunk
... lots of output ...
|
Syncing the Mephisto repository will take a while the first time you do it. The next step is to create a local svk branch to use. I tested this process out with my personal blog so I’m going to create a local branch called personal.
1 2 3 4 |
$ svk mkdir //local
$ svk mkdir //local/mephisto
$ svk cp //pull_repositories/mephisto/trunk //local/mephisto/personal
|
So now there’s a local copy you can checkout with svk co //local/mephisto/personal, make changes to and checkin with svk ci. See the installation instructions on the Mephisto wiki for how to proceed.
We won’t be able to deploy this yet, as Capistrano doesn’t support svk. What’s needed is the ability to push the local branch out to another subversion repository that Capistrano can use for deployments. You’ll need to make a new subversion repo wherever you usually keep them.
1 2 3 4 5 |
$ svk mkdir //push_repositories
$ svk mkdir //push_repositories/mephisto
$ svk mirror (url to your svn repo) //push_repositories/mephisto/personal
$ svk smerge --baseless //local/mephisto/personal //push_repositories/mephisto/personal
|
You can checkout the subversion repo with svn and you’ll see a copy of your local branch. You use this repository as your repo in your deploy.rb.
To help with the svk stuff and all for automatically pushing the latest local changes to the subversion repo when deploying, I created a lib/tasks/svk.rake that looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
app_name = File.basename(File.dirname(Dir.pwd)) install_name = File.basename(Dir.pwd) local_repo = "//local/#{app_name}/#{install_name}" push_repo = "//push_repositories/#{app_name}/#{install_name}" namespace :svk do desc "Get latest from upstream" task :pull do system("svk pull") end desc "Push local svk branch to remote svn" task :push do system("svk smerge --incremental --log #{local_repo} #{push_repo}") end end |
And added the following task to my deploy.rb1
1 2 3 4 5 |
desc "Push the latest svk code to the svn repo and set the svn revision number" task :before_update_code do system("rake svk:push") end |
That should be everything setup. You can now hack away on Mephisto to your hearts content in as many different branches as you want. Have a read of the svk manual to see what else is possible. For example, if you’ve some patches to contribute back, you can create a new local branch, apply just those patches to it, make sure the tests pass, then svk diff to create a patch.
1 Due to the way I have my svn setup I needed to do some other stuff to stop it trying to find the latest revision. If your deploy gets stuck in a loop trying mention it in the comments and I’ll add that code to the article.
I’m back in Dublin after the fantastic RailsConf, jetlagged and tired but very glad I went. Many other people have blogged about the conference itself, Phil Hagelburg set up Planet RailsConf to track it. I’m just going to write about the part of the conference I found most interesting, DHH’s keynote on CRUD. This has been covered by several different people. While I love the concept having recently finished reading Domain Driven Design, the bit that worried me about the example David showed was the lack of error checking and the case/switch like block statement:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class PeopleController < ActionController::Base def create @person = Person.create(params[:person]) respond_to do |format| format.html { :redirect_to :action => "index" } format.js # renders create.rjs format.xml do headers["Location"] = person_url(@person) render :nothing => true end end end end |
Seeing this made me think error handling when you’re responding to a lot of different formats could get messy, so I had a look at the bloction code from Rick, Courtenay and Justin’s Railsday project. Rick mentioned this as a good example of a RESTful app. Reading over the code they managed errors by using exceptions as in the example below:
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
def create @bid = @product.bids.build(:total => params[:bid][:total], :user_id => current_user.id) high_bidder = @product.bidder @bid.save! BidMailer.deliver_outbid_notice(high_bidder, @product.reload) if high_bidder && high_bidder.id.to_s != @bid.user_id.to_s respond_to do |format| format.html do flash[:notice] = "Your bid was sent." redirect_to product_url(@product.user, @product) end format.js do @bids = @product.bids.find(:all, :limit => 4) unless @highest_bid = @bids.shift @highest_bid = @product.bids.build(:amount => @product.starting_amount, :user => @product.user) @highest_bid.save_without_validation end end end rescue ActiveRecord::RecordInvalid respond_to do |format| format.html { render :partial => 'new' } format.js do render :update do |page| page.replace_html 'bid_messages', "Your bid was invalid. Please fix and try again." + @bid.errors.full_messages.join('<br />') end# todo: show the invalid partial and a reason why end end rescue Bid::InvalidBidder respond_to do |format| format.html { render :text => "You can't bid on your own auction!" } end rescue Bid::OutOfMoney respond_to do |format| format.html do flash[:notice] = "You don't have enough money to bid that much! Maybe you want to transfer some more." redirect_to user_url(current_user) end format.js do render :update do |page| page.replace_html 'bid_messages', :partial => 'users/out_of_money' end end end end |
This could get a little ugly if every action had to respond to every format, but I don’t think this is going to be the case. Plus, if you follow David’s lead on keeping things as CRUDdy as possible, you’re going to have smaller easy to manage controllers doing the same conceptual work on different nouns in your domain language. To me this means less to think about for each controller, so it shouldn’t be too hard to keep the error handling you have to do in your head.
If the case/switch like respond_to block statement becomes overly complex, perhaps it will be suited to the Replace conditional with polymorphism pattern. You could have a base controller that does the common work, then subclasses for each of the formats you want to respond to. I’ve no idea how that would work, or even if it would be less code but it’s a possibility I will keep in mind as I make things more RESTful in my own apps.
Having recently started a new project that’s greenfield development, I hope to introduce REST ideas as I go and see how I get along with them.
I’ve recently changed to using Mephisto as my blog engine rather than Typo. Mephisto is yet another product of Rick Olsen, Rails core member and uber plugin guy. I’ve wanted to switch for a while for several reasons, but have only got around to it now. I was looking for an engine that supports multiple sites per installation, can import my old Typo posts, has CMS features, and is extensible via plugins and Mephisto fits the bill. It’s not quite finished yet, but has enough features to be usable right now and is a lot easier to hack on than Typo.
Mephisto uses liquid templates, editable in it’s admin section meaning you’ve got total control over the look of your site. I found it a bit hard initially editing the templates in the browser, but I’m on a Mac and TextMate’s Edit In TextMate… feature helped with that. I’ve got a bit more used to it now, though I’d still like something similar to Vision so I could edit themes.
This post describes the process I used, the idea being I might get some more people interested in switching from Typo and maybe hacking on the Mephisto code to get it really production ready.
Set up a base Mephisto install
I like to have a local copy of the code to work on and use Capistrano to put it live. This doesn’t work so well yet with Mephisto, as they design is stored in the database. However I’m still going to do this so I can use my capistrano-runit-tasks library. As this probably isn’t what you want to do, see the example deploy.rb in the svn source for Mephisto and use that or just set it up live on the server. A basic live setup would be something like:
- Checkout the Mephisto trunk to where you’re going to run your site from
- Checkout/export a revision of rails into vendor/rails
- Set up a database and a database.yml pointing to it. Run mephisto in production mode from the start, so you only need a production database for now.
- Run rake db:bootstrap to do the initial mephisto setup
- Point your webserver of choice at the new setup and check it’s all working. (You should see an incredibly basic layout)
Initial configuration
Once you’ve got it all up and running on your environment, login to the admin section at http://<your site>/admin. The rake db:bootstrap sets up an admin user, but I couldn’t figure out the password so I changed it via script/console on the live server. (Update: the password is test)
The first thing you’ll want to do is hit the settings tab on the top right and change them to what you want. Make sure and set the filter to whatever you used in your old blog so the articles import correctly.
Import your old Typo articles
To do this you need to:
- edit config/database.yml to include a typo section pointing to your typo database
- from the console run
script/runner "Mephisto.convert_from :typo" -e production
You should see a count of the users, articles and comments. Typo uses http://<site name>/articles/... for permalinks, so we’ll need to add a route to support that. So add the following code to routes.rb. (Update: I don’t actually do the routes thing below anymore, I have the webserver configured to 301 redirect the old urls to the new ones)
1 2 3 4 |
# Support old Typo urls m.article 'articles/:year/:month/:day/:permalink', :action => 'show', :year => /\d{4}/, :month => /\d{1,2}/, :day => /\d{1,2}/ |
1 2 3 |
m.article ':year/:month/:day/:permalink', :action => 'show', :year => /\d{4}/, :month => /\d{1,2}/, :day => /\d{1,2}/ |
Create the design
Click on the design tab in the admin interface, down the right hand side you’ll see a list of templates. The first one we’ll do is the layout, which is used like views/layouts/application.rhtml in a rails app.
I couldn’t design my way out of a wet paper bag, so I headed over to oswd.org and looked around for one I liked. I eventually chose this one, and am using it slightly modified. The rest of this section assumes you’ve got something similar to what you’d get from oswd, i.e. a html mockup of the design.
The first thing is to edit the layout template in Mephisto. Clicking on the layout link on the right hand side will show you the current liquid template for the layout. You need to replace this with your layout, and add the liquid tags you need.
Let’s start in the head. Mephisto provides some liquid blocks for standard stuff, and one of these is the
tags. replace the and in your layout with {% head } and { endhead %}, and somewhere in between do {{ head.feed }}. This sets up the rss auto discovery tag. You’ll want to have your site’s title in the title of the page, so addThe site is setup in application.rb and made available to liquid. To see what other attributes are available, have a look at the to_liquid method in app/models/site.rb.
Once you’ve got the HTML/Liquid the way you want it, put {{ content_for_layout }} where you want the content to go.
You’ll need to create a new template for your stylesheet. Click on design again, paste the css into the big box, set the name to whatever is required (not including .css) and set the type to CSS. You may need to edit your layout to make sure the link tag is pointing to /stylesheets/
The next template you’ll need to edit is the ‘home’ template. This is used for the main page of your blog. Mine looks something like:
1 2 3 4 5 6 7 8 9 10 11 12 |
{% for article in articles %}
<h2>{{ article | link_to_article }}</h2>
<div class="meta">
<span class="date">{{ article.published_at | format_date: 'standard' }}</span>
<span class="postedBy">Posted by {{ article.author.login }}</span>
</div>
{{ article.body }}
<div class="comments">
{{ article | link_to_comments }}
</div>
{% endfor %}
|
Mephisto makes the articles collection available, again have a look at the to_liquid method in the Article and Author class to see what else is available to you.
Next is the single article display, which uses the ‘single’ template and allows the user to add comments. Mine looks like:
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 29 30 |
<h2>{{ article.title }}</h2> <div class="article"> <div class="meta"> <span class="date">{{ article.published_at | format_date: 'standard' }}</span> <span class="postedBy">Posted by {{ article.author.login }}</span> </div> {{ article.body }} </div> {% for comment in comments %} <div class="article-comments"> <div class="article-comments-meta"> <span class="date">{{ comment.created_at | format_date: 'standard' }}</span> <span class="postedBy">Comment by {{ comment.author }}</span> </div> {{ comment.body }} </div> {% endfor %} <div id="article-comment-form"> <h3>Got something to add?</h3> {% commentform %} <p>Your name:<br/> {{ form.name }}</p> <p>Your email:<br/> {{ form.email }}</p> <p>Your blog:<br/> {{ form.url }}</p> <p>Your comment:<br/> {{ form.body }}</p> {{ form.submit }} {% endcommentform %} </div> |
Notice the comments are available as a separate collection and how Mephisto provides some liquid tags for your comment form.
Handle Typo’s RSS feed URL
Typo and Mephisto have different URLs for their feeds. I added these routes to support people already subscribed:
1 2 3 4 |
map.typo_atom_feed 'xml/atom/feed.xml', :controller => 'feed', :sections => "", :action => 'feed' map.typo_rss_feed 'xml/rss/feed.xml', :controller => 'feed', :sections => "", :action => 'feed' map.typo_rss2_feed 'xml/rss20/feed.xml', :controller => 'feed', :sections => "", :action => 'feed' |
These all redirect to the atom feed, which may cause some problems for people but I posted an article explaining what was happening so I reckon anyone who’s still interested in reading shouldn’t be affected.
Creating other pages
So that’s the basic blog running. Mephisto also has lite CMS features, perfect for adding some more information like pages for projects I work on and contact information. This is done by adding a new section and setting the type to “Area with multiple pages (one article per page)”.
The content in these sections is displayed using the ‘page’ template, so that needs to be edited to display the correct information. Mine looks like:
1 2 3 4 5 |
<h2>{{ article.title }}</h2> <div class="article"> {{ article.body }} </div> |
Summary
So that’s pretty much what I did to get this blog switched over. I’ve set it up so that http://octopod.info and http://outside-thoughts.octopod.info are the same now. Let me know thoughts / problems and comments below.
I’m nearly finished switching this blog over to Mephisto. More details when I’ve actually switched. One casualty of moving over will be only providing an atom feed. A lot of the readers here use rss2, so if you’re still interested, switch your reader to use this link, which I’ll still support.
I’m here at the very interesting Reboot 8 where Jarkko Laine hosted a great discussion after dinner with a theme of:
“What is exactly the sweet spot Ruby on Rails has managed to hit? What in Rails makes a developer tick? The Railways is an open discussion where Rails hackers share the love and tell newcomers what in the Rails paradigm concretely made their views about web development turn somersaults.”
Some very interesting people turned up to share their experiences. The Rails users were people at small startups, people who’ve been running Rails apps for well over a year and people who work for large consultancies. The interested folks included .Net and J2EE developers, designers who wanted to know more about what their developers are excited about, and people who’ve been tracking Rails for a while without diving in and using it.
I’ll try to summarize here, and provide some information and tips that came out of the discussion. Apologies if I’ve missed anything.
People who are already using Rails are using it because they:
- Enjoy it.
- Get products and solutions out the door faster.
- Appreciate how the tools that come along with Rails make development and deployment easier.
- Appreciate how beautiful Ruby code can be made.
People who are interested in Rails but aren’t using it yet are either:
- Locked into other frameworks because of their clients.
- Unable to find documentation they can use either about the framework or about setting up production servers.
- Worried about whether time spent learning a new language and framework will be a good investment.
So what exactly did we talk about?
Speed of development
Try things out, fail or succeed faster, use less people to get the same things done so you don’t need as much or even any funding. This was illustrated earlier in the day by Jesper Rønn-Jensen’s talk about using Rails for prototyping in CapGemini. They’ve put some of their prototypes into production, and turned others into Java/.Net code after using Rails to work out the use cases with their clients.
Good abstractions
Lars Pinds talked about how the abstraction in Rails are at the right level. Rails abstracts the things that are fixed, e.g. the HTTP protocol, AJAX, SQL and leaves the rest up to the developer. He talked about the problems other frameworks had trying to abstract non-generic things like groups and users into a generic framework and why that didn’t work.
Ease of deployment
People who’d come from J2EE specifically mentioned Capistrano being a joy. Ben Griffith shared his secret for finding Java developers who want to become Rails developers. He took out adwords for terms like “J2EE deployment problems” that said “Stuck in J2EE hell?” and got a very good response!
Migrations
Several people mentioned this as something that other frameworks just don’t have. Anyone who has used migrations knows it would be hard to do it any other way.
script/console
Having a console on your production database means you don’t have to write lots of scripts to do DB stuff. Knowing you’re going to be working this way means you tend to have a richer domain model.
Testing is easy
As Rails comes with a testing framework built in it’s easy to be test infected.
Ruby
Ruby allows you to write beautiful code that is closer to the language of the domain. Ben actually gets his non techies to read the code!
Fun / Passion
Reno Marconi, one of the original developers of Java in Sun talked about how he hasn’t seen this much buzz since Java first came out.
Future directions
People wondered whether Rails would turn into the committee based mess of J2EE and were reassured that DHH’s benevolent delegator style of leadership would ensure this wouldn’t happen.
A few people talked about running Ruby inside the CLR or JVM. Jon Lam is working on making the CLR and Ruby talk together. Other people made the point that the JVM is a really nice piece of engineering, it’s J2EE that’s the mess on top of it.
Documentation
A couple of people mentioned this as an area they found lacking, both for Rails itself and getting your app running in production. rails.outertrack.com is a fairly new site aiming to be something like php.net for Rails. Several upcoming books were mentioned that will help improve documentation in other areas. Someone also made the point that when Tomcat first came out, sometimes the only way to figure out what was going on was to read the code.
Keeping up with what’s happening
We talked about how to keep up with what’s happening in the Rails world. A good set of RSS feeds to keep in your reader are:
- Riding Rails of course.
- Commits to the Rails trunk.
- New and updated plugins.
- Rodney is posting a weekly Rails core summary. This also goes to the mailing list.
The Rails weblog has mentioned some interesting blogs recently, have a look back through that to find some more.
I’ve recently switched to runit from spinner/spawner/reaper for fcgi supervision. This turned out to be a pretty painless process, and reduced the load on my VPS from 2 – 2.5 back down to 0 – 0.5 as it wasn’t swapping anymore.
In the process, I created capistrano-runit-tasks, a library for Capistrano that handles setting up the service directories and controlling the tasks. If you’re in a low memory situation, or just don’t like the spinner way of doing things give I’d highly recommend runit.
desc “Link sites in dev to a specific revision from ~/dev/revisions, use APPS=a,b,c REVISION=x Does nothing if any of the apps given do not exist. Defaults to HEAD if REVISION not used. Will get the revision if it doesn’t exist locally” task :link_apps_to_edge do if ENV[‘APPS’].nil? puts ‘Must specify some apps with APPS=a,b,c’ exit end apps = ENV[‘APPS’].split(’,’) exit unless apps_exist?(apps)
edge_path = get_edge(ENV['REVISION'])
apps.each do |app_path|
Dir.glob("#{app_path}/**/vendor").each do |vendor_path|
next if vendor_path.include?("_darcs")
rails_path = File.join(vendor_path, 'rails')
if File.symlink? rails_path
FileUtils.rm rails_path
end
if File.exists? rails_path
puts "A non-linked version of rails exists in app #{app_path}, dir #{rails_path}. Not doing anything"
else
FileUtils.ln_s File.expand_path(edge_path), rails_path
puts "linked #{edge_path} to #{rails_path}"
end
end
end
end
def get_edge(revision) revision ||= `svn info http://dev.rubyonrails.org/svn/rails/trunk`.match(/Revision: (\d+)/).captures0 export_path = “rails_revisions/#{revision}” if File.exists? export_path puts “Revision #{revision} already exists, doing nothing” else puts “About to export #{revision} to #{export_path}” puts `svn export -r #{revision} http://dev.rubyonrails.org/svn/rails/trunk #{export_path}` end export_path end
def apps_exist?(apps) non_existant_paths = apps.collect { |app_path| app_path unless File.exists? app_path }.compact if non_existant_paths.empty? true else non_existant_paths.each { |path| puts ”#{path} does not exist” } puts “Nothing done” false end endThis article should have my first working version of the backup script I use at Quantact attached. It’s very rough, it was the first Ruby code I wrote after I got back. I’d like to do a lot of work to it to make it nice, but it’s not a high priority right now. Aniero asked to see it on irc, so here it is. Read the comments and look for <<...>> for places you need to put in your own info. The Subversion part assumes you have all your Subversion repositories running under one user.
I run this in a root cron job. Feel free to improve and send back to me :)
Switching
So I’m well behind on this one, having had a Mac since June and going a whole six months without switching. I couldn’t help it, I was (and still am) really comfortable in Vim and if it integrated better with OS X i’d still be there. TextMate is a very nice editor, it integrates incredibly well with OS X and has a great bunch of Ruby and Rails shortcuts (among others). I’m getting more comfortable with it every day, though of course I still miss certain things about Vim.
The problem with switching is, of course, you don’t know what the editor can do and what all the shortcut keys are straight away. TextMate provides a list of them if you want, but that’s not very handy when you’re in the middle of an editing session. I used to have this problem in Vim until I discovered the Vim quick reference card, so I wanted something similar for TextMate.
Making a TextMate PDF reference card
The list TextMate shows is a HTML table, and when you copy and paste somewhere it turns in to something like a tab separated value file. Hmm… My thoughts turned to Ruby, and I reckoned with a little scripting I should be able to scrape the HTML and turn it into a PDF. I’ve noticed Austin Ziegler’s PDF::Writer before, but haven’t had a chance to use it, so here was the perfect opportunity.
After installation, I had a look through the documentation and the demo files and saw the Ruby language and Ruby library quick reference cards. These were written by Ryan Davis, aka zenspider, who wrote another quick reference I find invaluable, available here (in HTML form). Peering at the code, the first thing that jumped out was the:
Turning it into a Rails app – Keyref
I had been trying to think of a nice simple app I could write to get back into Rails after my trip. and thought that if I found making a PDF useful perhaps other people would too. So Keyref was born. Not only would it be something straightforward enough to write, but it would allow me to check out SwitchTower and set up my process of deploying an app from scratch to my VPS.
I had fun coding up the app, I found a simple design on oswd I liked and started from there. After a bit of a detour through a totally Ajax interface, considering writing a proper parser for the HTML and seeing if PDF::Writer would do UTF-8 characters1 and use an external font2, it ended up in the form it is now.
You can paste the key shortcuts into a form in the app, it parses (ok, scrapes) them and presents you with a list of available bundles from which you can pick eight to be on your card. This uses some Prototype / script.aculo.us magic to enable you to drag and drop between the lists and re-order the bundles you want printed.
I limited it to eight as PDF::Writer can use a lot of memory as it tries to find the best layout for the card and I didn’t want to kill my 96Mb VPS. Multiple requests at the same time would also be a problem, so I decided to store the parsed data in the database and generate the PDFs one at a time. A really cut down version of the prag progs order system if you like. I’ll write about the generator and mailer part of the system at a later date.
Keyref is up and running, TextMate users feel free to give it a go and let me know what you think.
1 It won’t, yet, the PDF spec only uses UTF16-BE which PDF:Writer supports (just about) and I couldn’t do what I wanted to do
2 I got this working, but the letter spacing was all wrong.
So with the decision to move made, there were a few things to sort out:
- Where to go to
- What OS to run
- How best to get everything set up
- How to handle backups
So it was research time!
Choosing a provider
Happily several people had just made the same kind of move, so I got some good advice. It boiled down to a choice between RimU and Quantact. I ended up deciding on Quantact, they were slightly cheaper, the main guy seemed nice on irc and, to be honest “rim-u” made me laugh every time I said or thought the name. I’m currently using their basic service, 96Mb of RAM, 4500Mb of disk and 40Gb/month data transfer. It’s only costing me a few dollars more per month than TextDrive, and they’ve proved to be very solid over the past few weeks. My host has only been down for half an hour because of a RAID issue, and I got plenty of warning that was going to happen.
They use Xen for virtualization, and I’ve been really impressed with how it does it. I’m guaranteed memory and disk, no problems with lack of file descriptors etc. and the scheduler means that no one can hog all the processing power. It just seems a much better way of organising things.
OS and Setup
This was actually a really easy decision in the end, after I saw the excellent guide Ezra Zygmuntowicz wrote for setting up the Rails stack on Debian. He’s turning it into a book which I’m really looking forward too. Debian is a pretty small distro, meaning less space taken up by the OS, and less to back up. It doesn’t have the latest and greatest, which can occasionally be a pain but backports help with that.
I pretty much followed the guide to the letter and in no time had transferred my sites and pointed the DNS to their new home.
Backup
This is the one thing I knew I was going to miss from TextDrive, they have excellent backup there. I had a look at various backup solutions, but in the end decided that as they’d been so good before, TextDrive’s Strongspace would be the best solution. They have a 4Gb package which is a perfect fit, as I’m not planning on using up anywhere near my 4500Mb on Quantact. After much reading around about what was important to back up and not I ended up with my own Ruby based solution using Rake and FileUtils. With that in the crontab I’m much happier. One of the next things on my sysadmin list is actually restoring that backup somewhere else as no backup strategy can be considered working until you’ve restored onto a new system and got everything going again!
I moved for a couple of reasons
I don’t think shared hosting works
I’ve come to the conclusion that shared hosting really doesn’t work. All those people trying on the same machine is bound to lead to resource issues. This might have been fine in TextDrive’s early days when the people there knew a lot about what exactly they were doing, but the more people you get on board, the less fully clued up people you’re going to have. It only takes one clueless person / idiot to start using all the resources and everybody on the box is screwed.
I had to restart my sites when I was away
TextDrive didn’t provide an easy way to ensure your sites were restarted when they rebooted the servers. If you put an entry in cron the process got zapped before it had a chance to start. Daedalus, which they’d originally recommended was banned and I couldn’t find any replacement.
Keeping up with what was best practice
I felt that I had to read the forums every day if I wanted to make sure I wasn’t doing anything wrong. The instructions on the best way of doing things were often buried in the middle of some topic, and if they got updated there was no way of knowing unless you kept your eyes peeled.
I want to learn for myself
Being on a shared host brings with it necessary restrictions, and also means the sysadmins can choose to change something and you have to go digging around to find out why. I wanted to learn for myself without the restrictions so when I saw other people looking around for a VPS where for all intents and purposes you get your own machine I decided it was time to make the switch.